Skip to content

feat: rise deployment - #16

Merged
arthcp merged 1 commit into
mainfrom
feat/rise-migration
Aug 11, 2026
Merged

feat: rise deployment#16
arthcp merged 1 commit into
mainfrom
feat/rise-migration

Conversation

@arthcp

@arthcp arthcp commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added support for deploying and verifying contracts on the RISE mainnet.
    • Added deployment support for the Math Manipulator, including address tracking.
    • Added RISE to supported network and end-to-end configuration.
    • Added production contract addresses for RISE deployments.
    • Added optional block explorer API key configuration for RISE verification.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds RISE network configuration, explorer verification support, OpenRouter integration, production addresses, and CREATE3 deployment scripts for MathManipulator and AcrossERC20AmountManipulator.

Changes

RISE network and explorer support

Layer / File(s) Summary
Network configuration and deployment registration
.env.example, hardhat.config.ts, scripts/deploy/networks.ts, scripts/deploy/openRouterBuild.ts, scripts/e2e/config.ts
RISE is configured with chain ID 4153, RPC settings, explorer URLs, transaction links, and OpenRouter deployment entries.
Optional explorer API-key handling
scripts/deploy/verifyAllowanceHolderBatch.ts
Custom verifiers can omit API keys when configured as optional. RISE uses this behavior.

Manipulator deployment persistence

Layer / File(s) Summary
Deployment address storage
scripts/deploy/manipulatorAddresses.ts, scripts/deploy/deployAcrossERC20AmountManipulator.ts, deployments/prod/addresses/rise.json
Manipulator addresses are read, updated, and written to stage and network deployment JSON files. Existing and new AcrossERC20AmountManipulator deployments now persist their addresses.

MathManipulator CREATE3 deployment

Layer / File(s) Summary
CREATE3 constants and deployment flow
scripts/deploy/create3.ts, scripts/deploy/deployMathManipulator.ts, package.json
The PR adds MathManipulator salt and expected-address constants, a validated CREATE3 deployment flow, optional verification, failure handling, and an npm deployment script.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant deployMathManipulator
  participant CreateX
  participant RISE
  participant DeploymentJSON
  participant Explorer
  deployMathManipulator->>CreateX: Predict CREATE3 address
  deployMathManipulator->>RISE: Submit deployment transaction
  RISE-->>deployMathManipulator: Return receipt and deployed address
  deployMathManipulator->>DeploymentJSON: Persist confirmed address
  deployMathManipulator->>Explorer: Verify deployment when enabled
Loading

Possibly related PRs

Suggested reviewers: sebastiantf

Poem

RISE joins the network bright,
CREATE3 fixes addresses tight.
Salt and bytecode chart the way,
JSON records where contracts stay.
Explorers verify the flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding Rise deployment support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rise-migration

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In `@hardhat.config.ts`:
- Around line 149-153: Normalize the RISE environment variables before applying
fallbacks: in the rise network configuration, use a trimmed, non-empty RISE_RPC
value or the default RPC URL; likewise, use a trimmed, non-empty
RISE_ETHERSCAN_KEY value or the intended blockscout fallback at
hardhat.config.ts lines 149-153 and 275-275. Update both affected sites
consistently.

In `@scripts/deploy/create3.ts`:
- Around line 58-64: Protect the MathManipulator CREATE3 deployment and reject
arbitrary pre-existing code: in scripts/deploy/create3.ts lines 58-64, derive
the salt using the deployment authority in the first 20 bytes and 0x00 at byte
20, then regenerate the expected address; in
scripts/deploy/deployMathManipulator.ts lines 40-48, validate existing runtime
bytecode against the compiled MathManipulator bytecode or enforce its
contract-specific invariants before persisting; in
scripts/deploy/deployMathManipulator.ts lines 62-79, use the protected salt and
regenerated expected address for both the static call and deployment.

In `@scripts/deploy/manipulatorAddresses.ts`:
- Around line 23-40: Wrap the read-modify-write sequence in the deployment
address update flow around deployments, deployments[contractName], and writeFile
with a per-file cross-process lock keyed by filePath. Acquire the lock before
reading and hold it through the write, then release it reliably on success or
error while preserving the existing ENOENT handling and output format.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9298cda5-5a73-46e7-8ef1-4f42c481e25d

📥 Commits

Reviewing files that changed from the base of the PR and between 5f0108e and ea7617c.

⛔ Files ignored due to path filters (1)
  • deployments.csv is excluded by !**/*.csv
📒 Files selected for processing (12)
  • .env.example
  • deployments/prod/addresses/rise.json
  • hardhat.config.ts
  • package.json
  • scripts/deploy/create3.ts
  • scripts/deploy/deployAcrossERC20AmountManipulator.ts
  • scripts/deploy/deployMathManipulator.ts
  • scripts/deploy/manipulatorAddresses.ts
  • scripts/deploy/networks.ts
  • scripts/deploy/openRouterBuild.ts
  • scripts/deploy/verifyAllowanceHolderBatch.ts
  • scripts/e2e/config.ts

Comment thread hardhat.config.ts
Comment on lines +149 to +153
rise: {
url: process.env.RISE_RPC ?? 'https://rpc.risechain.com/',
chainId: 4153,
accounts,
},

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

rg -n -C 2 'RISE_RPC|RISE_ETHERSCAN_KEY|process\.env\[' \
  .env.example hardhat.config.ts scripts/deploy/networks.ts

Repository: SocketDotTech/openrouter

Length of output: 2499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- hardhat.config.ts relevant definitions ---'
sed -n '1,45p;135,160p;245,285p' hardhat.config.ts

printf '%s\n' '--- environment and package metadata ---'
sed -n '35,48p;78,90p' .env.example
rg -n -C 3 'hardhat|`@nomicfoundation/hardhat-verify`|explorerApiKey|RISE_ETHERSCAN_KEY' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

printf '%s\n' '--- all RISE references ---'
rg -n -C 2 'RISE_RPC|RISE_ETHERSCAN_KEY' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .

Repository: SocketDotTech/openrouter

Length of output: 30993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const fallbackRpc = 'https://rpc.risechain.com/';
const fallbackExplorer = 'blockscout';
const samples = [undefined, '', '   ', 'https://custom.example/rpc'];

for (const value of samples) {
  const currentRpc = value ?? fallbackRpc;
  const currentExplorer = value ?? fallbackExplorer;
  const trimmed = value?.trim();
  const correctedRpc = trimmed || fallbackRpc;
  const correctedExplorer = trimmed || fallbackExplorer;

  console.log(JSON.stringify({
    input: value,
    currentRpc,
    currentExplorer,
    correctedRpc,
    correctedExplorer,
  }));
}
JS

python3 - <<'PY'
from pathlib import Path

text = Path("hardhat.config.ts").read_text()
assert "url: process.env.RISE_RPC ?? 'https://rpc.risechain.com/'" in text
assert "rise: process.env.RISE_ETHERSCAN_KEY ?? 'blockscout'" in text
assert "explorerApiKey(process.env.RISE_ETHERSCAN_KEY)" not in text
print("Both RISE sites use nullish coalescing directly, and the explorer site does not use the non-empty-key helper.")
PY

Repository: SocketDotTech/openrouter

Length of output: 908


Treat blank RISE variables as unset.

Use trimmed, non-empty values before applying the fallbacks at hardhat.config.ts#L149-L153 and hardhat.config.ts#L275-L275. Otherwise, a blank RISE_RPC produces an invalid RPC URL, and a blank RISE_ETHERSCAN_KEY bypasses the intended blockscout fallback.

📍 Affects 1 file
  • hardhat.config.ts#L149-L153 (this comment)
  • hardhat.config.ts#L275-L275
🤖 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 `@hardhat.config.ts` around lines 149 - 153, Normalize the RISE environment
variables before applying fallbacks: in the rise network configuration, use a
trimmed, non-empty RISE_RPC value or the default RPC URL; likewise, use a
trimmed, non-empty RISE_ETHERSCAN_KEY value or the intended blockscout fallback
at hardhat.config.ts lines 149-153 and 275-275. Update both affected sites
consistently.

Source: Path instructions

Comment thread scripts/deploy/create3.ts
Comment on lines +58 to +64
/** CREATE3 salt label used by `deployMathManipulator.ts`. */
export const MATH_MANIPULATOR_CREATE3_SALT_TEXT = 'MathManipulator';

/** Keccak256 salt for deterministic MathManipulator CREATE3 deployments. */
export const MATH_MANIPULATOR_CREATE3_SALT = keccak256(
toUtf8Bytes(MATH_MANIPULATOR_CREATE3_SALT_TEXT),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

For CreateX deployCreate3(bytes32,bytes), how must a salt encode msg.sender to enable permissioned deploy protection while preserving cross-chain deterministic addresses?

💡 Result:

To enable permissioned deploy protection while using CreateX's deployCreate3, you must encode msg.sender into the first 20 bytes of the salt [1][2][3]. Because CreateX uses these bytes to verify that the transaction initiator matches the address embedded in the salt, this ensures that only the specified address can perform the deployment [3]. Regarding the preservation of cross-chain deterministic addresses, you must decide whether to enable or disable cross-chain redeploy protection using the 21st byte of the salt [2][4]: 1. To preserve the same address across all chains (disable cross-chain redeploy protection), set the 21st byte (index 20) to 0x00 [3]. 2. To use different addresses per chain (enable cross-chain redeploy protection), set the 21st byte to 0x01 [2][4]. Any value greater than 0x01 in the 21st byte will cause the transaction to revert, as CreateX enforces developer explicitness regarding these protection mechanisms [2][4]. The remaining 11 bytes (indices 21–31) are available for arbitrary entropy, such as a unique identifier, to distinguish deployments while maintaining the desired protection levels [3]. Summary of salt structure: - Bytes 0-19: msg.sender (Permissioned Deploy Protection) [3] - Byte 20: 0x00 (Same address on all chains) or 0x01 (Different address per chain) [2][3][4] - Bytes 21-31: Entropy [3]

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- tracked candidate files ---'
git ls-files \
  'scripts/deploy/create3.ts' \
  'scripts/deploy/deployMathManipulator.ts' \
  '*create*x*' \
  '*MathManipulator*' \
  '*addresses*' | sed -n '1,160p'

printf '%s\n' '--- create3.ts ---'
cat -n scripts/deploy/create3.ts | sed -n '1,140p'

printf '%s\n' '--- deployMathManipulator.ts ---'
cat -n scripts/deploy/deployMathManipulator.ts | sed -n '1,150p'

printf '%s\n' '--- CREATE3 and expected-address references ---'
rg -n -S 'MATH_MANIPULATOR_(CREATE3_SALT|EXPECTED_ADDRESS)|deployCreate3|create3Factory|hasContractBytecode|persist\(' \
  scripts test tests contracts config deployments 2>/dev/null | sed -n '1,260p'

Repository: SocketDotTech/openrouter

Length of output: 20806


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- create3 helpers and ABI ---'
cat -n scripts/deploy/create3.ts | sed -n '140,370p'

printf '%s\n' '--- comparable deployment flows ---'
for f in \
  scripts/deploy/deployCctpClaimExecutor.ts \
  scripts/deploy/deployRFQVaultExecutor.ts \
  scripts/deploy/deployEtherealExecutor.ts \
  scripts/deploy/deployCelerExecutor.ts \
  scripts/deploy/deployHypercoreExecutor.ts \
  scripts/deploy/deployAcrossERC20AmountManipulator.ts \
  scripts/deploy/deployOpenRouter.ts
do
  if [ -f "$f" ]; then
    printf '\n### %s\n' "$f"
    rg -n -A18 -B8 'existingBytecode|deployCreate3|computeFinalAddress|persist\(' "$f" | sed -n '1,180p'
  fi
done

printf '%s\n' '--- tests and documentation mentioning MathManipulator/CreateX salts ---'
rg -n -S 'MathManipulator|CREATE3_SALT|CreateX|permissioned|salt' \
  test tests scripts README.md docs 2>/dev/null | sed -n '1,260p'

Repository: SocketDotTech/openrouter

Length of output: 39974


🌐 Web query:

CreateX source _guard deployCreate3 salt first 20 bytes msg.sender bytes 20 cross-chain redeploy protection exact implementation

💡 Result:

The CreateX deployCreate3 implementation uses a specific 32-byte salt structure to enable security features like permissioned deploy protection and cross-chain redeploy protection. The salt is structured as follows [1][2]: Salt Structure (32 bytes) - Bytes 0-19: Permissioned Deploy Protection. Set these to your msg.sender (e.g., your factory address or EOA) to ensure that only you can trigger the deployment using this salt [1][3]. - Byte 20: Cross-chain Redeploy Protection. - 0x00: Disables cross-chain redeploy protection (the same address is generated on all chains) [3]. - 0x01: Enables cross-chain redeploy protection (the address will differ across chains) [1][3]. - Any value > 0x01: Causes the transaction to revert with an InvalidSalt error, enforcing developer explicitness [1][4]. - Bytes 21-31: Entropy. These 11 bytes provide room for custom entropy (e.g., keccak256 hash of a unique ID) to generate deterministic addresses [1][3]. Implementation Logic CreateX enforces these protections via internal guard functions (typically _guard). When deployCreate3 is called, the provided 32-byte salt is not used raw; it is processed through an _efficientHash (which includes msg.sender and/or block.chainid) to create a guardedSalt [1][4][5]. This prevents attackers from simply copying a salt from one chain and reusing it on another [4][5]. Best Practices - Always explicitly define the 21st byte (index 20) in your salt generation logic to avoid accidental rejections [3]. - Use the permissioned deploy protection (first 20 bytes) to prevent unauthorized parties from frontrunning your deployments with the same salt [1][2][3]. Example of Salt Generation (Solidity) function buildSalt(bytes32 uniqueId) public view returns (bytes32 salt) { salt = bytes32( abi.encodePacked( address(this), // 20 bytes: Permissioned protection hex"00", // 1 byte: 0x00 = No cross-chain protection bytes11(uint88(uint256(keccak256(abi.encodePacked(uniqueId))))) // 11 bytes: Entropy)); // Assertion ensures explicit configuration assert(uint8(salt) == 0x00); }

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from urllib.request import urlopen
urls = [
    "https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol",
    "https://raw.githubusercontent.com/pcaversaccio/createx/main/README.md",
]
for url in urls:
    print(f"--- {url} ---")
    try:
        text = urlopen(url, timeout=10).read().decode()
    except Exception as exc:
        print(type(exc).__name__, exc)
        continue
    lines = text.splitlines()
    needles = ("function _guard", "deployCreate3", "permissioned", "cross-chain", "salt")
    for i, line in enumerate(lines):
        if any(n.lower() in line.lower() for n in needles):
            lo, hi = max(0, i-5), min(len(lines), i+15)
            print("\n".join(f"{j+1}:{lines[j]}" for j in range(lo, hi)))
            print()
PY

Repository: SocketDotTech/openrouter

Length of output: 612


🏁 Script executed:

#!/bin/bash
set -eu

for url in \
  https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol \
  https://raw.githubusercontent.com/pcaversaccio/createx/main/README.md
do
  printf '%s\n' "--- $url ---"
  curl -ksSfL "$url" | tr -d '\r' | \
    rg -n -A24 -B12 '_guard|deployCreate3|permissioned|cross.?chain|InvalidSalt|salt' | sed -n '1,320p'
done

Repository: SocketDotTech/openrouter

Length of output: 36777


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
curl -ksSfL https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol > "$tmp"

printf '%s\n' '--- relevant CreateX source locations ---'
rg -n '_guard|deployCreate3|computeCreate3Address|SenderBytes|RedeployProtectionFlag' "$tmp"

printf '%s\n' '--- guard implementation and CREATE3 implementation ---'
line=$(rg -n '^    function _guard' "$tmp" | head -1 | cut -d: -f1)
if [ -n "${line:-}" ]; then
  start=$((line-12)); end=$((line+80))
  sed -n "${start},${end}p" "$tmp"
fi

line=$(rg -n 'function deployCreate3\(bytes32' "$tmp" | head -1 | cut -d: -f1)
if [ -n "${line:-}" ]; then
  start=$((line-12)); end=$((line+70))
  sed -n "${start},${end}p" "$tmp"
fi

printf '%s\n' '--- README special-feature section ---'
line=$(rg -n 'Permissioned Deploy Protection and Cross-Chain Redeploy Protection' README.tmp "$tmp" 2>/dev/null | head -1 | cut -d: -f1 || true)
rg -n -A100 -B8 'Permissioned Deploy Protection and Cross-Chain Redeploy Protection' "$tmp" | sed -n '1,180p'

Repository: SocketDotTech/openrouter

Length of output: 16099


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- MathManipulator implementation and address persistence ---'
cat -n src/manipulators/MathManipulator.sol | sed -n '1,240p'
cat -n scripts/deploy/manipulatorAddresses.ts | sed -n '1,220p'

printf '%s\n' '--- MathManipulator consumers and persisted entries ---'
rg -n -S 'MathManipulator|mathManipulator|manipulator' \
  src scripts deployments config test tests 2>/dev/null | sed -n '1,300p'

printf '%s\n' '--- deployment address records containing MathManipulator ---'
rg -n -S 'MathManipulator' deployments *.json 2>/dev/null | sed -n '1,180p'

Repository: SocketDotTech/openrouter

Length of output: 7180


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

try:
    from Crypto.Hash import keccak
except Exception as exc:
    print("PyCryptodome unavailable:", exc)
    raise SystemExit(0)

def k256(data: bytes) -> bytes:
    h = keccak.new(digest_bits=256)
    h.update(data)
    return h.digest()

def abi_encode_bytes32(value: bytes) -> bytes:
    assert len(value) == 32
    return value

def rlp_string(value: bytes) -> bytes:
    if len(value) == 1 and value[0] < 0x80:
        return value
    if len(value) <= 55:
        return bytes([0x80 + len(value)]) + value
    n = len(value).to_bytes((len(value).bit_length() + 7) // 8, "big")
    return bytes([0xb7 + len(n)]) + n + value

def rlp_list(items: list[bytes]) -> bytes:
    payload = b"".join(items)
    if len(payload) <= 55:
        return bytes([0xc0 + len(payload)]) + payload
    n = len(payload).to_bytes((len(payload).bit_length() + 7) // 8, "big")
    return bytes([0xf7 + len(n)]) + n + payload

def address_from_create3(guarded_salt: bytes) -> str:
    factory = bytes.fromhex("ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"[2:] if False else "ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed")
    proxy_init_hash = bytes.fromhex("".join(re.findall(
        r"CREATE3_PROXY_INIT_CODE_HASH = keccak256\(\s*'0x([0-9a-f]+)'",
        Path("scripts/deploy/create3.ts").read_text(),
    )))
    proxy = k256(b"\xff" + factory + guarded_salt + proxy_init_hash)[12:]
    return "0x" + k256(rlp_list([rlp_string(proxy), rlp_string(b"\x01")]))[12:].hex()

salt_text = b"MathManipulator"
raw = k256(salt_text)
guarded = k256(abi_encode_bytes32(raw))
expected = "0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581"

print("raw salt:", raw.hex())
print("raw first 20 bytes:", "0x" + raw[:20].hex())
print("raw byte 20:", f"0x{raw[20]:02x}")
print("guarded salt for CreateX Random path:", guarded.hex())
print("computed final address:", address_from_create3(guarded))
print("matches repository expected:", address_from_create3(guarded).lower() == expected.lower())
print("raw salt is permissioned for arbitrary deployer:", raw[:20] == b"\x00" * 20)
print("raw salt uses an explicit valid flag:", raw[20] in (0, 1))
PY

Repository: SocketDotTech/openrouter

Length of output: 213


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

# Minimal read-only Keccak-256 implementation for deterministic address checks.
RC = [
    1, 0x8082, 0x800000000000808A, 0x8000000080008000,
    0x808B, 0x80000001, 0x8000000080008081, 0x8000000000008009,
    0x8A, 0x88, 0x80008009, 0x8000000A,
    0x8000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
    0x8000000000008002, 0x8000000000000080, 0x800A, 0x800000008000000A,
    0x8000000080008081, 0x8000000000008080, 0x80000001, 0x8000000080008008,
]
ROT = [
    [0, 36, 3, 41, 18],
    [1, 44, 10, 45, 2],
    [62, 6, 43, 15, 61],
    [28, 55, 25, 21, 56],
    [27, 20, 39, 8, 14],
]
MASK = (1 << 64) - 1

def rol(x, n):
    return ((x << n) | (x >> (64 - n))) & MASK if n else x

def keccak_f(a):
    for rc in RC:
        c = [a[x] ^ a[x+5] ^ a[x+10] ^ a[x+15] ^ a[x+20] for x in range(5)]
        d = [c[(x-1) % 5] ^ rol(c[(x+1) % 5], 1) for x in range(5)]
        for x in range(5):
            for y in range(5):
                a[x + 5*y] ^= d[x]
        b = [0] * 25
        for x in range(5):
            for y in range(5):
                b[y + 5*((2*x + 3*y) % 5)] = rol(a[x + 5*y], ROT[x][y])
        for x in range(5):
            for y in range(5):
                a[x + 5*y] = b[x + 5*y] ^ ((~b[(x+1) % 5 + 5*y]) & b[(x+2) % 5 + 5*y])
        a[0] ^= rc
    return a

def k256(data):
    rate = 136
    padded = bytearray(data)
    padded.append(0x01)
    padded.extend(b"\x00" * ((rate - (len(padded) % rate) - 1) % rate))
    padded.append(0x80)
    state = [0] * 25
    for off in range(0, len(padded), rate):
        block = padded[off:off+rate]
        for i in range(rate // 8):
            state[i] ^= int.from_bytes(block[i*8:i*8+8], "little")
        keccak_f(state)
    out = b"".join(x.to_bytes(8, "little") for x in state)
    return out[:32]

assert k256(b"") .hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
assert k256(b"MathManipulator").hex() == "TODO".replace("TODO", k256(b"MathManipulator").hex())

def rlp_string(value):
    if len(value) == 1 and value[0] < 0x80:
        return value
    return bytes([0x80 + len(value)]) + value

def rlp_list(items):
    payload = b"".join(items)
    return bytes([0xc0 + len(payload)]) + payload

raw = k256(b"MathManipulator")
guarded = k256(raw)
factory = bytes.fromhex("ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed")
init_hash_hex = re.search(
    r"CREATE3_PROXY_INIT_CODE_HASH = keccak256\(\s*'0x([0-9a-f]+)'",
    Path("scripts/deploy/create3.ts").read_text(),
).group(1)
proxy_init_hash = k256(bytes.fromhex(init_hash_hex))
proxy = k256(b"\xff" + factory + guarded + proxy_init_hash)[12:]
final = k256(rlp_list([rlp_string(proxy), rlp_string(b"\x01")]))[12:]

print("raw salt:", raw.hex())
print("raw first 20 bytes:", "0x" + raw[:20].hex())
print("raw byte 20:", f"0x{raw[20]:02x}")
print("guarded salt:", guarded.hex())
print("proxy init hash:", proxy_init_hash.hex())
print("computed final address:", "0x" + final.hex())
print("repository expected address:", "0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581")
print("matches repository expected:", final.hex().lower() == "3c3d4f3d636c06bceb3bcc71eafe36486ef40581")
print("raw salt is permissioned for arbitrary deployer:", raw[:20] == b"\x00" * 20)
print("raw salt uses an explicit valid flag:", raw[20] in (0, 1))
PY

Repository: SocketDotTech/openrouter

Length of output: 751


Protect the MathManipulator CREATE3 salt and validate existing code.

keccak256(toUtf8Bytes('MathManipulator')) has a nonzero first 20 bytes and 0xc8 at byte 20. CreateX treats this salt as Random/Unspecified, so any caller can deploy to the derived address first. The existing-bytecode branch then accepts arbitrary code and persists that address as MathManipulator.

  • scripts/deploy/create3.ts#L58-L64: derive the salt with the deployment authority in the first 20 bytes and 0x00 at byte 20 for the same address across chains. Regenerate the expected address.
  • scripts/deploy/deployMathManipulator.ts#L40-L48: compare the existing runtime bytecode with the compiled MathManipulator bytecode or enforce contract-specific invariants before persisting.
  • scripts/deploy/deployMathManipulator.ts#L62-L79: use the protected salt and regenerated expected address for both the static call and deployment.
📍 Affects 2 files
  • scripts/deploy/create3.ts#L58-L64 (this comment)
  • scripts/deploy/deployMathManipulator.ts#L40-L48
  • scripts/deploy/deployMathManipulator.ts#L62-L79
🤖 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 `@scripts/deploy/create3.ts` around lines 58 - 64, Protect the MathManipulator
CREATE3 deployment and reject arbitrary pre-existing code: in
scripts/deploy/create3.ts lines 58-64, derive the salt using the deployment
authority in the first 20 bytes and 0x00 at byte 20, then regenerate the
expected address; in scripts/deploy/deployMathManipulator.ts lines 40-48,
validate existing runtime bytecode against the compiled MathManipulator bytecode
or enforce its contract-specific invariants before persisting; in
scripts/deploy/deployMathManipulator.ts lines 62-79, use the protected salt and
regenerated expected address for both the static call and deployment.

Source: Path instructions

Comment on lines +23 to +40
try {
deployments = JSON.parse(await readFile(filePath, 'utf8')) as Record<
string,
string
>;
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
throw err;
}
}

deployments[contractName] = address;
await mkdir(dirname(filePath), { recursive: true });
await writeFile(
filePath,
`${JSON.stringify(deployments, null, 2)}\n`,
'utf8',
);

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 -eu
file=$(git ls-files | rg '^scripts/deploy/manipulatorAddresses\.ts$')
printf '%s\n' "$file"
ast-grep outline "$file" --lang typescript
printf '\n--- file ---\n'
cat -n "$file"
printf '\n--- helper usages ---\n'
rg -n -C 3 'writeManipulatorAddress|manipulatorAddresses' scripts
printf '\n--- deployment-related files ---\n'
git ls-files scripts/deploy | sort

Repository: SocketDotTech/openrouter

Length of output: 6463


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- deployMathManipulator.ts ---'
cat -n scripts/deploy/deployMathManipulator.ts
printf '%s\n' '--- deployAcrossERC20AmountManipulator.ts ---'
cat -n scripts/deploy/deployAcrossERC20AmountManipulator.ts
printf '%s\n' '--- package scripts and deployment references ---'
rg -n -C 2 'deploy(Math|AcrossERC20AmountManipulator)|deployAcrossERC20AmountManipulator|deployMathManipulator' package.json .github scripts README.md 2>/dev/null || true
printf '%s\n' '--- existing file-lock or serialization patterns ---'
rg -n -i -C 2 'lockfile|proper-lockfile|mkdir.*lock|flock|rename\(.*tmp|writeFile.*tmp|exclusive.*flag' --glob '*.{ts,js,mjs,cjs,json}' . 2>/dev/null || true

Repository: SocketDotTech/openrouter

Length of output: 11562


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- deploymentRegistry lock implementation ---'
cat -n scripts/deploy/deploymentRegistry.ts | sed -n '55,125p'
printf '%s\n' '--- deploymentRegistry write path ---'
cat -n scripts/deploy/deploymentRegistry.ts | sed -n '350,395p'
printf '%s\n' '--- deterministic read-modify-write race probe ---'
python3 - <<'PY'
import json

initial = {}
# Both invocations complete their read before either write.
a = dict(initial)
b = dict(initial)
a["MathManipulator"] = "0xmath"
b["AcrossERC20AmountManipulator"] = "0xacross"

# The second write replaces the complete file, rather than merging with it.
first_write = json.dumps(a, sort_keys=True)
final_write = json.dumps(b, sort_keys=True)
final = json.loads(final_write)
print("first_write:", first_write)
print("final_write:", final_write)
print("lost MathManipulator:", "MathManipulator" not in final)
PY

Repository: SocketDotTech/openrouter

Length of output: 4336


Serialize updates to each deployment address file.

Concurrent runs for the same network can lose one address during the read-modify-write sequence. This should use a per-file cross-process lock around both operations. An atomic rename alone does not prevent the lost update.

🤖 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 `@scripts/deploy/manipulatorAddresses.ts` around lines 23 - 40, Wrap the
read-modify-write sequence in the deployment address update flow around
deployments, deployments[contractName], and writeFile with a per-file
cross-process lock keyed by filePath. Acquire the lock before reading and hold
it through the write, then release it reliably on success or error while
preserving the existing ENOENT handling and output format.

Source: Path instructions

@arthcp
arthcp merged commit 03de364 into main Aug 11, 2026
2 checks passed
@arthcp
arthcp deleted the feat/rise-migration branch August 11, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants