Skip to content

Chore: clean up OFT deploy/configure scripts#12

Merged
blueogin merged 7 commits into
masterfrom
chore/oft-configure-scripts
Jul 10, 2026
Merged

Chore: clean up OFT deploy/configure scripts#12
blueogin merged 7 commits into
masterfrom
chore/oft-configure-scripts

Conversation

@blueogin

@blueogin blueogin commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Move OFT post-deploy scripts under scripts/oft/ with yarn oft:configure
  • Slim guide + LZ endpoint from @layerzerolabs/lz-evm-sdk-v2

About # (link your issue here)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

Checklist:

  • PR title matches follow: (Feature|Bug|Chore) Task Name
  • My code follows the style guidelines of this project
  • I have followed all the instructions described in the initial task (check Definitions of Done)
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added reference to a related issue in the repository
  • I have added a detailed description of the changes proposed in the pull request. I am as descriptive as possible, assisting reviewers as much as possible.
  • I have added screenshots related to my pull request (for frontend tasks)
  • I have pasted a gif showing the feature.
  • @mentions of the person or team responsible for reviewing proposed changes

Summary by Sourcery

Centralize OFT bridge deployment and post-deploy configuration into scripts with a single oft:configure entrypoint and shared LayerZero endpoint utilities.

New Features:

  • Add oft:configure script and helper steps to automate OFT post-deploy setup across networks.
  • Introduce a LayerZero wiring config using lz-evm-sdk-v2 metadata to define XDC↔CELO connections and enforced options.
  • Provide an OFT deployment and configuration guide documenting deploy, configure, upgrade, and network extension flows.

Enhancements:

  • Move OFT bridge smoke-test script from tests to scripts and update usage to reference the new LayerZero config path.
  • Refactor deploy and upgrade scripts to use a shared getLzEndpoint utility instead of hardcoded endpoint mappings.

Build:

  • Add @layerzerolabs/lz-evm-sdk-v2 dependency and wire it into OFT endpoint resolution and config tooling.

Documentation:

  • Add OFT_CONFIGURING_GUIDE.md under scripts/oft describing the bridge deployment, configuration steps, limits, wiring, and upgrade process.

Tests:

  • Remove legacy OFT configuration helper scripts and LayerZero config from the test directory now replaced by scripts-based tooling.

Chores:

  • Replace bespoke error-handling guidance in the bridge script with the standardized LayerZero configuration flow and tooling.

@sourcery-ai sourcery-ai 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.

Hey - I've found 6 issues, and left some high level feedback:

  • The parseLimit heuristic in scripts/oft/steps/set-bridge-limits.ts (length check + decimal detection) is a bit opaque; consider making the units explicit in config (e.g., always G$ as a decimal string) and removing the BigNumber fallback or at least documenting this behavior directly in code.
  • In configure-oft.ts, resolveOftPairNetworks infers production vs development purely from networkName.includes("production"); if you expect additional environments or differently named networks, it may be safer to use an explicit mapping or a stricter pattern to avoid misrouting wiring.
  • The removal of the detailed error diagnostics in scripts/oft/bridge-oft-token.ts reduces runtime guidance for common LayerZero misconfigurations; consider keeping a lightweight version of those checks or moving them into a reusable helper so operators still get actionable hints when bridging fails.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `parseLimit` heuristic in `scripts/oft/steps/set-bridge-limits.ts` (length check + decimal detection) is a bit opaque; consider making the units explicit in config (e.g., always G$ as a decimal string) and removing the BigNumber fallback or at least documenting this behavior directly in code.
- In `configure-oft.ts`, `resolveOftPairNetworks` infers production vs development purely from `networkName.includes("production")`; if you expect additional environments or differently named networks, it may be safer to use an explicit mapping or a stricter pattern to avoid misrouting wiring.
- The removal of the detailed error diagnostics in `scripts/oft/bridge-oft-token.ts` reduces runtime guidance for common LayerZero misconfigurations; consider keeping a lightweight version of those checks or moving them into a reusable helper so operators still get actionable hints when bridging fails.

## Individual Comments

### Comment 1
<location path="packages/bridge-contracts/deploy/utils/getLzEndpoint.ts" line_range="11-15" />
<code_context>
+  "production-xdc": xdcMainnetEndpoint.address,
+};
+
+export function getLzEndpoint(networkName: string): string {
+  const fromEnv = process.env.LAYERZERO_ENDPOINT;
+  if (fromEnv) return fromEnv;
+
+  const endpoint = LZ_ENDPOINT_BY_NETWORK[networkName];
+  if (!endpoint) {
+    throw new Error(
</code_context>
<issue_to_address>
**question (bug_risk):** Environment variable now overrides network mapping; consider whether this behavioral change is intended.

The previous logic only used `process.env.LAYERZERO_ENDPOINT` when no per-network mapping existed; now it always overrides mapped addresses. This increases flexibility but also makes a misconfigured env var silently override correct network-specific endpoints. If the old precedence was intentional, consider only using the env var as a fallback or add validation/logging when it’s present.
</issue_to_address>

### Comment 2
<location path="packages/bridge-contracts/scripts/oft/run-configure.js" line_range="24-33" />
<code_context>
+
+function wireLayerZero(networkName: string) {
+  const pair = resolveOftPairNetworks(networkName);
+  const result = spawnSync(
+    "npx",
+    ["hardhat", "lz:oapp:wire", "--oapp-config", "./scripts/oft/layerzero.config.ts", "--network", networkName, "--ci"],
</code_context>
<issue_to_address>
**suggestion:** Child process errors are not surfaced when `spawnSync` fails without a status code.

When `spawnSync` fails (e.g., `npx`/`hardhat` missing), `result.status` may be `null` and the error only appears in `result.error`. Right now we exit with code 1 but don’t log that error, which obscures the root cause in CI. Please check `result.error` and log it before exiting so these failures are easier to diagnose.
</issue_to_address>

### Comment 3
<location path="packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md" line_range="15" />
<code_context>
+npx hardhat deploy --tags OFT --network production-celo
+```
+
+Use `development-xdc` / `development-celo` for staging. Deploy **both** chains before configure (wiring needs both adapters).
+
+### Deploy parameters (`deploy/deployOFT.ts`)
</code_context>
<issue_to_address>
**suggestion (typo):** Consider changing "before configure" to "before configuring" or "before configuration" for smoother grammar.

This phrasing is slightly ungrammatical; using "before configuring" or "before configuration" would read more naturally.

```suggestion
Use `development-xdc` / `development-celo` for staging. Deploy **both** chains before configuring (wiring needs both adapters).
```
</issue_to_address>

### Comment 4
<location path="packages/bridge-contracts/scripts/oft/configure-oft.ts" line_range="29" />
<code_context>
+  };
+}
+
+function resolveOftPairNetworks(networkName: string): { xdc: string; celo: string } {
+  const isProduction = networkName.includes("production");
+  return {
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the configuration script to use explicit network mappings, a dedicated wiring runner, and an injectable flag source to simplify orchestration and make behavior clearer.

You can reduce the orchestration complexity without changing behavior by making the configuration more explicit and isolating responsibilities.

### 1. Make network pairing explicit instead of relying on naming heuristics

`resolveOftPairNetworks` currently couples behavior to `networkName.includes("production")`. A small explicit map keeps the behavior the same but makes it clearer and safer:

```ts
// Instead of inferring via "includes"
function resolveOftPairNetworks(networkName: string): { xdc: string; celo: string } {
  const pairs: Record<string, { xdc: string; celo: string }> = {
    "production-xdc": { xdc: "production-xdc", celo: "production-celo" },
    "production-celo": { xdc: "production-xdc", celo: "production-celo" },
    "development-xdc": { xdc: "development-xdc", celo: "development-celo" },
    "development-celo": { xdc: "development-xdc", celo: "development-celo" },
    // add any other aliases explicitly
  };

  const pair = pairs[networkName];
  if (!pair) {
    throw new Error(`Unknown network pairing for "${networkName}"`);
  }
  return pair;
}
```

This keeps the same production/development pairing, but removes the hidden dependency on string naming conventions.

### 2. Separate wiring invocation from env/network resolution

`wireLayerZero` currently mixes pair resolution, env building, and process spawning. Extract the spawn call into a small utility that takes explicit parameters:

```ts
function runLayerZeroWire(networkName: string, xdcNetwork: string, celoNetwork: string) {
  const result = spawnSync(
    "npx",
    [
      "hardhat",
      "lz:oapp:wire",
      "--oapp-config",
      "./scripts/oft/layerzero.config.ts",
      "--network",
      networkName,
      "--ci",
    ],
    {
      cwd: path.resolve(__dirname, "../.."),
      stdio: "inherit",
      env: {
        ...process.env,
        OFT_XDC_NETWORK: xdcNetwork,
        OFT_CELO_NETWORK: celoNetwork,
      },
    }
  );

  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`LayerZero wire failed with exit code ${result.status ?? "unknown"}`);
  }
}

function wireLayerZero(networkName: string) {
  const pair = resolveOftPairNetworks(networkName);
  runLayerZeroWire(networkName, pair.xdc, pair.celo);
}
```

This keeps all behavior intact while making the wiring path easier to understand and test (you can unit‑test `resolveOftPairNetworks` separately from `runLayerZeroWire`).

### 3. Decouple flag parsing from env to simplify orchestration

You can keep env support but move it behind an explicit flag source, making it easier to later add `argv` parsing or a Hardhat task without touching the orchestration logic:

```ts
type ConfigureFlags = {
  skipMinter: boolean;
  skipLimits: boolean;
  skipWire: boolean;
  skipAdapterOwnership: boolean;
};

function flagsFromEnv(): ConfigureFlags {
  const flagEnabled = (name: string): boolean => {
    const value = process.env[name];
    return value === "1" || value === "true";
  };

  return {
    skipMinter: flagEnabled("SKIP_MINTER"),
    skipLimits: flagEnabled("SKIP_LIMITS"),
    skipWire: flagEnabled("SKIP_WIRE"),
    skipAdapterOwnership: flagEnabled("SKIP_ADAPTER_OWNERSHIP"),
  };
}

// main orchestration only cares about a ConfigureFlags object
export const main = async (flags: ConfigureFlags = flagsFromEnv()) => {
  const networkName = network.name;
  console.log(`oft:configure ${networkName}`, flags);

  if (!flags.skipMinter) await grantMinterRole();
  if (!flags.skipLimits) await setBridgeLimits();
  if (!flags.skipWire) wireLayerZero(networkName);
  if (!flags.skipAdapterOwnership) await transferAdapterOwnership();

  console.log(`oft:configure done (${networkName})`);
};
```

With this, `main` is purely driven by a `ConfigureFlags` object. A future `argv`-based wrapper or Hardhat task can construct those flags directly, reducing the need for env‑only indirection while preserving current env behavior.
</issue_to_address>

### Comment 5
<location path="packages/bridge-contracts/scripts/oft/run-configure.js" line_range="1" />
<code_context>
+const { spawnSync } = require("child_process");
+const path = require("path");
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the wrapper script by passing CLI flags directly through to `configure-oft.ts` and handling all flag parsing there instead of via environment variables.

You can keep the same functionality (CLI flags to skip steps) while removing the indirection via env variables and the custom flag mapping. Let `configure-oft.ts` own its flag parsing and make this wrapper a thin pass-through.

Wrapper simplification:

```js
const { spawnSync } = require("child_process");
const path = require("path");

const hardhatArgs = process.argv.slice(2);

const result = spawnSync(
  "npx",
  ["hardhat", "run", "scripts/oft/configure-oft.ts", ...hardhatArgs],
  {
    cwd: path.resolve(__dirname, "../.."),
    stdio: "inherit",
    env: process.env,
  }
);

process.exit(result.status ?? 1);
```

Then in `scripts/oft/configure-oft.ts`, read the flags directly from `process.argv` instead of env:

```ts
const args = process.argv.slice(2);

const skipMinter = args.includes("--skip-minter");
const skipLimits = args.includes("--skip-limits");
const skipWire = args.includes("--skip-wire");
const skipAdapterOwnership = args.includes("--skip-adapter-ownership");

// use these booleans in the existing flow
```

This keeps the current behavior (same flags, same steps being skipped) but removes the duplicated flag handling and the hidden coupling via environment variable names, making the configuration flow easier to understand and maintain.
</issue_to_address>

### Comment 6
<location path="packages/bridge-contracts/scripts/oft/steps/set-bridge-limits.ts" line_range="6" />
<code_context>
+import { getOftDeploymentAddresses } from "../../../deploy/utils/getOftDeploymentAddresses";
+import config from "../oft.config.json";
+
+function parseLimit(value: string | undefined): BigNumber | null {
+  if (!value) return null;
+  const numValue = value.trim();
</code_context>
<issue_to_address>
**issue (complexity):** Consider making the limit parsing more explicit and extracting the bridge-limits equality check into a helper to reduce cognitive load without changing behavior.

You can reduce the cognitive load without changing behavior by making the parsing and comparison logic more explicit and centralized, while still preserving the existing heuristic as a fallback.

### 1. Make `parseLimit` explicit but backwards-compatible

Allow explicit units in the config (e.g. `"1.5 ether"`, `"1000000000000000000 wei"`), and only fall back to the current heuristic when no unit is given. This keeps existing configs working but makes future configs clearer.

```ts
function parseLimit(raw: string | undefined): BigNumber | null {
  if (!raw) return null;

  const value = raw.trim();
  // explicit unit support: "<number> ether" / "<number> wei"
  const [num, unit] = value.split(/\s+/);

  if (unit === "ether") {
    return ethers.utils.parseEther(num);
  }
  if (unit === "wei") {
    return ethers.BigNumber.from(num);
  }

  // fallback to existing heuristic for backwards compatibility
  if (num.includes(".") || num.length < 15) {
    return ethers.utils.parseEther(num);
  }
  return ethers.BigNumber.from(num);
}
```

Config examples that preserve current behavior but are now explicit:

```json
{
  "mainnet": {
    "limits": {
      "dailyLimit": "1000 ether",
      "txLimit": "5 ether",
      "accountDailyLimit": "1000000000000000000 wei",
      "minAmount": "0.1 ether",
      "onlyWhitelisted": true
    }
  }
}
```

Existing configs without units (`"1000"`, `"0.1"`) will still work via the heuristic.

### 2. Factor the `unchanged` check into a small helper

Centralize the equality logic to make it easier to audit and extend:

```ts
function bridgeLimitsEqual(
  a: typeof current,
  b: typeof current
): boolean {
  return (
    a.dailyLimit.eq(b.dailyLimit) &&
    a.txLimit.eq(b.txLimit) &&
    a.accountDailyLimit.eq(b.accountDailyLimit) &&
    a.minAmount.eq(b.minAmount) &&
    a.onlyWhitelisted === b.onlyWhitelisted
  );
}

// usage:
const unchanged = bridgeLimitsEqual(next, current);
```

This keeps behavior identical while making the comparison logic more discoverable and less dense in the main flow.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/bridge-contracts/deploy/utils/getLzEndpoint.ts Outdated
Comment thread packages/bridge-contracts/scripts/oft/run-configure.js Outdated
Comment thread packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md Outdated
Comment thread packages/bridge-contracts/scripts/oft/configure-oft.ts
Comment thread packages/bridge-contracts/scripts/oft/run-configure.js Outdated
Comment thread packages/bridge-contracts/scripts/oft/steps/set-bridge-limits.ts
blueogin added 3 commits July 10, 2026 14:40
…streamlined setup, enhancing LayerZero endpoint retrieval, and updating related documentation. Remove deprecated run-configure.js script.
…ore ownership transfer, and revise documentation for clarity on configuration steps and skip flags.
@blueogin
blueogin merged commit eb65788 into master Jul 10, 2026
3 checks passed
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.

1 participant