feat: rise deployment - #16
Conversation
WalkthroughThe PR adds RISE network configuration, explorer verification support, OpenRouter integration, production addresses, and CREATE3 deployment scripts for ChangesRISE network and explorer support
Manipulator deployment persistence
MathManipulator CREATE3 deployment
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
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
deployments.csvis excluded by!**/*.csv
📒 Files selected for processing (12)
.env.exampledeployments/prod/addresses/rise.jsonhardhat.config.tspackage.jsonscripts/deploy/create3.tsscripts/deploy/deployAcrossERC20AmountManipulator.tsscripts/deploy/deployMathManipulator.tsscripts/deploy/manipulatorAddresses.tsscripts/deploy/networks.tsscripts/deploy/openRouterBuild.tsscripts/deploy/verifyAllowanceHolderBatch.tsscripts/e2e/config.ts
| rise: { | ||
| url: process.env.RISE_RPC ?? 'https://rpc.risechain.com/', | ||
| chainId: 4153, | ||
| accounts, | ||
| }, |
There was a problem hiding this comment.
🩺 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.tsRepository: 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.")
PYRepository: 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
| /** 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), | ||
| ); |
There was a problem hiding this comment.
🔒 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:
- 1: https://github.com/pcaversaccio/createx
- 2: https://github.com/pcaversaccio/createx/blob/main/README.md
- 3: https://dev.to/beltsys-labs/building-a-deterministic-wallet-factory-for-evm-chains-with-createx-36m7
- 4: https://github.com/pcaversaccio/createx/blob/73336a84/README.md
🏁 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:
- 1: https://github.com/pcaversaccio/createx
- 2: https://github.com/pcaversaccio/createx/blob/main/README.md
- 3: https://dev.to/beltsys-labs/building-a-deterministic-wallet-factory-for-evm-chains-with-createx-36m7
- 4: https://github.com/pcaversaccio/createx/blob/main/src/CreateX.sol
- 5: 🐛 No Way to Avoid Salt Being Re-Hashed pcaversaccio/createx#98
🏁 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()
PYRepository: 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'
doneRepository: 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))
PYRepository: 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))
PYRepository: 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 and0x00at 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 compiledMathManipulatorbytecode 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-L48scripts/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
| 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', | ||
| ); |
There was a problem hiding this comment.
🗄️ 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 | sortRepository: 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 || trueRepository: 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)
PYRepository: 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
Summary by CodeRabbit