Commit 8df2b1d5 authored by Michael Amadi's avatar Michael Amadi Committed by GitHub

convert semver lock to go (#12775)

* convert semver lock to go

* updae just file

* delete old semver lock file

* fixes

* move snapshot files to snapshots folder

* fixes

* fix check-semver-diff
parent eb977a8e
...@@ -48,7 +48,6 @@ fs_permissions = [ ...@@ -48,7 +48,6 @@ fs_permissions = [
{ access='read', path='./deploy-config-periphery/' }, { access='read', path='./deploy-config-periphery/' },
{ access='read', path='./broadcast/' }, { access='read', path='./broadcast/' },
{ access='read', path = './forge-artifacts/' }, { access='read', path = './forge-artifacts/' },
{ access='write', path='./semver-lock.json' },
{ access='read-write', path='./.testdata/' }, { access='read-write', path='./.testdata/' },
{ access='read', path='./kout-deployment' }, { access='read', path='./kout-deployment' },
{ access='read', path='./test/fixtures' }, { access='read', path='./test/fixtures' },
......
...@@ -96,7 +96,7 @@ deploy: ...@@ -96,7 +96,7 @@ deploy:
# Generates a gas snapshot without building. # Generates a gas snapshot without building.
gas-snapshot-no-build: gas-snapshot-no-build:
forge snapshot --match-contract GasBenchMark forge snapshot --match-contract GasBenchMark --snap snapshots/.gas-snapshot
# Generates a gas snapshot. # Generates a gas snapshot.
gas-snapshot: build-go-ffi gas-snapshot-no-build gas-snapshot: build-go-ffi gas-snapshot-no-build
...@@ -116,9 +116,9 @@ kontrol-summary-full: kontrol-summary kontrol-summary-fp ...@@ -116,9 +116,9 @@ kontrol-summary-full: kontrol-summary kontrol-summary-fp
snapshots-abi-storage: snapshots-abi-storage:
go run ./scripts/autogen/generate-snapshots . go run ./scripts/autogen/generate-snapshots .
# Updates the semver-lock.json file. # Updates the snapshots/semver-lock.json file.
semver-lock: semver-lock:
forge script scripts/autogen/SemverLock.s.sol go run scripts/autogen/generate-semver-lock/main.go
# Generates core snapshots without building contracts. Currently just an alias for # Generates core snapshots without building contracts. Currently just an alias for
# snapshots-abi-storage because we no longer run Kontrol snapshots here. Run # snapshots-abi-storage because we no longer run Kontrol snapshots here. Run
...@@ -135,7 +135,7 @@ snapshots: build snapshots-no-build ...@@ -135,7 +135,7 @@ snapshots: build snapshots-no-build
# Checks that the gas snapshot is up to date without building. # Checks that the gas snapshot is up to date without building.
gas-snapshot-check-no-build: gas-snapshot-check-no-build:
forge snapshot --match-contract GasBenchMark --check forge snapshot --match-contract GasBenchMark --snap snapshots/.gas-snapshot --check
# Checks that the gas snapshot is up to date. # Checks that the gas snapshot is up to date.
gas-snapshot-check: build-go-ffi gas-snapshot-check-no-build gas-snapshot-check: build-go-ffi gas-snapshot-check-no-build
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Script } from "forge-std/Script.sol";
import { stdJson } from "forge-std/StdJson.sol";
import { console2 as console } from "forge-std/console2.sol";
import { Process } from "scripts/libraries/Process.sol";
contract SemverLock is Script {
function run() public {
// First, find all contracts with a Semver inheritance.
string memory rawFiles =
Process.bash("grep -rl '@custom:semver' src | jq -Rs 'split(\"\\n\") | map(select(length > 0))'");
string[] memory files = vm.parseJsonStringArray(rawFiles, "");
writeSemverLock(files);
}
/// @dev Writes a Semver lockfile
function writeSemverLock(string[] memory _files) internal {
string memory out;
for (uint256 i; i < _files.length; i++) {
// Use FFI to read the file to remove the need for FS permissions in the foundry.toml.
string[] memory commands = new string[](2);
commands[0] = "cat";
commands[1] = _files[i];
string memory fileContents = string(Process.run(commands));
// Grab the contract name
string memory contractName =
Process.bash(string.concat("echo \"", _files[i], "\"| sed -E 's|src/.*/(.+)\\.sol|\\1|'"));
string memory artifactsDir = Process.bash("forge config --json | jq -r .out");
// Handle the case where there are multiple artifacts for a contract. This happens
// when the same contract is compiled with multiple compiler versions.
string memory contractArtifactDir = string.concat(artifactsDir, "/", contractName, ".sol");
string memory artifactFiles = Process.bash(
string.concat(
"ls -1 --color=never ",
contractArtifactDir,
" | jq -R -s -c 'split(\"\n\") | map(select(length > 0))'"
)
);
string[] memory files = stdJson.readStringArray(artifactFiles, "");
require(files.length > 0, string.concat("SemverLock: no artifacts found for ", contractName));
string memory fileName = files[0];
// Parse the artifact to get the contract's initcode hash.
bytes memory initCode = vm.getCode(string.concat(artifactsDir, "/", contractName, ".sol/", fileName));
// Serialize the initcode hash + sourcecode hash in JSON.
vm.serializeBytes32(_files[i], "initCodeHash", keccak256(initCode));
string memory obj = vm.serializeBytes32(_files[i], "sourceCodeHash", keccak256(bytes(fileContents)));
// Serialize the map from the file name -> initcode hash + sourcecode hash in JSON.
out = vm.serializeString("", _files[i], obj);
}
// Write the semver lockfile.
vm.writeJson(out, "semver-lock.json");
console.logString("Wrote semver lock file to \"semver-lock.json\".");
}
}
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
}
...@@ -9,7 +9,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ...@@ -9,7 +9,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$SCRIPT_DIR/utils/semver-utils.sh" source "$SCRIPT_DIR/utils/semver-utils.sh"
# Path to semver-lock.json. # Path to semver-lock.json.
SEMVER_LOCK="semver-lock.json" SEMVER_LOCK="snapshots/semver-lock.json"
# Create a temporary directory. # Create a temporary directory.
temp_dir=$(mktemp -d) temp_dir=$(mktemp -d)
...@@ -22,7 +22,12 @@ if ! { git diff origin/develop...HEAD --name-only; git diff --name-only; git dif ...@@ -22,7 +22,12 @@ if ! { git diff origin/develop...HEAD --name-only; git diff --name-only; git dif
fi fi
# Get the upstream semver-lock.json. # Get the upstream semver-lock.json.
git show origin/develop:packages/contracts-bedrock/semver-lock.json > "$temp_dir/upstream_semver_lock.json" if ! git show origin/develop:packages/contracts-bedrock/snapshots/semver-lock.json > "$temp_dir/upstream_semver_lock.json" 2>/dev/null; then
if ! git show origin/develop:packages/contracts-bedrock/semver-lock.json > "$temp_dir/upstream_semver_lock.json" 2>/dev/null; then
echo "❌ Error: Could not find semver-lock.json in either snapshots/ or root directory of develop branch"
exit 1
fi
fi
# Copy the local semver-lock.json. # Copy the local semver-lock.json.
cp "$SEMVER_LOCK" "$temp_dir/local_semver_lock.json" cp "$SEMVER_LOCK" "$temp_dir/local_semver_lock.json"
......
...@@ -14,7 +14,7 @@ cd "$CONTRACTS_DIR" ...@@ -14,7 +14,7 @@ cd "$CONTRACTS_DIR"
echoerr "> Calculating contracts checksum..." echoerr "> Calculating contracts checksum..."
find . -type f -name '*.sol' -exec sha256sum {} + > manifest.txt find . -type f -name '*.sol' -exec sha256sum {} + > manifest.txt
sha256sum semver-lock.json >> manifest.txt sha256sum snapshots/semver-lock.json >> manifest.txt
sha256sum foundry.toml >> manifest.txt sha256sum foundry.toml >> manifest.txt
# need to specify the locale to ensure consistent sorting across platforms # need to specify the locale to ensure consistent sorting across platforms
LC_ALL=C sort -o manifest.txt manifest.txt LC_ALL=C sort -o manifest.txt manifest.txt
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment