From 3cb358996d69211bf945083946ad6b922b75dae0 Mon Sep 17 00:00:00 2001 From: worlldz <101180447+cryptoworlldz@users.noreply.github.com> Date: Fri, 28 Aug 2026 05:03:33 +0300 Subject: [PATCH] fix(semver-lock): normalize source line endings --- scripts/autogen/generate-semver-lock/main.go | 11 ++- .../autogen/generate-semver-lock/main_test.go | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 scripts/autogen/generate-semver-lock/main_test.go diff --git a/scripts/autogen/generate-semver-lock/main.go b/scripts/autogen/generate-semver-lock/main.go index c2b00e7a8..ccf675c14 100644 --- a/scripts/autogen/generate-semver-lock/main.go +++ b/scripts/autogen/generate-semver-lock/main.go @@ -88,17 +88,24 @@ func processFile(file string) (*SemverLockResult, []error) { return nil, []error{fmt.Errorf("failed to read source file: %w", err)} } - trimmedSourceCode := bytes.TrimSuffix(sourceCode, []byte("\n")) + normalizedSourceCode := normalizeSourceCode(sourceCode) return &SemverLockResult{ ContractKey: contractKey, SemverLockOutput: SemverLockOutput{ InitCodeHash: crypto.Keccak256Hash(initCodeBytes).Hex(), - SourceCodeHash: crypto.Keccak256Hash(trimmedSourceCode).Hex(), + SourceCodeHash: crypto.Keccak256Hash(normalizedSourceCode).Hex(), }, }, nil } +// normalizeSourceCode makes source hashes independent of the checkout's line-ending +// configuration while preserving the existing behavior of ignoring one trailing newline. +func normalizeSourceCode(sourceCode []byte) []byte { + sourceCode = bytes.ReplaceAll(sourceCode, []byte("\r\n"), []byte("\n")) + return bytes.TrimSuffix(sourceCode, []byte("\n")) +} + func hasSemverVersion(artifact *solc.ForgeArtifact, contractName string) bool { for _, node := range artifact.Ast.Nodes { if node.NodeType != "ContractDefinition" || node.Name != contractName { diff --git a/scripts/autogen/generate-semver-lock/main_test.go b/scripts/autogen/generate-semver-lock/main_test.go new file mode 100644 index 000000000..a3bff1dc8 --- /dev/null +++ b/scripts/autogen/generate-semver-lock/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeSourceCode(t *testing.T) { + tests := []struct { + name string + source string + want string + }{ + { + name: "Unix line endings", + source: "contract Test {\n function version() public pure returns (string memory) {}\n}\n", + want: "contract Test {\n function version() public pure returns (string memory) {}\n}", + }, + { + name: "Windows line endings", + source: "contract Test {\r\n function version() public pure returns (string memory) {}\r\n}\r\n", + want: "contract Test {\n function version() public pure returns (string memory) {}\n}", + }, + { + name: "No trailing newline", + source: "contract Test {}", + want: "contract Test {}", + }, + { + name: "Only one trailing newline is ignored", + source: "contract Test {}\r\n\r\n", + want: "contract Test {}\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, string(normalizeSourceCode([]byte(tt.source)))) + }) + } +} + +func TestProcessFile_LineEndingsProduceSameSourceHash(t *testing.T) { + t.Chdir(t.TempDir()) + require.NoError(t, os.Mkdir("src", 0755)) + + artifact := `{ + "ast": { + "nodes": [{ + "nodeType": "ContractDefinition", + "name": "Test", + "nodes": [{ + "nodeType": "FunctionDefinition", + "name": "version", + "documentation": {"text": "@custom:semver 1.0.0"} + }] + }] + }, + "bytecode": {"object": "0x00"}, + "metadata": { + "settings": { + "compilationTarget": {"src/Test.sol": "Test"} + } + } +}` + require.NoError(t, os.WriteFile("Test.json", []byte(artifact), 0644)) + + require.NoError(t, os.WriteFile("src/Test.sol", []byte("contract Test {\n}\n"), 0644)) + lf, errs := processFile("Test.json") + require.Empty(t, errs) + require.NotNil(t, lf) + + require.NoError(t, os.WriteFile("src/Test.sol", []byte("contract Test {\r\n}\r\n"), 0644)) + crlf, errs := processFile("Test.json") + require.Empty(t, errs) + require.NotNil(t, crlf) + + require.Equal(t, lf.SemverLockOutput.SourceCodeHash, crlf.SemverLockOutput.SourceCodeHash) +}