main.go 3.43 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/ethereum/go-ethereum/crypto"
)

const semverLockFile = "snapshots/semver-lock.json"

func main() {
	if err := run(); err != nil {
		panic(err)
	}
}

func run() error {
	// Find semver files
	// Execute grep command to find files with @custom:semver
	var cmd = exec.Command("bash", "-c", "grep -rl '@custom:semver' src | jq -Rs 'split(\"\n\") | map(select(length > 0))'")
	cmdOutput, err := cmd.Output()
	if err != nil {
		return err
	}

	// Parse the JSON array of files
	var files []string
	if err := json.Unmarshal(cmdOutput, &files); err != nil {
		return fmt.Errorf("failed to parse JSON output: %w", err)
	}

	// Hash and write to JSON file
	// Map to store our JSON output
	output := make(map[string]map[string]string)

	// regex to extract contract name from file path
	re := regexp.MustCompile(`src/.*/(.+)\.sol`)

	// Get artifacts directory
	cmd = exec.Command("forge", "config", "--json")
	out, err := cmd.Output()
	if err != nil {
		return fmt.Errorf("failed to get forge config: %w", err)
	}
	var config struct {
		Out string `json:"out"`
	}
	if err := json.Unmarshal(out, &config); err != nil {
		return fmt.Errorf("failed to parse forge config: %w", err)
	}

	for _, file := range files {
		// Read file contents
		fileContents, err := os.ReadFile(file)
		if err != nil {
			return fmt.Errorf("failed to read file %s: %w", file, err)
		}

		// Extract contract name from file path using regex
		matches := re.FindStringSubmatch(file)
		if len(matches) < 2 {
			return fmt.Errorf("invalid file path format: %s", file)
		}
		contractName := matches[1]

		// Get artifact files
		artifactDir := filepath.Join(config.Out, contractName+".sol")
		files, err := os.ReadDir(artifactDir)
		if err != nil {
			return fmt.Errorf("failed to read artifact directory: %w", err)
		}
		if len(files) == 0 {
			return fmt.Errorf("no artifacts found for %s", contractName)
		}

		// Read initcode from artifact
		artifactPath := filepath.Join(artifactDir, files[0].Name())
		artifact, err := os.ReadFile(artifactPath)
		if err != nil {
			return fmt.Errorf("failed to read initcode: %w", err)
		}
		artifactJson := json.RawMessage(artifact)
		var artifactObj struct {
			Bytecode struct {
				Object string `json:"object"`
			} `json:"bytecode"`
		}
		if err := json.Unmarshal(artifactJson, &artifactObj); err != nil {
			return fmt.Errorf("failed to parse artifact: %w", err)
		}

		// convert the hex bytecode to a uint8 array / bytes
		initCodeBytes, err := hex.DecodeString(strings.TrimPrefix(artifactObj.Bytecode.Object, "0x"))
		if err != nil {
			return fmt.Errorf("failed to decode hex: %w", err)
		}

		// Calculate hashes using Keccak256
		var sourceCode = []byte(strings.TrimSuffix(string(fileContents), "\n"))
		initCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(initCodeBytes))
		sourceCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(sourceCode))

		// Store in output map
		output[file] = map[string]string{
			"initCodeHash":   initCodeHash,
			"sourceCodeHash": sourceCodeHash,
		}
	}

	// Write to JSON file
	jsonData, err := json.MarshalIndent(output, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal JSON: %w", err)
	}
	if err := os.WriteFile(semverLockFile, jsonData, 0644); err != nil {
		return fmt.Errorf("failed to write semver lock file: %w", err)
	}

	fmt.Printf("Wrote semver lock file to \"%s\".\n", semverLockFile)
	return nil
}