Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,20 @@
additionalBuildInputs = [ pkgs.git ];
};

rainix-check-deploy-constants = mkTask {
name = "check-published-deploy-constants";
body = ''
set -euo pipefail
exec ${./lib/check-published-deploy-constants.sh} "$@"
'';
additionalBuildInputs = [
pkgs.curl
pkgs.gnugrep
# cut/sort/tr in the version-parsing pipeline.
pkgs.coreutils
];
};

rainix-rs-static = mkTask {
name = "rainix-rs-static";
body = ''
Expand All @@ -286,6 +300,7 @@
sol-tasks = [
rainix-sol-artifacts
rainix-sol-single-contract
rainix-check-deploy-constants
];

rs-tasks = [
Expand Down Expand Up @@ -381,6 +396,7 @@
bats test/bats/task/subgraph-build.test.bats
bats test/bats/task/subgraph-deploy-version.test.bats
bats test/bats/task/sol-single-contract.test.bats
bats test/bats/task/check-published-deploy-constants.test.bats
'';
additionalBuildInputs = [ pkgs.bats ] ++ sol-build-inputs ++ node-build-inputs;
};
Expand Down Expand Up @@ -586,6 +602,7 @@
inherit
rainix-sol-artifacts
rainix-sol-single-contract
rainix-check-deploy-constants
rainix-rs-static
prettier-bundle
sol-shell-test
Expand Down
76 changes: 76 additions & 0 deletions lib/check-published-deploy-constants.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LicenseRef-DCL-1.0
# SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
#
# Checks that every version published to the soldeer registry for a given
# package has a full suite of pinned deploy constants in the specified Solidity
# file. For each published version and each constant prefix, asserts that both
# a DEPLOYED_ADDRESS_<ver> and DEPLOYED_CODEHASH_<ver> constant exist (where
# <ver> is the version string with dots replaced by underscores).
#
# Usage:
# check-published-deploy-constants <soldeer-package> <deploy-lib-path> <prefix> [<prefix> ...]
#
# Arguments:
# soldeer-package soldeer package name to query (e.g. "raindex")
# deploy-lib-path path to the Solidity file holding the constants
# prefix... one or more constant name prefixes
#
# Output for a well-formed invocation (always exits 0, so registry state never
# reds a consumer pipeline):
# OK every published version has its full constant suite
# MISSING: <names> one or more expected constants are absent
# SKIP: <reason> nothing was verified; the reason distinguishes an
# unreachable registry from a reachable registry whose
# response yielded no parseable versions
#
# A malformed invocation (fewer than 3 arguments) is a caller bug, not a
# registry state: usage goes to stderr and the exit status is 1, so a miswired
# CI step fails loud instead of silently skipping forever.

set -euo pipefail

if [ "$#" -lt 3 ]; then
printf 'Usage: check-published-deploy-constants <soldeer-package> <deploy-lib-path> <prefix> [<prefix>...]\n' >&2
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

package="$1"
lib="$2"
shift 2

if ! response=$(curl -fsS "https://api.soldeer.xyz/api/v1/revision?project_name=${package}" 2>/dev/null); then
printf 'SKIP: could not fetch published soldeer versions'
exit 0
fi
Comment on lines +42 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced script and nearby content.
if [ -f lib/check-published-deploy-constants.sh ]; then
  wc -l lib/check-published-deploy-constants.sh
  sed -n '1,90p' lib/check-published-deploy-constants.sh | cat -n
else
  echo "Referenced file not found."
  fd -i 'check-published-deploy-constants\.sh'
fi

# Check whether other curl invocations in this script use timeout options.
echo
echo "curl timeout option usage in file:"
rg -n 'curl|connect-timeout|max-time|timeout' lib/check-published-deploy-constants.sh || true

Repository: rainlanguage/rainix

Length of output: 3609


Bound the registry request.

curl can wait indefinitely for DNS, connection establishment, or data transfer. Add --connect-timeout and --max-time before switching to SKIP if the registry request fails.

Suggested fix
- if ! response=$(curl -fsS "https://api.soldeer.xyz/api/v1/revision?project_name=${package}" 2>/dev/null); then
+ if ! response=$(curl -fsS --connect-timeout 10 --max-time 30 \
+   "https://api.soldeer.xyz/api/v1/revision?project_name=${package}" 2>/dev/null); then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ! response=$(curl -fsS "https://api.soldeer.xyz/api/v1/revision?project_name=${package}" 2>/dev/null); then
printf 'SKIP: could not fetch published soldeer versions'
exit 0
fi
if ! response=$(curl -fsS --connect-timeout 10 --max-time 30 \
"https://api.soldeer.xyz/api/v1/revision?project_name=${package}" 2>/dev/null); then
printf 'SKIP: could not fetch published soldeer versions'
exit 0
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/check-published-deploy-constants.sh` around lines 42 - 45, Update the
curl invocation in the published-version check to include both connection and
total-request time limits, using appropriate finite timeout values before the
existing failure handling skips the check. Preserve the current response
capture, silent error behavior, and SKIP path.


# grep exits non-zero on zero matches; that is the legitimate
# "registry reachable but no versions parsed" case (e.g. a package with no
# published releases yet), kept distinct from the connectivity SKIP above so
# parser/API drift is never masked as an unreachable registry.
versions=$(
printf '%s' "$response" \
| grep -oE '"version":"[0-9][0-9.]*"' | cut -d'"' -f4 | sort -u
) || true

if [ -z "$versions" ]; then
printf 'SKIP: no versions parsed from registry response'
exit 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
Comment on lines +51 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching check-published-deploy-constants.sh:\n'
fd -a 'check-published-deploy-constants\.sh$' . || true

file="$(fd 'check-published-deploy-constants\.sh$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  printf '\nLine count:\n'
  wc -l "$file"
  printf '\nSelected lines:\n'
  sed -n '1,140p' "$file" | cat -n
fi

printf '\nSearch references to publish/available versions in repo:\n'
rg -n "check-published-deploy-constants|versions=\(|grep -oE|supported|soldeer|registry" -S . --glob '!target/**' --glob '!node_modules/**' | head -n 200

Repository: rainlanguage/rainix

Length of output: 10785


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Current test file summary:\n'
wc -l test/bats/task/check-published-deploy-constants.test.bats
sed -n '1,180p' test/bats/task/check-published-deploy-constants.test.bats | cat -n

printf '\nProbe regex behavior for representative registry-format cases:\n'
python3 - <<'PY'
import re, subprocess, textwrap

script = r'''#!/usr/bin/env bash
set -euo pipefail
versions=$(
  printf '%s' "$1" \
    | grep -oE '"version":"[0-9][0-9.]*"' | cut -d'"' -f4 | sort -u
) || true
: "${versions:=<empty>}"
printf '%s\n' "${versions//$'\n'/|}"
'''
cases = {
    "compact_numeric": '{"data":[{"version":"1.0.0"}]}',
    "spaced_numeric": '{ "data" : [ { "version" : "1.0.0" } ] }',
    "string_around_numbers": '"version":" 1.0.0 "]',
    "numeric_version_with_pre": '"version":"1.0.0-alpha.1"',
    "nonnumeric": '"version":"main"',
    "unsupported_alphanumeric_in_version": '"version":"1.0.0a"',
    "missing_version_key": '{"data":[{"name":"1.0.0"}]}',
}
for name, payload in cases.items():
    env = {"VERSION_PAYLOAD": payload}
    out = subprocess.run(["bash", "-c", script, "script", "${VERSION_PAYLOAD}"], capture_output=True, text=True, env=env)
    print(f"{name}: exit={out.returncode}, stdout={out.stdout!r}, stderr={out.stderr!r}")

print("\nProbe bash for loop splitting on common SemVer values:")
for ver in ["1.0.0", "1.0.0-alpha", "1.0.0-alpha.1", "2.0.0-beta+build"]:
    out = subprocess.run(["bash", "-c", 'for v in $1; do printf "[%s]\\n" "$v"; done', "name", ver], capture_output=True, text=True)
    print(f"for {ver!r}: stdout={out.stdout.rstrip()}")
PY

Repository: rainlanguage/rainix

Length of output: 4197


🌐 Web query:

Soldeer API v1 revision version field data version string examples non-semver version strings registry

💡 Result:

In the Soldeer API v1, the registry uses a versioning system that primarily relies on SemVer, but the tool accepts flexible "version requirement" strings for dependency resolution [1][2]. Registry and Versioning Mechanics The Soldeer registry interaction (e.g., via the /api/v1/revision endpoint) manages dependencies indexed by project name and version [3]. While the registry typically expects valid SemVer versions for hosted packages, the Soldeer CLI provides advanced support for version requirement specifiers in configuration files (foundry.toml or soldeer.toml) and command-line inputs [1][2]. Version Requirement String Examples The following examples illustrate how version requirements are parsed by the Soldeer CLI, following the semver::VersionReq specification: - 1.2.3: Matches exactly version 1.2.3 (equivalent to =1.2.3) [1]. - ^1.2.3: Allows patch and minor version increases, but restricts the major version [1]. - 1.2.3: Allows only patch version increases [1]. - >=1.2.3: Matches any version greater than or equal to 1.2.3 [1]. - 1: Matches any version >=1.0.0 and <2.0.0 (compatible with SemVer range logic) [1]. - >1.2.3,<1.4.0: Matches a specific range [1]. Non-SemVer Handling For non-SemVer scenarios, such as tracking specific git-based dependencies, Soldeer uses revision identifiers (hashes, tags, or branches) rather than version strings [4][5]. When installing or managing these, you use distinct flags: - --rev <COMMIT_HASH>: Pins a dependency to a specific git commit [4][5]. - --tag : Pins a dependency to a specific git tag [4][5]. - --branch : Tracks a specific git branch [4][5]. These non-SemVer identifiers are distinct from the registry-based "version" field used in the package metadata (which should generally remain SemVer-compliant for registry publishing) [6]. When using git, the dependency is typically specified in the format library_nameversion (or alias) followed by the relevant git metadata flags [4][5].

Citations:


🌐 Web query:

Soldeer api/v1/revision response version field "version" string examples

💡 Result:

The Soldeer API endpoint /api/v1/revision is used by the Soldeer CLI (and via the forge soldeer integration in Foundry) to fetch dependency information from the central registry [1]. In the context of the Soldeer registry and its API responses, the version field is typically represented as a string following Semantic Versioning (SemVer) conventions [2][3]. Examples of version strings commonly encountered in Soldeer configurations, registry requests, and lockfiles include: Standard Semantic Versions: "1.0.0" "1.8.1" "5.0.2" "6.7.0" In the Soldeer CLI and dependency definitions (e.g., in foundry.toml or soldeer.toml), version strings are often associated with the package name using a tilde () separator, such as forge-std1.9.2 or @openzeppelin-contracts~5.0.2 [2][3]. When interacting with the registry API directly—for instance, when querying revisions for a project—the registry returns these version strings to identify specific releases of a package [1]. As an authoritative reference, the soldeer-core Rust crate defines the data structures for these API interactions, where registry responses map version identifiers to the corresponding dependency metadata [4][5]. If you are working with the API programmatically, expect the version field to be a standard JSON string adhering to the format defined by your specific project's releases on the registry [1].

Citations:


🌐 Web query:

Soldeer registry non-semver published version string

💡 Result:

The Soldeer registry is designed to work with packages that follow semantic versioning (SemVer) [1]. While Soldeer uses a version requirement string for installing dependencies [2], which allows for flexibility like wildcards (*) and range operators (^, ~, >, >=), these mechanisms are fundamentally built to operate on SemVer-compliant version identifiers [2]. There is no explicit support or official documentation indicating that the Soldeer registry supports or validates non-SemVer version strings. In practice, Soldeer expects registry packages to be published with valid SemVer versions to ensure that its dependency resolution, version locking, and range-matching features function correctly [2][1]. If a package does not follow SemVer, it may cause issues with automated dependency resolution or prevent the registry from correctly identifying and serving the requested versions. For developers needing to manage non-SemVer dependencies, the Soldeer CLI provides alternative installation methods, such as using direct Git URLs (with support for specific branches, tags, or commit hashes) or custom ZIP URLs [3]. These methods bypass the registry's versioning requirements and allow for arbitrary version identifiers that do not need to adhere to SemVer [2][3].

Citations:


Use an API-compatible JSON parser for registry versions.

The current grep -oE '"version":"[0-9][0-9.]*"' pattern rejects valid JSON spacing, SemVer pre-release/build metadata, and any non-numeric registry version strings. If the response is reachable, an unsupported JSON shape should be treated as parsing drift, not “no parseable versions,” and known supported formats should not be dropped. Use a JSON parser for the data[].version field, with separate outcomes for fetch failure, parse failure, empty version list, and unsupported version format.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/check-published-deploy-constants.sh` around lines 51 - 59, The version
extraction in the registry-response parsing block must use an API-compatible
JSON parser to read data[].version instead of the restrictive grep pattern.
Distinguish fetch failure, JSON parse failure, an empty version list, and
unsupported version formats, while preserving all valid registry version strings
including SemVer metadata and non-numeric values.


missing=""
for v in $versions; do
suffix=$(printf '%s' "$v" | tr . _)
for p in "$@"; do
for kind in ADDRESS CODEHASH; do
name="${p}_${kind}_${suffix}"
grep -qE "constant ${name} =" "$lib" || missing="${missing} ${name}"
done
done
done

if [ -n "$missing" ]; then
printf 'MISSING:%s' "$missing"
else
printf 'OK'
fi
5 changes: 5 additions & 0 deletions test/bats/devshell/sol-shell/sol-tasks.test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
run command -v rainix-sol-artifacts
[ "$status" -eq 0 ]
}

@test "check-published-deploy-constants should be available on PATH" {
run command -v check-published-deploy-constants
[ "$status" -eq 0 ]
}
85 changes: 85 additions & 0 deletions test/bats/task/check-published-deploy-constants.test.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
setup() {
TESTDIR="$(mktemp -d)"
# Stub curl on PATH so no test touches the network. STUB_CURL_FAIL simulates
# an unreachable registry; otherwise the stub prints the file named by
# STUB_CURL_PAYLOAD as the registry response.
mkdir -p "$TESTDIR/bin"
cat > "$TESTDIR/bin/curl" << 'EOF'
#!/usr/bin/env bash
if [ -n "${STUB_CURL_FAIL:-}" ]; then
exit 22
fi
cat "$STUB_CURL_PAYLOAD"
EOF
chmod +x "$TESTDIR/bin/curl"
PATH="$TESTDIR/bin:$PATH"
SCRIPT="./lib/check-published-deploy-constants.sh"
}

teardown() {
rm -rf "$TESTDIR"
}

_write_payload() {
STUB_CURL_PAYLOAD="$TESTDIR/payload.json"
export STUB_CURL_PAYLOAD
cat > "$STUB_CURL_PAYLOAD"
}

# The flake task execs the script's nix store copy directly, and the store
# canonicalizes the git file mode (644 -> 0444, 755 -> 0555), so the tracked
# file must carry the exec bit or the task dies with EACCES.
@test "script is executable" {
[ -x "$SCRIPT" ]
}

@test "malformed invocation prints usage to stderr and exits 1" {
run "$SCRIPT" only-two-args "$TESTDIR/lib.sol"
[ "$status" -eq 1 ]
[[ "$output" == *"Usage: check-published-deploy-constants"* ]]
}

@test "unreachable registry prints connectivity SKIP and exits 0" {
export STUB_CURL_FAIL=1
run "$SCRIPT" somepkg "$TESTDIR/lib.sol" DEPLOYED
[ "$status" -eq 0 ]
[ "$output" = "SKIP: could not fetch published soldeer versions" ]
}

@test "reachable registry with no parseable versions prints a distinct SKIP and exits 0" {
_write_payload << 'EOF'
{"data":[],"status":"success"}
EOF
run "$SCRIPT" somepkg "$TESTDIR/lib.sol" DEPLOYED
[ "$status" -eq 0 ]
[ "$output" = "SKIP: no versions parsed from registry response" ]
}

@test "prints OK when every published version has its full constant suite" {
_write_payload << 'EOF'
{"data":[{"version":"1.0.0"},{"version":"1.2.3"}]}
EOF
cat > "$TESTDIR/lib.sol" << 'EOF'
address constant DEPLOYED_ADDRESS_1_0_0 = address(1);
bytes32 constant DEPLOYED_CODEHASH_1_0_0 = bytes32(0);
address constant DEPLOYED_ADDRESS_1_2_3 = address(2);
bytes32 constant DEPLOYED_CODEHASH_1_2_3 = bytes32(0);
EOF
run "$SCRIPT" somepkg "$TESTDIR/lib.sol" DEPLOYED
[ "$status" -eq 0 ]
[ "$output" = "OK" ]
}

@test "prints MISSING with each absent constant name across prefixes and exits 0" {
_write_payload << 'EOF'
{"data":[{"version":"1.0.0"}]}
EOF
cat > "$TESTDIR/lib.sol" << 'EOF'
address constant OBV2_ADDRESS_1_0_0 = address(1);
bytes32 constant OBV2_CODEHASH_1_0_0 = bytes32(0);
address constant OBV3_ADDRESS_1_0_0 = address(2);
EOF
run "$SCRIPT" somepkg "$TESTDIR/lib.sol" OBV2 OBV3
[ "$status" -eq 0 ]
[ "$output" = "MISSING: OBV3_CODEHASH_1_0_0" ]
}
Loading