Skip to content

Conversation

ameeshaagrawal
Copy link
Collaborator

@ameeshaagrawal ameeshaagrawal commented Jul 25, 2025

Summary by CodeRabbit

  • New Features

    • Introduces MessageSwitchboard and CCTPSwitchboard for cross-chain messaging, with trigger, attestation, sibling configs, fees, and remote endpoints.
    • Switchboard management via numeric IDs; adds disconnect support and helper views.
    • Moves plugs/sockets/on-chain addresses to bytes32; adds converters and related constants.
    • New scripts for configuration, labels, attestations; Foundry address labels.
  • Refactor

    • Broad API/event updates: ID-based fields, payloadPointer digests, transmitter proof, configurable gasLimitBuffer.
  • Chores

    • Deployment configs reorganized; environment and constants updated; version bumped; trace script added.
  • Tests

    • New and updated suites for messaging, fees, auctions, proxies.

tHeMaskedMan981 and others added 30 commits June 4, 2025 14:20
feat: reduced low finality to 0 blocks
Copy link

coderabbitai bot commented Jul 31, 2025

Walkthrough

Refactors protocol to use switchboardId (uint64) and bytes32 IDs for plugs/targets across contracts, interfaces, events, and tooling. Adds MessageSwitchboard and CCTPSwitchboard subsystems, new Converters, ID packing in payloadId, revamped Socket trigger/execute flows, watcher/request handling updates, deployment metadata and scripts, plus extensive test updates.

Changes

Cohort / File(s) Summary
Ignore/config bumps
/.gitignore, /.prettierignore, /foundry.toml, /package.json
Added IDE/script ignores; labels section; new script and devDependency; version bump.
Docs: public API refs
/Errors.md, /EventTopics.md, /FunctionSignatures.md
Updated error catalog; event topics/types migrate to IDs; major selector table overhauls, new modules, removed SocketBatcher section.
Core protocol interfaces
contracts/protocol/interfaces/*
Shifted switchboard address → uint64 ID; plug/targets → bytes32; added ICCTPSwitchboard, IMessageHandler, IMessageTransmitter; ISwitchboard replaces attest with processTrigger and adds getTransmitter; ISocket gains helpers and disconnect.
Protocol core
contracts/protocol/Socket.sol, .../SocketConfig.sol, .../SocketUtils.sol, .../SocketBatcher.sol
Socket now emits/consumes ID-based fields, adds triggerAppGateway/receive, uses payloadPointer and switchboardId; Config tracks switchboard IDs and mappings, gasLimitBuffer, connect/disconnect; Utils digests use bytes32-encoded addresses; SocketBatcher adds ID-based attest+execute and CCTP execution path.
Switchboards
contracts/protocol/switchboard/*, contracts/protocol/base/MessagePlugBase.sol, contracts/protocol/base/PlugBase.sol
New MessageSwitchboard (attest, sibling configs, fees); new CCTPSwitchboard (attest/verify/prove/sync, message handling); SwitchboardBase stores/registers switchboardId and provides getTransmitter; FastSwitchboard tweaks; PlugBase/initSocket take switchboardId; MessagePlugBase added.
EVMx layer changes
contracts/evmx/*
AuctionManager moves to promise-based flow; AppGatewayBase uses onDeployComplete and bytes32 on-chain addresses; Fees/Credit migrate to bytes32 plug IDs; Forwarder stores bytes32 onChainAddress with compatibility; Deploy/AsyncDeployer use LibCall and bytes32 identifiers; watcher/precompiles adopt bytes32 targets, payloadPointer encoding, new events.
Common utils and structs
contracts/utils/common/*
Added Converters (address↔bytes32), new constants (CCTP, Solana slugs, APP_GATEWAY_ID); IdUtils packs payloadId from payloadPointer+switchboardId; Structs switch to bytes32 plugs/targets, payloadPointer, transmitterProof; add CCTP/Solana structs.
Deployments metadata
/deployments/*.json
Networks rekeyed to include switchboard IDs, maps, and new contracts; startBlocks updated; reorganized groups.
Hardhat admin/deploy/scripts
/hardhat-scripts/**
End-to-end adoption of switchboardId and bytes32 plugs; new scripts (disable-sb, getAttestations), retries for verify, config/build adjustments, fee token utilities, type updates, removed accounts util.
Foundry scripts
/script/**
New helpers (deposit/withdraw/transfer credits), supertoken deploy/transfer, config for bytes32 addresses; fee amounts adjusted.
TS library
/src/**
Added CCTP mappings; new chain IDs (Solana); enum/event updates; exports; signer nonce util; types extended with switchboard IDs/maps and S3Config.evmxChainSlug.
Tests
/test/**
Updated to new APIs/types (bytes32 plugs, switchboardId, payloadPointer); added MessageSwitchboard tests; adapted Forwarder/FeesManager/AsyncDeployer tests.

Sequence Diagram(s)

sequenceDiagram
  participant Plug
  participant Socket
  participant Switchboard
  participant Watcher
  participant Target

  Plug->>Socket: triggerAppGateway(data)
  Socket->>Switchboard: processTrigger(plug, triggerId, payload, overrides) [payable]
  Note right of Switchboard: Emits TriggerProcessed
  Watcher->>Switchboard: attest(digest, proof)
  Socket->>Switchboard: getTransmitter(sender, payloadId, transmitterProof)
  Socket->>Switchboard: allowPayload(digest, payloadId)?
  Switchboard-->>Socket: true/false
  Socket->>Target: _execute(...)
Loading
sequenceDiagram
  participant Caller
  participant SocketBatcher
  participant Socket
  participant CCTP as CCTPSwitchboard

  Caller->>SocketBatcher: attestCCTPAndProveAndExecute(execParams, cctpParams, switchboardId)
  SocketBatcher->>CCTP: attestVerifyAndProveExecutions(execParams, cctpParams, payloadId)
  SocketBatcher->>Socket: execute(executeParams, transmissionParams, refund)
  SocketBatcher->>CCTP: syncOut(payloadId, nextBatchRemoteChainSlugs)
Loading
sequenceDiagram
  participant Owner
  participant Switchboard
  participant Socket
  participant Plug

  Owner->>Switchboard: registerSwitchboard()
  Switchboard->>Socket: registerSwitchboard() -> returns id
  Owner-->>Switchboard: switchboardId stored
  Owner->>Plug: initSocket(appGatewayId, socket, switchboardId)
  Plug->>Socket: connect(appGatewayId, switchboardId)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

  • Deploy scripts #185 — Mirrors the same error, event, and interface refactors (switchboardId, bytes32 plugs, new switchboards), indicating shared code lineage.

Suggested reviewers

  • arthcp
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ameeshaagrawal ameeshaagrawal marked this pull request as ready for review August 12, 2025 11:58
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 95

🔭 Outside diff range comments (19)
src/finality.ts (1)

12-16: Avoid leaking mutable shared config objects

getFinalityBlocks returns references to module-level objects. A consumer mutating the returned object will mutate shared config for everyone. This should return a defensive copy.

 export const getFinalityBlocks = (
   chainSlug: ChainSlug
 ): ChainFinalityBlocks => {
-  return finalityBlockOverrides[chainSlug] ?? defaultFinalityBlocks;
+  const blocks = finalityBlockOverrides[chainSlug] ?? defaultFinalityBlocks;
+  // Return a defensive copy to prevent accidental mutation of shared config.
+  return { ...blocks };
 };
hardhat-scripts/utils/overrides.ts (2)

8-14: Use BigNumberish for overrides storage to avoid lossy number conversions

chainOverrides currently stores gasLimit/gasPrice as number, which then forces toNumber() elsewhere. This should use BigNumberish end-to-end to prevent precision loss and future overflow issues.

Apply this diff:

 export const chainOverrides: {
   [chainSlug in ChainSlug]?: {
     type?: number;
-    gasLimit?: number;
-    gasPrice?: number;
+    gasLimit?: BigNumberish;
+    gasPrice?: BigNumberish;
   };
 } = {

64-70: Stop coercing gasPrice to number; keep as BigNumber/BigNumberish

toNumber() is lossy and can overflow for larger values. The return type already allows BigNumberish. This should return the BigNumber directly.

Apply this diff:

-  if (gasPrice == undefined || gasPrice == null)
-    gasPrice = (await getGasPrice(chainSlug, provider)).toNumber();
+  if (gasPrice == undefined || gasPrice == null)
+    gasPrice = await getGasPrice(chainSlug, provider);

Note: This relies on the earlier type change so gasPrice in chainOverrides is also BigNumberish.

hardhat.config.ts (1)

103-104: Apply env-configurable EVMX explorer URLs & confirm Etherscan API compatibility

The sanity-check script returned "Unknown module" for both proxy calls, so Hardhat’s verify plugin will fail against this host. This should externalize the URLs to env vars and point at an Etherscan-compatible API (supporting proxy, account, contract, etc.), or swap in a host that does.

Files to update:

  • hardhat.config.ts at lines 103–104 (and similarly at 142–144)

Apply this diff:

       {
         network: "EVMX",
         chainId: EVMX_CHAIN_ID,
         urls: {
-          apiURL: "https://explorer-evmx-1dpy1f56o4.t.conduit.xyz/api",
-          browserURL: "https://explorer-evmx-1dpy1f56o4.t.conduit.xyz/",
+          apiURL: process.env.EVMX_EXPLORER_API_URL || "https://explorer-evmx-1dpy1f56o4.t.conduit.xyz/api",
+          browserURL: process.env.EVMX_EXPLORER_BROWSER_URL || "https://explorer-evmx-1dpy1f56o4.t.conduit.xyz/",
         },
       },

After updating, confirm verify support by running:

npx hardhat verify --network EVMX <DEPLOYED_CONTRACT_ADDRESS>
src/signer.ts (1)

10-25: Refactor nonce generation to use CSPRNG and allow injection

This should replace the insecure getNonce() (Date.now + Math.random) with a CSPRNG-backed value and make nonce injectable for deterministic tests.

• In src/signer.ts, update getNonce to something like:

import { randomBytes } from "crypto";
import { BigNumber } from "ethers";

export const getNonce = (): BigNumber => {
  // 16 bytes of entropy → up to uint128
  const buf = randomBytes(16);
  return BigNumber.from(buf);
};

• Change the signer function signature to accept an optional override:

-export const signWatcherMultiCallMessage = async (
-  watcherContractAddress: string,
-  evmxChainId: number,
-  targetContractAddress: string,
-  calldata: string,
-  signer: ethers.Signer
-) => {
-  const signatureNonce = getNonce();
+export const signWatcherMultiCallMessage = async (
+  watcherContractAddress: string,
+  evmxChainId: number,
+  targetContractAddress: string,
+  calldata: string,
+  signer: ethers.Signer,
+  opts?: { nonce?: BigNumberish }
+) => {
+  const signatureNonce = opts?.nonce ?? getNonce();

• Confirm ABI encoding types still match your protocol:

  • evmxChainId is encoded as "uint32" here. If chain IDs have moved to uint64, update the first tuple element to "uint64".
  • targetContractAddress remains "address". If you now use a bytes32 identifier for targets, change that element accordingly.
hardhat-scripts/deploy/7.upload.ts (2)

24-37: Validate CONFIG_FILE_NAME before using it as an S3 key.

This should reject unsafe characters and enforce a .json key to avoid surprising S3 paths and ensure consistent content-type handling.

Apply this diff to validate and normalize the key:

 const getFileName = () => {
   switch (mode) {
     case DeploymentMode.LOCAL:
-      return process.env.CONFIG_FILE_NAME || "pocConfig.json";
+      return sanitizeS3Key(process.env.CONFIG_FILE_NAME || "pocConfig.json");
     case DeploymentMode.DEV:
       return "devConfig.json";
     case DeploymentMode.STAGE:
       return "stageConfig.json";
     case DeploymentMode.PROD:
       return "prodConfig.json";
     default:
       throw new Error(`Invalid deployment mode: ${mode}`);
   }
 };

Add this helper near the top of the file (outside the selected range):

// Only allow simple filenames and enforce .json suffix
const sanitizeS3Key = (key: string): string => {
  const normalized = key.endsWith(".json") ? key : `${key}.json`;
  if (!/^[A-Za-z0-9._-]+\.json$/.test(normalized)) {
    throw new Error("Invalid CONFIG_FILE_NAME: only A-Z, a-z, 0-9, ., _, - are allowed and must end with .json");
  }
  return normalized;
};

46-47: Stop logging the full S3 config; it includes RPC credentials.

getS3Config() includes RPC and WSS endpoints sourced from env vars; these often embed API keys. Printing JSON to stdout will leak secrets to CI logs.

Apply this diff to remove the sensitive log and replace it with a safe message:

 async function uploadToS3() {
   const fileName = getFileName();
   const bucketName = getBucketName();
   const data = getS3Config();
-  console.log(JSON.stringify(data, null, 2));
+  console.log(`Uploading ${fileName} to S3 bucket ${bucketName}`);
   const params = {
     Bucket: bucketName,
     Key: fileName,
     Body: JSON.stringify(data, null, 2),
     ContentType: "application/json",
   };
hardhat-scripts/s3Config/buildConfig.ts (1)

18-27: Deduplicate supportedChainSlugs in buildConfig.ts

The S3Config type already declares evmxChainSlug, so no type change is needed. However, if chains ever includes EVMX_CHAIN_ID, the current spread will produce duplicates in supportedChainSlugs. This should use a Set to guarantee uniqueness.

• File: hardhat-scripts/s3Config/buildConfig.ts
– Replace the assignment at line 19:

-export const getS3Config = () => {
-  const supportedChainSlugs = [EVMX_CHAIN_ID as ChainSlug, ...chains];
+export const getS3Config = () => {
+  const supportedChainSlugs = Array.from(
+    new Set<ChainSlug>([EVMX_CHAIN_ID as ChainSlug, ...chains])
+  );
hardhat-scripts/constants/types.ts (1)

1-1: Import BigNumberish for the updated type

Required by the proposed switchboardId type change.

Apply this diff:

-import { ChainAddressesObj, ChainSlug } from "../../src";
+import { ChainAddressesObj, ChainSlug } from "../../src";
+import { BigNumberish } from "ethers";
hardhat-scripts/deploy/9.setupTransmitter.ts (2)

74-82: Deposit only the credit shortfall, not the full threshold

This should top up to the threshold instead of always depositing the full threshold. Saves funds and avoids over-wrapping on repeated runs.

Apply this diff:

-      .wrap(transmitterAddress, {
-        ...(await overrides(EVMX_CHAIN_ID as ChainSlug)),
-        value: TRANSMITTER_CREDIT_THRESHOLD,
-      });
+      .wrap(transmitterAddress, {
+        ...(await overrides(EVMX_CHAIN_ID as ChainSlug)),
+        // Top up only the deficit
+        value: TRANSMITTER_CREDIT_THRESHOLD.sub(credits),
+      });

90-102: Guard against missing provider and fund only the native shortfall

  • provider! can be undefined; hard-failing early is safer than confusing runtime errors.
  • Send only the deficit to reach threshold. Prevents over-funding the transmitter across repeated runs.

Apply this diff:

 export const checkAndDepositNative = async () => {
   console.log("Checking and depositing native");
-  const nativeBalance = await transmitterSigner.provider!.getBalance(
+  if (!transmitterSigner.provider) {
+    throw new Error("Transmitter signer has no provider");
+  }
+  const nativeBalance = await transmitterSigner.provider.getBalance(
     transmitterAddress
   );
 
   if (nativeBalance.lt(TRANSMITTER_NATIVE_THRESHOLD)) {
     console.log("Depositing native for transmitter...");
-    const tx = await getWatcherSigner().sendTransaction({
+    const tx = await getWatcherSigner().sendTransaction({
       to: transmitterAddress,
-      value: TRANSMITTER_NATIVE_THRESHOLD,
+      // Top up only the deficit
+      value: TRANSMITTER_NATIVE_THRESHOLD.sub(nativeBalance),
       ...(await overrides(EVMX_CHAIN_ID as ChainSlug)),
     });
contracts/protocol/SocketUtils.sol (1)

77-90: Critical: Digest calculation changed to use packed encoding

The switch from abi.encode to abi.encodePacked fundamentally alters digest calculation. This creates tightly packed data without padding, resulting in different digest values. This is a breaking change that invalidates all existing signatures.

Ensure all components generating or verifying these digests are updated:

  • Off-chain signers must use the new packed format
  • Any stored/cached digests become invalid
  • Signature verification will fail for pre-migration payloads

Consider adding a version field or migration path if backward compatibility is needed.

contracts/evmx/AuctionManager.sol (1)

173-178: Critical: Early return without proper state transition

The function changes from reverting to returning early when auction status is CLOSED or NOT_STARTED. This could leave the system in an inconsistent state if callers expect the auction to transition states.

-        if (
-            auctionStatus[requestCount_] == AuctionStatus.CLOSED ||
-            auctionStatus[requestCount_] == AuctionStatus.NOT_STARTED
-        ) return;
+        if (auctionStatus[requestCount_] == AuctionStatus.CLOSED) {
+            revert AuctionClosed();
+        }
+        if (auctionStatus[requestCount_] == AuctionStatus.NOT_STARTED) {
+            revert AuctionNotOpen();
+        }
test/TriggerTest.t.sol (1)

54-54: Missing _encodeTriggerId function

The test calls _encodeTriggerId on line 54 but this function has been removed according to the summary. This will cause a compilation error.

The _encodeTriggerId function is called but not defined. This needs to be either:

  1. Re-implemented in the test
  2. Replaced with the new ID construction approach
  3. Imported from a helper library
-        bytes32 triggerId = _encodeTriggerId(address(arbConfig.socket), arbChainSlug);
+        // Use the new ID construction approach
+        bytes32 triggerId = keccak256(abi.encodePacked(address(arbConfig.socket), arbChainSlug));
contracts/evmx/base/AppGatewayBase.sol (1)

170-177: Return type change from address to bytes32 breaks interface compatibility

Changing getOnChainAddress return type from address to bytes32 is a breaking change that will fail for existing integrations expecting an address type.

This needs migration support or a new method name:

+// Legacy support - mark as deprecated
+function getOnChainAddressLegacy(
+    bytes32 contractId_,
+    uint32 chainSlug_
+) public view returns (address) {
+    bytes32 addr = getOnChainAddress(contractId_, chainSlug_);
+    if (addr == bytes32(0)) return address(0);
+    // Convert bytes32 back to address for legacy callers
+    return address(uint160(uint256(addr)));
+}

 function getOnChainAddress(
     bytes32 contractId_,
     uint32 chainSlug_
 ) public view returns (bytes32 onChainAddress) {
contracts/evmx/watcher/precompiles/WritePrecompile.sol (1)

109-113: Zero address check bypasses connection verification

When target is zero, the code assigns contractFactoryPlug without verifying if it's configured. This could result in using an unconfigured (zero) factory plug.

Add validation:

 if (queueParams_.transaction.target == bytes32(0)) {
-    queueParams_.transaction.target = contractFactoryPlugs[
-        queueParams_.transaction.chainSlug
-    ];
+    bytes32 factoryPlug = contractFactoryPlugs[queueParams_.transaction.chainSlug];
+    if (factoryPlug == bytes32(0)) revert("Contract factory plug not configured");
+    queueParams_.transaction.target = factoryPlug;
     appGateway_ = address(this);
hardhat-scripts/admin/disconnect.ts (1)

293-293: Undefined variable sbType

The code references sbType which is not defined in this file. Based on the changes in contracts/evmx/fees/Credit.sol, this should be deprecatedSbType.

-        queueParams.switchboardType = sbType;
+        queueParams.switchboardType = deprecatedSbType;

However, since this is a hardhat script and not a contract, this variable needs to be properly defined or imported. This should reference a constant value representing the switchboard type for the disconnection flow.

contracts/evmx/fees/Credit.sol (1)

282-295: Async function pattern needs error handling

The _createRequest function is marked as async but there's no visible error handling for the asynchronous operations. The watcher__().queue() call could fail.

Add proper error handling:

-    ) internal async {
+    ) internal async {
         _setMaxFees(maxFees_);
         _setOverrides(consumeFrom_);

         QueueParams memory queueParams;
         queueParams.overrideParams = overrideParams;
         queueParams.transaction = Transaction({
             chainSlug: chainSlug_,
             target: _getFeesPlugAddress(chainSlug_),
             payload: payload_
         });
-        queueParams.switchboardType = sbType;
+        queueParams.switchboardType = deprecatedSbType;
-        watcher__().queue(queueParams, address(this));
+        try watcher__().queue(queueParams, address(this)) {
+            // Success case handled by async callback
+        } catch {
+            revert("Failed to queue request");
+        }
     }
contracts/evmx/watcher/Configurations.sol (1)

84-94: Missing validation for plug configuration

The function accepts any plug configuration without validating that the appGatewayId or switchboardId are valid.

     function setAppGatewayConfigs(AppGatewayConfig[] calldata configs_) external onlyWatcher {
         for (uint256 i = 0; i < configs_.length; i++) {
+            // Validate non-zero values
+            require(configs_[i].plug != bytes32(0), "Invalid plug");
+            require(configs_[i].plugConfig.appGatewayId != bytes32(0), "Invalid app gateway ID");
+            
             // Store the plug configuration for this network and plug
             _plugConfigs[configs_[i].chainSlug][configs_[i].plug] = configs_[i].plugConfig;

             emit PlugAdded(
                 configs_[i].plugConfig.appGatewayId,
                 configs_[i].chainSlug,
                 configs_[i].plug
             );
         }
     }
🧹 Nitpick comments (47)
script/counter/DeployEVMxCounterApp.s.sol (3)

16-23: Stop the broadcast after deployments.

Explicitly stopping the broadcast avoids accidentally broadcasting any subsequent operations if edits are made later. This is a safe, low-cost guardrail.

         vm.startBroadcast(deployerPrivateKey);
 
         // Setting fee payment on Arbitrum Sepolia
         uint256 fees = 1 ether;
 
         CounterAppGateway gateway = new CounterAppGateway(addressResolver, fees);
 
+        vm.stopBroadcast();
+
         console.log("Contracts deployed:");

8-8: Fix the command’s filename casing.

The file on disk is “DeployEVMxCounterApp.s.sol”, but the comment uses lowercase “deploy…”. On case-sensitive systems this breaks.

-// source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0
+// source .env && forge script script/counter/DeployEVMxCounterApp.s.sol --broadcast --skip-simulation

18-18: Generalize the network-specific comment.

This line references Arbitrum Sepolia, but the fork comes from EVMX_RPC, which may point elsewhere. This should avoid naming a specific network.

-        // Setting fee payment on Arbitrum Sepolia
+        // Set fee parameter for the selected fork (value is expected to be in wei)
script/supertoken/TransferSuperToken.s.sol (3)

8-8: Update the run command comment; this script doesn’t broadcast.

The flags suggest a tx broadcast, but the script only encodes and logs bytes. This should not advise broadcast-related flags.

Apply this diff:

-// source .env && forge script script/supertoken/TransferSuperToken.s.sol --broadcast --skip-simulation --legacy --gas-price 0
+// Usage (no broadcast): source .env && forge script script/supertoken/TransferSuperToken.s.sol -vvvv

20-27: Make units explicit for srcAmount and make deadline intention clear.

This should document the unit to avoid accidental human-readable vs base-unit mistakes. Also clarify the deadline’s time horizon.

Apply this diff:

                 srcAmount: 100,
-                deadline: block.timestamp + 1000000
+                // Amount in base units (e.g., wei).
+                // Consider parameterizing via env (e.g., SRC_AMOUNT) for reuse.
+                deadline: block.timestamp + 1_000_000 // ~11.6 days from the forked head

34-35: Log helpful context alongside the payload.

Logging the key fields helps consumers validate what the payload contains without local decoding.

Apply this diff:

-        console.logBytes(encodedPayload);
+        console.log("superToken:", gateway.superToken());
+        console.log("forwarderArb:", forwarderArb);
+        console.log("forwarderOpt:", forwarderOpt);
+        console.log("user:", user);
+        console.log("payload length:", encodedPayload.length);
+        console.logBytes(encodedPayload);
hardhat-scripts/utils/overrides.ts (1)

72-82: Prefer EIP-1559 fee data when available; expose maxFeePerGas/maxPriorityFeePerGas

Hard-coding gasPrice and boosting for Sepolia ignores EIP-1559. This should populate maxFeePerGas and maxPriorityFeePerGas when the provider supports it, and fall back to gasPrice otherwise.

Minimal changes to add EIP-1559 support:

 export const overrides = async (
   chain: ChainSlug | number
 ): Promise<{
   type?: number | undefined;
   gasLimit?: BigNumberish | undefined;
-  gasPrice?: BigNumberish | undefined;
+  gasPrice?: BigNumberish | undefined;
+  maxFeePerGas?: BigNumberish | undefined;
+  maxPriorityFeePerGas?: BigNumberish | undefined;
 }> => {
 export const getOverrides = async (
   chainSlug: ChainSlug,
   provider: providers.StaticJsonRpcProvider
 ) => {
   let overrides = chainOverrides[chainSlug];
-  let gasPrice = overrides?.gasPrice;
+  let gasPrice = overrides?.gasPrice;
+  let maxFeePerGas: BigNumberish | undefined;
+  let maxPriorityFeePerGas: BigNumberish | undefined;
@@
-  if (gasPrice == undefined || gasPrice == null)
-    gasPrice = (await getGasPrice(chainSlug, provider)).toNumber();
+  if (gasPrice == undefined || gasPrice == null) {
+    const feeData = await provider.getFeeData();
+    // Prefer EIP-1559 if available
+    if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
+      maxFeePerGas = feeData.maxFeePerGas;
+      maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
+    } else {
+      gasPrice = await getGasPrice(chainSlug, provider);
+    }
+  }
@@
-  return { gasLimit, gasPrice, type };
+  return { gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas, type };

If you keep the Sepolia 1.5x heuristic (Lines 78–81), apply it to whichever field you actually use (gasPrice or maxFeePerGas) to avoid underpricing on testnets.

script/helpers/DepositCredit.s.sol (2)

12-12: Make token amount clearer; prefer 2e6 literal or env override.

Using 2e6 conveys USDC’s 6 decimals. Keeping it configurable via env avoids code edits per run.

Apply this diff for readability:

-        uint256 feesAmount = 2000000; // 2 USDC
+        uint256 feesAmount = 2e6; // 2 USDC (6 decimals)

Optionally, allow an env override:

uint256 feesAmount = vm.envOr("FEES_AMOUNT_USDC", uint256(2e6));

28-30: Fix misleading log label: USDC is not wei.

This should avoid confusion when reading logs. USDC has 6 decimals.

-        console.log("Sender balance in wei:", balance);
+        console.log("Sender USDC balance (base units, 6 decimals):", balance);
package.json (1)

15-24: Add prepack to guarantee dist is built before publish

This should ensure consumers always get a built package even if someone publishes directly without using the custom script.

Apply this diff:

   "scripts": {
     "build": "yarn abi && tsc --project lib.tsconfig.json",
     "tsc": "tsc --project lib.tsconfig.json",
     "abi": "hardhat export-abi",
     "lint": "prettier \"./**\" --write",
     "lintContracts": "prettier \"./**\"  --write --plugin=prettier-plugin-solidity",
     "compile": "forge build",
     "deploy": "bash setupInfraContracts.sh",
     "publish-core": "yarn build && yarn publish --patch --no-git-tag-version",
-    "trace": "source .env && bash trace.sh"
+    "trace": "source .env && bash trace.sh",
+    "prepack": "yarn build"
   },
hardhat-scripts/verify/verify.ts (1)

57-73: Add minimal tracing to see retry progress and failures

This should log the attempt number and how many items remain, which helps diagnose Etherscan flakiness and rate limiting.

You can augment the loop:

-      while (retryCount < 5) {
+      while (retryCount < 5) {
+        console.log(`Verify attempt #${retryCount + 1} for ${chainName}`);
         const unverifiedChainParams: VerifyArgs[] = [];
         if (pendingParams.length) {
           const len = pendingParams.length;
           for (let index = 0; index < len; index++) {
             const res = await verify(...pendingParams[index]);
             if (!res) {
               unverifiedChainParams.push(pendingParams[index]);
             }
           }
         }
         await storeUnVerifiedParams(unverifiedChainParams, chain, mode);
-        if (unverifiedChainParams.length == 0) break;
+        console.log(`Remaining unverified: ${unverifiedChainParams.length}`);
+        if (unverifiedChainParams.length == 0) break;
         pendingParams = unverifiedChainParams;
         retryCount++;
         await new Promise((resolve) => setTimeout(resolve, 1000));
       }
hardhat-scripts/constants/constants.ts (1)

12-13: Use constants.HashZero for a canonical bytes32 zero.

ethers.utils.hexZeroPad(constants.AddressZero, 32) yields 0x00…00, but it’s indirect. This should use constants.HashZero for clarity and to avoid depending on hex padding semantics.

Apply this diff:

-export const BYTES32_ZERO = ethers.utils.hexZeroPad(constants.AddressZero, 32);
+export const BYTES32_ZERO = constants.HashZero;
contracts/utils/common/IdUtils.sol (1)

5-7: Enhance NatSpec to document bit layout.

Explicitly documenting the bit ranges eliminates ambiguity and future regressions.

Apply this diff:

-/// @param payloadPointer_ The payload pointer
-/// @param switchboardId_ The switchboard id
+/// @param payloadPointer_ The payload pointer (lower 160 bits: [159:0])
+/// @param switchboardId_ The switchboard id (next 64 bits: [223:160])
 /// @param chainSlug_ The chain slug
contracts/utils/common/Constants.sol (1)

21-22: Solana chain slug values need documentation

The constants CHAIN_SLUG_SOLANA_MAINNET (10000001) and CHAIN_SLUG_SOLANA_DEVNET (10000002) use a different numbering scheme than typical EVM chain IDs. Document the rationale for this 10000000+ range.

Add documentation:

+// Solana chain slugs use 10000000+ range to avoid conflicts with EVM chain IDs
 uint32 constant CHAIN_SLUG_SOLANA_MAINNET = 10000001;
 uint32 constant CHAIN_SLUG_SOLANA_DEVNET = 10000002;
src/types.ts (2)

22-26: Use consistent ID type naming convention

The ID fields mix different naming patterns. FastSwitchboardId, CCTPSwitchboardId, and MessageSwitchboardId should follow the same convention as the address fields.

-  FastSwitchboardId: string;
-  CCTPSwitchboardId: string;
-  MessageSwitchboardId: string;
+  fastSwitchboardId: string;
+  cctpSwitchboardId: string;
+  messageSwitchboardId: string;

31-31: Use specific type for switchboard ID mapping

The SwitchboardIdToAddressMap should use a more specific key type since switchboard IDs are uint64 in the contracts.

Consider defining a type alias or using a more specific type annotation:

type SwitchboardId = string; // represents uint64
SwitchboardIdToAddressMap: { [switchboardId: SwitchboardId]: string };

This improves type safety and makes the expected format clearer.

contracts/protocol/interfaces/ISocketBatcher.sol (1)

19-26: Add parameter documentation for switchboardId_

The function documentation should include the new switchboardId_ parameter.

     /**
      * @notice Attests a payload and executes it
      * @param executeParams_ The execution parameters
+     * @param switchboardId_ The ID of the switchboard to use for attestation
      * @param digest_ The digest of the payload
      * @param proof_ The proof of the payload
      * @param transmitterSignature_ The signature of the transmitter
+     * @param refundAddress_ The address to refund excess fees to
      * @return The return data after execution
      */
contracts/evmx/plugs/FeesPlug.sol (1)

119-125: Document the new switchboardId parameter

The function lacks documentation for its parameters, especially the new switchboardId_ parameter which is part of the migration to ID-based addressing.

Add documentation before the function:

/// @notice Connects the plug to a socket
/// @param appGatewayId_ The ID of the app gateway
/// @param socket_ The socket address
/// @param switchboardId_ The ID of the switchboard to use
script/supertoken/DeployEVMxSuperTokenApp.s.sol (2)

6-6: Relative import path could break when script location changes.

This relative import path ../../test/apps/app-gateways/super-token/SuperTokenAppGateway.sol makes the script fragile to directory restructuring. This should use an absolute import path from the project root.

-import "../../test/apps/app-gateways/super-token/SuperTokenAppGateway.sol";
+import "test/apps/app-gateways/super-token/SuperTokenAppGateway.sol";

8-8: Remove commented deployment command from production code.

This deployment command comment should be moved to documentation or removed from the source file.

-// source .env && forge script script/supertoken/deployEVMxSuperTokenApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0
hardhat-scripts/misc-scripts/createLabels.ts (3)

1-1: Align file header/comment with actual path to avoid confusion

The header says scripts/generate-labels.ts, but this file lives at hardhat-scripts/misc-scripts/createLabels.ts. This should not mislead users running the script.

Apply this diff:

-// scripts/generate-labels.ts
+// hardhat-scripts/misc-scripts/createLabels.ts

12-15: Ensure clean section replacement and separation

If foundry.toml doesn’t end with a newline, the new [labels] header will attach to the last line. Add a separator.

Apply this diff:

-  // Generate new labels section
-  let labelsSection = "[labels]\n";
+  // Generate new labels section
+  let labelsSection = (foundryContent.endsWith("\n") ? "" : "\n") + "[labels]\n";

Also applies to: 20-23


62-64: Document correct CLI usage

Node can’t execute TS directly without ts-node. Either ship the compiled JS or document using ts-node. Also the filename in the comment should match.

Apply this diff:

-// Get chainSlug from command line arguments
-const chainSlug = process.argv[2];
-generateFoundryLabels(chainSlug);
+// Usage:
+//   npx ts-node hardhat-scripts/misc-scripts/createLabels.ts <chainId?>
+//   or run the compiled JS equivalent
+const chainSlug = process.argv[2];
+generateFoundryLabels(chainSlug);
contracts/evmx/interfaces/IAsyncDeployer.sol (1)

23-33: Update NatSpec to reflect bytes32-based chain contract identifier

These functions shifted from address to bytes32 for chainContractAddress_. This should be documented to avoid misuse and to guide encoding (e.g., Converters.toBytes32Format).

Apply this diff:

     // Forwarder Management
-    function getOrDeployForwarderContract(
+    /// @notice Get or deploy the forwarder for a given chain contract identifier
+    /// @param chainContractAddress_ Chain-specific contract identifier (bytes32-encoded address/ID)
+    /// @param chainSlug_ Chain slug/domain identifier
+    function getOrDeployForwarderContract(
         bytes32 chainContractAddress_,
         uint32 chainSlug_
     ) external returns (address);
 
-    function getForwarderAddress(
+    /// @notice Resolve the forwarder address for a given chain contract identifier (no deploy)
+    /// @param chainContractAddress_ Chain-specific contract identifier (bytes32-encoded address/ID)
+    /// @param chainSlug_ Chain slug/domain identifier
+    function getForwarderAddress(
         bytes32 chainContractAddress_,
         uint32 chainSlug_
     ) external view returns (address);
script/helpers/DepositCreditMainnet.s.sol (4)

7-8: Remove unused import

TestUSDC is not used.

Apply this diff:

-import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol";

9-11: Fix script header comment to match file and intended usage

The header points to a different script name. Keep the command accurate to avoid operator error.

Apply this diff:

-// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation
+// source .env && forge script script/helpers/DepositCreditMainnet.s.sol --rpc-url "$ARBITRUM_RPC" --broadcast --skip-simulation

16-35: Stop broadcasting at the end of the script

This should call vm.stopBroadcast() to clearly delimit the broadcast window.

Apply this diff:

         console.log("Fees Amount:", feesAmount);
         feesPlug.depositCredit(address(USDCContract), appGateway, feesAmount);
+        vm.stopBroadcast();
     }

28-30: Use require for balance check for consistent style

Use require with message to match the rest of the guard style.

Apply this diff:

-        if (balance < feesAmount) {
-            revert("Sender does not have enough USDC");
-        }
+        require(balance >= feesAmount, "Sender does not have enough USDC");
script/helpers/TransferRemainingCredits.s.sol (1)

34-35: Low-level call lacks detailed error reporting

The generic "Transfer failed" message doesn't help diagnose why the transfer failed. The return data could contain useful revert reasons.

Decode and report the actual error:

-        (bool success, ) = appGateway.call(data);
-        require(success, "Transfer failed");
+        (bool success, bytes memory returnData) = appGateway.call(data);
+        if (!success) {
+            if (returnData.length > 0) {
+                assembly {
+                    revert(add(32, returnData), mload(returnData))
+                }
+            } else {
+                revert("Transfer failed without error data");
+            }
+        }
contracts/protocol/interfaces/ISwitchboard.sol (1)

40-44: Duplicate @notice tags in documentation

The function has two @notice tags on lines 33 and 34, which is non-standard documentation practice.

Merge the documentation:

     /**
-     * @notice Gets the transmitter for a given payload
-     * @notice Switchboard are required to implement this function to allow for the verification of the transmitters
+     * @notice Gets the transmitter for a given payload. Switchboards are required to implement this function to allow for the verification of the transmitters
      * @param sender_ The sender of the payload
      * @param payloadId_ The payload ID
      * @param transmitterSignature_ The transmitter signature
      * @return The transmitter address
      */
contracts/evmx/helpers/AsyncDeployer.sol (1)

163-164: tryCall error handling loses gas information

Using tryCall with gasleft() and then requiring success doesn't provide information about whether the failure was due to insufficient gas or a revert in the initialization logic.

Consider using a more specific gas amount or capturing the return data:

-        (bool success, , ) = proxy.tryCall(0, gasleft(), 0, initData_);
-        require(success, "Initialization failed");
+        (bool success, bytes memory returnData, ) = proxy.tryCall(0, gasleft(), 0, initData_);
+        if (!success) {
+            if (returnData.length > 0) {
+                assembly {
+                    revert(add(32, returnData), mload(returnData))
+                }
+            } else {
+                revert("Initialization failed without error data");
+            }
+        }
contracts/evmx/plugs/ContractFactoryPlug.sol (1)

58-59: Conditional logic should validate enum value explicitly

Using direct equality check with enum is less readable. This should use explicit enum comparison for clarity.

-        if (isPlug_ == IsPlug.YES)
-            IPlug(addr).initSocket(appGatewayId_, msg.sender, switchboardId_);
+        if (isPlug_ == IsPlug.YES) {
+            IPlug(addr).initSocket(appGatewayId_, msg.sender, switchboardId_);
+        }
hardhat-scripts/utils/sign.ts (2)

35-35: Unnecessary type casting to string

The as string cast on line 35 is redundant since EVMxAddressesObj already defines Watcher as a string type.

-    evmxAddresses[Contracts.Watcher] as string,
+    evmxAddresses[Contracts.Watcher],

51-51: Duplicate unnecessary type casting

Same redundant as string cast as in line 35.

-      evmxAddresses[Contracts.Watcher] as string
+      evmxAddresses[Contracts.Watcher]
src/enums.ts (1)

58-62: ID entries should follow consistent naming pattern

The new ID entries mix "Id" suffix inconsistently. All switchboard ID entries should use consistent casing.

-  FastSwitchboardId = "FastSwitchboardId",
+  FastSwitchboardID = "FastSwitchboardID",
-  CCTPSwitchboardId = "CCTPSwitchboardId",
+  CCTPSwitchboardID = "CCTPSwitchboardID",
-  MessageSwitchboardId = "MessageSwitchboardId",
+  MessageSwitchboardID = "MessageSwitchboardID",
contracts/evmx/watcher/WatcherStorage.sol (1)

19-23: Storage slot comment needs update

The comment on line 19 indicates "slot 50 (32 + 32)" but should be "(32 + 32 = 64 bits)" to clarify that two uint32 values are packed into a single 256-bit slot.

-    // slot 50 (32 + 32)
+    // slot 50 (32 + 32 = 64 bits used)
contracts/evmx/helpers/Forwarder.sol (1)

24-29: Typo in comment

Line 24 has a typo: "kep" should be "kept".

-    /// @notice old on-chain address kep for storage compatibility - can be removed after redeployment
+    /// @notice old on-chain address kept for storage compatibility - can be removed after redeployment
test/MessageSwitchboardTest.t.sol (1)

26-46: Missing validation of digest creation in test

The test doesn't validate that _createDigest returns a valid digest before using it in assertions.

Add digest validation:

 function testIncrementWithMessageSwitchboard() public {
     (uint160 payloadPointer, DigestParams memory digestParams) = _getTriggerData(
         arbPlug,
         optPlug,
         arbConfig,
         optConfig,
         abi.encodeWithSignature("increment()")
     );
+    
+    bytes32 digest = _createDigest(digestParams);
+    require(digest != bytes32(0), "Invalid digest created");

     vm.expectEmit(true, true, true, true);
     emit TriggerProcessed(
         optConfig.chainSlug,
         msgSbFees,
-        _createDigest(digestParams),
+        digest,
         digestParams
     );
contracts/protocol/interfaces/ICCTPSwitchboard.sol (1)

8-12: Fix incorrect interface documentation

The NatSpec comment says "ISwitchboard" but this is ICCTPSwitchboard interface.

 /**
- * @title ISwitchboard
+ * @title ICCTPSwitchboard
  * @dev The interface for a switchboard contract that is responsible for verification of payloads if the correct
  * digest is executed.
  */
contracts/protocol/Socket.sol (2)

54-55: gasLimitBuffer should be immutable

The gasLimitBuffer is set in constructor but never modified. Should be declared as immutable for gas optimization.

Declare gasLimitBuffer as immutable in the base contract:

+uint256 public immutable gasLimitBuffer;
 constructor(
     uint32 chainSlug_,
     address owner_,
     string memory version_
 ) SocketUtils(chainSlug_, owner_, version_) {
     gasLimitBuffer = 105;
 }

203-204: Unused return value from overrides() call

The plugOverrides is retrieved but might be empty. Should validate if overrides are actually needed.

Consider checking if overrides are non-empty before processing:

 bytes memory plugOverrides = IPlug(plug_).overrides();
+// Only process if overrides exist
+if (plugOverrides.length == 0) {
+    plugOverrides = "";
+}
contracts/evmx/fees/Credit.sol (1)

297-300: Inconsistent zero check pattern

The function checks if feesPlugs[chainSlug_] == bytes32(0) but this differs from the BYTES32_ZERO constant used elsewhere in the codebase.

Use a consistent pattern for zero checks across the codebase. Consider importing and using the BYTES32_ZERO constant for clarity.

EventTopics.md (1)

22-38: Documentation structure needs consistency with table formatting

The Socket event table has inconsistent column widths compared to other sections. The Arguments column is significantly wider due to the new complex signatures, causing misalignment.

Apply consistent column formatting:

-| Event                        | Arguments                                                                                                             | Topic                                                                |
-| ---------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
+| Event                        | Arguments                                                                                                    | Topic                                                                |
+| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
test/SetupTest.t.sol (2)

499-515: Extract signature creation to reduce duplication

The signature creation logic with the "\x19Ethereum Signed Message:\n32" prefix and assembly operations is duplicated across multiple test files. This should be centralized.

Create a dedicated signature helper contract or library:

+contract SignatureHelper {
+    function createSignature(bytes32 digest_, uint256 privateKey_) public pure returns (bytes memory sig) {
+        bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_));
+        (uint8 sigV, bytes32 sigR, bytes32 sigS) = vm.sign(privateKey_, digest);
+        sig = new bytes(65);
+        bytes1 v32 = bytes1(sigV);
+        assembly {
+            mstore(add(sig, 96), v32)
+            mstore(add(sig, 32), sigR)
+            mstore(add(sig, 64), sigS)
+        }
+    }
+}

1458-1464: Helper functions belong in a dedicated utilities contract

These conversion functions are generic utilities that will likely be needed across multiple test files. They should be in a shared location.

Move to a dedicated test utilities contract:

+// In test/utils/Converters.t.sol
+library TestConverters {
+    function addressToBytes32(address addr_) internal pure returns (bytes32) {
+        return bytes32(uint256(uint160(addr_)));
+    }
+
+    function bytes32ToAddress(bytes32 addrBytes32_) internal pure returns (address) {
+        return address(uint160(uint256(addrBytes32_)));
+    }
+}

Then import and use in test files:

+import "./utils/Converters.t.sol";
-function addressToBytes32(address addr_) pure returns (bytes32) {
-    return bytes32(uint256(uint160(addr_)));
-}
-
-function bytes32ToAddress(bytes32 addrBytes32_) pure returns (address) {
-    return address(uint160(uint256(addrBytes32_)));
-}
contracts/protocol/switchboard/MessageSwitchboard.sol (1)

98-109: registerSibling function vulnerable to griefing

Any address can call registerSibling and overwrite their sibling plug mapping without authorization checks. While this only affects the caller's own mapping, it could lead to accidental misconfiguration.

Add a confirmation mechanism to prevent accidental overwrites:

     function registerSibling(uint32 chainSlug_, bytes32 siblingPlug_) external {
         if (
             siblingSockets[chainSlug_] == bytes32(0) ||
             siblingSwitchboards[chainSlug_] == bytes32(0)
         ) {
             revert SiblingNotFound();
         }
+        
+        // Prevent accidental overwrites
+        bytes32 existing = siblingPlugs[chainSlug_][msg.sender];
+        if (existing != bytes32(0) && existing != siblingPlug_) {
+            revert SiblingAlreadyRegistered();
+        }

         // Register the sibling for the calling plug
         siblingPlugs[chainSlug_][msg.sender] = siblingPlug_;
         emit SiblingRegistered(chainSlug_, msg.sender, siblingPlug_);
     }
FunctionSignatures.md (1)

1-643: Documentation format inconsistency and missing context

The function signatures table lacks important context about parameter types and return values. This makes it difficult to understand the actual interface changes without cross-referencing the code.

Consider enhancing the documentation format to include parameter and return types:

 ## AuctionManager

-| Function                     | Signature    |
-| ---------------------------- | ------------ |
-| `addressResolver__`          | `0x6a750469` |
+| Function                     | Signature    | Parameters | Returns |
+| ---------------------------- | ------------ | ---------- | ------- |
+| `addressResolver__`          | `0x6a750469` | ()         | address |

This would make the documentation more useful for developers integrating with these contracts.

address owner_,
bytes32 sbType_
) public reinitializer(1) {
) public reinitializer(2) {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify reinitializer version increment is intentional

The reinitializer changed from version 1 to 2. This should only happen when upgrading an already deployed contract. Ensure existing deployments are properly migrated.


🏁 Script executed:

#!/bin/bash
# Check for migration scripts or deployment history

# Look for deployment scripts that handle this upgrade
fd -e sol -e js -e ts | xargs rg -l "FeesManager.*reinitializer|upgrade.*FeesManager"

# Check for other reinitializer(2) usage patterns
ast-grep --pattern 'reinitializer(2)'

# Look for migration documentation
fd README -e md | xargs rg -i "migration|upgrade.*fees"

Length of output: 245


Add migration logic for FeesManager reinitializer bump

The constructor was changed to reinitializer(2) without any migration scripts or documented upgrade path. This should only happen when upgrading an existing deployment—existing contracts will skip the new initializer and miss any added state. Provide explicit migration logic and tests to ensure storage slot compatibility:

  • contracts/evmx/fees/FeesManager.sol (line 51): implement an upgrade initializer or external migration script to move from version 1 → 2.
  • Add tests that deploy the v1 contract, perform the upgrade, then verify storage slots and new state are correctly initialized.

Comment on lines +44 to 47
/// @notice get the switchboard type
/// @return sbType The switchboard type
function sbType() external view returns (bytes32);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

New sbType() requirement: ensure all gateways implement and tests assert it

This should be implemented across concrete AppGateways and validated in tests to prevent silent zero-value returns.

Search for implementations missing sbType:


🏁 Script executed:

#!/bin/bash
# Find contracts implementing IAppGateway but missing sbType()
rg -n -A 5 -B 3 $'contract .* is .*IAppGateway'
rg -n -A 2 -B 2 $'function sbType\\s*\\(\\)'

Length of output: 1178


🏁 Script executed:

#!/bin/bash
# List all concrete AppGateway contracts extending the abstract base
rg -n 'contract .* is .*AppGatewayBase'

Length of output: 1221


🏁 Script executed:

#!/bin/bash
# Display AppGatewayBase.sol contents around potential sbType implementation
sed -n '1,200p' contracts/evmx/base/AppGatewayBase.sol

Length of output: 7193


🏁 Script executed:

#!/bin/bash
# Check where gateways call the base initializer
rg -n "_initializeAppGateway" -n contracts/evmx

# Inspect AuctionManager initializer and sbType usage
rg -n -A3 -B3 "function initialize" contracts/evmx/AuctionManager.sol
rg -n "sbType" -n contracts/evmx/AuctionManager.sol

Length of output: 721


Add sbType() assertions in gateway tests

All concrete gateways already inherit the public sbType getter via AppGatewayBase, but no tests currently validate it—silent zero‐value defaults could slip through. This should be covered in every gateway’s test:

• In test/apps/Counter.t.sol: after deploying/initializing CounterAppGateway, assert gateway.sbType() == FAST.
• In test/apps/SuperToken.t.sol: after setup of SuperTokenAppGateway, assert gateway.sbType() == FAST.
• In test/apps/ParallelCounter.t.sol: assert gateway.sbType() == FAST.
• In test/AuctionManager.t.sol: after initializing AuctionManager, assert auctionManager.sbType() == FAST.
• In any future gateway tests, include the same check.

Example snippet to add:

 import { FAST } from "../utils/common/Constants.sol";

 function test_sbTypeIsFast() public {
-  // existing setup...
+  // setup gateway
+  GatewayType gateway = new GatewayType();
+  gateway.initialize(addressResolver);
+
+  // verify sbType
+  assertEq(gateway.sbType(), FAST, "sbType must be FAST");
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contracts/evmx/interfaces/IAppGateway.sol around lines 44 to 47, the public
sbType getter is present but tests are missing assertions that concrete gateways
return the expected value; add assertions in the specified test files
immediately after each gateway is deployed/initialized: in
test/apps/Counter.t.sol, test/apps/SuperToken.t.sol,
test/apps/ParallelCounter.t.sol, and test/AuctionManager.t.sol assert
gateway.sbType() == FAST (or auctionManager.sbType() == FAST) using the same
FAST constant/import used elsewhere in the test suite; ensure the assertion is
added to any future gateway tests as well so zero-value defaults cannot slip
through.

Comment on lines +46 to +51
function triggerFromChainSlug() external view returns (uint32);

function triggerFromPlug() external view returns (bytes32);

function isAppGatewayCalled(bytes32 triggerId) external view returns (bool);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Document and verify new getters across implementations.

These public getters change the interface surface. This should:

  • Add brief NatSpec for each to define semantics and storage origin.
  • Verify all Watcher implementations conform.

I can add concise NatSpec like:

  • triggerFromChainSlug: “Chain slug of the current trigger context”
  • triggerFromPlug: “Plug (bytes32) of the current trigger context”
  • isAppGatewayCalled: “Whether AppGateway call occurred for triggerId”

Run this to spot implementing contracts needing updates:


🏁 Script executed:

#!/bin/bash
rg -n $'contract\\s+Watcher\\b|is AppGatewayCalled|triggerFromChainSlug|triggerFromPlug|function\\s+triggerFromChainSlug\\(|function\\s+triggerFromPlug\\(' -A 3 -B 2

Length of output: 3442


🏁 Script executed:

#!/bin/bash
rg -n "mapping.*isAppGatewayCalled" -A3 -B3 contracts/evmx/watcher/WatcherStorage.sol

Length of output: 450


Document IWatcher getters with NatSpec
Implementation already includes storage‐level comments in WatcherStorage.sol. This should be reflected in the interface to define semantics and storage origin.

• File: contracts/evmx/interfaces/IWatcher.sol
Add above each function:

-    function triggerFromChainSlug() external view returns (uint32);
+    /// @notice Chain slug of the current trigger context stored in WatcherStorage
+    /// @return chainSlug slug of the origin chain for the current trigger
+    function triggerFromChainSlug() external view returns (uint32);

-    function triggerFromPlug() external view returns (bytes32);
+    /// @notice Plug (bytes32) of the current trigger context stored in WatcherStorage
+    /// @return plug identifier of the plug for the current trigger
+    function triggerFromPlug() external view returns (bytes32);

-    function isAppGatewayCalled(bytes32 triggerId) external view returns (bool);
+    /// @notice Whether AppGateway has been called for the given triggerId
+    /// @param triggerId identifier of the trigger to check
+    /// @return called true if AppGateway call occurred for triggerId
+    function isAppGatewayCalled(bytes32 triggerId) external view returns (bool);

No other Watcher implementations exist; WatcherStorage.sol already uses override and carries matching NatSpec.

📝 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
function triggerFromChainSlug() external view returns (uint32);
function triggerFromPlug() external view returns (bytes32);
function isAppGatewayCalled(bytes32 triggerId) external view returns (bool);
/// @notice Chain slug of the current trigger context stored in WatcherStorage
/// @return chainSlug slug of the origin chain for the current trigger
function triggerFromChainSlug() external view returns (uint32);
/// @notice Plug (bytes32) of the current trigger context stored in WatcherStorage
/// @return plug identifier of the plug for the current trigger
function triggerFromPlug() external view returns (bytes32);
/// @notice Whether AppGateway has been called for the given triggerId
/// @param triggerId identifier of the trigger to check
/// @return called true if AppGateway call occurred for triggerId
function isAppGatewayCalled(bytes32 triggerId) external view returns (bool);
🤖 Prompt for AI Agents
In contracts/evmx/interfaces/IWatcher.sol around lines 46 to 51, the three
getter declarations lack NatSpec; add Ethereum NatSpec comments above each
function mirroring the storage-level comments in WatcherStorage.sol to document
semantics and storage origin. For each getter, add a brief description line, an
@return tag describing the returned value and its units/type, and note that the
value is read from WatcherStorage (or the matching storage variable name) so
implementations and callers understand provenance; keep wording identical to the
existing WatcherStorage.sol comments and ensure formatting follows Solidity
NatSpec conventions.

function increaseFees(uint40 requestCount_, uint256 newFees_) external;

function setIsValidPlug(bool isValid_, uint32 chainSlug_, address onchainAddress_) external;
function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainAddress_) external;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Breaking change: on-chain identifier is bytes32; rename param for clarity.

The type change is correct for ID-based plugs, but the name onchainAddress_ now misleads (it’s not an address). Rename to reflect it’s an ID.

Apply this diff:

-    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainAddress_) external;
+    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainId_) external;

Note: Param rename does not affect ABI, but all impls should update for readability.


🏁 Script executed:

#!/bin/bash
rg -n $'setIsValidPlug\\s*\\(' -A 1 -B 2

Length of output: 2923


🏁 Script executed:

#!/bin/bash
# Interface IConfigurations
echo "=== contracts/evmx/interfaces/IConfigurations.sol (lines 44–55) ==="
sed -n '44,55p' contracts/evmx/interfaces/IConfigurations.sol

# Implementation Configurations.sol
echo
echo "=== contracts/evmx/watcher/Configurations.sol (lines 120–135) ==="
sed -n '120,135p' contracts/evmx/watcher/Configurations.sol

# Mock precompile
echo
echo "=== test/mock/MockWatcherPrecompile.sol (lines 50–70) ==="
sed -n '50,70p' test/mock/MockWatcherPrecompile.sol

Length of output: 2372


Rename onchainAddress_ to onchainId_ in IWatcher and all implementations

This should clarify that the third parameter is an identifier, not an address.

This should update:

  • contracts/evmx/interfaces/IWatcher.sol: change bytes32 onchainAddress_bytes32 onchainId_
  • test/mock/MockWatcherPrecompile.sol: change bytes32 onchainAddress_bytes32 onchainId_
  • contracts/evmx/watcher/Watcher.sol: rename function param plug_onchainId_ and propagate it to the call
  • contracts/evmx/base/AppGatewayBase.sol: rename local var onchainAddressonchainId and pass it

Diff examples:

--- a/contracts/evmx/interfaces/IWatcher.sol
@@ -87,7 +87,7 @@
     function increaseFees(uint40 requestCount_, uint256 newFees_) external;

-    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainAddress_) external;
+    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainId_) external;
--- a/test/mock/MockWatcherPrecompile.sol
@@ -51,7 +51,7 @@
     function increaseFees(uint40 requestCount_, uint256 newFees_) external override {}

-    function setIsValidPlug(
-        bool isValid_,
-        uint32 chainSlug_,
-        bytes32 onchainAddress_
-    ) external override {}
+    function setIsValidPlug(
+        bool isValid_,
+        uint32 chainSlug_,
+        bytes32 onchainId_
+    ) external override {}
--- a/contracts/evmx/watcher/Watcher.sol
@@ -155,7 +155,7 @@
-    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 plug_) external override {
+    function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainId_) external override {
         configurations__.setIsValidPlug(isValid_, chainSlug_, onchainId_, msg.sender);
     }
--- a/contracts/evmx/base/AppGatewayBase.sol
@@ -224,7 +224,7 @@
-        bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
-        watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
+        bytes32 onchainId = getOnChainAddress(contractId_, chainSlug_);
+        watcher__().setIsValidPlug(isValid, chainSlug_, onchainId);
📝 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
function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainAddress_) external;
function setIsValidPlug(bool isValid_, uint32 chainSlug_, bytes32 onchainId_) external;
🤖 Prompt for AI Agents
In contracts/evmx/interfaces/IWatcher.sol around line 90, rename the third
parameter from bytes32 onchainAddress_ to bytes32 onchainId_; then update all
implementations and call sites accordingly: in
test/mock/MockWatcherPrecompile.sol change the function signature to use
onchainId_; in contracts/evmx/watcher/Watcher.sol rename the function parameter
plug_ to onchainId_ and forward that value to the IWatcher call; in
contracts/evmx/base/AppGatewayBase.sol rename local variable onchainAddress to
onchainId and pass onchainId into the Watcher call so parameter names match
across interface, implementations, and callers.

Comment on lines +127 to 129
function disconnectSocket() external onlyOwner {
socket__.disconnect();
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add documentation and consider security implications

The new disconnectSocket function lacks documentation. This is a critical operation that forcibly disconnects the socket.

+    /// @notice Forcibly disconnects the socket
+    /// @dev This will disconnect the plug from the socket, preventing any further operations
+    /// @dev Ensure this is only called when absolutely necessary as it may leave the system in an inconsistent state
     function disconnectSocket() external onlyOwner {
         socket__.disconnect();
     }

Also verify that disconnecting won't leave fees stuck or cause issues with in-flight transactions.


🏁 Script executed:

#!/bin/bash
# Check for any cleanup or state management around disconnect operations
ast-grep --pattern 'function disconnect() {
  $$$
}'

# Look for any disconnect event emissions or state cleanup
rg -A 10 "disconnect\(\)" --type solidity

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Search for disconnect implementations in Solidity contracts
rg -n "function disconnect" -g "*.sol"

# Search for any calls to disconnect across Solidity files
rg -n "disconnect(" -g "*.sol"

# Locate the declaration of socket__ to identify its interface and implementation
rg -n "socket__" -g "*.sol"

Length of output: 4228


🏁 Script executed:

#!/bin/bash
# Inspect implementation of disconnect in SocketConfig.sol
rg -n -C5 "function disconnect" -g "contracts/protocol/SocketConfig.sol"

# Inspect implementation of disconnect in PlugBase.sol
rg -n -C5 "function disconnect" -g "contracts/protocol/base/PlugBase.sol"

# Check for any state cleanup logic (delete/remove) in these methods
rg -n -C5 "delete" -g "contracts/protocol/SocketConfig.sol"
rg -n -C5 "delete" -g "contracts/protocol/base/PlugBase.sol"

# Search for event emissions related to disconnect
rg -n -C5 "emit" -g "contracts/protocol/SocketConfig.sol"
rg -n -C5 "emit" -g "contracts/protocol/base/PlugBase.sol"

Length of output: 6077


🏁 Script executed:

#!/bin/bash
# Show FeesPlug.sol around the disconnectSocket function and class declaration
rg -n -C10 "contract FeesPlug" -g "contracts/evmx/plugs/FeesPlug.sol"
rg -n -C10 "function disconnectSocket" -g "contracts/evmx/plugs/FeesPlug.sol"

Length of output: 3352


Add NatSpec, emit proper event, and handle fee cleanup

This should:

  • Add NatSpec to disconnectSocket explaining its impact.
  • Call internal _disconnectSocket() (not socket__.disconnect() directly) so ConnectorPlugDisconnected is emitted.
  • Ensure any locked fees are rescued (e.g. via rescueFunds) before or after disconnect to prevent stuck balances.

Points of attention:

  • File: contracts/evmx/plugs/FeesPlug.sol at lines 127–129.

Proposed diff:

     /// @notice Disconnects this plug from its socket, preventing further fee operations.
     /// @dev Calls internal `_disconnectSocket()` to emit `ConnectorPlugDisconnected`
     ///      and clear plug state on the socket side. Any remaining fees should be
     ///      rescued via `rescueFunds` to avoid stuck balances.
-    function disconnectSocket() external onlyOwner {
-        socket__.disconnect();
-    }
+    function disconnectSocket() external onlyOwner {
+        _disconnectSocket();
+    }
📝 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
function disconnectSocket() external onlyOwner {
socket__.disconnect();
}
/// @notice Disconnects this plug from its socket, preventing further fee operations.
/// @dev Calls internal `_disconnectSocket()` to emit `ConnectorPlugDisconnected`
/// and clear plug state on the socket side. Any remaining fees should be
/// rescued via `rescueFunds` to avoid stuck balances.
function disconnectSocket() external onlyOwner {
_disconnectSocket();
}
🤖 Prompt for AI Agents
In contracts/evmx/plugs/FeesPlug.sol around lines 127–129, the disconnectSocket
function lacks NatSpec, does not use the internal disconnect routine that emits
ConnectorPlugDisconnected, and may leave locked fees stuck; add a NatSpec
comment describing that this disconnects the plug and may reject further
operations, replace the direct socket__.disconnect() call with a call to the
internal _disconnectSocket() so the ConnectorPlugDisconnected event is emitted,
and invoke the fee rescue path (e.g. call rescueFunds or an internal
_rescueLockedFees()) either immediately before or after calling
_disconnectSocket() to ensure any locked fees are returned; preserve the
onlyOwner modifier and error handling.

Comment on lines +16 to +19
SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Validate configuration and align chainId typing with the ID refactor.

  • This should assert that resolved forwarder addresses are non-zero to fail fast on misconfiguration.
  • Per PR-wide refactor, chain identifiers often moved to uint64. Ensure forwarderAddresses’ signature matches and cast accordingly.

Apply this diff to add safety checks (keep the casting step after verifying the signature per the script below):

-        SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
-        address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
-        address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
+        ISuperTokenAppGateway gateway = ISuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
+        // Cast to uint64 if the gateway expects uint64 per the protocol refactor (verify with the script below).
+        address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
+        address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
+        require(forwarderArb != address(0), "forwarderArb not set");
+        require(forwarderOpt != address(0), "forwarderOpt not set");

Run this script to confirm the signature of forwarderAddresses and whether it takes uint64:


🏁 Script executed:

#!/bin/bash
# Verify the signature of forwarderAddresses across the repo.
# Expectation: second param is uint64 after the refactor.

set -euo pipefail

echo "Searching for forwarderAddresses signature..."
rg -n -A 3 -B 3 $'forwarderAddresses\\s*\\('

echo
echo "Grepping interface/contract definitions for param types..."
rg -n $'function\\s+forwarderAddresses\\s*\\(' -A 1 -B 1

Length of output: 3773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check for ISuperTokenAppGateway interface
rg -n "interface\s+ISuperTokenAppGateway" -A1 -B1

# Inspect SuperTokenAppGateway contract definition
rg -n "contract\s+SuperTokenAppGateway" -A3 -B3

Length of output: 71


Validate forwarder addresses and correct chainSlug typing

  • forwarderAddresses uses the IAppGateway signature forwarderAddresses(bytes32,uint32). No uint64 cast is required—the literals 421614 and 11155420 fit in uint32.
  • This should assert that both resolved forwarder addresses are non-zero to fail fast on misconfiguration.

Locations to update (script/supertoken/TransferSuperToken.s.sol lines 16–19):

-        SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
-        address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
-        address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
+        // Optionally use the IAppGateway interface for looser coupling:
+        // IAppGateway gateway = IAppGateway(vm.envAddress("APP_GATEWAY"));
+        SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
+        address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
+        address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
+        require(forwarderArb != address(0), "forwarderArb not set");
+        require(forwarderOpt != address(0), "forwarderOpt not set");
📝 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
SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
// Optionally use the IAppGateway interface for looser coupling:
// IAppGateway gateway = IAppGateway(vm.envAddress("APP_GATEWAY"));
SuperTokenAppGateway gateway = SuperTokenAppGateway(vm.envAddress("APP_GATEWAY"));
address forwarderArb = gateway.forwarderAddresses(gateway.superToken(), 421614);
address forwarderOpt = gateway.forwarderAddresses(gateway.superToken(), 11155420);
require(forwarderArb != address(0), "forwarderArb not set");
require(forwarderOpt != address(0), "forwarderOpt not set");
🤖 Prompt for AI Agents
In script/supertoken/TransferSuperToken.s.sol around lines 16–19, the call to
gateway.forwarderAddresses should match the IAppGateway signature
forwarderAddresses(bytes32,uint32): ensure the numeric chain IDs are passed as
uint32 (e.g. uint32(421614) and uint32(11155420)) rather than a uint64 cast, and
after resolving each forwarder address add an assertion/require that it is
non-zero (require(forwarder != address(0), "forwarder not configured for chain
X")) to fail fast on misconfiguration.

Comment on lines +29 to +33
bytes memory encodedOrder = abi.encode(transferOrder);
bytes memory encodedPayload = abi.encodeWithSelector(
bytes4(keccak256("transfer(bytes)")),
encodedOrder
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use a typed selector instead of a string literal hash for safety.

A typed selector guards against typos and signature drift. This should reference an interface’s selector.

Apply this diff:

-        bytes memory encodedPayload = abi.encodeWithSelector(
-            bytes4(keccak256("transfer(bytes)")),
-            encodedOrder
-        );
+        bytes memory encodedPayload = abi.encodeWithSelector(
+            ITransfer.transfer.selector,
+            encodedOrder
+        );

Add this minimal interface in this file (outside the selected range), above the contract:

interface ITransfer {
    function transfer(bytes calldata data) external;
}
🤖 Prompt for AI Agents
In script/supertoken/TransferSuperToken.s.sol around lines 29 to 33, the code
uses bytes4(keccak256("transfer(bytes)")) as the selector; add the minimal
interface `ITransfer` (with function transfer(bytes calldata data) external)
above the contract (outside the selected range) and replace the keccak selector
expression with the typed selector ITransfer.transfer.selector so the selector
is type-checked and guarded against typos/signature drift.

Events.RequestCompletedWithErrors,
];

export const configurationsEvents = [Events.PlugAdded];
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

configurationsEvents array needs more events

The configurationsEvents array only includes PlugAdded but configurations typically involve more operations like removal or updates.


🏁 Script executed:

#!/bin/bash
# Check if there are other configuration-related events that should be included
ast-grep --pattern 'event Plug$_($$$)' 

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Search for configuration-related events in the Configurations contract
rg "event.*Plug|event.*Config" contracts/evmx/Configurations.sol contracts/evmx/interfaces/IConfigurations.sol -A 1

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -e
# List all Solidity files to locate the Configurations contract
echo "=== Solidity files in repo ==="
fd -e sol

# Search for interface IConfigurations definitions
echo "=== interface IConfigurations declarations ==="
rg "interface IConfigurations" -n -t sol || echo "No interface IConfigurations found"

# Search for contract Configurations definitions
echo "=== contract Configurations declarations ==="
rg "contract Configurations" -n -t sol || echo "No contract Configurations found"

# Search for events related to configuration (Config) and plugs (Plug)
echo "=== Events containing 'Config' ==="
rg "event\s+\w*Config\w*" -n -t sol || echo "No Config-related events found"

echo "=== Events containing 'Plug' ==="
rg "event\s+\w*Plug\w*" -n -t sol || echo "No Plug-related events found"

Length of output: 5368


🏁 Script executed:

#!/bin/bash
set -e
# Inspect event declarations in the watcher Configurations contract
rg -n 'event ' contracts/evmx/watcher/Configurations.sol

Length of output: 383


Add missing configuration watcher events

This should include all events emitted by the watcher’s Configurations contract.
File: src/events.ts, line 28.

-export const configurationsEvents = [Events.PlugAdded];
+export const configurationsEvents = [
+  Events.PlugAdded,
+  Events.SwitchboardSet,
+  Events.SocketSet,
+  Events.IsValidPlugSet,
+];

This ensures handlers react to SwitchboardSet, SocketSet, and IsValidPlugSet as well as PlugAdded.

📝 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 const configurationsEvents = [Events.PlugAdded];
export const configurationsEvents = [
Events.PlugAdded,
Events.SwitchboardSet,
Events.SocketSet,
Events.IsValidPlugSet,
];
🤖 Prompt for AI Agents
In src/events.ts around line 28, the configurationsEvents array only contains
Events.PlugAdded but must include all events emitted by the Configurations
contract; update the array to include Events.SwitchboardSet, Events.SocketSet,
and Events.IsValidPlugSet alongside Events.PlugAdded so handlers will react to
SwitchboardSet, SocketSet, IsValidPlugSet, and PlugAdded.

Comment on lines 6 to 10
export const defaultFinalityBlocks: ChainFinalityBlocks = {
[FinalityBucket.LOW]: 1,
[FinalityBucket.LOW]: 0,
[FinalityBucket.MEDIUM]: "safe",
[FinalityBucket.HIGH]: "finalized",
};
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

LOW finality = 0 can cause reorg exposure and falsy-defaulting bugs

  • Security: 0 means "head-of-chain" (no confirmations). For chains without overrides, this weakens safety and increases reorg risk.
  • Correctness: Any consumer using || to apply defaults will treat 0 as falsy and silently override it, leading to subtle bugs.

This should either:

  • Be explicitly documented as intentional “head” semantics and enforced across consumers (use ?? instead of ||, and explicit undefined checks), or
  • Remain 1 for a safer default if production paths rely on it.

If keeping 0, add an in-file note to make the contract explicit:

 export const defaultFinalityBlocks: ChainFinalityBlocks = {
+  // NOTE: LOW=0 intentionally means "head" (no confirmation wait).
+  // Consumers MUST NOT use `||` to default this value, use nullish coalescing `??` instead.
   [FinalityBucket.LOW]: 0,
   [FinalityBucket.MEDIUM]: "safe",
   [FinalityBucket.HIGH]: "finalized",
 };

Run this script to find risky || usages near finality logic and all call sites of getFinalityBlocks. Inspect and replace || with ?? where 0 is a valid value.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Potential falsy-default patterns near finality-related identifiers:"
rg -n -C 2 --glob '!dist/*' --glob '!build/*' $'(finality|FinalityBucket|getFinalityBlocks)[^\n]{0,120}\\|\\|' || true

echo
echo "All uses of getFinalityBlocks:"
rg -n -A 3 -B 1 --glob '!dist/*' --glob '!build/*' 'getFinalityBlocks\\s*\\(' || true

echo
echo "Broad AST scan for '||' expressions; review matches that involve finality logic:"
ast-grep --pattern $'$_ || $_' --lang ts | rg -n -C 2 '(finality|FinalityBucket|blocks|confirm|finalized|safe)' || true

Length of output: 850


Document LOW=0 as Intentional Head Semantics & Enforce Nullish Coalescing

No internal ||–based defaults around finality were found, but keeping 0 without documentation risks future consumers silently overriding it. This should be addressed in src/finality.ts:

• Add an explicit note beside [FinalityBucket.LOW]: 0
• Require all consumers to use ?? instead of || when defaulting this value
• Alternatively, bump the LOW default to 1 if you want a safer confirmation baseline

Suggested diff:

 export const defaultFinalityBlocks: ChainFinalityBlocks = {
+  // NOTE: LOW=0 intentionally means "head" (no confirmation wait).
+  // Consumers MUST use nullish coalescing (`??`) instead of `||` when defaulting this value.
   [FinalityBucket.LOW]: 0,
   [FinalityBucket.MEDIUM]: "safe",
   [FinalityBucket.HIGH]: "finalized",
 };
🤖 Prompt for AI Agents
In src/finality.ts around lines 6 to 10, the LOW finality bucket is set to 0
without clarification which can be misused by consumers who default with ||; add
an inline comment next to [FinalityBucket.LOW]: 0 that documents this is an
intentional “head semantics” zero value, update any nearby documentation/export
comment to instruct callers to use nullish coalescing (??) when applying
defaults, and either (A) keep 0 and add that guidance or (B) change the default
to 1 if you prefer a non-zero confirmation baseline — choose one approach and
make the code comment and public docs consistent with it.

Comment on lines +27 to +32
export const getNonce = () => {
const timestamp = Date.now();
const random = Math.floor(Math.random() * 1000);
const nonce = Number(String(timestamp) + String(random));
return nonce;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace Math.random + string concat with crypto-grade randomness and arithmetic

This should use a CSPRNG and avoid string concatenation (leading zeros are lost and it’s slower). Keep the value within JS safe integer range and preserve the “timestamp + 3-digit suffix” shape.

Apply this diff:

-export const getNonce = () => {
-  const timestamp = Date.now();
-  const random = Math.floor(Math.random() * 1000);
-  const nonce = Number(String(timestamp) + String(random));
-  return nonce;
-};
+export const getNonce = (): number => {
+  const timestampMs = Date.now(); // 13 digits for now
+  // Derive a cryptographically secure 0..999 value.
+  const randBytes = ethers.utils.randomBytes(2); // Uint8Array length 2
+  const rand = (((randBytes[0] << 8) | randBytes[1]) % 1000); // 0..999
+  // Equivalent to zero-padded 3-digit suffix without string ops.
+  return timestampMs * 1000 + rand; // stays < 2^53 until ~year 2255
+};

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/signer.ts around lines 27 to 32, replace the non-cryptographic
Math.random + string concatenation with a CSPRNG and arithmetic: generate a
0–999 integer suffix using a crypto API (e.g., Node's crypto.randomInt(0,1000)
or web crypto getRandomValues), compute nonce = timestamp * 1000 + suffix
(preserving leading zeros and the "timestamp + 3-digit suffix" shape), ensure
this stays within Number.MAX_SAFE_INTEGER (timestamp*1000 is safe for
Date.now()), and return that numeric nonce; also add the required import/use of
the crypto API for your environment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +238 to 243
) internal async {
_setMaxFees(maxFees_);
_setOverrides(consumeFrom_);
_setSchedule(delayInSeconds_);
then(callbackSelector_, callbackData_);
watcher__().submitRequest(maxFees_, address(this), consumeFrom_, bytes(""));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Async modifier requires verification

The function signature changed to internal async. Verify that the async modifier is properly defined and that the promise-based flow correctly handles asynchronous execution.


🏁 Script executed:

#!/bin/bash
# Search for async modifier definition and usage patterns
rg "modifier async" -A 5

# Check for other internal async functions to understand the pattern
ast-grep --pattern 'function $_($$$) internal async'

Length of output: 399


Inspect async helper implementations for correct hook behavior and promise chaining.


🏁 Script executed:

#!/bin/bash
# Verify definitions of async lifecycle hooks and then(...)
rg "function _preAsync" -A5
rg "function _postAsync" -A5
rg "function then" -A5

Length of output: 2486


Relocate then() after the promise is created
Calling then() inside your async‐wrapped function attaches the callback to the previous promise (since _postAsync()—which creates the new promise—is invoked after your body). This should register the callback on the newly created promise.

• AuctionManager.sol (lines 238–243): remove the in-body then(callbackSelector_, callbackData_).
• Either call then(...) immediately after the async function returns, or extend the async modifier to accept the selector/data and invoke IPromise(...).then(...) inside _postAsync().

Example modifier change:

 modifier async(bytes4 selector_, bytes memory data_) {
     _preAsync();
     _;
-    _postAsync();
-    // body must call then(), but it's too early
+    _postAsync();
+    IPromise(watcher__().latestAsyncPromise()).then(selector_, data_);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contracts/evmx/AuctionManager.sol around lines 238–243, the in-body
then(callbackSelector_, callbackData_) is attaching the callback to the previous
promise because the new promise is created after the async function body; remove
the then(...) call from inside this async-wrapped function and instead attach
the callback to the promise returned by the call (i.e., call .then(selector,
data) immediately after invoking the async function), or modify the
async/_postAsync machinery to accept the selector/data and call
IPromise(...).then(...) inside _postAsync so the callback is registered on the
newly created promise.

Comment on lines +124 to +128
onCompleteData = abi.encodeWithSelector(
this.onDeployComplete.selector,
watcher__().getCurrentRequestCount(),
abi.encode(chainSlug_)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Potential reentrancy vulnerability in deployment callback flow

The new onDeployComplete callback mechanism encodes watcher__().getCurrentRequestCount() at deployment time but doesn't validate it during callback execution. This creates a window for request manipulation between deployment initiation and completion.

The deployment flow should validate that the request count hasn't been tampered with:

-function onDeployComplete(uint40, bytes calldata onCompleteData_) external virtual onlyWatcher {
+function onDeployComplete(uint40 requestCount_, bytes calldata onCompleteData_) external virtual onlyWatcher {
+    // Validate the request count matches expected state
+    require(requestCount_ <= watcher__().getCurrentRequestCount(), "Invalid request count");
     if (onCompleteData_.length == 0) return;
     uint32 chainSlug = abi.decode(onCompleteData_, (uint32));
     initializeOnChain(chainSlug);
 }
📝 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
onCompleteData = abi.encodeWithSelector(
this.onDeployComplete.selector,
watcher__().getCurrentRequestCount(),
abi.encode(chainSlug_)
);
function onDeployComplete(uint40 requestCount_, bytes calldata onCompleteData_) external virtual onlyWatcher {
// Validate the request count matches expected state
require(requestCount_ <= watcher__().getCurrentRequestCount(), "Invalid request count");
if (onCompleteData_.length == 0) return;
uint32 chainSlug = abi.decode(onCompleteData_, (uint32));
initializeOnChain(chainSlug);
}

Comment on lines 145 to 149
forwarderAddresses[contractId][chainSlug] = asyncDeployer__().getOrDeployForwarderContract(
abi.decode(returnData_, (address)),
toBytes32Format(abi.decode(returnData_, (address))),
chainSlug
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unchecked address-to-bytes32 conversion could silently fail

The toBytes32Format conversion in setAddress doesn't validate that the decoded address is valid before conversion.

Add validation before conversion:

 function setAddress(bytes memory data_, bytes memory returnData_) external onlyPromises {
     (uint32 chainSlug, bytes32 contractId) = abi.decode(data_, (uint32, bytes32));
+    address decodedAddr = abi.decode(returnData_, (address));
+    require(decodedAddr != address(0), "Invalid deployed address");
     forwarderAddresses[contractId][chainSlug] = asyncDeployer__().getOrDeployForwarderContract(
-        toBytes32Format(abi.decode(returnData_, (address))),
+        toBytes32Format(decodedAddr),
         chainSlug
     );
 }
📝 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
forwarderAddresses[contractId][chainSlug] = asyncDeployer__().getOrDeployForwarderContract(
abi.decode(returnData_, (address)),
toBytes32Format(abi.decode(returnData_, (address))),
chainSlug
);
}
address decodedAddr = abi.decode(returnData_, (address));
require(decodedAddr != address(0), "Invalid deployed address");
forwarderAddresses[contractId][chainSlug] = asyncDeployer__().getOrDeployForwarderContract(
toBytes32Format(decodedAddr),
chainSlug
);
}
🤖 Prompt for AI Agents
In contracts/evmx/base/AppGatewayBase.sol around lines 145-149, the code decodes
an address from returnData_ and directly passes it into toBytes32Format which
can silently fail for invalid or zero addresses; decode returnData_ into a local
address variable first, validate returnData_ length if applicable and require
the decoded address is non-zero (address != address(0)) before calling
toBytes32Format, and handle the failure path (revert with a clear error or skip
assignment) so only a validated address is converted and used to set
forwarderAddresses.

Comment on lines +225 to 227
bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing validation for zero bytes32 address

The _setValidPlug function doesn't validate if onchainAddress is zero before passing it to the watcher, which could register invalid plugs.

Add validation:

 function _setValidPlug(bool isValid, uint32 chainSlug_, bytes32 contractId_) internal {
     bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
+    require(onchainAddress != bytes32(0), "Invalid onchain address");
     watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
 }
📝 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
bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
}
function _setValidPlug(bool isValid, uint32 chainSlug_, bytes32 contractId_) internal {
bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
require(onchainAddress != bytes32(0), "Invalid onchain address");
watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
}
🤖 Prompt for AI Agents
In contracts/evmx/base/AppGatewayBase.sol around lines 225 to 227, the function
calls watcher__().setIsValidPlug without ensuring onchainAddress is non-zero;
add a check that onchainAddress != bytes32(0) and revert with a clear error
message if it is zero (e.g., require(onchainAddress != bytes32(0), "Invalid
onchain address")), then proceed to call watcher__().setIsValidPlug(isValid,
chainSlug_, onchainAddress).

Comment on lines 172 to 175
bytes32 prevBatchDigestHash = getPrevBatchDigestHash(
payloadParams.requestCount,
payloadParams.batchCount
uint40(payloadParams.payloadPointer >> 120),
uint40(payloadParams.payloadPointer >> 80)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bit-shift operations lack validation

The bit-shift operations to extract requestCount and batchCount from payloadPointer don't validate if the values are reasonable. Invalid values could cause issues in getPrevBatchDigestHash.

Add validation:

 bytes32 prevBatchDigestHash = getPrevBatchDigestHash(
     uint40(payloadParams.payloadPointer >> 120),
     uint40(payloadParams.payloadPointer >> 80)
 );
+// Validate extracted values are non-zero and reasonable
+uint40 requestCount = uint40(payloadParams.payloadPointer >> 120);
+uint40 batchCount = uint40(payloadParams.payloadPointer >> 80);
+if (requestCount == 0) revert("Invalid request count");
+bytes32 prevBatchDigestHash = getPrevBatchDigestHash(requestCount, batchCount);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In contracts/evmx/watcher/precompiles/WritePrecompile.sol around lines 172-175,
the code directly shifts payloadParams.payloadPointer to derive two uint40
values and passes them to getPrevBatchDigestHash without validating them; change
this to first mask the shifted values to 40 bits into local uint40 variables,
then validate those variables against expected ranges (e.g., non-zero if that's
required, and within any protocol-specific max counts or the current payload
length/known limits), and revert with a clear error message if they are out of
bounds before calling getPrevBatchDigestHash.

Comment on lines +29 to +35
bytes memory data = abi.encodeWithSignature(
"transferCredits(address,uint256)",
newAppGateway,
availableFees
);
(bool success, ) = appGateway.call(data);
require(success, "Transfer failed");
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing validation before credit transfer

The script doesn't validate that availableFees > 0 before attempting the transfer. This could result in unnecessary transaction costs if there are no credits to transfer.

Add validation:

         uint256 availableFees = feesManager.getAvailableCredits(appGateway);
         console.log("Available fees:", availableFees);
+        require(availableFees > 0, "No available credits to transfer");
         bytes memory data = abi.encodeWithSignature(
             "transferCredits(address,uint256)",
             newAppGateway,
             availableFees
         );
📝 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
bytes memory data = abi.encodeWithSignature(
"transferCredits(address,uint256)",
newAppGateway,
availableFees
);
(bool success, ) = appGateway.call(data);
require(success, "Transfer failed");
uint256 availableFees = feesManager.getAvailableCredits(appGateway);
console.log("Available fees:", availableFees);
require(availableFees > 0, "No available credits to transfer");
bytes memory data = abi.encodeWithSignature(
"transferCredits(address,uint256)",
newAppGateway,
availableFees
);
(bool success, ) = appGateway.call(data);
require(success, "Transfer failed");
🤖 Prompt for AI Agents
In script/helpers/TransferRemainingCredits.s.sol around lines 29 to 35, the code
attempts to transfer credits without checking if availableFees is greater than
zero; add a validation that availableFees > 0 before encoding and calling
transferCredits and either revert with a clear message or return early to avoid
paying gas for a no-op transfer; if reverting, use require(availableFees > 0,
"No credits to transfer"); otherwise skip the call when availableFees == 0.

Comment on lines +24 to +26
uint256 availableFees = feesManager.getAvailableCredits(appGateway);
console.log("Available fees:", availableFees);
feesManager.transferCredits(appGateway, vm.addr(deployerPrivateKey), availableFees);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Credit transfer logic is correct but lacks safety check.

The script transfers all available credits but should verify that availableFees > 0 before attempting the transfer to avoid unnecessary transactions.

         uint256 availableFees = feesManager.getAvailableCredits(appGateway);
         console.log("Available fees:", availableFees);
-        feesManager.transferCredits(appGateway, vm.addr(deployerPrivateKey), availableFees);
+        if (availableFees > 0) {
+            feesManager.transferCredits(appGateway, vm.addr(deployerPrivateKey), availableFees);
+        } else {
+            console.log("No available credits to withdraw");
+        }
📝 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
uint256 availableFees = feesManager.getAvailableCredits(appGateway);
console.log("Available fees:", availableFees);
feesManager.transferCredits(appGateway, vm.addr(deployerPrivateKey), availableFees);
uint256 availableFees = feesManager.getAvailableCredits(appGateway);
console.log("Available fees:", availableFees);
if (availableFees > 0) {
feesManager.transferCredits(appGateway, vm.addr(deployerPrivateKey), availableFees);
} else {
console.log("No available credits to withdraw");
}
🤖 Prompt for AI Agents
In script/helpers/WithdrawRemainingCredits.s.sol around lines 24 to 26, the code
transfers all available credits without checking if availableFees is greater
than zero; add a guard that checks if availableFees > 0 before calling
feesManager.transferCredits (log a message and skip/return when zero) so you
avoid unnecessary transactions when there are no credits to transfer.

Comment on lines +18 to +19
// Setting fee payment on Arbitrum Sepolia
uint256 fees = 1 ether;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Hardcoded fee value should be configurable.

The fixed 1 ether fee should be sourced from environment variables or configuration to support different deployment environments.

-        // Setting fee payment on Arbitrum Sepolia
-        uint256 fees = 1 ether;
+        uint256 fees = vm.envUint("GATEWAY_FEES");
📝 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
// Setting fee payment on Arbitrum Sepolia
uint256 fees = 1 ether;
uint256 fees = vm.envUint("GATEWAY_FEES");
🤖 Prompt for AI Agents
In script/supertoken/DeployEVMxSuperTokenApp.s.sol around lines 18-19, the fee
is hardcoded as `1 ether`; change this to read a configurable value (e.g., from
an environment variable or config) with a sensible default. Update the script to
parse the env/config value into a uint256 (apply appropriate unit conversion so
input can be in ether or wei), validate it (non-zero and within expected
bounds), and use that variable instead of the hardcoded literal; also document
the env variable name and default in the script header or README.

Comment on lines +39 to +46
hoax(user);
feesManager.withdrawCredits(
feesChainSlug,
address(feesConfig.testUSDC),
withdrawAmount,
feesAmount,
address(receiver)
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing validation of withdrawal parameters

The test doesn't verify that the withdrawal was successful or that the correct amounts were transferred. This makes the test incomplete as a validation tool.

Add balance checks:

+        uint256 userBalanceBefore = user.balance;
+        uint256 receiverBalanceBefore = receiver.balance;
+        
         hoax(user);
         feesManager.withdrawCredits(
             feesChainSlug,
             address(feesConfig.testUSDC),
             withdrawAmount,
             feesAmount,
             address(receiver)
         );
+        
+        // Verify the withdrawal was processed
+        assertEq(user.balance, userBalanceBefore - feesAmount, "Incorrect fee deduction");
📝 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
hoax(user);
feesManager.withdrawCredits(
feesChainSlug,
address(feesConfig.testUSDC),
withdrawAmount,
feesAmount,
address(receiver)
);
uint256 userBalanceBefore = user.balance;
uint256 receiverBalanceBefore = receiver.balance;
hoax(user);
feesManager.withdrawCredits(
feesChainSlug,
address(feesConfig.testUSDC),
withdrawAmount,
feesAmount,
address(receiver)
);
// Verify the withdrawal was processed
assertEq(user.balance, userBalanceBefore - feesAmount, "Incorrect fee deduction");
🤖 Prompt for AI Agents
In test/AuctionManager.t.sol around lines 39 to 46, the test calls
feesManager.withdrawCredits but does not assert outcomes; capture relevant
pre-transaction balances (contract/feesManager token balance, receiver token
balance and any on-chain credit/accounting value tracked by feesManager) then
call withdrawCredits and capture post-transaction balances, and add assertions
that (1) the receiver's token balance increased by the expected withdrawAmount
(or withdrawAmount minus fees if applicable), (2) the feesManager/contract token
balance decreased by that same amount, and (3) any internal credits or fees
accounting value (feesAmount or credits ledger) was updated/decreased by the
expected fees/credits; use the test framework's assert functions
(assertEq/assertTrue) to compare before/after values with clear messages.

Comment on lines +47 to +51
executeRequest();

// expire bid settle
executeRequest();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Test lacks assertions for critical state changes

The test executes requests but doesn't verify the expected outcomes. After expiring a bid, the test should validate that the bid was actually expired and the expected state changes occurred.

Add assertions to verify the test objectives:

         executeRequest();
 
         // expire bid settle
         executeRequest();
+        
+        // Add assertions to verify:
+        // 1. The bid was actually expired
+        // 2. Any refunds were processed correctly
+        // 3. The auction state was updated appropriately
+        // Example:
+        // assertEq(auctionManager.getBidStatus(bidId), BidStatus.Expired);
+        // assertEq(receiver.balance, expectedRefundAmount);
📝 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
executeRequest();
// expire bid settle
executeRequest();
}
executeRequest();
// expire bid settle
executeRequest();
// Add assertions to verify:
// 1. The bid was actually expired
// 2. Any refunds were processed correctly
// 3. The auction state was updated appropriately
// Example:
// assertEq(auctionManager.getBidStatus(bidId), BidStatus.Expired);
// assertEq(receiver.balance, expectedRefundAmount);
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

// slot 51
/// @notice switchboard type
bytes32 public sbType;
bytes32 public deprecatedSbType;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Deprecated field naming convention

The field is renamed to deprecatedSbType but is still actively used in line 293. If this field is truly deprecated, it shouldn't be used in active logic.

Either:

  1. Remove the "deprecated" prefix if the field is still in use
  2. Complete the migration to remove usage of this deprecated field

Comment on lines +317 to 320
function handleRevert(bytes32 payloadId_) external override {
if (watcher__().getPayloadParams(payloadId_).asyncPromise != msg.sender) return;
emit WithdrawFailed(payloadId_);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing access control in handleRevert

The handleRevert function only checks if the caller is the async promise but doesn't revert for unauthorized callers. This silently ignores invalid calls.

 function handleRevert(bytes32 payloadId_) external override {
-    if (watcher__().getPayloadParams(payloadId_).asyncPromise != msg.sender) return;
+    require(
+        watcher__().getPayloadParams(payloadId_).asyncPromise == msg.sender,
+        "Unauthorized caller"
+    );
     emit WithdrawFailed(payloadId_);
 }
📝 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
function handleRevert(bytes32 payloadId_) external override {
if (watcher__().getPayloadParams(payloadId_).asyncPromise != msg.sender) return;
emit WithdrawFailed(payloadId_);
}
function handleRevert(bytes32 payloadId_) external override {
require(
watcher__().getPayloadParams(payloadId_).asyncPromise == msg.sender,
"Unauthorized caller"
);
emit WithdrawFailed(payloadId_);
}
🤖 Prompt for AI Agents
In contracts/evmx/fees/Credit.sol around lines 317 to 320, handleRevert
currently just returns when msg.sender is not the expected asyncPromise which
silently ignores unauthorized calls; change it to explicitly revert on
unauthorized callers (e.g.,
require(watcher__().getPayloadParams(payloadId_).asyncPromise == msg.sender,
"handleRevert: unauthorized caller")) so only the authorized asyncPromise can
call the function, and emit WithdrawFailed(payloadId_) only after the
authorization check passes.

Comment on lines +97 to +106
function connect(bytes32 appGatewayId_, uint64 switchboardId_) external override {
if (isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED)
revert InvalidSwitchboard();

PlugConfig storage _plugConfig = _plugConfigs[msg.sender];

PlugConfigEvm storage _plugConfig = _plugConfigs[msg.sender];
_plugConfig.appGatewayId = appGatewayId_;
_plugConfig.switchboard = switchboard_;
_plugConfig.switchboardId = switchboardId_;

emit PlugConnected(msg.sender, appGatewayId_, switchboardId_);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect function signature in connect

The function signature doesn't match the interface declaration. The interface ISocket expects connect(bytes32 appGatewayId_, address socket_, uint64 switchboardId_) but this implementation omits the socket_ parameter.

-    function connect(bytes32 appGatewayId_, uint64 switchboardId_) external override {
+    function connect(bytes32 appGatewayId_, address socket_, uint64 switchboardId_) external override {
+        // socket_ parameter can be validated or ignored based on requirements
         if (isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED)
             revert InvalidSwitchboard();

         PlugConfigEvm storage _plugConfig = _plugConfigs[msg.sender];
         _plugConfig.appGatewayId = appGatewayId_;
         _plugConfig.switchboardId = switchboardId_;

         emit PlugConnected(msg.sender, appGatewayId_, switchboardId_);
     }
📝 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
function connect(bytes32 appGatewayId_, uint64 switchboardId_) external override {
if (isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED)
revert InvalidSwitchboard();
PlugConfig storage _plugConfig = _plugConfigs[msg.sender];
PlugConfigEvm storage _plugConfig = _plugConfigs[msg.sender];
_plugConfig.appGatewayId = appGatewayId_;
_plugConfig.switchboard = switchboard_;
_plugConfig.switchboardId = switchboardId_;
emit PlugConnected(msg.sender, appGatewayId_, switchboardId_);
}
function connect(bytes32 appGatewayId_, address socket_, uint64 switchboardId_) external override {
// socket_ parameter can be validated or ignored based on requirements
if (isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED)
revert InvalidSwitchboard();
PlugConfigEvm storage _plugConfig = _plugConfigs[msg.sender];
_plugConfig.appGatewayId = appGatewayId_;
_plugConfig.switchboardId = switchboardId_;
emit PlugConnected(msg.sender, appGatewayId_, switchboardId_);
}
🤖 Prompt for AI Agents
In contracts/protocol/SocketConfig.sol around lines 97 to 106, the connect
function signature is missing the address socket_ parameter required by ISocket;
update the function signature to connect(bytes32 appGatewayId_, address socket_,
uint64 switchboardId_) and then use socket_ in place of msg.sender when indexing
_plugConfigs and when emitting PlugConnected(msg.sender, ...) so the stored
PlugConfig and event use the supplied socket address; keep the same switchboard
validation and the override specifier.

Comment on lines +47 to +65
function syncOut(bytes32 payloadId_, uint32[] calldata remoteChainSlugs_) external {
bytes32 digest = socket__.payloadIdToDigest(payloadId_);
// not attested
if (digest == bytes32(0) || !isAttested[digest]) revert NotAttested();

// already synced out
if (isSyncedOut[digest]) return;
isSyncedOut[digest] = true;

// not executed
ExecutionStatus isExecuted = socket__.payloadExecuted(payloadId_);
if (isExecuted != ExecutionStatus.Executed) revert NotExecuted();

bytes memory message = abi.encode(payloadId_, digest);
for (uint256 i = 0; i < remoteChainSlugs_.length; i++) {
RemoteEndpoint memory endpoint = chainSlugToRemoteEndpoint[remoteChainSlugs_[i]];
messageTransmitter.sendMessage(endpoint.remoteDomain, endpoint.remoteAddress, message);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing access control on syncOut function

The syncOut function lacks access control, allowing any caller to trigger cross-chain message transmission. This could lead to spam or DoS attacks.

-    function syncOut(bytes32 payloadId_, uint32[] calldata remoteChainSlugs_) external {
+    function syncOut(bytes32 payloadId_, uint32[] calldata remoteChainSlugs_) external onlyOwner {
         bytes32 digest = socket__.payloadIdToDigest(payloadId_);
         // not attested
         if (digest == bytes32(0) || !isAttested[digest]) revert NotAttested();
📝 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
function syncOut(bytes32 payloadId_, uint32[] calldata remoteChainSlugs_) external {
bytes32 digest = socket__.payloadIdToDigest(payloadId_);
// not attested
if (digest == bytes32(0) || !isAttested[digest]) revert NotAttested();
// already synced out
if (isSyncedOut[digest]) return;
isSyncedOut[digest] = true;
// not executed
ExecutionStatus isExecuted = socket__.payloadExecuted(payloadId_);
if (isExecuted != ExecutionStatus.Executed) revert NotExecuted();
bytes memory message = abi.encode(payloadId_, digest);
for (uint256 i = 0; i < remoteChainSlugs_.length; i++) {
RemoteEndpoint memory endpoint = chainSlugToRemoteEndpoint[remoteChainSlugs_[i]];
messageTransmitter.sendMessage(endpoint.remoteDomain, endpoint.remoteAddress, message);
}
}
function syncOut(bytes32 payloadId_, uint32[] calldata remoteChainSlugs_) external onlyOwner {
bytes32 digest = socket__.payloadIdToDigest(payloadId_);
// not attested
if (digest == bytes32(0) || !isAttested[digest]) revert NotAttested();
// already synced out
if (isSyncedOut[digest]) return;
isSyncedOut[digest] = true;
// not executed
ExecutionStatus isExecuted = socket__.payloadExecuted(payloadId_);
if (isExecuted != ExecutionStatus.Executed) revert NotExecuted();
bytes memory message = abi.encode(payloadId_, digest);
for (uint256 i = 0; i < remoteChainSlugs_.length; i++) {
RemoteEndpoint memory endpoint = chainSlugToRemoteEndpoint[remoteChainSlugs_[i]];
messageTransmitter.sendMessage(endpoint.remoteDomain, endpoint.remoteAddress, message);
}
}

Comment on lines +86 to +122
function proveRemoteExecutions(
bytes32[] calldata previousPayloadIds_,
bytes32 payloadId_,
bytes calldata transmitterSignature_,
ExecuteParams calldata executeParams_
) public {
// Calculate prevBatchDigestHash from stored remoteExecutedDigests
bytes32 prevBatchDigestHash = bytes32(0);
for (uint256 i = 0; i < previousPayloadIds_.length; i++) {
if (remoteExecutedDigests[previousPayloadIds_[i]] == bytes32(0))
revert RemoteExecutionNotFound();
prevBatchDigestHash = keccak256(
abi.encodePacked(prevBatchDigestHash, remoteExecutedDigests[previousPayloadIds_[i]])
);
}
// Check if the calculated prevBatchDigestHash matches the one in executeParams_
if (prevBatchDigestHash != executeParams_.prevBatchDigestHash)
revert PrevBatchDigestHashMismatch();

address transmitter = _recoverSigner(
keccak256(abi.encode(address(socket__), payloadId_)),
transmitterSignature_
);

// Construct current digest
(bytes32 appGatewayId, ) = socket__.getPlugConfig(executeParams_.target);
bytes32 constructedDigest = _createDigest(
transmitter,
payloadId_,
appGatewayId,
executeParams_
);

// Verify the constructed digest matches the stored one
if (!isAttested[constructedDigest]) revert NotAttested();
isRemoteExecuted[payloadId_] = true;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Race condition in proveRemoteExecutions

The function calculates prevBatchDigestHash from stored digests but doesn't verify these digests are final. If remote executions are still being processed, the hash calculation could be incorrect.

This should verify all previous payloads are marked as executed before calculating the hash:

     function proveRemoteExecutions(
         bytes32[] calldata previousPayloadIds_,
         bytes32 payloadId_,
         bytes calldata transmitterSignature_,
         ExecuteParams calldata executeParams_
     ) public {
         // Calculate prevBatchDigestHash from stored remoteExecutedDigests
         bytes32 prevBatchDigestHash = bytes32(0);
         for (uint256 i = 0; i < previousPayloadIds_.length; i++) {
             if (remoteExecutedDigests[previousPayloadIds_[i]] == bytes32(0))
                 revert RemoteExecutionNotFound();
+            // Verify the previous payload is marked as executed
+            if (!isRemoteExecuted[previousPayloadIds_[i]]) 
+                revert("Previous payload not executed");
             prevBatchDigestHash = keccak256(
                 abi.encodePacked(prevBatchDigestHash, remoteExecutedDigests[previousPayloadIds_[i]])
             );
         }
📝 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
function proveRemoteExecutions(
bytes32[] calldata previousPayloadIds_,
bytes32 payloadId_,
bytes calldata transmitterSignature_,
ExecuteParams calldata executeParams_
) public {
// Calculate prevBatchDigestHash from stored remoteExecutedDigests
bytes32 prevBatchDigestHash = bytes32(0);
for (uint256 i = 0; i < previousPayloadIds_.length; i++) {
if (remoteExecutedDigests[previousPayloadIds_[i]] == bytes32(0))
revert RemoteExecutionNotFound();
prevBatchDigestHash = keccak256(
abi.encodePacked(prevBatchDigestHash, remoteExecutedDigests[previousPayloadIds_[i]])
);
}
// Check if the calculated prevBatchDigestHash matches the one in executeParams_
if (prevBatchDigestHash != executeParams_.prevBatchDigestHash)
revert PrevBatchDigestHashMismatch();
address transmitter = _recoverSigner(
keccak256(abi.encode(address(socket__), payloadId_)),
transmitterSignature_
);
// Construct current digest
(bytes32 appGatewayId, ) = socket__.getPlugConfig(executeParams_.target);
bytes32 constructedDigest = _createDigest(
transmitter,
payloadId_,
appGatewayId,
executeParams_
);
// Verify the constructed digest matches the stored one
if (!isAttested[constructedDigest]) revert NotAttested();
isRemoteExecuted[payloadId_] = true;
}
function proveRemoteExecutions(
bytes32[] calldata previousPayloadIds_,
bytes32 payloadId_,
bytes calldata transmitterSignature_,
ExecuteParams calldata executeParams_
) public {
// Calculate prevBatchDigestHash from stored remoteExecutedDigests
bytes32 prevBatchDigestHash = bytes32(0);
for (uint256 i = 0; i < previousPayloadIds_.length; i++) {
if (remoteExecutedDigests[previousPayloadIds_[i]] == bytes32(0))
revert RemoteExecutionNotFound();
// Verify the previous payload is marked as executed
if (!isRemoteExecuted[previousPayloadIds_[i]])
revert("Previous payload not executed");
prevBatchDigestHash = keccak256(
abi.encodePacked(prevBatchDigestHash, remoteExecutedDigests[previousPayloadIds_[i]])
);
}
// Check if the calculated prevBatchDigestHash matches the one in executeParams_
if (prevBatchDigestHash != executeParams_.prevBatchDigestHash)
revert PrevBatchDigestHashMismatch();
address transmitter = _recoverSigner(
keccak256(abi.encode(address(socket__), payloadId_)),
transmitterSignature_
);
// Construct current digest
(bytes32 appGatewayId, ) = socket__.getPlugConfig(executeParams_.target);
bytes32 constructedDigest = _createDigest(
transmitter,
payloadId_,
appGatewayId,
executeParams_
);
// Verify the constructed digest matches the stored one
if (!isAttested[constructedDigest]) revert NotAttested();
isRemoteExecuted[payloadId_] = true;
}
🤖 Prompt for AI Agents
In contracts/protocol/switchboard/CCTPSwitchboard.sol around lines 86-122, the
loop that computes prevBatchDigestHash uses remoteExecutedDigests without
guaranteeing those remote executions are final; add a check inside the loop to
ensure each previousPayloadIds_[i] has been marked executed (e.g.,
require(isRemoteExecuted[previousId], "RemoteExecutionNotFinal") or revert
RemoteExecutionNotFinal()) before reading remoteExecutedDigests[previousId], so
you fail early if a prior execution is still processing and avoid computing an
incorrect hash.

Comment on lines +233 to +242
## IMessageSwitchboard

| Event | Arguments | Topic |
| ----- | --------- | ----- |

## ICCTPSwitchboard

| Event | Arguments | Topic |
| ----- | --------- | ----- |

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove empty interface sections that add no documentation value

The IMessageSwitchboard and ICCTPSwitchboard sections are empty placeholders that clutter the documentation.

Remove both empty sections:

-## IMessageSwitchboard
-
-| Event | Arguments | Topic |
-| ----- | --------- | ----- |
-
-## ICCTPSwitchboard
-
-| Event | Arguments | Topic |
-| ----- | --------- | ----- |
-
📝 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
## IMessageSwitchboard
| Event | Arguments | Topic |
| ----- | --------- | ----- |
## ICCTPSwitchboard
| Event | Arguments | Topic |
| ----- | --------- | ----- |
🤖 Prompt for AI Agents
In EventTopics.md around lines 233 to 242, the IMessageSwitchboard and
ICCTPSwitchboard sections are empty and add no documentation value; remove both
entire empty sections (the headers and their empty markdown tables) so the file
contains only meaningful, populated sections and headings remain consistent.
Ensure surrounding content spacing and table of contents (if any) are updated to
reflect the removals.

if (
await isConfigSetOnSocket(plug, socket, ZERO_APP_GATEWAY_ID, switchboard)
) {
if (await isConfigSetOnSocket(plug, socket, BYTES32_ZERO, switchboard)) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect switchboard parameter in config check

The isConfigSetOnSocket call passes switchboard (an address) as the fourth parameter, but based on the function signature in hardhat-scripts/utils/appConfig.ts, it expects switchboardId (a string).

-  if (await isConfigSetOnSocket(plug, socket, BYTES32_ZERO, switchboard)) {
+  const switchboardId = "0"; // Disconnected state uses switchboardId "0"
+  if (await isConfigSetOnSocket(plug, socket, BYTES32_ZERO, switchboardId)) {
📝 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
if (await isConfigSetOnSocket(plug, socket, BYTES32_ZERO, switchboard)) {
const switchboardId = "0"; // Disconnected state uses switchboardId "0"
if (await isConfigSetOnSocket(plug, socket, BYTES32_ZERO, switchboardId)) {
🤖 Prompt for AI Agents
In hardhat-scripts/admin/disconnect.ts around line 54, the call to
isConfigSetOnSocket passes the Switchboard contract object/address as the fourth
argument but the function expects a switchboardId string; replace the argument
with the switchboardId string (e.g., a local switchboardId variable) — if
switchboardId is not already available, derive it from the contract/address (for
example use switchboard.address or switchboard.address.toLowerCase() as the id)
and pass that string instead.

Comment on lines 93 to 96
// disconnect old fees plug
arbConfig.feesPlug.connectSocket(
bytes32(0),
address(arbConfig.socket),
address(arbConfig.switchboard)
);
hoax(socketOwner);
arbConfig.feesPlug.disconnectSocket();

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test should verify disconnection state

The test disconnects the fees plug but doesn't verify the disconnection was successful before proceeding.

     function testDisconnectFeesPlug() public {
         // disconnect old fees plug
         hoax(socketOwner);
         arbConfig.feesPlug.disconnectSocket();
+        
+        // Verify disconnection
+        (bytes32 appGatewayId, uint64 switchboardId) = arbConfig.socket.getPlugConfig(address(arbConfig.feesPlug));
+        assertEq(appGatewayId, bytes32(0), "App gateway ID should be reset");
+        assertEq(switchboardId, 0, "Switchboard ID should be reset");
📝 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
// disconnect old fees plug
arbConfig.feesPlug.connectSocket(
bytes32(0),
address(arbConfig.socket),
address(arbConfig.switchboard)
);
hoax(socketOwner);
arbConfig.feesPlug.disconnectSocket();
function testDisconnectFeesPlug() public {
// disconnect old fees plug
hoax(socketOwner);
arbConfig.feesPlug.disconnectSocket();
// Verify disconnection
(bytes32 appGatewayId, uint64 switchboardId) = arbConfig.socket.getPlugConfig(address(arbConfig.feesPlug));
assertEq(appGatewayId, bytes32(0), "App gateway ID should be reset");
assertEq(switchboardId, 0, "Switchboard ID should be reset");
}
🤖 Prompt for AI Agents
In test/FeesTest.t.sol around lines 93-96, after calling
arbConfig.feesPlug.disconnectSocket() the test must assert the plug is actually
disconnected; add a check using the plug's public state getter (for example
assertEq(arbConfig.feesPlug.socket(), address(0)) or
assertFalse(arbConfig.feesPlug.isConnected())), or alternatively call an action
that requires a connected socket and expect it to revert, to ensure the
disconnect succeeded before proceeding.

Comment on lines +1078 to +1081
(, Transaction memory transaction, , , , ) = abi.decode(
params.precompileData,
(address, Transaction, WriteFinality, uint256, uint256, address)
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect type in abi.decode causes compilation error

The decode statement expects address as the last parameter but the comment and context suggest it should be uint64 for switchboard ID.

Fix the type mismatch:

            (, Transaction memory transaction, , , , ) = abi.decode(
                params.precompileData,
-               (address, Transaction, WriteFinality, uint256, uint256, address)
+               (address, Transaction, WriteFinality, uint256, uint256, uint64)
            );
📝 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
(, Transaction memory transaction, , , , ) = abi.decode(
params.precompileData,
(address, Transaction, WriteFinality, uint256, uint256, address)
);
(, Transaction memory transaction, , , , ) = abi.decode(
params.precompileData,
(address, Transaction, WriteFinality, uint256, uint256, uint64)
);
🤖 Prompt for AI Agents
In test/SetupTest.t.sol around lines 1078-1081, the abi.decode tuple uses
address as the final type but the context/comment indicates that value is a
uint64 Switchboard ID; change the last type in the abi.decode type list from
address to uint64 so the decoded tuple matches the expected types (e.g.,
(address, Transaction, WriteFinality, uint256, uint256, uint64)); ensure any
variables receiving that decoded value are declared as uint64 (or explicitly
cast) to resolve the compilation error.

Comment on lines +1131 to 1168
// Count valid plugs first. In some cases we might have contractIds such that oly a subset is
// deployed on a chain. for ex, vault on source, and supertoken on destination.
uint256 validPlugCount = 0;
for (uint i = 0; i < contractIds_.length; i++) {
bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
if (plug != bytes32(0)) {
validPlugCount++;
}
}

// Create array with exact size needed
AppGatewayConfig[] memory configs = new AppGatewayConfig[](validPlugCount);
uint256 configIndex = 0;

SocketContracts memory socketConfig = getSocketConfig(chainSlug_);
for (uint i = 0; i < contractIds_.length; i++) {
address plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
uint64 switchboardId = configurations.switchboards(chainSlug_, appGateway_.sbType());
if (plug != bytes32(0)) {
configs[configIndex] = AppGatewayConfig({
plug: plug,
chainSlug: chainSlug_,
plugConfig: PlugConfigGeneric({
appGatewayId: toBytes32Format(address(appGateway_)),
switchboardId: switchboardId
})
});
configIndex++;
}
}

configs[i] = AppGatewayConfig({
plug: plug,
chainSlug: chainSlug_,
plugConfig: PlugConfig({
appGatewayId: encodeAppGatewayId(address(appGateway_)),
switchboard: address(socketConfig.switchboard)
})
});
// Only call watcher if we have valid configs
if (validPlugCount > 0) {
watcherMultiCall(
address(configurations),
abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
);
}
watcherMultiCall(
address(configurations),
abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize setupGatewayAndPlugs to avoid redundant iterations

The function iterates through contractIds_ twice - once to count valid plugs and again to populate configs. This doubles the computational cost unnecessarily.

Combine into a single pass with dynamic array:

    function setupGatewayAndPlugs(
        uint32 chainSlug_,
        IAppGateway appGateway_,
        bytes32[] memory contractIds_
    ) internal {
-       // Count valid plugs first. In some cases we might have contractIds such that oly a subset is
-       // deployed on a chain. for ex, vault on source, and supertoken on destination.
-       uint256 validPlugCount = 0;
-       for (uint i = 0; i < contractIds_.length; i++) {
-           bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
-           if (plug != bytes32(0)) {
-               validPlugCount++;
-           }
-       }
-
-       // Create array with exact size needed
-       AppGatewayConfig[] memory configs = new AppGatewayConfig[](validPlugCount);
-       uint256 configIndex = 0;
+       AppGatewayConfig[] memory tempConfigs = new AppGatewayConfig[](contractIds_.length);
+       uint256 configCount = 0;

        for (uint i = 0; i < contractIds_.length; i++) {
            bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
            uint64 switchboardId = configurations.switchboards(chainSlug_, appGateway_.sbType());
            if (plug != bytes32(0)) {
-               configs[configIndex] = AppGatewayConfig({
+               tempConfigs[configCount] = AppGatewayConfig({
                    plug: plug,
                    chainSlug: chainSlug_,
                    plugConfig: PlugConfigGeneric({
                        appGatewayId: toBytes32Format(address(appGateway_)),
                        switchboardId: switchboardId
                    })
                });
-               configIndex++;
+               configCount++;
            }
        }

        // Only call watcher if we have valid configs
-       if (validPlugCount > 0) {
+       if (configCount > 0) {
+           // Resize array to actual count
+           AppGatewayConfig[] memory configs = new AppGatewayConfig[](configCount);
+           for (uint i = 0; i < configCount; i++) {
+               configs[i] = tempConfigs[i];
+           }
            watcherMultiCall(
                address(configurations),
                abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
            );
        }
    }
📝 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
// Count valid plugs first. In some cases we might have contractIds such that oly a subset is
// deployed on a chain. for ex, vault on source, and supertoken on destination.
uint256 validPlugCount = 0;
for (uint i = 0; i < contractIds_.length; i++) {
bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
if (plug != bytes32(0)) {
validPlugCount++;
}
}
// Create array with exact size needed
AppGatewayConfig[] memory configs = new AppGatewayConfig[](validPlugCount);
uint256 configIndex = 0;
SocketContracts memory socketConfig = getSocketConfig(chainSlug_);
for (uint i = 0; i < contractIds_.length; i++) {
address plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
uint64 switchboardId = configurations.switchboards(chainSlug_, appGateway_.sbType());
if (plug != bytes32(0)) {
configs[configIndex] = AppGatewayConfig({
plug: plug,
chainSlug: chainSlug_,
plugConfig: PlugConfigGeneric({
appGatewayId: toBytes32Format(address(appGateway_)),
switchboardId: switchboardId
})
});
configIndex++;
}
}
configs[i] = AppGatewayConfig({
plug: plug,
chainSlug: chainSlug_,
plugConfig: PlugConfig({
appGatewayId: encodeAppGatewayId(address(appGateway_)),
switchboard: address(socketConfig.switchboard)
})
});
// Only call watcher if we have valid configs
if (validPlugCount > 0) {
watcherMultiCall(
address(configurations),
abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
);
}
watcherMultiCall(
address(configurations),
abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
);
}
function setupGatewayAndPlugs(
uint32 chainSlug_,
IAppGateway appGateway_,
bytes32[] memory contractIds_
) internal {
// Allocate a temp array as large as contractIds_, then compact it in place
AppGatewayConfig[] memory tempConfigs = new AppGatewayConfig[](contractIds_.length);
uint256 configCount = 0;
for (uint i = 0; i < contractIds_.length; i++) {
bytes32 plug = appGateway_.getOnChainAddress(contractIds_[i], chainSlug_);
uint64 switchboardId = configurations.switchboards(chainSlug_, appGateway_.sbType());
if (plug != bytes32(0)) {
tempConfigs[configCount] = AppGatewayConfig({
plug: plug,
chainSlug: chainSlug_,
plugConfig: PlugConfigGeneric({
appGatewayId: toBytes32Format(address(appGateway_)),
switchboardId: switchboardId
})
});
configCount++;
}
}
// Only call watcher if we have valid configs
if (configCount > 0) {
// Resize array to the actual number of configs
AppGatewayConfig[] memory configs = new AppGatewayConfig[](configCount);
for (uint i = 0; i < configCount; i++) {
configs[i] = tempConfigs[i];
}
watcherMultiCall(
address(configurations),
abi.encodeWithSelector(Configurations.setAppGatewayConfigs.selector, configs)
);
}
}
🤖 Prompt for AI Agents
In test/SetupTest.t.sol around lines 1131 to 1168, remove the two-pass iteration
over contractIds_ and replace it with a single pass: allocate a temporary
AppGatewayConfig[] memory tmp = new AppGatewayConfig[](contractIds_.length),
iterate once over contractIds_, for each non-zero plug build and store the
AppGatewayConfig into tmp[index++] and increment count, then after the loop
allocate the final AppGatewayConfig[] memory configs = new
AppGatewayConfig[](count) and copy the populated entries from tmp[0..count-1]
into configs; finally call watcherMultiCall only if count > 0. Ensure you keep
computing switchboardId inside the loop and update variable names accordingly.

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.

4 participants