diff --git a/.github/workflows/check-upstream-abigen.yml b/.github/workflows/check-upstream-abigen.yml new file mode 100644 index 00000000..76c925dc --- /dev/null +++ b/.github/workflows/check-upstream-abigen.yml @@ -0,0 +1,127 @@ +name: Check Upstream Abigen Updates + +on: + pull_request: + branches: + - main + - "releases/**" + workflow_dispatch: + +jobs: + check-upstream: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Check latest go-ethereum release + id: upstream + run: | + LATEST=$(curl -s https://api.github.com/repos/ethereum/go-ethereum/releases/latest | jq -r .tag_name) + echo "latest=$LATEST" >> "$GITHUB_OUTPUT" + echo "Latest go-ethereum: $LATEST" + + - name: Get current fork version + id: current + run: | + CURRENT=$(grep "Upstream Version:" cmd/generate-bindings/bindings/abigen/FORK_METADATA.md | cut -d: -f2 | tr -d ' ') + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "Current fork version: $CURRENT" + + - name: Compare versions + id: compare + run: | + CURRENT="${{ steps.current.outputs.current }}" + LATEST="${{ steps.upstream.outputs.latest }}" + + # Extract major.minor version (e.g., "1.16" from "v1.16.0") + CURRENT_MAJOR_MINOR=$(echo "$CURRENT" | sed 's/^v//' | cut -d. -f1,2) + LATEST_MAJOR_MINOR=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f1,2) + + echo "Current major.minor: $CURRENT_MAJOR_MINOR" + echo "Latest major.minor: $LATEST_MAJOR_MINOR" + + if [ "$CURRENT_MAJOR_MINOR" != "$LATEST_MAJOR_MINOR" ]; then + echo "outdated=true" >> "$GITHUB_OUTPUT" + echo "::warning::Fork has a major version difference. Current: $CURRENT, Latest: $LATEST" + else + echo "outdated=false" >> "$GITHUB_OUTPUT" + echo "Fork is on the same major.minor version ($CURRENT_MAJOR_MINOR)" + fi + + - name: Check for recent security-related commits + id: security + run: | + CURRENT="${{ steps.current.outputs.current }}" + echo "Checking for security-related commits since $CURRENT..." + + # Search for security-related keywords in commit messages + SECURITY_COMMITS=$(curl -s "https://api.github.com/repos/ethereum/go-ethereum/commits?sha=master&per_page=100" | \ + jq -r '[.[] | select(.commit.message | test("security|vulnerability|CVE|exploit"; "i")) | "- \(.commit.message | split("\n")[0]) ([link](\(.html_url)))"] | join("\n")' || echo "") + + if [ -n "$SECURITY_COMMITS" ]; then + echo "has_security=true" >> "$GITHUB_OUTPUT" + # Save to file to handle multiline + echo "$SECURITY_COMMITS" > /tmp/security_commits.txt + else + echo "has_security=false" >> "$GITHUB_OUTPUT" + fi + + - name: Comment on PR - Outdated + if: steps.compare.outputs.outdated == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const current = '${{ steps.current.outputs.current }}'; + const latest = '${{ steps.upstream.outputs.latest }}'; + const hasSecurity = '${{ steps.security.outputs.has_security }}' === 'true'; + + let securitySection = ''; + if (hasSecurity) { + try { + const commits = fs.readFileSync('/tmp/security_commits.txt', 'utf8'); + securitySection = ` + + ### ⚠️ Potential Security-Related Commits Detected + + ${commits} + `; + } catch (e) { + // File might not exist + } + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## ⚠️ Abigen Fork Check - Update Available + + The forked abigen package is **outdated** and may be missing important updates. + + | Version | Value | + |---------|-------| + | **Current Fork** | \`${current}\` | + | **Latest Upstream** | \`${latest}\` | + + ### Action Required + + 1. Review [abigen changes in upstream](https://github.com/ethereum/go-ethereum/commits/${latest}/accounts/abi/bind) (only the \`accounts/abi/bind\` directory matters) + 2. Compare with our fork in \`cmd/generate-bindings/bindings/abigen/\` + 3. If relevant changes exist, sync them and update \`FORK_METADATA.md\` + 4. If no abigen changes, just update the version in \`FORK_METADATA.md\` to \`${latest}\` + ${securitySection} + ### Files to Review + + - \`cmd/generate-bindings/bindings/abigen/bind.go\` + - \`cmd/generate-bindings/bindings/abigen/bindv2.go\` + - \`cmd/generate-bindings/bindings/abigen/template.go\` + + --- + ⚠️ **Note to PR author**: This is not something you need to fix. The Platform Expansion team is responsible for maintaining the abigen fork. + + cc @smartcontractkit/bix-framework` + }); diff --git a/cmd/generate-bindings/bindings/abigen/FORK_METADATA.md b/cmd/generate-bindings/bindings/abigen/FORK_METADATA.md new file mode 100644 index 00000000..7cc9a1b5 --- /dev/null +++ b/cmd/generate-bindings/bindings/abigen/FORK_METADATA.md @@ -0,0 +1,36 @@ +# Abigen Fork Metadata + +## Upstream Information + +- Source Repository: https://github.com/ethereum/go-ethereum +- Original Package: accounts/abi/bind +- Fork Date: 2025-06-18 +- Upstream Version: v1.16.0 +- Upstream Commit: 4997a248ab4acdb40383f1e1a5d3813a634370a6 + +## Modifications + +1. Custom Template Support (bindv2.go:300) + - Description: Added `templateContent` parameter to `BindV2()` function signature + - Reason: Enable CRE-specific binding generation with custom templates + +2. isDynTopicType Function (bindv2.go:401-408) + - Description: Added template function for event topic type checking + - Registered `isDynTopicType` in the template function map + - Reason: Distinguish hashed versus unhashed indexed event fields for dynamic types (tuples, strings, bytes, slices, arrays) + +3. sanitizeStructNames Function (bindv2.go:383-395) + - Reason: Generate cleaner, less verbose struct names in bindings + - Description: Added function to remove contract name prefixes from struct names + +4. Copyright Header Addition (bindv2.go:17-18) + - Description: Added SmartContract ChainLink Limited SEZC copyright notice + - Reason: Proper attribution for modifications + +## Sync History + +- 2025-06-18: Initial fork from v1.16.0 + +## Security Patches Applied + +None yet. diff --git a/cmd/generate-bindings/generate-bindings.go b/cmd/generate-bindings/generate-bindings.go index fb7b4f8e..7da55c94 100644 --- a/cmd/generate-bindings/generate-bindings.go +++ b/cmd/generate-bindings/generate-bindings.go @@ -200,6 +200,17 @@ func (h *handler) processAbiDirectory(inputs Inputs) error { return fmt.Errorf("no .abi files found in directory: %s", inputs.AbiPath) } + packageNames := make(map[string]bool) + for _, abiFile := range files { + contractName := filepath.Base(abiFile) + contractName = contractName[:len(contractName)-4] + packageName := contractNameToPackage(contractName) + if _, exists := packageNames[packageName]; exists { + return fmt.Errorf("package name collision: multiple contracts would generate the same package name '%s' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict", packageName) + } + packageNames[packageName] = true + } + // Process each ABI file for _, abiFile := range files { // Extract contract name from filename (remove .abi extension) diff --git a/cmd/generate-bindings/generate-bindings_test.go b/cmd/generate-bindings/generate-bindings_test.go index c0479aca..e36ed870 100644 --- a/cmd/generate-bindings/generate-bindings_test.go +++ b/cmd/generate-bindings/generate-bindings_test.go @@ -442,6 +442,47 @@ func TestProcessAbiDirectory_NoAbiFiles(t *testing.T) { assert.Contains(t, err.Error(), "no .abi files found") } +func TestProcessAbiDirectory_PackageNameCollision(t *testing.T) { + tempDir, err := os.MkdirTemp("", "generate-bindings-test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + abiDir := filepath.Join(tempDir, "abi") + outDir := filepath.Join(tempDir, "generated") + + err = os.MkdirAll(abiDir, 0755) + require.NoError(t, err) + + abiContent := `[{"type":"function","name":"test","inputs":[],"outputs":[]}]` + + // "TestContract" -> "test_contract" + // "test_contract" -> "test_contract" + err = os.WriteFile(filepath.Join(abiDir, "TestContract.abi"), []byte(abiContent), 0600) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(abiDir, "test_contract.abi"), []byte(abiContent), 0600) + require.NoError(t, err) + + logger := zerolog.New(os.Stderr).With().Timestamp().Logger() + runtimeCtx := &runtime.Context{ + Logger: &logger, + } + handler := newHandler(runtimeCtx) + + inputs := Inputs{ + ProjectRoot: tempDir, + ChainFamily: "evm", + Language: "go", + AbiPath: abiDir, + PkgName: "bindings", + OutPath: outDir, + } + + err = handler.processAbiDirectory(inputs) + fmt.Println(err.Error()) + require.Error(t, err) + require.Equal(t, err.Error(), "package name collision: multiple contracts would generate the same package name 'test_contract' (contracts are converted to snake_case for package names). Please rename one of your contract files to avoid this conflict") +} + func TestProcessAbiDirectory_NonExistentDirectory(t *testing.T) { logger := zerolog.New(os.Stderr).With().Timestamp().Logger() runtimeCtx := &runtime.Context{