Skip to content

Commit a6f7d18

Browse files
Derive subgraph networks from deploy networks and the live candidate
networks.json holds exactly the supported networks where the candidate RaindexV6 is live, at its on-chain deploy block; subgraph.yaml points at the candidate. Fork test fails on any missing, extra or stale entry. Removes the duplicate LibRaindexV6StartBlock. Closes #2942 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9f1d266 commit a6f7d18

20 files changed

Lines changed: 243 additions & 373 deletions

‎CLAUDE.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ Read and follow @AGENTS.md.
44

55
Deploy repo: a source change regenerates `src/generated/candidate/` and merges
66
without deploying. Deploying and publishing happen together at a release
7-
(below). `LibRaindexDeploy` is the candidate. The start-block, `networks.json`
8-
and `subgraph.yaml` tests read the released RaindexV6 from the newest entry of
9-
the generated `LibRaindexV6Released`. Address/codehash churn is routine; don't
10-
stop to ask about it.
7+
(below). `LibRaindexDeploy` is the candidate. `subgraph.yaml` holds its
8+
RaindexV6 address; `networks.json` lists exactly the supported networks where it
9+
is live, at its deploy block (fork-tested). Address/codehash churn is routine;
10+
don't stop to ask about it.
1111

1212
## Merging a PR
1313

@@ -34,7 +34,8 @@ stop to ask about it.
3434

3535
Regenerate artifacts — run the `rainix-copy-artifacts` step sequence (see that
3636
workflow) in `nix develop github:rainlanguage/rainix#sol-shell`. Stage ALL
37-
changed artifacts or `git-clean` drifts red. Nothing is deployed.
37+
changed artifacts or `git-clean` drifts red. Nothing is deployed. If RaindexV6
38+
moved, run `script/build-start-blocks.sh` (`networks.json`, `subgraph.yaml`).
3839

3940
## Releasing (deploy + publish)
4041

@@ -46,9 +47,8 @@ changed artifacts or `git-clean` drifts red. Nothing is deployed.
4647
`ONCHAIN EXECUTION COMPLETE & SUCCESSFUL` (only `--verify` failed).
4748
2. One PR: bump `foundry.toml [external.package].version`, run
4849
`forge script ./script/Build.sol --sig 'cutRelease()'` (freezes `candidate/`
49-
as `src/generated/<tag>/`), and if RaindexV6 moved run
50-
`script/build-start-blocks.sh` (`LibRaindexV6StartBlock`, `networks.json`,
51-
`subgraph.yaml`).
50+
as `src/generated/<tag>/`), and if RaindexV6 was deployed run
51+
`script/build-start-blocks.sh`.
5252
3. Merge, then push tag `sol-v<version>`. Via rainix `rainix-tag-release.yaml`
5353
(`package-release.yaml`) it re-runs the fork suite and `release-guard`
5454
(version == tag, frozen dir byte-identical to `candidate/`, clean tree, no

‎foundry.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fs_permissions = [
5858
# `src/generated/` and the generated released-suites libs under `src/lib/`.
5959
{ access = "read-write", path = "./src" },
6060
{ access = "read", path = "./subgraph" },
61+
{ access = "read-write", path = "./subgraph/networks.json" },
6162
# script/Build.sol reads `[external.package].version` to name the frozen
6263
# per-release snapshot dir.
6364
{ access = "read", path = "foundry.toml" },

‎script/BuildSubgraphNetworks.sol‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {Script} from "forge-std-1.17.0/src/Script.sol";
6+
import {LibRainDeploy, SupportedNetwork} from "rain-deploy-0.1.25/src/lib/LibRainDeploy.sol";
7+
import {RaindexDeploySuites} from "../src/abstract/RaindexDeploySuites.sol";
8+
import {LibRaindexDeploy} from "../src/lib/deploy/LibRaindexDeploy.sol";
9+
import {LibRaindexSubgraphNetwork} from "../src/lib/deploy/LibRaindexSubgraphNetwork.sol";
10+
11+
/// @title BuildSubgraphNetworks
12+
/// @notice Writes `subgraph/networks.json`: one entry per supported network
13+
/// where the candidate RaindexV6 is live, at its deploy block.
14+
contract BuildSubgraphNetworks is Script, RaindexDeploySuites {
15+
function run() external {
16+
address raindex = LibRaindexDeploy.RAINDEX_DEPLOYED_ADDRESS;
17+
bytes32 codehash = LibRaindexDeploy.RAINDEX_DEPLOYED_CODEHASH;
18+
SupportedNetwork[] memory networks = LibRainDeploy.declaredNetworkConfigs(supportedNetworks());
19+
string memory json = "{}";
20+
for (uint256 i = 0; i < networks.length; i++) {
21+
vm.createSelectFork(networks[i].name);
22+
LibRainDeploy.checkNetworkChainId(networks[i]);
23+
if (raindex.codehash != codehash) {
24+
continue;
25+
}
26+
uint256 startBlock = deployBlock(raindex, codehash);
27+
string memory entryKey = string.concat(networks[i].name, ".Raindex");
28+
vm.serializeAddress(entryKey, "address", raindex);
29+
string memory entry = vm.serializeUint(entryKey, "startBlock", startBlock);
30+
string memory dataSources = vm.serializeString(networks[i].name, "Raindex", entry);
31+
json = vm.serializeString(
32+
"networks", LibRaindexSubgraphNetwork.subgraphNetwork(networks[i].name), dataSources
33+
);
34+
}
35+
vm.writeJson(json, "subgraph/networks.json");
36+
vm.writeLine("subgraph/networks.json", "");
37+
}
38+
39+
/// Not `LibBlockSearch.findDeployBlock`: on Arbitrum chains `block.number`
40+
/// is the L1 block, so the head comes from the RPC. Steps back
41+
/// geometrically for bounded archives.
42+
function deployBlock(address target, bytes32 codehash) internal returns (uint256) {
43+
uint256 high = rpcBlockNumber();
44+
uint256 delta = 250000;
45+
uint256 low = high > delta ? high - delta : 0;
46+
vm.rollFork(low);
47+
while (target.codehash == codehash) {
48+
if (low == 0) {
49+
return 0;
50+
}
51+
high = low;
52+
delta *= 4;
53+
low = high > delta ? high - delta : 0;
54+
vm.rollFork(low);
55+
}
56+
while (high - low > 1) {
57+
uint256 mid = (low + high) / 2;
58+
vm.rollFork(mid);
59+
if (target.codehash == codehash) {
60+
high = mid;
61+
} else {
62+
low = mid;
63+
}
64+
}
65+
return high;
66+
}
67+
68+
function rpcBlockNumber() internal returns (uint256) {
69+
bytes memory result = vm.rpc("eth_blockNumber", "[]");
70+
uint256 n = 0;
71+
for (uint256 i = 0; i < result.length; i++) {
72+
n = (n << 8) | uint8(result[i]);
73+
}
74+
return n;
75+
}
76+
}

‎script/build-start-blocks.sh‎

Lines changed: 7 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,133 +2,13 @@
22
# SPDX-License-Identifier: LicenseRef-DCL-1.0
33
# SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
44

5-
# Refresh the RaindexV6 deploy-block bookkeeping after a release.
6-
#
7-
# Finds the exact block at which the newest released RaindexV6 (the last entry
8-
# of LibRaindexV6Released) was deployed on each mainnet by binary-searching
9-
# `eth_getCode` over an archive RPC, then rewrites the new per-chain blocks +
10-
# that address into:
11-
# - the RAINDEX_START_BLOCK_* constants in src/lib/deploy/LibRaindexV6StartBlock.sol
12-
# - subgraph/networks.json (address + startBlock per network)
13-
# - subgraph/subgraph.yaml (data source address)
14-
#
15-
# RaindexV6 is CREATE2-deployed, so its address only ever holds the one bytecode
16-
# it was created with: the first block with code at that address IS the deploy
17-
# block. Per-chain RPCs come from the foundry rpc_endpoints env vars, falling
18-
# back to public archive endpoints. Set BUILD_START_BLOCKS_INPUT to a file of
19-
# `STARTBLOCK <network> <block>` lines to skip the (RPC-bound) search, e.g. for
20-
# tests.
5+
# Rewrites subgraph/networks.json from chain (script/BuildSubgraphNetworks.sol)
6+
# and points subgraph/subgraph.yaml at the candidate RaindexV6. Needs an
7+
# archive RPC per supported network in the `[rpc_endpoints]` env vars.
218
set -euo pipefail
229

23-
LIB="src/lib/deploy/LibRaindexV6StartBlock.sol"
24-
RELEASED="src/lib/LibRaindexV6Released.sol"
25-
NET="subgraph/networks.json"
26-
YAML="subgraph/subgraph.yaml"
27-
TAG=$(grep -aoE 'storedDeployedAddress: RaindexV6_[A-Za-z0-9_]+_DEPLOYED_ADDRESS' "$RELEASED" | tail -1 \
28-
| sed -E 's/.*RaindexV6_(.+)_DEPLOYED_ADDRESS/\1/' || true)
29-
PTR="src/generated/${TAG}/RaindexV6.sol"
30-
[ -n "$TAG" ] && [ -f "$PTR" ] || { echo "could not read the newest released RaindexV6 snapshot from $RELEASED" >&2; exit 1; }
10+
ADDR=$(grep -aoE 'DEPLOYED_ADDRESS = address\(0x[0-9a-fA-F]{40}' src/generated/candidate/RaindexV6.sol | grep -aoE '0x[0-9a-fA-F]{40}')
11+
[ -n "$ADDR" ] || { echo "could not read DEPLOYED_ADDRESS from src/generated/candidate/RaindexV6.sol" >&2; exit 1; }
3112

32-
ARB_RPC="${ARBITRUM_RPC_URL:-https://arbitrum.drpc.org}"
33-
BASE_RPC="${BASE_RPC_URL:-https://mainnet.base.org}"
34-
FLARE_RPC="${FLARE_RPC_URL:-https://flare-api.flare.network/ext/C/rpc}"
35-
POLY_RPC="${POLYGON_RPC_URL:-https://polygon.drpc.org}"
36-
37-
ADDR=$(grep -aoE 'DEPLOYED_ADDRESS = address\(0x[0-9a-fA-F]{40}' "$PTR" | grep -aoE '0x[0-9a-fA-F]{40}')
38-
[ -n "$ADDR" ] || { echo "could not read RAINDEX_DEPLOYED_ADDRESS from $PTR" >&2; exit 1; }
39-
40-
# rpc_call <rpc> <method> <params-json> -> raw JSON (empty on transport error).
41-
rpc_call() {
42-
curl -fsSL -m 25 -X POST "$1" -H 'Content-Type: application/json' \
43-
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$2\",\"params\":$3}" 2>/dev/null || true
44-
}
45-
46-
# code_at <rpc> <addr> <block-dec> -> yes | no | err. Distinguishes an RPC error
47-
# (retryable) from a genuinely empty account, so the search never mistakes an
48-
# archive-window/timeout boundary for the deploy block.
49-
code_at() {
50-
local hex res
51-
hex=$(printf '0x%x' "$3")
52-
res=$(rpc_call "$1" eth_getCode "[\"$2\",\"$hex\"]")
53-
case "$res" in
54-
*'"result":"0x"'*) echo no ;;
55-
*'"result":"0x'*) echo yes ;;
56-
*) echo err ;;
57-
esac
58-
}
59-
60-
head_of() {
61-
local res
62-
res=$(rpc_call "$1" eth_blockNumber "[]")
63-
printf '%d' "$(printf '%s' "$res" | grep -aoE '"result":"0x[0-9a-f]+"' | grep -aoE '0x[0-9a-f]+')"
64-
}
65-
66-
# find_deploy_block <rpc> <addr> -> first block with code (the CREATE2 deploy).
67-
find_deploy_block() {
68-
local rpc=$1 addr=$2 head delta floor lo hi mid errs=0
69-
head=$(head_of "$rpc")
70-
[ "${head:-0}" -gt 0 ] || { echo "could not read head block from $rpc" >&2; return 1; }
71-
[ "$(code_at "$rpc" "$addr" "$head")" = "yes" ] || { echo "no code for $addr at head on $rpc" >&2; return 1; }
72-
73-
# Walk a lower bound back in growing steps until the address has no code,
74-
# so the search holds however long ago the deploy happened.
75-
delta=250000
76-
floor=$(( head > delta ? head - delta : 1 ))
77-
while [ "$floor" -gt 1 ] && [ "$(code_at "$rpc" "$addr" "$floor")" = "yes" ]; do
78-
delta=$(( delta * 4 ))
79-
floor=$(( head > delta ? head - delta : 1 ))
80-
done
81-
82-
lo=$floor; hi=$head
83-
while [ "$lo" -lt "$hi" ]; do
84-
mid=$(( (lo + hi) / 2 ))
85-
case "$(code_at "$rpc" "$addr" "$mid")" in
86-
yes) hi=$mid ;;
87-
no) lo=$(( mid + 1 )) ;;
88-
err)
89-
errs=$(( errs + 1 ))
90-
[ "$errs" -gt 12 ] && { echo "too many RPC errors searching $rpc" >&2; return 1; }
91-
sleep 2
92-
;;
93-
esac
94-
done
95-
96-
# Verify the boundary on a second, independent read to catch load-balanced
97-
# archives that answer the same historical block inconsistently.
98-
[ "$(code_at "$rpc" "$addr" "$lo")" = "yes" ] || { echo "boundary check failed: no code at $lo on $rpc" >&2; return 1; }
99-
[ "$lo" -le 1 ] || [ "$(code_at "$rpc" "$addr" "$(( lo - 1 ))")" = "no" ] \
100-
|| { echo "boundary check failed: code already at $(( lo - 1 )) on $rpc" >&2; return 1; }
101-
echo "$lo"
102-
}
103-
104-
if [ -n "${BUILD_START_BLOCKS_INPUT:-}" ]; then
105-
OUT=$(cat "$BUILD_START_BLOCKS_INPUT")
106-
else
107-
echo "finding RaindexV6 ($ADDR) deploy block on each chain..." >&2
108-
OUT="STARTBLOCK arbitrum $(find_deploy_block "$ARB_RPC" "$ADDR")
109-
STARTBLOCK base $(find_deploy_block "$BASE_RPC" "$ADDR")
110-
STARTBLOCK flare $(find_deploy_block "$FLARE_RPC" "$ADDR")
111-
STARTBLOCK polygon $(find_deploy_block "$POLY_RPC" "$ADDR")"
112-
fi
113-
114-
get() { printf '%s\n' "$OUT" | grep -aoE "STARTBLOCK $1 [0-9]+" | grep -aoE '[0-9]+$' | head -1; }
115-
ARB=$(get arbitrum); BASE=$(get base); FLARE=$(get flare); POLY=$(get polygon)
116-
for pair in "arbitrum:$ARB" "base:$BASE" "flare:$FLARE" "polygon:$POLY"; do
117-
[ -n "${pair#*:}" ] || { echo "missing start block for ${pair%%:*}; finder output:" >&2; printf '%s\n' "$OUT" >&2; exit 1; }
118-
done
119-
120-
sed -i -E "s/(RAINDEX_START_BLOCK_ARBITRUM = )[0-9]+/\1${ARB}/" "$LIB"
121-
sed -i -E "s/(RAINDEX_START_BLOCK_BASE = )[0-9]+/\1${BASE}/" "$LIB"
122-
sed -i -E "s/(RAINDEX_START_BLOCK_FLARE = )[0-9]+/\1${FLARE}/" "$LIB"
123-
sed -i -E "s/(RAINDEX_START_BLOCK_POLYGON = )[0-9]+/\1${POLY}/" "$LIB"
124-
125-
sed -i -E "s/(address: \")0x[0-9a-fA-F]+(\")/\1${ADDR}\2/" "$YAML"
126-
127-
jq --arg addr "$ADDR" --argjson arb "$ARB" --argjson base "$BASE" --argjson flare "$FLARE" --argjson poly "$POLY" '
128-
."arbitrum-one".Raindex = {address: $addr, startBlock: $arb}
129-
| .matic.Raindex = {address: $addr, startBlock: $poly}
130-
| .base.Raindex = {address: $addr, startBlock: $base}
131-
| .flare.Raindex = {address: $addr, startBlock: $flare}
132-
' "$NET" >"${NET}.tmp" && mv "${NET}.tmp" "$NET"
133-
134-
echo "start blocks: arbitrum=${ARB} base=${BASE} flare=${FLARE} polygon=${POLY} address=${ADDR}"
13+
forge script script/BuildSubgraphNetworks.sol
14+
sed -i -E "s/(address: \")0x[0-9a-fA-F]+(\")/\1${ADDR}\2/" subgraph/subgraph.yaml
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity ^0.8.25;
4+
5+
import {LibRainDeploy} from "rain-deploy-0.1.25/src/lib/LibRainDeploy.sol";
6+
7+
/// @title LibRaindexSubgraphNetwork
8+
/// @notice The subgraph network each deploy network is indexed under, as the
9+
/// `subgraph/networks.json` key and the Ormi network id
10+
/// (https://ormilabs.com/docs/supported-chains/subgraphs).
11+
library LibRaindexSubgraphNetwork {
12+
/// @param network The deploy network with no subgraph network.
13+
error NoSubgraphNetwork(string network);
14+
15+
/// @param network A deploy network, as an `[rpc_endpoints]` alias.
16+
/// @return The subgraph network it is indexed under.
17+
function subgraphNetwork(string memory network) internal pure returns (string memory) {
18+
bytes32 name = keccak256(bytes(network));
19+
if (name == keccak256(bytes(LibRainDeploy.ARBITRUM_ONE))) return "arbitrum-one";
20+
if (name == keccak256(bytes(LibRainDeploy.BASE))) return "base";
21+
if (name == keccak256(bytes(LibRainDeploy.BASE_SEPOLIA))) return "base-sepolia";
22+
if (name == keccak256(bytes(LibRainDeploy.BSC))) return "bsc";
23+
if (name == keccak256(bytes(LibRainDeploy.ETHEREUM))) return "mainnet";
24+
if (name == keccak256(bytes(LibRainDeploy.FLARE))) return "flare";
25+
if (name == keccak256(bytes(LibRainDeploy.HYPEREVM))) return "hyperevm";
26+
if (name == keccak256(bytes(LibRainDeploy.POLYGON))) return "matic";
27+
if (name == keccak256(bytes(LibRainDeploy.ROBINHOOD))) return "robinhood";
28+
revert NoSubgraphNetwork(network);
29+
}
30+
}

‎src/lib/deploy/LibRaindexV6StartBlock.sol‎

Lines changed: 0 additions & 13 deletions
This file was deleted.

‎subgraph/networks.json‎

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1 @@
1-
{
2-
"arbitrum-one": {
3-
"Raindex": {
4-
"address": "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20",
5-
"startBlock": 473899359
6-
}
7-
},
8-
"matic": {
9-
"Raindex": {
10-
"address": "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20",
11-
"startBlock": 88573024
12-
}
13-
},
14-
"base": {
15-
"Raindex": {
16-
"address": "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20",
17-
"startBlock": 47387582
18-
}
19-
},
20-
"flare": {
21-
"Raindex": {
22-
"address": "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20",
23-
"startBlock": 63011168
24-
}
25-
},
26-
"robinhood": {
27-
"Raindex": {
28-
"address": "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20",
29-
"startBlock": 59557818
30-
}
31-
}
32-
}
1+
{}

‎subgraph/subgraph.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dataSources:
66
name: Raindex
77
network: sepolia
88
source:
9-
address: "0x37FC0EFec37D19f8A221aa4F8F7600C9ba2AcD20"
9+
address: "0x91b95120D085Aa99C05167f3767eB63460c1dCaC"
1010
abi: Raindex
1111
startBlock: 0
1212
mapping:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {Test} from "forge-std-1.17.0/src/Test.sol";
6+
import {RaindexDeploySuites} from "src/abstract/RaindexDeploySuites.sol";
7+
import {LibRaindexSubgraphNetwork} from "src/lib/deploy/LibRaindexSubgraphNetwork.sol";
8+
9+
/// @title LibRaindexSubgraphNetworkSubgraphNetworkTest
10+
/// @notice Every supported network has its own subgraph network, and any other
11+
/// name is refused.
12+
contract LibRaindexSubgraphNetworkSubgraphNetworkTest is Test, RaindexDeploySuites {
13+
function subgraphNetworkExternal(string memory network) external pure returns (string memory) {
14+
return LibRaindexSubgraphNetwork.subgraphNetwork(network);
15+
}
16+
17+
function testSubgraphNetworkDistinctPerSupportedNetwork() external view {
18+
string[] memory networks = supportedNetworks();
19+
for (uint256 i = 0; i < networks.length; i++) {
20+
string memory a = LibRaindexSubgraphNetwork.subgraphNetwork(networks[i]);
21+
for (uint256 j = i + 1; j < networks.length; j++) {
22+
assertNotEq(a, LibRaindexSubgraphNetwork.subgraphNetwork(networks[j]), networks[i]);
23+
}
24+
}
25+
}
26+
27+
function testSubgraphNetworkUnknown(string memory network) external {
28+
string[] memory networks = supportedNetworks();
29+
for (uint256 i = 0; i < networks.length; i++) {
30+
vm.assume(keccak256(bytes(network)) != keccak256(bytes(networks[i])));
31+
}
32+
vm.expectRevert(abi.encodeWithSelector(LibRaindexSubgraphNetwork.NoSubgraphNetwork.selector, network));
33+
this.subgraphNetworkExternal(network);
34+
}
35+
}

0 commit comments

Comments
 (0)