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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ PRIVATE_KEY=

# Constructor arguments / deployment checks
OWNER_ADDRESS=
SOLVER_SIGNER_ADDRESS= # BungeeReceiver deploy only; defaults to deployer
OPENROUTER_ADDRESS= # optional override for check:openrouter
ACROSS_MANIPULATOR_ADDRESS= # optional override for check:across-manipulator

# External API keys
RELAY_API_KEY= # optional, relay.link x-api-key header
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ docs/

# Dotenv file
.env
.env.op

dist/

node_modules

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
"scripts": {
"compile": "hardhat compile",
"deploy": "hardhat run scripts/deploy/deployOpenRouter.ts --network",
"deploy:openrouter": "hardhat run scripts/deploy/deployOpenRouter.ts --network",
"deploy:receiver-executor": "hardhat run scripts/deploy/deployReceiverAndExecutor.ts --network",
"deploy:across-manipulator": "hardhat run scripts/deploy/deployAcrossERC20AmountManipulator.ts --network",
"deploy:v2": "hardhat run scripts/deploy/deployOpenRouterV2.ts --network",
"check:openrouter": "hardhat run scripts/deploy/checkOpenRouterDeployment.ts",
"check:receiver": "hardhat run scripts/deploy/checkReceiverDeployment.ts",
"check:across-manipulator": "hardhat run scripts/deploy/checkAcrossManipulatorDeployment.ts",
"typechain": "hardhat typechain",
"slither": "bash scripts/docker-slither.sh"
},
Expand Down
113 changes: 113 additions & 0 deletions scripts/deploy/checkReceiverDeployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Checks that BungeeReceiver and CalldataExecutor are deployed on the current network.
* Addresses are derived from CREATE3 salts via CreateX (guarded salt + factory deployer).
*
* Usage:
* npx hardhat run scripts/deploy/checkReceiverDeployment.ts --network <network>
*
* Optional env vars:
* OWNER_ADDRESS — if set, assert BungeeReceiver owner() matches this address
*/

import hre from 'hardhat';
import { ethers } from 'hardhat';
import { Contract } from 'ethers';
import {
BUNGEE_RECEIVER_CREATE3_SALT,
CALLDATA_EXECUTOR_CREATE3_SALT,
CREATE_X_FACTORY,
Create3ABI,
computeFinalAddress,
getBungeeReceiverDeploymentStatus,
getCalldataExecutorDeploymentStatus,
} from './create3';

async function main() {
const networkName = hre.network.name;
const { chainId } = await ethers.provider.getNetwork();

const create3Factory = new Contract(
CREATE_X_FACTORY,
Create3ABI,
ethers.provider,
);

// Do not use computeCreate3Address(rawSalt, eoa): CreateX uses guarded salt + factory deployer.
const receiverAddress = await computeFinalAddress(
BUNGEE_RECEIVER_CREATE3_SALT,
create3Factory,
);
const executorAddress = await computeFinalAddress(
CALLDATA_EXECUTOR_CREATE3_SALT,
create3Factory,
);

let hasError = false;

// ── Check CalldataExecutor ───────────────────────────────────────────────────

const executorStatus = await getCalldataExecutorDeploymentStatus({
provider: ethers.provider,
address: executorAddress,
});

if (!executorStatus.deployed) {
console.error(
`CalldataExecutor NOT deployed on ${networkName} (chainId=${chainId}) at ${executorAddress}`,
);
hasError = true;
} else {
if (
executorStatus.bungeeReceiver?.toLowerCase() !==
receiverAddress.toLowerCase()
) {
console.error(
`CalldataExecutor BUNGEE_RECEIVER mismatch on ${networkName} (chainId=${chainId}): ` +
`executor=${executorAddress}, expected receiver=${receiverAddress}, got ${executorStatus.bungeeReceiver}`,
);
hasError = true;
} else {
console.log(
`CalldataExecutor deployed on ${networkName} (chainId=${chainId}) at ${executorAddress}, BUNGEE_RECEIVER=${executorStatus.bungeeReceiver}`,
);
}
}

// ── Check BungeeReceiver ─────────────────────────────────────────────────────

const receiverStatus = await getBungeeReceiverDeploymentStatus({
provider: ethers.provider,
address: receiverAddress,
});

if (!receiverStatus.deployed) {
console.error(
`BungeeReceiver NOT deployed on ${networkName} (chainId=${chainId}) at ${receiverAddress}`,
);
hasError = true;
} else {
const expectedOwner = process.env.OWNER_ADDRESS?.trim();
if (
expectedOwner &&
receiverStatus.owner?.toLowerCase() !== expectedOwner.toLowerCase()
) {
console.error(
`BungeeReceiver owner mismatch on ${networkName} (chainId=${chainId}): expected ${expectedOwner}, got ${receiverStatus.owner}`,
);
hasError = true;
} else {
console.log(
`BungeeReceiver deployed on ${networkName} (chainId=${chainId}) at ${receiverAddress}, owner=${receiverStatus.owner}`,
);
}
}

if (hasError) {
process.exit(1);
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
120 changes: 119 additions & 1 deletion scripts/deploy/create3.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
import { Contract, Log, Provider, TransactionReceipt, keccak256, toUtf8Bytes } from 'ethers';
import {
AbiCoder,
Contract,
Log,
Provider,
TransactionReceipt,
keccak256,
toUtf8Bytes,
} from 'ethers';

// CreateX factory — https://createx.rocks/
export const CREATE_X_FACTORY = '0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed';

/** CREATE3 salt label used by `deployOpenRouter.ts`. */
export const OPEN_ROUTER_CREATE3_SALT_TEXT = 'OpenRouter' + '000';

/** CREATE3 salt label used by `deployReceiverAndExecutor.ts`. */
export const BUNGEE_RECEIVER_CREATE3_SALT_TEXT = 'BungeeReceiver' + '000';

/** CREATE3 salt label used by `deployReceiverAndExecutor.ts`. */
export const CALLDATA_EXECUTOR_CREATE3_SALT_TEXT = 'CalldataExecutor' + '000';

/** Keccak256 salt for deterministic OpenRouter CREATE3 deployments. */
export const OPEN_ROUTER_CREATE3_SALT = keccak256(
toUtf8Bytes(OPEN_ROUTER_CREATE3_SALT_TEXT),
);

/** Keccak256 salt for deterministic BungeeReceiver CREATE3 deployments. */
export const BUNGEE_RECEIVER_CREATE3_SALT = keccak256(
toUtf8Bytes(BUNGEE_RECEIVER_CREATE3_SALT_TEXT),
);

/** Keccak256 salt for deterministic CalldataExecutor CREATE3 deployments. */
export const CALLDATA_EXECUTOR_CREATE3_SALT = keccak256(
toUtf8Bytes(CALLDATA_EXECUTOR_CREATE3_SALT_TEXT),
);

/** CREATE3 salt label used by `deployAcrossERC20AmountManipulator.ts`. */
export const ACROSS_MANIPULATOR_CREATE3_SALT_TEXT =
'AcrossERC20AmountManipulator' + '1';
Expand All @@ -30,6 +54,43 @@ export const OPEN_ROUTER_EXPECTED_ADDRESS =
export const ACROSS_MANIPULATOR_EXPECTED_ADDRESS =
'0x05481b7163c376ab4cb0ebc7d17f2cf7651042ee';

/**
* BungeeReceiver CREATE3 address for salt `BungeeReceiver000` via canonical CreateX.
* Verified with {@link computeFinalAddress} (guarded salt + factory deployer), not
* `computeCreate3Address(rawSalt, eoa)`.
*/
export const BUNGEE_RECEIVER_EXPECTED_ADDRESS =
'0xCa81DA19B02265bB9aD42Fc12b943999DDd80b81';

/**
* CalldataExecutor CREATE3 address for salt `CalldataExecutor000` via canonical CreateX.
* Verified with {@link computeFinalAddress} (guarded salt + factory deployer), not
* `computeCreate3Address(rawSalt, eoa)`.
*/
export const CALLDATA_EXECUTOR_EXPECTED_ADDRESS =
'0x3e610B7bFDf0ad3bbA2417Cd086Ca4Fa91A2Ee31';

/**
* Mirrors CreateX `_guard` for random/unspecified salts: `keccak256(abi.encode(rawSalt))`.
*/
export function computeGuardedSalt(rawSalt: string): string {
return keccak256(AbiCoder.defaultAbiCoder().encode(['bytes32'], [rawSalt]));
}

/**
* Resolves the final CREATE3 deployment address for `rawSalt` through CreateX.
* Uses guarded salt and the one-arg `computeCreate3Address` overload (factory `_SELF` deployer).
*/
export async function computeFinalAddress(
rawSalt: string,
create3Factory: Contract,
): Promise<string> {
const guardedSalt = computeGuardedSalt(rawSalt);
return (await create3Factory['computeCreate3Address(bytes32)'](
guardedSalt,
)) as string;
}

/**
* Resolves the OpenRouter contract address for deployment checks.
*
Expand Down Expand Up @@ -136,6 +197,7 @@ export async function getOpenRouterDeploymentStatus(params: {
export const Create3ABI = [
'function computeCreate2Address(bytes32,bytes32,address) view returns (address)',
'function deployCreate2(bytes32,bytes) payable returns (address)',
'function computeCreate3Address(bytes32) view returns (address)',
'function computeCreate3Address(bytes32,address) view returns (address)',
'function deployCreate3(bytes32,bytes) payable returns (address)',
];
Expand Down Expand Up @@ -167,3 +229,59 @@ export function decodeCreate3DeploymentFromTxReceipt(params: {

return '0x' + eventData.slice(26);
}

/**
* Checks whether BungeeReceiver bytecode is present at the given address.
* When deployed, reads `owner()` to confirm the contract responds.
*/
export async function getBungeeReceiverDeploymentStatus(params: {
provider: Provider;
address: string;
}): Promise<{ address: string; deployed: boolean; owner?: string }> {
const { provider, address } = params;
const bytecode = await provider.getCode(address);

if (!hasContractBytecode(bytecode)) {
return { address, deployed: false };
}

try {
const contract = new Contract(
address,
['function owner() view returns (address)'],
provider,
);
const owner = (await contract.owner()) as string;
return { address, deployed: true, owner };
} catch {
return { address, deployed: false };
}
}
Comment on lines +237 to +259
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Strengthen getBungeeReceiverDeploymentStatus contract identity check.

Current logic marks deployment as valid if owner() responds. That can false-positive on unrelated Ownable contracts. This should also probe BungeeReceiver-specific view(s), e.g. CALLDATA_EXECUTOR() and/or SOLVER_SIGNER().

Suggested direction
 export async function getBungeeReceiverDeploymentStatus(params: {
   provider: Provider;
   address: string;
-}): Promise<{ address: string; deployed: boolean; owner?: string }> {
+}): Promise<{ address: string; deployed: boolean; owner?: string; calldataExecutor?: string }> {
@@
   try {
     const contract = new Contract(
       address,
-      ['function owner() view returns (address)'],
+      [
+        'function owner() view returns (address)',
+        'function CALLDATA_EXECUTOR() view returns (address)',
+      ],
       provider,
     );
     const owner = (await contract.owner()) as string;
-    return { address, deployed: true, owner };
+    const calldataExecutor = (await contract.CALLDATA_EXECUTOR()) as string;
+    return { address, deployed: true, owner, calldataExecutor };
   } catch {
     return { address, deployed: false };
   }
 }

As per coding guidelines, “Check for resource leaks, race conditions, and unhandled edge cases. Flag over-engineering and premature abstractions.”

📝 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
export async function getBungeeReceiverDeploymentStatus(params: {
provider: Provider;
address: string;
}): Promise<{ address: string; deployed: boolean; owner?: string }> {
const { provider, address } = params;
const bytecode = await provider.getCode(address);
if (!hasContractBytecode(bytecode)) {
return { address, deployed: false };
}
try {
const contract = new Contract(
address,
['function owner() view returns (address)'],
provider,
);
const owner = (await contract.owner()) as string;
return { address, deployed: true, owner };
} catch {
return { address, deployed: false };
}
}
export async function getBungeeReceiverDeploymentStatus(params: {
provider: Provider;
address: string;
}): Promise<{ address: string; deployed: boolean; owner?: string; calldataExecutor?: string }> {
const { provider, address } = params;
const bytecode = await provider.getCode(address);
if (!hasContractBytecode(bytecode)) {
return { address, deployed: false };
}
try {
const contract = new Contract(
address,
[
'function owner() view returns (address)',
'function CALLDATA_EXECUTOR() view returns (address)',
],
provider,
);
const owner = (await contract.owner()) as string;
const calldataExecutor = (await contract.CALLDATA_EXECUTOR()) as string;
return { address, deployed: true, owner, calldataExecutor };
} catch {
return { address, deployed: false };
}
}
🤖 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 124 - 146, Update
getBungeeReceiverDeploymentStatus to verify the contract is actually a
BungeeReceiver by calling one or more BungeeReceiver-specific view methods
(e.g., CALLDATA_EXECUTOR() and/or SOLVER_SIGNER()) in addition to owner(); after
confirming bytecode exists, instantiate the Contract as you do now, call owner()
and then call CALLDATA_EXECUTOR() and/or SOLVER_SIGNER(), treat the deployment
as valid only if those Bungee-specific calls succeed and return plausible values
(addresses/non-zero), and propagate or handle errors so that exceptions from
unrelated Ownable contracts do not yield a false positive; keep the existing
return shape { address, deployed: boolean, owner?: string } and only set
deployed=true when the Bungee-specific probes succeed.


/**
* Checks whether CalldataExecutor bytecode is present at the given address.
* When deployed, reads `BUNGEE_RECEIVER()` to confirm the contract responds.
*/
export async function getCalldataExecutorDeploymentStatus(params: {
provider: Provider;
address: string;
}): Promise<{ address: string; deployed: boolean; bungeeReceiver?: string }> {
const { provider, address } = params;
const bytecode = await provider.getCode(address);

if (!hasContractBytecode(bytecode)) {
return { address, deployed: false };
}

try {
const contract = new Contract(
address,
['function BUNGEE_RECEIVER() view returns (address)'],
provider,
);
const bungeeReceiver = (await contract.BUNGEE_RECEIVER()) as string;
return { address, deployed: true, bungeeReceiver };
} catch {
return { address, deployed: false };
}
}
Loading