Skip to content

Conversation

gwalen
Copy link

@gwalen gwalen commented Aug 27, 2025

Summary by CodeRabbit

  • New Features

    • Solana support: Solana forwarder and EVM–Solana gateway with Borsh-based cross-chain read/write and Solana-aware digests; sUSDC bridging and SUSDC token/plug.
  • Documentation

    • Updated event/function docs and standardized event/topic tables for new Solana APIs and socket features.
  • Tests

    • Large suite added: Borsh encoder/decoder, digest, Solana return-value, gateway flows, socket, switchboard, and many integration tests.
  • Chores

    • Deployment/config updates for Solana, address remappings, tooling tweaks, Solidity optimizer/IR enabled, and package version bump.

Copy link

coderabbitai bot commented Aug 27, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (9)
  • main
  • master
  • dev
  • stage
  • prod
  • staging
  • development
  • production
  • release

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds Solana integration: new ForwarderSolana, Borsh encoder/decoder/utils, Solana payload types/constants, Solana-aware digest path in WritePrecompile, ERC20-based SUSDC credits and plugs, many interface/signature changes, extensive test suites and deployment/config updates across toolchains.

Changes

Cohort / File(s) Summary
Forwarder & gateway
contracts/evmx/helpers/ForwarderSolana.sol, test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol
New upgradeable ForwarderSolana (initializer, getOnChainAddress/getChainSlug, callSolana) and example Evm↔Solana App Gateway with deploy/mint/transfer/read flows and events.
Borsh serde & utils
contracts/evmx/watcher/borsh-serde/*
New BorshEncoder, BorshDecoder, BorshUtils libraries: encode/decode primitives, vectors, fixed arrays, strings, endian helpers, memory utilities and GenericSchema support.
Write/Watcher precompiles & digest
contracts/evmx/watcher/precompiles/WritePrecompile.sol, contracts/evmx/watcher/precompiles/SchedulePrecompile.sol
Add Solana digest branch (uses BorshEncoder), expand ScheduleRequested event with localInvoker/callbackSelector/callbackData, and related imports/logic adjustments.
Fees / SUSDC / Credits refactor
contracts/evmx/fees/Credit.sol, contracts/evmx/fees/FeesManager.sol, contracts/evmx/plugs/SUSDC.sol, contracts/evmx/plugs/FeesPlug.sol, contracts/evmx/interfaces/ISUSDC.sol, contracts/evmx/interfaces/IFeesManager.sol, contracts/evmx/interfaces/IFeesPlug.sol
Migrate to ERC20-based SUSDC credits (mint/burn), add SUSDC plug contract, adjust FeesManager API (mint/burn, balanceOf/totalBalanceOf/getBlockedCredits, setMaxFees), extend deposit APIs with bytes data, and rename/reshape approval APIs.
Watcher / RequestHandler / Trigger / WatcherBase
contracts/evmx/watcher/*, contracts/evmx/watcher/WatcherBase.sol, contracts/evmx/watcher/Configurations.sol, contracts/evmx/watcher/RequestHandler.sol, contracts/evmx/watcher/Trigger.sol, contracts/evmx/watcher/Watcher.sol
Introduce events (PrecompileSet, RequestPayloadCountLimitSet, VerifyConnectionsSB, CoreContractsSet), add IERC20 transfers via transferFrom where fees were moved to ERC20, replace string reverts with custom Errors and new errors in Errors.sol.
Socket / SocketConfig / SocketBatcher / SocketUtils / SocketFeeManager
contracts/protocol/Socket.sol, contracts/protocol/SocketConfig.sol, contracts/protocol/SocketBatcher.sol, contracts/protocol/SocketUtils.sol, contracts/protocol/SocketFeeManager.sol
Add TransmissionParams to execute API, emit new GasLimitBufferUpdated/MaxCopyBytesUpdated, add attest/attestCCTPAndProveAndExecute flow, off-chain simulate gating, fee manager getters/setters/events/errors.
PlugBase / IReceiver / IPlug changes
contracts/protocol/base/PlugBase.sol, contracts/evmx/interfaces/IReceiver.sol, contracts/protocol/interfaces/IPlug.sol
Add socket init flow (initSocket, isSocketInitialized, overrides) and new IReceiver.onTransfer interface; minor docs on overrides encoding.
Structs / Constants / Errors / Interfaces
contracts/utils/common/Structs.sol, contracts/utils/common/Constants.sol, contracts/utils/common/Errors.sol, various contracts/evmx/interfaces/*.sol
Add Solana structs/enums (SolanaInstruction, SolanaReadRequest, GenericSchema), constants TOKEN_ACCOUNT/MINT_ACCOUNT, two new Errors, and multiple interface updates (IERC20, ISUSDC, IReceiver, IPromise additions, IAuctionManager signature changes, ISocket additions, etc.).
Rescue / RescueFundsLib
contracts/utils/RescueFundsLib.sol
Add ERC165/ERC721 detection and NFT-aware rescue path (safeTransferFrom for ERC721, fallback to fungible transfers).
AuctionManager / related changes
contracts/evmx/AuctionManager.sol, contracts/evmx/interfaces/IAuctionManager.sol
Change endAuction/expireBid to accept encoded bytes payloads (decode uint40 requestCount), adjust logic and import IERC20 for fee transfers.
Tests & Mocks
test/**, test/mock/*, test/apps/**, test/protocol/**
Large new/updated test suites: BorshEncoder/Decoder, Digest, ReturnValueSolana, Socket protocol tests, FastSwitchboard tests, many mocks (MockERC721, MockFastSwitchboard, MockFeesManager, MockPlug/Target), plus new EVM↔Solana example tests and app gateway tests.
Scripts, deployment & tooling
hardhat-scripts/deploy/*, hardhat-scripts/config/*, hardhat-scripts/constants/constants.ts, foundry.toml, hardhat.config.ts, deployments/dev_addresses.json, deployments/dev_verification.json, package.json, src/enums.ts, src/types.ts, src/signer.ts
Add ForwarderSolana deployment attempt and mock address, add WRITE_MAX_FEES, SUSDC enum/addresses/types, remap many label addresses, enable IR and optimizer runs=200, change nonce to UUID string, update many deploy/connect/config scripts to pass new params and set Solana switchboard/SUSDC, update dev deployments/verification.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Forwarder as ForwarderSolana
  participant Resolver as AddressResolver
  participant AG as IAppGateway
  participant Watcher

  rect rgba(230,245,255,0.5)
    note over User,Forwarder: ForwarderSolana synchronous enqueue flow
    User->>Forwarder: callSolana(solanaPayload, target)
    Forwarder->>Resolver: addressResolver__()
    alt Resolver not set
      Forwarder-->>User: revert AddressResolverNotSet()
    else Resolver returns
      Resolver-->>Forwarder: resolverAddr
      Forwarder->>AG: check AsyncModifier / getOverrideParams()
      alt AsyncModifier missing
        Forwarder-->>User: revert AsyncModifierNotSet()
      else
        AG-->>Forwarder: overrideParams, switchboardType
        Forwarder->>Watcher: watcher.queue(queueParams, msg.sender)
        Watcher-->>Forwarder: queuedRequestId
        Forwarder-->>User: return queuedRequestId
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  participant WP as WritePrecompile
  participant BEnc as BorshEncoder
  participant EVM as EvmDigestBuilder

  rect rgba(240,255,240,0.5)
    note right of WP: digest path selection by chainSlug
    WP->>WP: _isSolanaChainSlug(chainSlug)
    alt Solana chain
      WP->>BEnc: encodeFunctionArgs(instruction)
      BEnc-->>WP: borshEncodedArgs
      WP->>WP: _createSolanaDigestParams(..., borshEncodedArgs)
    else EVM-like chain
      WP->>EVM: _createEvmDigestParams(...)
    end
    WP->>WP: compute keccak256(abi.encodePacked(...)) -> digest
    WP-->>Caller: store digest / emit WriteProofRequested
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes
This is high-effort: many new libraries with low-level binary parsing, significant public API/signature changes, new tokens/bridging logic, broad test surface and deployment tool changes. This should be reviewed thoroughly for security, gas, and API compatibility.

Suggested reviewers

  • arthcp

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Solana triggers - DO NOT MERGE" correctly identifies the main change (introducing Solana trigger/forwarder/Borsh support across the codebase) and is short, but it includes the noisy "DO NOT MERGE" suffix which reduces clarity and doesn't belong in a final PR title.

Poem

Solana bytes and Borsh in flight,
Forwarders queue into watcher’s sight,
Encoders whisper little-endian tunes,
Tests hum proofs beneath the moons.
New bridges built — merge green lights 🌉✨

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Solana triggers - DO NOT MERGE" correctly identifies the main change (introducing Solana trigger/forwarder/Borsh support across the codebase) and is short, but it includes the noisy "DO NOT MERGE" suffix which reduces clarity and doesn't belong in a final PR title.

Poem

Solana bytes and Borsh in flight,
Forwarders queue into watcher’s sight,
Encoders whisper little-endian tunes,
Tests hum proofs beneath the moons.
New bridges built — merge green lights 🌉✨

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Solana triggers - DO NOT MERGE" correctly identifies the main change (introducing Solana trigger/forwarder/Borsh support across the codebase) and is short, but it includes the noisy "DO NOT MERGE" suffix which reduces clarity and doesn't belong in a final PR title.

Poem

Solana bytes and Borsh in flight,
Forwarders queue into watcher’s sight,
Encoders whisper little-endian tunes,
Tests hum proofs beneath the moons.
New bridges built — merge green lights 🌉✨

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Solana triggers - DO NOT MERGE" correctly identifies the main change (introducing Solana trigger/forwarder/Borsh support across the codebase) and is short, but it includes the noisy "DO NOT MERGE" suffix which reduces clarity and doesn't belong in a final PR title.

Poem

Solana bytes and Borsh in flight,
Forwarders queue into watcher’s sight,
Encoders whisper little-endian tunes,
Tests hum proofs beneath the moons.
New bridges built — merge green lights 🌉✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title includes the directive “DO NOT MERGE” which is noise and does not describe the change, and “Solana triggers” is too generic to convey the main purpose of the pull request. It should clearly summarize the primary update without merge instructions. Update the title to be concise and descriptive by removing “DO NOT MERGE” and capturing the core change, for example: “Add Solana trigger support to the protocol.”
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
hardhat.config.ts (1)

35-43: Prevent invalid account key (“0xundefined”) and drop unsafe type-cast.

Env is optional right now; the current cast to HardhatNetworkAccountUserConfig and blind "0x" prefixing can yield bad keys. This should sanitize and avoid accidental bad config.

-const privateKey: HardhatNetworkAccountUserConfig = process.env
-  .SOCKET_SIGNER_KEY as unknown as HardhatNetworkAccountUserConfig;
+const rawSignerKey = process.env.SOCKET_SIGNER_KEY || "";
+const signerKey = rawSignerKey.startsWith("0x") ? rawSignerKey : (rawSignerKey ? `0x${rawSignerKey}` : "");

 function getChainConfig(chainSlug: ChainSlug): NetworkUserConfig {
   return {
-    accounts: [`0x${privateKey}`],
+    accounts: signerKey ? [signerKey] : [],
     chainId: ChainSlugToId[chainSlug],
     url: getJsonRpcUrl(chainSlug),
   };
 }

Optionally re-enable the missing-key guard once setup scripts are fixed.

foundry.toml (2)

1-11: Match Foundry IR pipeline with Hardhat

Foundry’s config (foundry.toml line 10) sets via_ir = false, while Hardhat’s config (hardhat.config.ts line 177) uses viaIR: true. This mismatch causes divergent bytecode outputs. This should be aligned to guarantee identical runtime bytecode.

– In foundry.toml (line 10):

-via_ir = false
+via_ir = true

– Reference in hardhat.config.ts (line 177):

    viaIR: true,

This should be validated by rebuilding with Foundry and Hardhat and diffing the ForwarderSolana/WritePrecompile bytecode for parity.


12-39: Fix hardcoded ForwarderSolana address in deploy script

The forwarderSolanaAddress is still hardcoded in script/super-token-solana/DeployEVMSolanaApps.s.sol, which will drift if labels change. This should be replaced with the label lookup from foundry.toml to guarantee consistency.

• script/super-token-solana/DeployEVMSolanaApps.s.sol (line 26):

- address forwarderSolanaAddress = 0xC570206ACBa112fC8d438235BF8cE31b3548aa96;
+ address forwarderSolanaAddress = vm.label("ForwarderSolana");

No other occurrences of APP_GATEWAY, WritePrecompile or Watcher remain hardcoded outside foundry.toml.

hardhat-scripts/deploy/3.configureChains.ts (1)

90-92: Guard against missing FeesPlug; current code will throw when undefined.

toBytes32FormatHexString(chainAddresses[Contracts.FeesPlug]!) will crash if FeesPlug is not deployed on a chain. Compute feesPlug only when present.

Apply:

-  const feesPlug = toBytes32FormatHexString(
-    chainAddresses[Contracts.FeesPlug]!
-  );
+  const feesPlug =
+    chainAddresses[Contracts.FeesPlug]
+      ? toBytes32FormatHexString(chainAddresses[Contracts.FeesPlug] as string)
+      : undefined;
hardhat-scripts/deploy/1.deploy.ts (1)

93-95: Await getWatcherSigner here for consistency and correctness.

Else signer.address will throw if getWatcherSigner is async (it is awaited elsewhere in this file).

Apply:

-      const signer: Wallet = getWatcherSigner();
+      const signer: Wallet = await getWatcherSigner();
contracts/evmx/watcher/precompiles/WritePrecompile.sol (1)

372-379: Docstring contradicts access control.

Comment says “Only callable by the contract owner” but function is onlyWatcher. Fix comment or modifier.

-    /// @dev Only callable by the contract owner
+    /// @dev Only callable by the watcher
-    function setExpiryTime(uint256 expiryTime_) external onlyWatcher {
+    function setExpiryTime(uint256 expiryTime_) external onlyWatcher {

If the intent is owner control, switch to onlyOwner instead.

🧹 Nitpick comments (26)
contracts/evmx/watcher/borsh-serde/BorshUtils.sol (1)

98-124: Require closing bracket when parsing array length.

Currently, “[u8; 32 foo” would pass because only semicolon+digits are required. This should track ‘]’ to avoid false positives.

-    function extractArrayLength(string memory typeName) internal pure returns (uint256) {
+    function extractArrayLength(string memory typeName) internal pure returns (uint256) {
         bytes memory typeBytes = bytes(typeName);
         uint256 length = 0;
         bool foundSemicolon = false;
         bool foundDigit = false;
+        bool foundClosingBracket = false;
         
         // Parse patterns like "[u8; 32]"
         for (uint256 i = 0; i < typeBytes.length; i++) {
             bytes1 char = typeBytes[i];
             
             if (char == 0x3B) { // ';'
                 foundSemicolon = true;
             } else if (foundSemicolon && char >= 0x30 && char <= 0x39) { // '0' to '9'
                 foundDigit = true;
                 length = length * 10 + uint256(uint8(char)) - 48; // Convert ASCII to number
-            } else if (foundSemicolon && foundDigit && char == 0x5D) { // ']'
-                break; // End of array type declaration
+            } else if (foundSemicolon && foundDigit && char == 0x5D) { // ']'
+                foundClosingBracket = true;
+                break; // End of array type declaration
             } else if (foundSemicolon && foundDigit && char != 0x20) { // Not a space
                 // If we found digits but hit a non-digit non-space, invalid format
                 revert("Invalid array length format");
             }
             // Skip spaces and other characters before semicolon
         }
         
-        require(foundSemicolon && foundDigit && length > 0, "Could not extract array length");
+        require(foundSemicolon && foundDigit && foundClosingBracket && length > 0, "Could not extract array length");
         return length;
     }
contracts/utils/common/Structs.sol (1)

239-249: Document invariants for SolanaReadRequest.

When schemaType=PREDEFINED, predefinedSchemaNameHash must be nonzero; when GENERIC, it should be zero. This should be documented and enforced at use-site to avoid ambiguous decoding.

test/DigestTest.t.sol (1)

84-86: Add CI cross-check to lock test’s getDigest to the canonical implementation

The test in test/DigestTest.t.sol (lines 84–86) currently calls its own local getDigest and compares it against a hard-coded expectedDigest. This only verifies the test’s copy of the logic—not the canonical function in contracts/evmx/watcher/precompiles/WritePrecompile.sol. If the parameter packing or ordering ever drifts, the test will still pass even though production code would break.

This should be fixed by deriving the expected value at runtime from the canonical function and adding a constant-locking assertion:

  • In test/DigestTest.t.sol import (or deploy via a fixture) the contract defining the canonical getDigest(DigestParams) in WritePrecompile.sol.
  • Change the test to:
    1. Compute bytes32 constantExpected = /* existing constant */;
    2. Assert keccak256(abi.encodePacked(/* params */)) == constantExpected;
    3. Assert canonicalContract.getDigest(inputDigestParams) == constantExpected;

Alternatively, drop the local getDigest entirely and assert:

bytes32 runtimeExpected = canonicalContract.getDigest(inputDigestParams);
assertEq(actualDigest, runtimeExpected);

This guarantees CI will catch any divergence between the test data and the on-chain logic.

contracts/evmx/base/AppGatewayBase.sol (2)

162-173: Returning zero on missing forwarder masks misconfiguration (Solana path).

This should not silently succeed for Solana; it leads to “valid plug” checks using zero address. Add an explicit Solana branch (resolver-based lookup) or revert with a clear error on Solana slugs to surface config issues early.


220-225: Add Solana-specific valid plug setter
This should bypass the EVM-only getOnChainAddress lookup for Solana by passing the programId directly to the watcher.

• File: contracts/evmx/base/AppGatewayBase.sol (after line 225)
• No changes needed on the watcher side—IWatcher.setIsValidPlug(bool,uint32,bytes32) accepts any bytes32 as the plug parameter.

     function _setValidPlug(bool isValid, uint32 chainSlug_, bytes32 contractId_) internal {
         bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_);
         watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress);
     }

+    /// @notice Solana path: set validity using programId directly (no contractId lookup)
+    /// @dev Use only for Solana chainSlug values.
+    function _setValidPlugSolana(bool isValid, uint32 chainSlug_, bytes32 programId_) internal {
+        watcher__().setIsValidPlug(isValid, chainSlug_, programId_);
+    }
contracts/evmx/watcher/Configurations.sol (1)

47-49: Debug-only event extends ABI but is unused. Remove or emit, then drop before merge.

Option A: remove now.

-    // TODO:GW: remove after testing Solana
-    event VerifyConnectionsSB(bytes32 switchboard, bytes32 switchboardExpected);

Option B: emit inside verifyConnections during bring-up:

     function verifyConnections(
@@
-    ) external view {
-        (bytes32 appGatewayId, bytes32 switchboard) = getPlugConfigs(chainSlug_, target_);
+    ) external view {
+        (bytes32 appGatewayId, bytes32 switchboard) = getPlugConfigs(chainSlug_, target_);
+        // debug emission; remove before merge
+        emit VerifyConnectionsSB(switchboard, switchboards[chainSlug_][switchboardType_]);
         if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway();
         if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard();
     }
hardhat-scripts/deploy/3.configureChains.ts (1)

107-115: Remove noisy debug log and fix misleading “reversed” message.

Keep logs actionable. The “reversed” note is incorrect and risks confusion about endianness.

Apply:

-  console.log("XXX Setting solana switchboard");
-  console.log("FAST_SWITCHBOARD_TYPE: ", FAST_SWITCHBOARD_TYPE);
+  console.log("Setting Solana switchboard for FAST_SWITCHBOARD_TYPE");
@@
-  console.log(
-    "solanaSwitchboard as bytes32 reversed: ",
-    Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")
-  );
+  console.log(
+    "solanaSwitchboard bytes32:",
+    Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")
+  );
hardhat-scripts/deploy/1.deploy.ts (1)

40-44: Validate constant size at definition time.

Ensure the mocked Solana program id is exactly 32 bytes to prevent ABI encoding issues in initializer calls.

Optionally assert in code:

 export const mockForwarderSolanaOnChainAddress32Bytes = Buffer.from(
   "0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c2448",
   "hex"
 );
+if (mockForwarderSolanaOnChainAddress32Bytes.length !== 32) {
+  throw new Error("mockForwarderSolanaOnChainAddress32Bytes must be 32 bytes");
+}
hardhat-scripts/deploy/6.connect.ts (1)

178-184: Remove commented alt payload with wrong shape.

The commented object lacks plugConfig nesting and will mislead future edits.

Delete lines 178–184.

test/ReturnValueSolanaTest.t.sol (3)

6-6: Remove unused Constants import.

Avoids compiler warnings and keeps the test lean.

Apply:

-import "../contracts/utils/common/Constants.sol";

24-31: Assert parsedData arity to harden the test.

This should fail fast if schema decoding returns unexpected lengths.

Apply:

 bytes[] memory parsedData = BorshDecoder.decodeGenericSchema(returnDataSchema, returnData);
+assertEq(parsedData.length, 2);

32-39: Logs are fine for local debugging; consider dropping for CI speed.

Not blocking; retain if helpful for flakiness triage.

script/super-token-solana/DeployEVMSolanaApps.s.sol (5)

6-7: Don't import deploy targets from test paths.

This script imports deployment targets from test directories. This should deploy real contracts or explicitly mark this script as dev-only and avoid cross-importing test artifacts to prevent accidental production usage.

Apply:

-import {EvmSolanaAppGateway} from "../../test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol";
-import {SuperTokenAppGateway} from "../../test/apps/app-gateways/super-token/SuperTokenAppGateway.sol";
+// TODO(dev): point to production deploy targets or move script under a dev-only folder explicitly.
+import {EvmSolanaAppGateway} from "../../apps/app-gateways/super-token/EvmSolanaAppGateway.sol";
+import {SuperTokenAppGateway} from "../../apps/app-gateways/super-token/SuperTokenAppGateway.sol";

8-8: Unused import.

ETH_ADDRESS is not used. Remove to keep the script lean.

-import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol";

12-12: Outdated invocation hint.

The comment points to a different script path. Update to prevent confusion.

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

53-59: External reads may revert if the address is not a ForwarderSolana; guard with try/catch.

Keep logs but don’t let the script die on introspection.

-        console.log("Forwarder Solana address resolver:");
-        console.log(address(ForwarderSolana(forwarderSolanaAddress).addressResolver__()));
-        console.log("ForwarderSolana chain slug:");
-        console.log(ForwarderSolana(forwarderSolanaAddress).chainSlug());
-        console.log("ForwarderSolana onChainAddress:");
-        console.logBytes32(ForwarderSolana(forwarderSolanaAddress).onChainAddress());
+        try ForwarderSolana(forwarderSolanaAddress).addressResolver__() returns (AddressResolver res) {
+            console.log("Forwarder Solana address resolver:");
+            console.log(address(res));
+            console.log("ForwarderSolana chain slug:");
+            console.log(ForwarderSolana(forwarderSolanaAddress).chainSlug());
+            console.log("ForwarderSolana onChainAddress:");
+            console.logBytes32(ForwarderSolana(forwarderSolanaAddress).onChainAddress());
+        } catch {
+            console.log("ForwarderSolana introspection failed (not deployed or wrong address).");
+        }

28-30: Gas limit comment is chain-specific; assert or move to config.

Hard-coding “Arbitrum Sepolia” in script comments drifts. This should be passed via env or documented as example.

FunctionSignatures.md (1)

300-315: ForwarderSolana selectors verified; table formatting tweak

All 4-byte selectors in FunctionSignatures.md match the compiled ABI.
This should ensure the table entries under “## ForwarderSolana” are correct.

• Add a blank line immediately before the table (lines 300–315) so GitHub Markdown renders it as a standalone table.

contracts/evmx/watcher/borsh-serde/BorshDecoder.sol (2)

130-136: EOI check message clarity.

“unexpected EOI” is fine; keep consistent wording (“unexpected end of input”).

-            require(data.ptr + length <= data.end, "Parse error: unexpected EOI");
+            require(data.ptr + length <= data.end, "Parse error: unexpected end of input");

146-149: done(): message clarity.

-        require(data.ptr == data.end, "Parse error: EOI expected");
+        require(data.ptr == data.end, "Parse error: trailing bytes");
contracts/evmx/watcher/precompiles/WritePrecompile.sol (1)

51-52: Dead code.

Commented using directive has no effect; remove.

-    // using BorshEncoder for borsh;
contracts/evmx/watcher/borsh-serde/BorshEncoder.sol (2)

48-52: Duplicate [u8;N] branch. Remove one to avoid divergence.

The [u8;N] case appears twice; keep the later “fixed length” section with the rest.

-            // Handle array types with fixed length
-            else if (BorshUtils.startsWith(typeName, "[u8;")) {
-                uint8[] memory abiDecodedArg = abi.decode(data, (uint8[]));
-                bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg);
-                functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg);
-            } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16[]"))) {
+            } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16[]"))) {

Also applies to: 100-104


240-286: Packing loops are O(n^2) due to bytes.concat in a loop.

This should preallocate and write with assembly to avoid repeated allocations, especially for large arrays. Keep as-is if inputs stay small in practice.

script/super-token-solana/EvmSolanaOnchainCalls.s.sol (1)

23-24: Remove misleading comment.

Line 23 contains an outdated command with incorrect script path. The comment references "counter/EvmSolanaOnchainCalls.s.sol" but this file is in "super-token-solana" directory.

-// source .env && forge script script/counter/EvmSolanaOnchainCalls.s.sol --broadcast --skip-simulation --legacy --gas-price 0
+// source .env && forge script script/super-token-solana/EvmSolanaOnchainCalls.s.sol --broadcast --skip-simulation --legacy --gas-price 0
contracts/evmx/helpers/ForwarderSolana.sol (2)

17-19: Update contract documentation.

The title and description don't reflect that this is specifically for Solana. Should be "Forwarder Contract for Solana".

-/// @title Forwarder Contract
-/// @notice This contract acts as a forwarder for async calls to the on-chain contracts.
+/// @title Forwarder Contract for Solana
+/// @notice This contract acts as a forwarder for async calls to Solana programs.

29-30: Fix typo in comment.

Line 29 has duplicate forward slashes at the beginning.

-    //// @param onChainAddress_ on-chain address associated with this forwarder
+    /// @param onChainAddress_ on-chain address associated with this forwarder
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c48fdd5 and 701e866.

📒 Files selected for processing (25)
  • Errors.md (1 hunks)
  • EventTopics.md (1 hunks)
  • FunctionSignatures.md (1 hunks)
  • contracts/evmx/base/AppGatewayBase.sol (2 hunks)
  • contracts/evmx/helpers/ForwarderSolana.sol (1 hunks)
  • contracts/evmx/watcher/Configurations.sol (1 hunks)
  • contracts/evmx/watcher/borsh-serde/BorshDecoder.sol (1 hunks)
  • contracts/evmx/watcher/borsh-serde/BorshEncoder.sol (1 hunks)
  • contracts/evmx/watcher/borsh-serde/BorshUtils.sol (1 hunks)
  • contracts/evmx/watcher/precompiles/WritePrecompile.sol (6 hunks)
  • contracts/utils/common/Constants.sol (1 hunks)
  • contracts/utils/common/Structs.sol (2 hunks)
  • foundry.toml (1 hunks)
  • hardhat-scripts/deploy/1.deploy.ts (3 hunks)
  • hardhat-scripts/deploy/3.configureChains.ts (2 hunks)
  • hardhat-scripts/deploy/6.connect.ts (2 hunks)
  • hardhat.config.ts (1 hunks)
  • script/super-token-solana/DeployEVMSolanaApps.s.sol (1 hunks)
  • script/super-token-solana/EvmSolanaOnchainCalls.s.sol (1 hunks)
  • src/enums.ts (1 hunks)
  • test/BorshDecoderTest.t.sol (1 hunks)
  • test/BorshEncoderTest.t.sol (1 hunks)
  • test/DigestTest.t.sol (1 hunks)
  • test/ReturnValueSolanaTest.t.sol (1 hunks)
  • test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
hardhat-scripts/deploy/3.configureChains.ts (5)
hardhat-scripts/constants/constants.ts (1)
  • FAST_SWITCHBOARD_TYPE (9-9)
hardhat-scripts/utils/address.ts (1)
  • toBytes32Format (54-61)
hardhat-scripts/utils/deployUtils.ts (1)
  • updateContractSettings (323-362)
hardhat-scripts/config/config.ts (2)
  • EVMX_CHAIN_ID (92-92)
  • mode (6-8)
hardhat-scripts/constants/feeConstants.ts (1)
  • getFeeTokens (27-29)
hardhat-scripts/deploy/6.connect.ts (1)
hardhat-scripts/deploy/1.deploy.ts (1)
  • mockForwarderSolanaOnChainAddress32Bytes (41-44)
🪛 LanguageTool
Errors.md

[grammar] ~21-~21: There might be a mistake here.
Context: ...rwarderSolana.sol | Error | Signature | |-------|-----------| | `InvalidSolanaCh...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | InvalidSolanaChainSlug() | `0xe37803...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...validSolanaChainSlug()|0xe37803ab| |AddressResolverNotSet()|0x6d55276...

(QB_NEW_EN)

EventTopics.md

[grammar] ~117-~117: There might be a mistake here.
Context: ...ncPromise | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~132-~132: There might be a mistake here.
Context: ...Forwarder | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...derSolana | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~152-~152: There might be a mistake here.
Context: ... TestUSDC | Event | Arguments | Topic | | ----- | --------- | ----- | | `Approva...

(QB_NEW_EN)


[grammar] ~153-~153: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Approval | `(owner: address, spender...

(QB_NEW_EN)


[grammar] ~154-~154: There might be a mistake here.
Context: ...e84f3dd0314c0f7b2291e5b200ac8c7c3b925| |Transfer|(from: address, to: addr...

(QB_NEW_EN)

FunctionSignatures.md

[grammar] ~302-~302: There might be a mistake here.
Context: ...orwarderSolana | Function | Signature | | -------- | --------- | | `addressResol...

(QB_NEW_EN)


[grammar] ~303-~303: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | addressResolver__ | 0x6a750469 | |...

(QB_NEW_EN)


[grammar] ~304-~304: There might be a mistake here.
Context: ...| | addressResolver__ | 0x6a750469 | | asyncDeployer__ | 0x2a39e801 | | `...

(QB_NEW_EN)


[grammar] ~305-~305: There might be a mistake here.
Context: ...| |asyncDeployer__|0x2a39e801| |callSolana|0x4ef7957b| |chain...

(QB_NEW_EN)


[grammar] ~306-~306: There might be a mistake here.
Context: ...9e801| |callSolana|0x4ef7957b| |chainSlug|0xb349ba65| |deploy...

(QB_NEW_EN)


[grammar] ~307-~307: There might be a mistake here.
Context: ...f7957b| |chainSlug|0xb349ba65| |deployForwarder__|0xd4e3b034` | |...

(QB_NEW_EN)


[grammar] ~308-~308: There might be a mistake here.
Context: ...| | deployForwarder__ | 0xd4e3b034 | | feesManager__ | 0x70568b58 | | `ge...

(QB_NEW_EN)


[grammar] ~309-~309: There might be a mistake here.
Context: ...34| |feesManager__|0x70568b58| |getChainSlug|0x0b8c6568| |get...

(QB_NEW_EN)


[grammar] ~310-~310: There might be a mistake here.
Context: ...b58| |getChainSlug|0x0b8c6568| |getOnChainAddress|0x9da48789` | |...

(QB_NEW_EN)


[grammar] ~311-~311: There might be a mistake here.
Context: ...| | getOnChainAddress | 0x9da48789 | | initialize | 0x148841cb | | `onCha...

(QB_NEW_EN)


[grammar] ~312-~312: There might be a mistake here.
Context: ...48789| |initialize|0x148841cb| |onChainAddress|0x8bd0b363| |w...

(QB_NEW_EN)


[grammar] ~313-~313: There might be a mistake here.
Context: ...b| |onChainAddress|0x8bd0b363| |watcher__|0x300bb063` | ## Proxy...

(QB_NEW_EN)

🪛 Gitleaks (8.27.2)
script/super-token-solana/EvmSolanaOnchainCalls.s.sol

72-72: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


134-134: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


148-148: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


160-160: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


202-202: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


326-326: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (17)
hardhat.config.ts (1)

164-179: Align Hardhat viaIR with Foundry to avoid divergent bytecode.

Hardhat compiles with viaIR=true (Line 177) while foundry.toml has via_ir=false (Line 10). This should be consistent across toolchains or you will get different bytecode/sizes, breaking CREATE2 addresses, size-based assumptions, and verifications.

Run both pipelines and compare runtime bytecode for affected contracts (ForwarderSolana, WritePrecompile, etc.). If you standardize on viaIR, flip Foundry to via_ir=true (see suggested diff under foundry.toml comment).

contracts/utils/common/Constants.sol (1)

23-26: LGTM: Stable identifiers for Solana schemas.

The new TOKEN_ACCOUNT and MINT_ACCOUNT hashes are fine and scoped clearly for predefined schema selection.

contracts/evmx/watcher/borsh-serde/BorshUtils.sol (3)

8-18: LGTM: Thin wrappers for mload/mstore.

Simple and correct; no risk as long as callers pass valid pointers.


86-96: LGTM: startsWith implementation is correct.

Bounds check is in place and the loop is tight.


20-40: Fix infinite loop and incorrect allocation in memoryToBytes.

end is computed without the +32 header, so destPtr starts past end and the loop never terminates. Zero-length also returns an uninitialized pointer. This should allocate [32 + ceil32(length)] bytes, advance the free pointer, and loop until dest reaches end.

-    function memoryToBytes(uint256 ptr, uint256 length) internal pure returns (bytes memory res) {
-        if (length != 0) {
-            assembly {
-                // 0x40 is the address of free memory pointer.
-                res := mload(0x40)
-                let end :=
-                    add(res, and(add(length, 63), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0))
-                // end = res + 32 + 32 * ceil(length / 32).
-                mstore(0x40, end)
-                mstore(res, length)
-                let destPtr := add(res, 32)
-                // prettier-ignore
-                for {} 1 {} {
-                    mstore(destPtr, mload(ptr))
-                    destPtr := add(destPtr, 32)
-                    if eq(destPtr, end) { break }
-                    ptr := add(ptr, 32)
-                }
-            }
-        }
-    }
+    function memoryToBytes(uint256 ptr, uint256 length) internal pure returns (bytes memory res) {
+        assembly {
+            // Allocate: 32 (length slot) + ceil32(length)
+            let free := mload(0x40)
+            let size := and(add(length, 31), not(31))       // ceil32(length)
+            let newFree := add(add(free, 32), size)
+            mstore(0x40, newFree)
+            // Write length and copy payload
+            mstore(free, length)
+            let dest := add(free, 32)
+            let end := add(dest, size)
+            for { } lt(dest, end) { dest := add(dest, 32) ptr := add(ptr, 32) } {
+                mstore(dest, mload(ptr))
+            }
+            res := free
+        }
+    }

Likely an incorrect or invalid review comment.

contracts/utils/common/Structs.sol (1)

222-235: Enforce accounts/accountFlags length parity at encode time.

description.accountFlags should match data.accounts length one-to-one (bit 0 = isWritable). This should be validated where SolanaInstruction is consumed (e.g., in BorshEncoder/WritePrecompile) to prevent malformed payloads.

If absent, add a require/assert before encoding/transmission.

test/DigestTest.t.sol (1)

12-16: Confirm logBytes4 support in your Forge-std version
I couldn’t locate logBytes4 in the vendored console.sol. This should prevent test breakage on Forge-std variants that don’t implement logBytes4.

• File test/DigestTest.t.sol, lines 12–16

Suggested change:

-        console.logBytes4(READ);
+        console.logBytes(abi.encodePacked(READ));
...
-        console.logBytes4(WRITE);
+        console.logBytes(abi.encodePacked(WRITE));
...
-        console.logBytes4(SCHEDULE);
+        console.logBytes(abi.encodePacked(SCHEDULE));
src/enums.ts (1)

74-75: Confirmed consistent usage of Contracts.ForwarderSolana

Found exactly two references in the TypeScript deploy script, matching expectations:

  • hardhat-scripts/deploy/1.deploy.ts – lines 262 and 273: uses Contracts.ForwarderSolana

No other TS files reference the new enum. No changes required.

Errors.md (1)

19-25: Error signatures verified and correct

Computed selectors via keccak256(first 4 bytes) for
InvalidSolanaChainSlug() → 0xe37803ab
AddressResolverNotSet() → 0x6d55276d

match the entries in Errors.md (lines 19–25) and ForwarderSolana.sol exactly. No further changes needed.

hardhat-scripts/deploy/3.configureChains.ts (1)

63-76: Remove unnecessary await on getWatcherSigner

getWatcherSigner is defined as a synchronous function returning an ethers.Wallet (no async keyword or Promise in its signature), so it does not need to be awaited. Leaving the code as-is (const signer: Wallet = getWatcherSigner()) is correct and safe—no change required.

Likely an incorrect or invalid review comment.

hardhat-scripts/deploy/1.deploy.ts (1)

258-277: ForwarderSolana initializer signature verified.

This should confirm that the deployed args match the contract’s initialize signature:

  • Contract signature: function initialize(uint32 chainSlug_, bytes32 onChainAddress_, address addressResolver_)
  • Passed args: [ChainId.SOLANA_DEVNET, mockForwarderSolanaOnChainAddress32Bytes, addressResolver.address]

No changes needed.

EventTopics.md (2)

5-19: Tables render fine; ignore grammar-tool noise.

Automated grammar flags are false positives for Markdown tables. No action needed.


130-141: Verify Initialized event documentation in EventTopics.md

EventTopics.md should list only the Initialized event because neither Forwarder nor ForwarderSolana declares any events nor inherits Ownable. AddressResolverUtil.sol and ForwarderStorage are event-free, so no ownership or custom events are emitted. This should reflect the actual compiled ABI:

  • contracts/evmx/helpers/Forwarder.sol — no event declarations
  • contracts/evmx/helpers/ForwarderSolana.sol — no event declarations
  • Inherited Initialized event comes from solady/utils/Initializable.sol
  • docs/EventTopics.md (lines 130–141) — currently only shows Initialized(version: uint64)

Manually confirm that the Initialized(uint64) signature and topic hash in EventTopics.md match the compiled Initialized event from Solady’s Initializable (which may use a different integer size).

hardhat-scripts/deploy/6.connect.ts (1)

101-109: Incorrect assumption about getWatcherSigner

getWatcherSigner is a synchronous function that returns an ethers.Wallet instance directly (see hardhat-scripts/utils/sign.ts), not a Promise. Adding await is unnecessary—awaiting a non-Promise simply wraps the value in a resolved Promise and has no functional effect, but it misrepresents the implementation and may confuse readers. Leave the code as:

const signer = getWatcherSigner();

Likely an incorrect or invalid review comment.

contracts/evmx/watcher/precompiles/WritePrecompile.sol (1)

181-205: Digest param split by chain is a good direction.

Branching to Solana/EVM helpers improves readability and reduces accidental coupling.

test/BorshEncoderTest.t.sol (1)

1-249: LGTM! Comprehensive test coverage.

The test suite thoroughly covers all Borsh encoding scenarios including primitives, arrays, vectors, and strings with appropriate assertions and edge cases.

test/BorshDecoderTest.t.sol (1)

1-836: LGTM! Excellent test coverage.

The test suite provides comprehensive coverage of Borsh decoding functionality including:

  • All primitive types (u8 through u128)
  • Vectors and arrays with various types
  • Complex nested structures
  • Edge cases and error conditions
  • Real-world Solana account decoding scenarios

The tests are well-structured with clear assertions and appropriate use of vm.expectRevert for error cases.

Comment on lines +39 to +42
} else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) {
string memory value = data.decodeString();
decodedParams[i] = abi.encode(value);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

String type casing mismatch with encoder. Support both "String" and "string".

Encoder uses "string" while decoder matches "String". This will fail round-trips.

-            } else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) {
+            } else if (
+                keccak256(bytes(typeName)) == keccak256(bytes("String")) ||
+                keccak256(bytes(typeName)) == keccak256(bytes("string"))
+            ) {
                 string memory value = data.decodeString();
                 decodedParams[i] = abi.encode(value);
📝 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
} else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) {
string memory value = data.decodeString();
decodedParams[i] = abi.encode(value);
}
} else if (
keccak256(bytes(typeName)) == keccak256(bytes("String")) ||
keccak256(bytes(typeName)) == keccak256(bytes("string"))
) {
string memory value = data.decodeString();
decodedParams[i] = abi.encode(value);
}
🤖 Prompt for AI Agents
In contracts/evmx/watcher/borsh-serde/BorshDecoder.sol around lines 39 to 42,
the decoder only matches keccak256(bytes("String")) while the encoder emits
"string", breaking round-trips; update the conditional to accept both casings
(e.g., compare keccak256(bytes(typeName)) against keccak256("String") OR
keccak256("string")) or normalize typeName to a single case (lowercase) and
compare to "string", then call data.decodeString() and abi.encode(value) as
before.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
contracts/protocol/SocketConfig.sol (1)

69-74: Fix enableSwitchboard: this registers GOVERNANCE msg.sender, not the target switchboard

connect() checks isValidSwitchboard[switchboard_] on the passed address, but enableSwitchboard() marks the GOVERNANCE caller as REGISTERED. This should take an address parameter and update that entry.

Apply:

-    function enableSwitchboard() external onlyRole(GOVERNANCE_ROLE) {
-        isValidSwitchboard[msg.sender] = SwitchboardStatus.REGISTERED;
-        emit SwitchboardEnabled(msg.sender);
-    }
+    function enableSwitchboard(address switchboard_) external onlyRole(GOVERNANCE_ROLE) {
+        // prevent duplicate enables; reuse existing error
+        if (isValidSwitchboard[switchboard_] == SwitchboardStatus.REGISTERED) revert SwitchboardExists();
+        isValidSwitchboard[switchboard_] = SwitchboardStatus.REGISTERED;
+        emit SwitchboardEnabled(switchboard_);
+    }
hardhat-scripts/deploy/3.configureChains.ts (2)

90-96: Guard FeesPlug normalization; current non-null assertion can crash when missing.

toBytes32FormatHexString(chainAddresses[Contracts.FeesPlug]!) will throw if FeesPlug isn’t deployed for a chain. Move normalization inside the guarded block that sets FeesPlug.

Apply:

-  const feesPlug = toBytes32FormatHexString(
-    chainAddresses[Contracts.FeesPlug]!
-  );
+  // Normalize only when present; set below in guarded update

And update the guarded block (see separate diff for lines 141-151).


---

`141-151`: **Normalize FeesPlug only when present and pass normalized bytes correctly.**

This should prevent crashes and keep required/setter values consistent.


Apply:

```diff
-  if (chainAddresses[Contracts.FeesPlug])
-    await updateContractSettings(
+  if (chainAddresses[Contracts.FeesPlug]) {
+    const feesPlug = toBytes32FormatHexString(
+      chainAddresses[Contracts.FeesPlug] as string
+    );
+    await updateContractSettings(
       EVMX_CHAIN_ID,
       Contracts.FeesManager,
       "feesPlugs",
       [chain],
       feesPlug,
       "setFeesPlug",
       [chain, toBytes32Format(feesPlug)],
       signer
-    );
+    );
+  }
♻️ Duplicate comments (2)
hardhat-scripts/deploy/3.configureChains.ts (2)

208-209: Correct getFeeTokens signature now used.

This fixes the previous runtime mismatch. Good.


107-128: Normalize SWITCHBOARD_SOLANA and use the normalized value for compare + setter.

updateContractSettings compares strings from the getter with requiredValue. Passing the raw env risks perpetual mismatches or invalid formatting. Normalize once to 0x-prefixed bytes32 and reuse for required and setter args. Also trim noisy debug logs.

Apply:

-  console.log("XXX Setting solana switchboard");
-  console.log("FAST_SWITCHBOARD_TYPE: ", FAST_SWITCHBOARD_TYPE);
-  const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA;
-  if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set");
-  console.log(
-    "solanaSwitchboard as bytes32 reversed: ",
-    Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")
-  );
+  console.log("Setting Solana switchboard");
+  const solanaSwitchboardRaw = process.env.SWITCHBOARD_SOLANA;
+  if (!solanaSwitchboardRaw) throw new Error("SWITCHBOARD_SOLANA is not set");
+  const solanaSwitchboardHex = toBytes32FormatHexString(solanaSwitchboardRaw);
+  console.log("FAST_SWITCHBOARD_TYPE:", FAST_SWITCHBOARD_TYPE, "SOLANA:", solanaSwitchboardHex);
   await updateContractSettings(
     EVMX_CHAIN_ID,
     Contracts.Configurations,
     "switchboards",
     [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE],
-    solanaSwitchboard,
+    solanaSwitchboardHex,
     "setSwitchboard",
     [
       ChainSlug.SOLANA_DEVNET,
       FAST_SWITCHBOARD_TYPE,
-      toBytes32Format(solanaSwitchboard),
+      toBytes32Format(solanaSwitchboardHex),
     ],
     signer
   );
🧹 Nitpick comments (6)
contracts/protocol/SocketConfig.sol (6)

62-67: Resolve access-control model: register vs enable is inconsistent

As written: anyone can registerSwitchboard() to REGISTERED; disable needs SWITCHBOARD_DISABLER_ROLE; enable requires GOVERNANCE but currently affects msg.sender. This should define a clear state machine:

  • registerSwitchboard() → PENDING (self-registration)
  • enableSwitchboard(address) [GOVERNANCE_ROLE] → REGISTERED (only from DISABLED/PENDING)
  • disableSwitchboard(address) [SWITCHBOARD_DISABLER_ROLE] → DISABLED

Alternatively, remove self-registration and make GOVERNANCE the sole registrar.


46-54: Self-registration grants REGISTERED to any address

registerSwitchboard() sets REGISTERED with no role or allowlist, which undermines governance gating. This should not directly elevate to REGISTERED. Either restrict to GOVERNANCE_ROLE or gate through a PENDING state and require enableSwitchboard() to finalize.


76-79: Validate fee manager address and avoid no-op updates

This should reject zero address and skip emitting when unchanged. Also set before emitting for conventional ordering.

-    function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) {
-        emit SocketFeeManagerUpdated(address(socketFeeManager), socketFeeManager_);
-        socketFeeManager = ISocketFeeManager(socketFeeManager_);
-    }
+    function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) {
+        require(socketFeeManager_ != address(0), "socketFeeManager is zero");
+        address old = address(socketFeeManager);
+        if (old == socketFeeManager_) return;
+        socketFeeManager = ISocketFeeManager(socketFeeManager_);
+        emit SocketFeeManagerUpdated(old, socketFeeManager_);
+    }

96-101: Enforce MAX_COPY_BYTES ceiling; imported constant currently unused

Without bounds, an unsafe value can be set and break downstream assumptions. This should validate range and use MAX_COPY_BYTES.

-    function setMaxCopyBytes(uint16 maxCopyBytes_) external onlyRole(GOVERNANCE_ROLE) {
-        maxCopyBytes = maxCopyBytes_;
-    }
+    function setMaxCopyBytes(uint16 maxCopyBytes_) external onlyRole(GOVERNANCE_ROLE) {
+        require(maxCopyBytes_ > 0 && maxCopyBytes_ <= MAX_COPY_BYTES, "maxCopyBytes out of range");
+        maxCopyBytes = maxCopyBytes_;
+    }

33-37: Unused error: SwitchboardExists

Currently unused in this file; use it in enableSwitchboard() (see above) or remove to keep the surface area lean.


56-61: Consider idempotent disable

Repeated disables emit duplicate events. This should no-op if already DISABLED (or revert with a specific error for a cleaner state machine).

-    function disableSwitchboard(address switchboard_) external onlyRole(SWITCHBOARD_DISABLER_ROLE) {
-        isValidSwitchboard[switchboard_] = SwitchboardStatus.DISABLED;
-        emit SwitchboardDisabled(switchboard_);
-    }
+    function disableSwitchboard(address switchboard_) external onlyRole(SWITCHBOARD_DISABLER_ROLE) {
+        if (isValidSwitchboard[switchboard_] == SwitchboardStatus.DISABLED) return;
+        isValidSwitchboard[switchboard_] = SwitchboardStatus.DISABLED;
+        emit SwitchboardDisabled(switchboard_);
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 701e866 and baaf1fa.

📒 Files selected for processing (7)
  • Errors.md (1 hunks)
  • EventTopics.md (1 hunks)
  • FunctionSignatures.md (1 hunks)
  • contracts/protocol/SocketConfig.sol (1 hunks)
  • foundry.toml (1 hunks)
  • hardhat-scripts/deploy/3.configureChains.ts (1 hunks)
  • script/super-token-solana/DeployEVMSolanaApps.s.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • script/super-token-solana/DeployEVMSolanaApps.s.sol
🧰 Additional context used
🧬 Code graph analysis (1)
hardhat-scripts/deploy/3.configureChains.ts (4)
hardhat-scripts/constants/constants.ts (1)
  • FAST_SWITCHBOARD_TYPE (9-9)
hardhat-scripts/utils/address.ts (1)
  • toBytes32Format (54-61)
hardhat-scripts/utils/deployUtils.ts (1)
  • updateContractSettings (323-362)
hardhat-scripts/config/config.ts (1)
  • EVMX_CHAIN_ID (92-92)
🪛 LanguageTool
FunctionSignatures.md

[grammar] ~89-~89: There might be a mistake here.
Context: ... SocketBatcher | Function | Signature | | -------- | --------- | | `attestAndExe...

(QB_NEW_EN)


[grammar] ~90-~90: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | attestAndExecute | 0x66c7748a | | ...

(QB_NEW_EN)


[grammar] ~91-~91: There might be a mistake here.
Context: ... | | attestAndExecute | 0x66c7748a | | cancelOwnershipHandover | `0x54d1f13...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...ancelOwnershipHandover|0x54d1f13d| |completeOwnershipHandover|0xf04e2...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...pleteOwnershipHandover|0xf04e283e| |owner|0x8da5cb5b| |ownershipH...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...0xf04e283e| |owner|0x8da5cb5b| |ownershipHandoverExpiresAt|0xfee8...

(QB_NEW_EN)


[grammar] ~95-~95: There might be a mistake here.
Context: ...rshipHandoverExpiresAt|0xfee81cf4| |renounceOwnership|0x715018a6` | |...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...| | renounceOwnership | 0x715018a6 | | requestOwnershipHandover | `0x256929...

(QB_NEW_EN)


[grammar] ~97-~97: There might be a mistake here.
Context: ...questOwnershipHandover|0x25692962| |rescueFunds|0x6ccae054| |sock...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ...2962| |rescueFunds|0x6ccae054| |socket__|0xc6a261d2| |transfe...

(QB_NEW_EN)


[grammar] ~99-~99: There might be a mistake here.
Context: ...ccae054| |socket__|0xc6a261d2| |transferOwnership|0xf2fde38b` | ...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ...cketFeeManager | Function | Signature | | -------- | --------- | | `cancelOwners...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | cancelOwnershipHandover | `0x54d1f13...

(QB_NEW_EN)


[grammar] ~106-~106: There might be a mistake here.
Context: ...ancelOwnershipHandover|0x54d1f13d| |completeOwnershipHandover|0xf04e2...

(QB_NEW_EN)


[grammar] ~107-~107: There might be a mistake here.
Context: ...pleteOwnershipHandover|0xf04e283e| |getMinSocketFees|0xd383b688` | | ...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ... | | getMinSocketFees | 0xd383b688 | | grantRole | 0x2f2ff15d | | `hasRol...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...83b688| |grantRole|0x2f2ff15d| |hasRole|0x91d14854| |owner` |...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...2f2ff15d| |hasRole|0x91d14854| |owner|0x8da5cb5b| |ownershipH...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...0x91d14854| |owner|0x8da5cb5b| |ownershipHandoverExpiresAt|0xfee8...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...rshipHandoverExpiresAt|0xfee81cf4| |payAndCheckFees|0xd9d29ae3| |...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...| |payAndCheckFees|0xd9d29ae3| |renounceOwnership|0x715018a6` | |...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ...| | renounceOwnership | 0x715018a6 | | requestOwnershipHandover | `0x256929...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...questOwnershipHandover|0x25692962| |rescueFunds|0x6ccae054| |revo...

(QB_NEW_EN)


[grammar] ~116-~116: There might be a mistake here.
Context: ...2962| |rescueFunds|0x6ccae054| |revokeRole|0xd547741f| |setSo...

(QB_NEW_EN)


[grammar] ~117-~117: There might be a mistake here.
Context: ...ae054| |revokeRole|0xd547741f| |setSocketFees|0x47a406f6| |so...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ...1f| |setSocketFees|0x47a406f6| |socketFees|0xab1b33a8| |trans...

(QB_NEW_EN)


[grammar] ~119-~119: There might be a mistake here.
Context: ...406f6| |socketFees|0xab1b33a8| |transferOwnership|0xf2fde38b` | ## FeesManager | Fun...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...| ## FeesPool | Function | Signature | | -------- | --------- | | `cancelOwners...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | cancelOwnershipHandover | `0x54d1f13...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ...ancelOwnershipHandover|0x54d1f13d| |completeOwnershipHandover|0xf04e2...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ...pleteOwnershipHandover|0xf04e283e| |getBalance|0x12065fe0| |grant...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...e283e| |getBalance|0x12065fe0| |grantRole|0x2f2ff15d| |hasRol...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...065fe0| |grantRole|0x2f2ff15d| |hasRole|0x91d14854| |owner` |...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ...2f2ff15d| |hasRole|0x91d14854| |owner|0x8da5cb5b| |ownershipH...

(QB_NEW_EN)


[grammar] ~176-~176: There might be a mistake here.
Context: ...0x91d14854| |owner|0x8da5cb5b| |ownershipHandoverExpiresAt|0xfee8...

(QB_NEW_EN)


[grammar] ~177-~177: There might be a mistake here.
Context: ...rshipHandoverExpiresAt|0xfee81cf4| |renounceOwnership|0x715018a6` | |...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ...| | renounceOwnership | 0x715018a6 | | requestOwnershipHandover | `0x256929...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ...questOwnershipHandover|0x25692962| |revokeRole|0xd547741f| |trans...

(QB_NEW_EN)


[grammar] ~180-~180: There might be a mistake here.
Context: ...92962| |revokeRole|0xd547741f| |transferOwnership|0xf2fde38b` | |...

(QB_NEW_EN)


[grammar] ~181-~181: There might be a mistake here.
Context: ...| | transferOwnership | 0xf2fde38b | | withdraw | 0xf3fef3a3 | ## Addres...

(QB_NEW_EN)


[grammar] ~242-~242: There might be a mistake here.
Context: ...# AsyncPromise | Function | Signature | | -------- | --------- | | `addressResol...

(QB_NEW_EN)


[grammar] ~243-~243: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | addressResolver__ | 0x6a750469 | |...

(QB_NEW_EN)


[grammar] ~244-~244: There might be a mistake here.
Context: ...| | addressResolver__ | 0x6a750469 | | asyncDeployer__ | 0x2a39e801 | | `...

(QB_NEW_EN)


[grammar] ~245-~245: There might be a mistake here.
Context: ...| |asyncDeployer__|0x2a39e801| |callbackData|0xef44c272| |cal...

(QB_NEW_EN)


[grammar] ~246-~246: There might be a mistake here.
Context: ...801| |callbackData|0xef44c272| |callbackSelector|0x2764f92f` | | ...

(QB_NEW_EN)


[grammar] ~247-~247: There might be a mistake here.
Context: ... | | callbackSelector | 0x2764f92f | | deployForwarder__ | 0xd4e3b034 | |...

(QB_NEW_EN)


[grammar] ~248-~248: There might be a mistake here.
Context: ...| | deployForwarder__ | 0xd4e3b034 | | exceededMaxCopy | 0xaf598c7c | | `...

(QB_NEW_EN)


[grammar] ~249-~249: There might be a mistake here.
Context: ...| |exceededMaxCopy|0xaf598c7c| |feesManager__|0x70568b58| |in...

(QB_NEW_EN)


[grammar] ~250-~250: There might be a mistake here.
Context: ...7c| |feesManager__|0x70568b58| |initialize|0x0ece6089| |local...

(QB_NEW_EN)


[grammar] ~251-~251: There might be a mistake here.
Context: ...68b58| |initialize|0x0ece6089| |localInvoker|0x45eb87f4| |mar...

(QB_NEW_EN)


[grammar] ~252-~252: There might be a mistake here.
Context: ...089| |localInvoker|0x45eb87f4| |markOnchainRevert|0xd0e7af1b` | |...

(QB_NEW_EN)


[grammar] ~253-~253: There might be a mistake here.
Context: ...| | markOnchainRevert | 0xd0e7af1b | | markResolved | 0x822d5d1f | | `req...

(QB_NEW_EN)


[grammar] ~254-~254: There might be a mistake here.
Context: ...f1b| |markResolved|0x822d5d1f| |requestCount|0x5badbe4c| |res...

(QB_NEW_EN)


[grammar] ~255-~255: There might be a mistake here.
Context: ...d1f| |requestCount|0x5badbe4c| |rescueFunds|0x6ccae054| |retu...

(QB_NEW_EN)


[grammar] ~256-~256: There might be a mistake here.
Context: ...be4c| |rescueFunds|0x6ccae054| |returnData|0xebddbaf6| |state...

(QB_NEW_EN)


[grammar] ~257-~257: There might be a mistake here.
Context: ...ae054| |returnData|0xebddbaf6| |state|0xc19d93fb| |then|0...

(QB_NEW_EN)


[grammar] ~264-~264: There might be a mistake here.
Context: ...eployForwarder | Function | Signature | | -------- | --------- | | `addressResol...

(QB_NEW_EN)


[grammar] ~265-~265: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | addressResolver__ | 0x6a750469 | |...

(QB_NEW_EN)


[grammar] ~266-~266: There might be a mistake here.
Context: ...| | addressResolver__ | 0x6a750469 | | asyncDeployer__ | 0x2a39e801 | | `...

(QB_NEW_EN)


[grammar] ~267-~267: There might be a mistake here.
Context: ...| |asyncDeployer__|0x2a39e801| |cancelOwnershipHandover|0x54d1f13...

(QB_NEW_EN)


[grammar] ~268-~268: There might be a mistake here.
Context: ...ancelOwnershipHandover|0x54d1f13d| |completeOwnershipHandover|0xf04e2...

(QB_NEW_EN)


[grammar] ~269-~269: There might be a mistake here.
Context: ...pleteOwnershipHandover|0xf04e283e| |deploy|0x940f11af| |deployFor...

(QB_NEW_EN)


[grammar] ~270-~270: There might be a mistake here.
Context: ...xf04e283e| |deploy|0x940f11af| |deployForwarder__|0xd4e3b034` | |...

(QB_NEW_EN)


[grammar] ~271-~271: There might be a mistake here.
Context: ...| | deployForwarder__ | 0xd4e3b034 | | deployerSwitchboardType | `0xaa381f9...

(QB_NEW_EN)


[grammar] ~272-~272: There might be a mistake here.
Context: ...eployerSwitchboardType|0xaa381f9a| |feesManager__|0x70568b58| |in...

(QB_NEW_EN)


[grammar] ~273-~273: There might be a mistake here.
Context: ...9a| |feesManager__|0x70568b58| |initialize|0x6133f985| |owner...

(QB_NEW_EN)


[grammar] ~274-~274: There might be a mistake here.
Context: ...68b58| |initialize|0x6133f985| |owner|0x8da5cb5b| |ownershipH...

(QB_NEW_EN)


[grammar] ~275-~275: There might be a mistake here.
Context: ...0x6133f985| |owner|0x8da5cb5b| |ownershipHandoverExpiresAt|0xfee8...

(QB_NEW_EN)


[grammar] ~276-~276: There might be a mistake here.
Context: ...rshipHandoverExpiresAt|0xfee81cf4| |renounceOwnership|0x715018a6` | |...

(QB_NEW_EN)


[grammar] ~277-~277: There might be a mistake here.
Context: ...| | renounceOwnership | 0x715018a6 | | requestOwnershipHandover | `0x256929...

(QB_NEW_EN)


[grammar] ~278-~278: There might be a mistake here.
Context: ...questOwnershipHandover|0x25692962| |rescueFunds|0x6ccae054| |salt...

(QB_NEW_EN)


[grammar] ~286-~286: There might be a mistake here.
Context: ... ## Forwarder | Function | Signature | | -------- | --------- | | `addressResol...

(QB_NEW_EN)


[grammar] ~287-~287: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | addressResolver__ | 0x6a750469 | |...

(QB_NEW_EN)


[grammar] ~288-~288: There might be a mistake here.
Context: ...| | addressResolver__ | 0x6a750469 | | asyncDeployer__ | 0x2a39e801 | | `...

(QB_NEW_EN)


[grammar] ~289-~289: There might be a mistake here.
Context: ...| |asyncDeployer__|0x2a39e801| |chainSlug|0xb349ba65| |deploy...

(QB_NEW_EN)


[grammar] ~290-~290: There might be a mistake here.
Context: ...39e801| |chainSlug|0xb349ba65| |deployForwarder__|0xd4e3b034` | |...

(QB_NEW_EN)


[grammar] ~291-~291: There might be a mistake here.
Context: ...| | deployForwarder__ | 0xd4e3b034 | | feesManager__ | 0x70568b58 | | `ge...

(QB_NEW_EN)


[grammar] ~292-~292: There might be a mistake here.
Context: ...34| |feesManager__|0x70568b58| |getChainSlug|0x0b8c6568| |get...

(QB_NEW_EN)


[grammar] ~293-~293: There might be a mistake here.
Context: ...b58| |getChainSlug|0x0b8c6568| |getOnChainAddress|0x9da48789` | |...

(QB_NEW_EN)


[grammar] ~294-~294: There might be a mistake here.
Context: ...| | getOnChainAddress | 0x9da48789 | | initialize | 0x148841cb | | `onCha...

(QB_NEW_EN)


[grammar] ~295-~295: There might be a mistake here.
Context: ...48789| |initialize|0x148841cb| |onChainAddress|0x8bd0b363| |r...

(QB_NEW_EN)


[grammar] ~296-~296: There might be a mistake here.
Context: ...b| |onChainAddress|0x8bd0b363| |rescueFunds|0x6ccae054| |watc...

(QB_NEW_EN)


[grammar] ~297-~297: There might be a mistake here.
Context: ...b363| |rescueFunds|0x6ccae054| |watcher__|0x300bb063` | ## Forwa...

(QB_NEW_EN)


[grammar] ~302-~302: There might be a mistake here.
Context: ...orwarderSolana | Function | Signature | | -------- | --------- | | `addressResol...

(QB_NEW_EN)


[grammar] ~303-~303: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | addressResolver__ | 0x6a750469 | |...

(QB_NEW_EN)


[grammar] ~304-~304: There might be a mistake here.
Context: ...| | addressResolver__ | 0x6a750469 | | asyncDeployer__ | 0x2a39e801 | | `...

(QB_NEW_EN)


[grammar] ~305-~305: There might be a mistake here.
Context: ...| |asyncDeployer__|0x2a39e801| |callSolana|0x4ef7957b| |chain...

(QB_NEW_EN)


[grammar] ~306-~306: There might be a mistake here.
Context: ...9e801| |callSolana|0x4ef7957b| |chainSlug|0xb349ba65| |deploy...

(QB_NEW_EN)


[grammar] ~307-~307: There might be a mistake here.
Context: ...f7957b| |chainSlug|0xb349ba65| |deployForwarder__|0xd4e3b034` | |...

(QB_NEW_EN)


[grammar] ~308-~308: There might be a mistake here.
Context: ...| | deployForwarder__ | 0xd4e3b034 | | feesManager__ | 0x70568b58 | | `ge...

(QB_NEW_EN)


[grammar] ~309-~309: There might be a mistake here.
Context: ...34| |feesManager__|0x70568b58| |getChainSlug|0x0b8c6568| |get...

(QB_NEW_EN)


[grammar] ~310-~310: There might be a mistake here.
Context: ...b58| |getChainSlug|0x0b8c6568| |getOnChainAddress|0x9da48789` | |...

(QB_NEW_EN)


[grammar] ~311-~311: There might be a mistake here.
Context: ...| | getOnChainAddress | 0x9da48789 | | initialize | 0x148841cb | | `onCha...

(QB_NEW_EN)


[grammar] ~312-~312: There might be a mistake here.
Context: ...48789| |initialize|0x148841cb| |onChainAddress|0x8bd0b363| |w...

(QB_NEW_EN)


[grammar] ~313-~313: There might be a mistake here.
Context: ...b| |onChainAddress|0x8bd0b363| |watcher__|0x300bb063` | ## Proxy...

(QB_NEW_EN)


[grammar] ~318-~318: There might be a mistake here.
Context: ...# ProxyFactory | Function | Signature | | -------- | --------- | | adminOf | `...

(QB_NEW_EN)


[grammar] ~319-~319: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | adminOf | 0x2abbef15 | | `changeAd...

(QB_NEW_EN)


[grammar] ~320-~320: There might be a mistake here.
Context: ...--------- | | adminOf | 0x2abbef15 | | changeAdmin | 0x1acfd02a | | `depl...

(QB_NEW_EN)


[grammar] ~321-~321: There might be a mistake here.
Context: ...ef15| |changeAdmin|0x1acfd02a| |deploy|0x545e7c61| |deployAnd...

(QB_NEW_EN)


[grammar] ~322-~322: There might be a mistake here.
Context: ...x1acfd02a| |deploy|0x545e7c61| |deployAndCall|0x4314f120| |de...

(QB_NEW_EN)


[grammar] ~323-~323: There might be a mistake here.
Context: ...61| |deployAndCall|0x4314f120| |deployDeterministic|0x3729f922` |...

(QB_NEW_EN)


[grammar] ~324-~324: There might be a mistake here.
Context: ...| deployDeterministic | 0x3729f922 | | deployDeterministicAndCall | `0xa97b...

(QB_NEW_EN)


[grammar] ~325-~325: There might be a mistake here.
Context: ...oyDeterministicAndCall|0xa97b90d5| |initCodeHash|0xdb4c545e| |pre...

(QB_NEW_EN)


[grammar] ~326-~326: There might be a mistake here.
Context: ...0d5| |initCodeHash|0xdb4c545e| |predictDeterministicAddress|0x541...

(QB_NEW_EN)


[grammar] ~327-~327: There might be a mistake here.
Context: ...ctDeterministicAddress|0x5414dff0| |upgrade|0x99a88ec4| |upgradeA...

(QB_NEW_EN)


[grammar] ~328-~328: There might be a mistake here.
Context: ...5414dff0| |upgrade|0x99a88ec4| |upgradeAndCall|0x9623609d` | ## ...

(QB_NEW_EN)


[grammar] ~333-~333: There might be a mistake here.
Context: ...| ## TestUSDC | Function | Signature | | -------- | --------- | | `DOMAIN_SEPAR...

(QB_NEW_EN)


[grammar] ~334-~334: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | DOMAIN_SEPARATOR | 0x3644e515 | | ...

(QB_NEW_EN)


[grammar] ~335-~335: There might be a mistake here.
Context: ... | | DOMAIN_SEPARATOR | 0x3644e515 | | allowance | 0xdd62ed3e | | `approv...

(QB_NEW_EN)


[grammar] ~336-~336: There might be a mistake here.
Context: ...44e515| |allowance|0xdd62ed3e| |approve|0x095ea7b3| |balanceO...

(QB_NEW_EN)


[grammar] ~337-~337: There might be a mistake here.
Context: ...dd62ed3e| |approve|0x095ea7b3| |balanceOf|0x70a08231| |decima...

(QB_NEW_EN)


[grammar] ~338-~338: There might be a mistake here.
Context: ...5ea7b3| |balanceOf|0x70a08231| |decimals|0x313ce567| |mint` |...

(QB_NEW_EN)


[grammar] ~339-~339: There might be a mistake here.
Context: ...0a08231| |decimals|0x313ce567| |mint|0x40c10f19| |name|0x...

(QB_NEW_EN)


[grammar] ~340-~340: There might be a mistake here.
Context: ...0x313ce567 | | mint | 0x40c10f19 | | name | 0x06fdde03 | | nonces | `...

(QB_NEW_EN)


[grammar] ~341-~341: There might be a mistake here.
Context: ...0x40c10f19 | | name | 0x06fdde03 | | nonces | 0x7ecebe00 | | owner | ...

(QB_NEW_EN)


[grammar] ~342-~342: There might be a mistake here.
Context: ...x06fdde03| |nonces|0x7ecebe00| |owner|0x8da5cb5b| |permit` | ...

(QB_NEW_EN)


[grammar] ~343-~343: There might be a mistake here.
Context: ...0x7ecebe00| |owner|0x8da5cb5b| |permit|0xd505accf| |symbol` |...

(QB_NEW_EN)


[grammar] ~344-~344: There might be a mistake here.
Context: ...x8da5cb5b| |permit|0xd505accf| |symbol|0x95d89b41| |totalSupp...

(QB_NEW_EN)


[grammar] ~345-~345: There might be a mistake here.
Context: ...xd505accf| |symbol|0x95d89b41| |totalSupply|0x18160ddd| |tran...

(QB_NEW_EN)


[grammar] ~346-~346: There might be a mistake here.
Context: ...9b41| |totalSupply|0x18160ddd| |transfer|0xa9059cbb| |transfe...

(QB_NEW_EN)


[grammar] ~347-~347: There might be a mistake here.
Context: ...8160ddd| |transfer|0xa9059cbb| |transferFrom|0x23b872dd` | ## Co...

(QB_NEW_EN)


[grammar] ~400-~400: There might be a mistake here.
Context: ...romiseResolver | Function | Signature | | -------- | --------- | | markRevert ...

(QB_NEW_EN)


[grammar] ~401-~401: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | markRevert | 0x56501015 | | `rescu...

(QB_NEW_EN)


[grammar] ~402-~402: There might be a mistake here.
Context: ...------ | | markRevert | 0x56501015 | | rescueFunds | 0x6ccae054 | | `reso...

(QB_NEW_EN)


[grammar] ~403-~403: There might be a mistake here.
Context: ...1015| |rescueFunds|0x6ccae054| |resolvePromises|0xbf8484b8| |...

(QB_NEW_EN)


[grammar] ~404-~404: There might be a mistake here.
Context: ...| |resolvePromises|0xbf8484b8| |watcher__|0x300bb063` | ## Reque...

(QB_NEW_EN)


[grammar] ~493-~493: There might be a mistake here.
Context: ...astSwitchboard | Function | Signature | | -------- | --------- | | `allowPayload...

(QB_NEW_EN)


[grammar] ~494-~494: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | allowPayload | 0x31c23f66 | | `att...

(QB_NEW_EN)


[grammar] ~495-~495: There might be a mistake here.
Context: ...---- | | allowPayload | 0x31c23f66 | | attest | 0x63671b60 | | `cancelOwn...

(QB_NEW_EN)


[grammar] ~496-~496: There might be a mistake here.
Context: ...x31c23f66| |attest|0x63671b60| |cancelOwnershipHandover|0x54d1f13...

(QB_NEW_EN)


[grammar] ~497-~497: There might be a mistake here.
Context: ...ancelOwnershipHandover|0x54d1f13d| |chainSlug|0xb349ba65| |comple...

(QB_NEW_EN)


[grammar] ~498-~498: There might be a mistake here.
Context: ...d1f13d| |chainSlug|0xb349ba65| |completeOwnershipHandover|0xf04e2...

(QB_NEW_EN)


[grammar] ~499-~499: There might be a mistake here.
Context: ...pleteOwnershipHandover|0xf04e283e| |grantRole|0x2f2ff15d| |hasRol...

(QB_NEW_EN)


[grammar] ~500-~500: There might be a mistake here.
Context: ...4e283e| |grantRole|0x2f2ff15d| |hasRole|0x91d14854| |isAttest...

(QB_NEW_EN)


[grammar] ~501-~501: There might be a mistake here.
Context: ...2f2ff15d| |hasRole|0x91d14854| |isAttested|0xc13c2396| |owner...

(QB_NEW_EN)


[grammar] ~502-~502: There might be a mistake here.
Context: ...14854| |isAttested|0xc13c2396| |owner|0x8da5cb5b| |ownershipH...

(QB_NEW_EN)


[grammar] ~503-~503: There might be a mistake here.
Context: ...0xc13c2396| |owner|0x8da5cb5b| |ownershipHandoverExpiresAt|0xfee8...

(QB_NEW_EN)


[grammar] ~504-~504: There might be a mistake here.
Context: ...rshipHandoverExpiresAt|0xfee81cf4| |registerSwitchboard|0x74f5b1fc` |...

(QB_NEW_EN)


[grammar] ~505-~505: There might be a mistake here.
Context: ...| registerSwitchboard | 0x74f5b1fc | | renounceOwnership | 0x715018a6 | |...

(QB_NEW_EN)


[grammar] ~506-~506: There might be a mistake here.
Context: ...| | renounceOwnership | 0x715018a6 | | requestOwnershipHandover | `0x256929...

(QB_NEW_EN)


[grammar] ~507-~507: There might be a mistake here.
Context: ...questOwnershipHandover|0x25692962| |rescueFunds|0x6ccae054| |revo...

(QB_NEW_EN)


[grammar] ~508-~508: There might be a mistake here.
Context: ...2962| |rescueFunds|0x6ccae054| |revokeRole|0xd547741f| |socke...

(QB_NEW_EN)


[grammar] ~515-~515: There might be a mistake here.
Context: ...ReadPrecompile | Function | Signature | | -------- | --------- | | expiryTime ...

(QB_NEW_EN)


[grammar] ~516-~516: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | expiryTime | 0x99bc0aea | | `getPr...

(QB_NEW_EN)


[grammar] ~517-~517: There might be a mistake here.
Context: ...------ | | expiryTime | 0x99bc0aea | | getPrecompileFees | 0xb7a3d04c | |...

(QB_NEW_EN)


[grammar] ~518-~518: There might be a mistake here.
Context: ...| | getPrecompileFees | 0xb7a3d04c | | handlePayload | 0x1d5e1d98 | | `re...

(QB_NEW_EN)


[grammar] ~519-~519: There might be a mistake here.
Context: ...4c| |handlePayload|0x1d5e1d98| |readFees|0xe06357a2| |rescueF...

(QB_NEW_EN)


[grammar] ~520-~520: There might be a mistake here.
Context: ...d5e1d98| |readFees|0xe06357a2| |rescueFunds|0x6ccae054| |reso...

(QB_NEW_EN)


[grammar] ~521-~521: There might be a mistake here.
Context: ...57a2| |rescueFunds|0x6ccae054| |resolvePayload|0xea92e825| |s...

(QB_NEW_EN)


[grammar] ~522-~522: There might be a mistake here.
Context: ...4| |resolvePayload|0xea92e825| |setExpiryTime|0x30fc4cff| |se...

(QB_NEW_EN)


[grammar] ~523-~523: There might be a mistake here.
Context: ...25| |setExpiryTime|0x30fc4cff| |setFees|0x3d18678e| |validate...

(QB_NEW_EN)


[grammar] ~524-~524: There might be a mistake here.
Context: ...30fc4cff| |setFees|0x3d18678e| |validateAndGetPrecompileData|0x99...

(QB_NEW_EN)


[grammar] ~525-~525: There might be a mistake here.
Context: ...teAndGetPrecompileData|0x997f5bef| |watcher__|0x300bb063` | ## Sched...

(QB_NEW_EN)


[grammar] ~530-~530: There might be a mistake here.
Context: ...dulePrecompile | Function | Signature | | -------- | --------- | | expiryTime ...

(QB_NEW_EN)


[grammar] ~531-~531: There might be a mistake here.
Context: ...n | Signature | | -------- | --------- | | expiryTime | 0x99bc0aea | | `getPr...

(QB_NEW_EN)


[grammar] ~532-~532: There might be a mistake here.
Context: ...------ | | expiryTime | 0x99bc0aea | | getPrecompileFees | 0xb7a3d04c | |...

(QB_NEW_EN)


[grammar] ~533-~533: There might be a mistake here.
Context: ...| | getPrecompileFees | 0xb7a3d04c | | handlePayload | 0x1d5e1d98 | | `ma...

(QB_NEW_EN)


[grammar] ~534-~534: There might be a mistake here.
Context: ...4c| |handlePayload|0x1d5e1d98| |maxScheduleDelayInSeconds|0x3ef01...

(QB_NEW_EN)


[grammar] ~535-~535: There might be a mistake here.
Context: ...ScheduleDelayInSeconds|0x3ef01cdb| |rescueFunds|0x6ccae054| |reso...

(QB_NEW_EN)


[grammar] ~536-~536: There might be a mistake here.
Context: ...1cdb| |rescueFunds|0x6ccae054| |resolvePayload|0xea92e825| |s...

(QB_NEW_EN)


[grammar] ~537-~537: There might be a mistake here.
Context: ...4| |resolvePayload|0xea92e825| |scheduleCallbackFees|0x4c5b6007` ...

(QB_NEW_EN)


[grammar] ~538-~538: There might be a mistake here.
Context: ... scheduleCallbackFees | 0x4c5b6007 | | scheduleFeesPerSecond | 0x852a74c1...

(QB_NEW_EN)


[grammar] ~539-~539: There might be a mistake here.
Context: ...scheduleFeesPerSecond | 0x852a74c1 | | setExpiryTime | 0x30fc4cff | | `se...

(QB_NEW_EN)


[grammar] ~540-~540: There might be a mistake here.
Context: ...c1| |setExpiryTime|0x30fc4cff| |setMaxScheduleDelayInSeconds|0x12...

(QB_NEW_EN)


[grammar] ~541-~541: There might be a mistake here.
Context: ...ScheduleDelayInSeconds|0x12953318| |setScheduleCallbackFees|0xec8fd71...

(QB_NEW_EN)


[grammar] ~542-~542: There might be a mistake here.
Context: ...etScheduleCallbackFees|0xec8fd71e| |setScheduleFeesPerSecond|0x28e59e...

(QB_NEW_EN)


[grammar] ~543-~543: There might be a mistake here.
Context: ...tScheduleFeesPerSecond|0x28e59e57| |validateAndGetPrecompileData|0x99...

(QB_NEW_EN)


[grammar] ~544-~544: There might be a mistake here.
Context: ...teAndGetPrecompileData|0x997f5bef| |watcher__|0x300bb063` | ## Write...

(QB_NEW_EN)

EventTopics.md

[grammar] ~40-~40: There might be a mistake here.
Context: ...etBatcher | Event | Arguments | Topic | | ----- | --------- | ----- | | `Ownersh...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | OwnershipHandoverCanceled | `(pendin...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...464468f9bfbae89d1675917346ca6d8fe3c92| |OwnershipHandoverRequested|(pendi...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...abf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d| |OwnershipTransferred|(oldOwner: a...

(QB_NEW_EN)


[grammar] ~117-~117: There might be a mistake here.
Context: ...ncPromise | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ...Forwarder | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~125-~125: There might be a mistake here.
Context: ...99e1f2633a7b5936321eed1cdaeb6115181d2| |OwnershipHandoverCanceled|(pendin...

(QB_NEW_EN)


[grammar] ~126-~126: There might be a mistake here.
Context: ...464468f9bfbae89d1675917346ca6d8fe3c92| |OwnershipHandoverRequested|(pendi...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ...abf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d| |OwnershipTransferred|(oldOwner: a...

(QB_NEW_EN)


[grammar] ~132-~132: There might be a mistake here.
Context: ...Forwarder | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...derSolana | Event | Arguments | Topic | | ----- | --------- | ----- | | `Initial...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Initialized | (version: uint64) | ...

(QB_NEW_EN)


[grammar] ~144-~144: There might be a mistake here.
Context: ...xyFactory | Event | Arguments | Topic | | ----- | --------- | ----- | | `AdminCh...

(QB_NEW_EN)


[grammar] ~145-~145: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | AdminChanged | `(proxy: address, adm...

(QB_NEW_EN)


[grammar] ~146-~146: There might be a mistake here.
Context: ...588d331ebfa28653d42ae832dc59e38c9798f| |Deployed|(proxy: address, impleme...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...27ae891d20b2fb91cf3994b6a3bf2b8178082| |Upgraded|(proxy: address, impleme...

(QB_NEW_EN)


[grammar] ~152-~152: There might be a mistake here.
Context: ... TestUSDC | Event | Arguments | Topic | | ----- | --------- | ----- | | `Approva...

(QB_NEW_EN)


[grammar] ~153-~153: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | Approval | `(owner: address, spender...

(QB_NEW_EN)


[grammar] ~154-~154: There might be a mistake here.
Context: ...e84f3dd0314c0f7b2291e5b200ac8c7c3b925| |Transfer|(from: address, to: addr...

(QB_NEW_EN)


[grammar] ~200-~200: There might be a mistake here.
Context: ...eResolver | Event | Arguments | Topic | | ----- | --------- | ----- | | `MarkedR...

(QB_NEW_EN)


[grammar] ~201-~201: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | MarkedRevert | `(payloadId: bytes32,...

(QB_NEW_EN)


[grammar] ~202-~202: There might be a mistake here.
Context: ...c834fe98da3ddac44deb77fffd22ad933824c| |PromiseNotResolved|(payloadId: by...

(QB_NEW_EN)


[grammar] ~203-~203: There might be a mistake here.
Context: ...439395bd5fb5c39bca3547b126fe6ee467937| |PromiseResolved|(payloadId: bytes...

(QB_NEW_EN)


[grammar] ~247-~247: There might be a mistake here.
Context: ...recompile | Event | Arguments | Topic | | ----- | --------- | ----- | | `ExpiryT...

(QB_NEW_EN)


[grammar] ~248-~248: There might be a mistake here.
Context: ... | Topic | | ----- | --------- | ----- | | ExpiryTimeSet | `(expiryTime: uint25...

(QB_NEW_EN)


[grammar] ~249-~249: There might be a mistake here.
Context: ...bbf12de19f06df79cb0be12b3a7d7f2397fa9| |ReadFeesSet|(readFees: uint256)` ...

(QB_NEW_EN)


[grammar] ~250-~250: There might be a mistake here.
Context: ...606ffc54ac8a169e1568fc834c7813010f926| |ReadRequested|(transaction: tuple...

(QB_NEW_EN)

Errors.md

[grammar] ~6-~6: There might be a mistake here.
Context: ...fees/FeesPool.sol | Error | Signature | |-------|-----------| | `TransferFailed(...

(QB_NEW_EN)


[grammar] ~7-~7: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | TransferFailed() | 0x90b8ec18 | #...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: .../AsyncPromise.sol | Error | Signature | |-------|-----------| | `PromiseAlreadyR...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | PromiseAlreadyResolved() | `0x56b635...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...omiseAlreadyResolved()|0x56b63537| |OnlyInvoker()|0x74ed21f5| |Pr...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...37| |OnlyInvoker()|0x74ed21f5| |PromiseAlreadySetUp()|0x927c53d5`...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...PromiseAlreadySetUp() | 0x927c53d5 | | PromiseRevertFailed() | 0x0175b9de...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...PromiseRevertFailed() | 0x0175b9de | | NotLatestPromise() | 0x39ca95d3 | ...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...rwarderSolana.sol | Error | Signature | |-------|-----------| | `InvalidSolanaCh...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | InvalidSolanaChainSlug() | `0xe37803...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...validSolanaChainSlug()|0xe37803ab| |AddressResolverNotSet()|0x6d55276...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ctFactoryPlug.sol | Error | Signature | |-------|-----------| | `DeploymentFaile...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | DeploymentFailed() | 0x30116425 | ...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ... | DeploymentFailed() | 0x30116425 | | ExecutionFailed(bytes32,bytes) | `0x...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...nFailed(bytes32,bytes)|0xd255d8a3| |information(bool,,bytes)|0x3a82a1...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...lugs/FeesPlug.sol | Error | Signature | |-------|-----------| | `InsufficientTok...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | `InsufficientTokenBalance(address,uint...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...dress,uint256,uint256)|0xebd6ced9| |InvalidDepositAmount()|0xfe9ba5cd...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...InvalidDepositAmount()|0xfe9ba5cd| |TokenNotWhitelisted(address)|0xea...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ...equestHandler.sol | Error | Signature | |-------|-----------| | `InsufficientMax...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | InsufficientMaxFees() | 0x0e5bc492...

(QB_NEW_EN)


[grammar] ~51-~51: There might be a mistake here.
Context: ...otocol/Socket.sol | Error | Signature | |-------|-----------| | `PayloadAlreadyE...

(QB_NEW_EN)


[grammar] ~52-~52: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | `PayloadAlreadyExecuted(ExecutionStatu...

(QB_NEW_EN)


[grammar] ~53-~53: There might be a mistake here.
Context: ...cuted(ExecutionStatus)|0xf4c54edd| |VerificationFailed()|0x439cc0cd` ...

(QB_NEW_EN)


[grammar] ~54-~54: There might be a mistake here.
Context: ... VerificationFailed() | 0x439cc0cd | | LowGasLimit() | 0xd38edae0 | | `In...

(QB_NEW_EN)


[grammar] ~55-~55: There might be a mistake here.
Context: ...cd| |LowGasLimit()|0xd38edae0| |InsufficientMsgValue()|0x78f38f76...

(QB_NEW_EN)


[grammar] ~60-~60: There might be a mistake here.
Context: .../SocketConfig.sol | Error | Signature | |-------|-----------| | `SwitchboardExis...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | SwitchboardExists() | 0x2dff8555 |...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...| SwitchboardExists() | 0x2dff8555 | | SwitchboardExistsOrDisabled() | `0x1...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...ketFeeManager.sol | Error | Signature | |-------|-----------| | `InsufficientFee...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | InsufficientFees() | 0x8d53e553 | ...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ... | InsufficientFees() | 0x8d53e553 | | FeeTooLow() | 0x732f9413 | ## pro...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...l/SocketUtils.sol | Error | Signature | |-------|-----------| | OnlyOffChain()...

(QB_NEW_EN)


[grammar] ~75-~75: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | OnlyOffChain() | 0x9cbfe066 | | `S...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...---| | OnlyOffChain() | 0x9cbfe066 | | SimulationFailed() | 0x2fbab3ac | ...

(QB_NEW_EN)


[grammar] ~81-~81: There might be a mistake here.
Context: ...stSwitchboard.sol | Error | Signature | |-------|-----------| | `AlreadyAttested...

(QB_NEW_EN)


[grammar] ~82-~82: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | AlreadyAttested() | 0x35d90805 | |...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...| | AlreadyAttested() | 0x35d90805 | | WatcherNotFound() | 0xa278e4ad | ...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...AccessControl.sol | Error | Signature | |-------|-----------| | `NoPermit(bytes3...

(QB_NEW_EN)


[grammar] ~89-~89: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | NoPermit(bytes32) | 0x962f6333 | ...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...on/Converters.sol | Error | Signature | |-------|-----------| | `NotAnEvmAddress...

(QB_NEW_EN)


[grammar] ~95-~95: There might be a mistake here.
Context: ...rror | Signature | |-------|-----------| | NotAnEvmAddress(bytes32) | `0x33b960...

(QB_NEW_EN)

🔇 Additional comments (8)
EventTopics.md (4)

5-18: Docs reflow is consistent and readable.

Uniform 3-column tables improve scanability; hashes and args look unchanged.


132-135: Confirm Forwarder ownership events removal from this section.

If Forwarder still inherits/uses OwnershipHandover* and OwnershipTransferred, this section should list them for discoverability.


136-141: ForwarderSolana: add-only looks good; verify no other events are emitted.

If the contract also emits ownership/role events via base contracts, include them here for parity with other sections.


266-278: WritePrecompile events: new digest fields acknowledged.

The WriteProofRequested args list (including prevBatchDigestHash, deadline) matches recent code paths.

foundry.toml (1)

12-39: Verify label references and Contracts enum updates

No duplicate addresses detected in foundry.toml. You still need to confirm that the new labels are properly wired into your code:

• Ensure the Contracts enum (wherever it’s defined) includes both ForwarderSolana and ForwarderSolanaImpl.
• Search code and scripts for usages of these labels (e.g. labels.ForwarderSolana, labels.APP_GATEWAY, labels.WritePrecompileImpl, labels.RequestHandlerImpl) and add missing references.
• Update any deployment or test helpers that consume the [labels] block to handle the new entries.

Errors.md (1)

35-42: Ignore stale ownership comment: docs correctly list errors in FeesPlug.sol

rg shows both InvalidDepositAmount() and TokenNotWhitelisted(address token_) are still declared in contracts/evmx/plugs/FeesPlug.sol (at lines 31 and 33). No other “Configurations” contract declares these errors. The docs should continue listing them under FeesPlug to match the code.

Likely an incorrect or invalid review comment.

FunctionSignatures.md (2)

284-299: Forwarder section lists both chainSlug and getChainSlug; confirm both exist.

If one is legacy, deprecate or annotate to reduce confusion for integrators.


300-315: Verify ForwarderSolana selectors align with compiled output
The documented selectors in FunctionSignatures.md must match the on-chain compiled signatures to prevent tooling drift. The automated Node.js check failed due to a missing ethers dependency. This should be manually verified against the contract ABI or using a keccak256 tool.

• Functions to verify:
– callSolana((bytes32,bytes,bytes,bytes,bytes,bytes,bytes)[]) → 0x4ef7957b
– getChainSlug() → 0xb349ba65
– getOnChainAddress() → 0x9da48789
– initialize(uint32,bytes32,address) → 0x148841cb
– onChainAddress() → 0x8bd0b363
– chainSlug() → 0xb349ba65
– addressResolver__() → 0x6a750469
– asyncDeployer__() → 0x2a39e801
– deployForwarder__() → 0xd4e3b034
– feesManager__() → 0x70568b58
– watcher__() → 0x300bb063

Errors.md Outdated
Comment on lines 31 to 34
| `DeploymentFailed()` | `0x30116425` |
| `ExecutionFailed(bytes32,bytes)` | `0xd255d8a3` |
| `information(bool,,bytes)` | `0x3a82a1f3` |
| `information(bool,,bytes)` | `0x3a82a1f3` |

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in error entry: double comma in parameters.

information(bool,,bytes) has an extra comma and likely an incorrect name. Fix the signature immediately to avoid propagating a bad ABI reference.

Apply:

-| `information(bool,,bytes)` | `0x3a82a1f3` |
+| `information(bool,bytes)` | `0x3a82a1f3` |

If the actual error differs, replace with the correct identifier and selector.

📝 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
| `DeploymentFailed()` | `0x30116425` |
| `ExecutionFailed(bytes32,bytes)` | `0xd255d8a3` |
| `information(bool,,bytes)` | `0x3a82a1f3` |
| `information(bool,,bytes)` | `0x3a82a1f3` |
| `DeploymentFailed()` | `0x30116425` |
| `ExecutionFailed(bytes32,bytes)` | `0xd255d8a3` |
| `information(bool,bytes)` | `0x3a82a1f3` |
🧰 Tools
🪛 LanguageTool

[grammar] ~31-~31: There might be a mistake here.
Context: ... | DeploymentFailed() | 0x30116425 | | ExecutionFailed(bytes32,bytes) | `0x...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...nFailed(bytes32,bytes)|0xd255d8a3| |information(bool,,bytes)|0x3a82a1...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In Errors.md around lines 31 to 34, the error entry `information(bool,,bytes)`
contains a stray comma and possibly the wrong name; remove the extra comma to
make the signature `information(bool,bytes)` and then verify against the
contract ABI to ensure the error name and its selector (0x3a82a1f3) are correct
— if the ABI shows a different name or selector, update the entry to the exact
ABI signature and corresponding selector.

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: 28

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (21)
contracts/protocol/interfaces/ISocketBatcher.sol (1)

13-20: Missing @param for switchboardId_ and incomplete @return docs.

NatSpec must cover all params and both return values.

      * @notice Attests a payload and executes it
      * @param executeParams_ The execution parameters
+     * @param switchboardId_ The Switchboard identifier used to verify the 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 the fees to
-     * @return The return data after execution
+     * @return success True if execution succeeded
+     * @return returnData ABI-encoded return data from the executed call
+     * @dev Define behavior for refundAddress_ == address(0) (revert or default to msg.sender).
contracts/utils/common/Errors.sol (1)

78-80: Unify access-control errors — remove legacy Not and use Only*Allowed*

Both error families coexist; this should be unified to avoid inconsistent revert types and downstream decoding complexity.

  • This should replace revert NotRequestHandler();revert OnlyRequestHandlerAllowed(); in contracts/evmx/fees/FeesManager.sol (line 33).
  • This should replace revert NotPromiseResolver();revert OnlyPromiseResolverAllowed(); in contracts/evmx/watcher/RequestHandler.sol (line 91).
  • This should remove the legacy definitions error NotRequestHandler(); and error NotPromiseResolver(); from contracts/utils/common/Errors.sol (lines ~61, ~63). WatcherBase already uses OnlyRequestHandlerAllowed/OnlyPromiseResolverAllowed (lines ~23, ~28).

Verify there are no remaining usages: rg -n '\bNot(RequestHandler|PromiseResolver)('.

contracts/evmx/watcher/Configurations.sol (2)

121-132: NatSpec says “Only callable by the app gateway” but code restricts to watcher. Fix the authorization docs.

The function is onlyWatcher. The doc is wrong and misleading for integrators and audits. Update the comment to match code.

-    /// @dev Only callable by the app gateway
+    /// @dev Only callable by the watcher

67-73: Revert event parameter reordering or add IsValidPlugSetV2 — this is a breaking ABI change.

Reordering event params changes the ABI and breaks existing consumers (EventTopics.md documents the old signature). This should keep the original IsValidPlugSet signature and introduce a new IsValidPlugSetV2 with the new order; emit both during a deprecation window.

Locations:

  • contracts/evmx/watcher/Configurations.sol — event declaration (line ~72) and emit (line ~134).
  • test/evmx/Watcher.t.sol — vm.expectEmit / emit usage (around line ~465).
  • EventTopics.md — current entry documents the prior signature (line ~191).

Apply (keep original event, add V2, emit both):

-    /// @notice Emitted when a valid plug is set for an app gateway
-    /// @param appGateway The address of the app gateway
-    /// @param chainSlug The identifier of the network
-    /// @param plug The address of the plug
-    /// @param isValid Whether the plug is valid
-    event IsValidPlugSet(bool isValid, uint32 chainSlug, bytes32 plug, address appGateway);
+    /// @notice Emitted when a valid plug is set for an app gateway (backwards-compatible)
+    /// @param appGateway The address of the app gateway
+    /// @param chainSlug The identifier of the network
+    /// @param plug The address of the plug
+    /// @param isValid Whether the plug is valid
+    event IsValidPlugSet(address appGateway, uint32 chainSlug, bytes32 plug, bool isValid);
+    /// @notice New event with reordered params
+    event IsValidPlugSetV2(bool isValid, uint32 chainSlug, bytes32 plug, address appGateway);
@@
-        emit IsValidPlugSet(isValid_, chainSlug_, plug_, appGateway_);
+        emit IsValidPlugSet(appGateway_, chainSlug_, plug_, isValid_);
+        emit IsValidPlugSetV2(isValid_, chainSlug_, plug_, appGateway_);
contracts/protocol/base/PlugBase.sol (2)

76-89: Lock down initSocket: anyone can one-shot bind this plug to an arbitrary socket

initSocket is external, callable once, and permanently sets socket__/appGatewayId. Any EOAs/contracts can front‑run deployment wiring and hijack the plug. This should be owner/factory gated (or made internal and exposed by derived contracts with their own ACL). Emit an initialization event for observability.

Primary fix (Ownable-based ACL + event):

-abstract contract PlugBase is IPlug {
+import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+abstract contract PlugBase is IPlug, Ownable {
+    event SocketInitialized(bytes32 appGatewayId, address socket, uint64 switchboardId);
@@
-    function initSocket(
+    function initSocket(
         bytes32 appGatewayId_,
         address socket_,
         uint64 switchboardId_
-    ) external virtual socketInitializer {
-        _connectSocket(appGatewayId_, socket_, switchboardId_);
-    }
+    ) external virtual socketInitializer onlyOwner {
+        _connectSocket(appGatewayId_, socket_, switchboardId_);
+        emit SocketInitialized(appGatewayId_, socket_, switchboardId_);
+    }

If OZ Ownable is not available, alternative (make it internal; derived contracts expose a guarded external wrapper):

-    ) external virtual socketInitializer {
+    ) internal virtual socketInitializer {
         _connectSocket(appGatewayId_, socket_, switchboardId_);
     }

41-55: Validate inputs before connecting socket

This should reject zero appGatewayId and zero socket to avoid writing invalid state and external calls that revert deeper.

     function _connectSocket(
         bytes32 appGatewayId_,
         address socket_,
         uint64 switchboardId_
     ) internal {
-        _setSocket(socket_);
-        appGatewayId = appGatewayId_;
+        require(socket_ != address(0), "PlugBase: socket zero");
+        require(appGatewayId_ != bytes32(0), "PlugBase: appGatewayId zero");
+        _setSocket(socket_);
+        appGatewayId = appGatewayId_;
         // connect to the app gateway and switchboard
         socket__.connect(appGatewayId_, switchboardId_);
     }
script/counter/WithdrawFeesArbitrumFeesPlug.s.sol (1)

30-41: Do not subtract ETH gas cost from a USDC-denominated balance.

availableFees are token credits (USDC). estimatedGasCost is in wei. Subtracting across currencies is a logic bug and will produce incorrect amounts.

Minimal fix (keep it operational; remove the cross-currency subtraction):

-            // Calculate amount to withdraw
-            uint256 amountToWithdraw = availableFees > estimatedGasCost
-                ? availableFees - estimatedGasCost
-                : 0;
+            // Calculate amount to withdraw (no cross-currency subtraction)
+            uint256 amountToWithdraw = availableFees;

If you need gas-cost-aware withdrawals, this should convert ETH gas cost to USDC via a price feed (off-chain or on-chain oracle) before comparing.

contracts/evmx/watcher/Trigger.sol (1)

31-44: Set the "already-called" guard before any external calls — pull fees after state changes.

This should follow checks-effects-interactions: set triggerFromChainSlug, triggerFromPlug and isAppGatewayCalled[params_.triggerId] = true before calling the ERC20 transferFrom or appGateway.tryCall. Use OpenZeppelin SafeERC20.safeTransferFrom (or otherwise require the transfer success) to handle non-standard tokens. A failing transferFrom that reverts will roll back the state (so it will NOT leave the trigger marked); only an unhandled, non-reverting token could leave the guard set.

File: contracts/evmx/watcher/Trigger.sol (around lines 31–44).

contracts/evmx/watcher/precompiles/SchedulePrecompile.sol (4)

59-62: Fix invalid delay check: comparing against uninitialized expiryTime.

Constructor checks maxScheduleDelayInSeconds < expiryTime before assigning expiryTime, so the guard is ineffective. Set expiryTime first, then validate.

-        if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay();
-        expiryTime = expiryTime_;
+        expiryTime = expiryTime_;
+        if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay();

73-77: Use the parameter in the guard, not the old state.

The setter validates the old maxScheduleDelayInSeconds instead of the incoming value.

-        if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay();
-        maxScheduleDelayInSeconds = maxScheduleDelayInSeconds_;
+        if (maxScheduleDelayInSeconds_ < expiryTime) revert InvalidScheduleDelay();
+        maxScheduleDelayInSeconds = maxScheduleDelayInSeconds_;

101-105: Validate new expiryTime against current max delay.

This check accidentally compares current expiryTime instead of the new expiryTime_.

-        if (maxScheduleDelayInSeconds < expiryTime) revert InvalidScheduleDelay();
-        expiryTime = expiryTime_;
+        if (maxScheduleDelayInSeconds < expiryTime_) revert InvalidScheduleDelay();
+        expiryTime = expiryTime_;

64-67: Decode shape mismatch vs callers encoding two words.

Callers encode (delayInSeconds, executeAfterPlaceholder), but getPrecompileFees decodes a single uint256. Make the decoder accept both encodings.

-    function getPrecompileFees(bytes memory precompileData_) public view returns (uint256) {
-        uint256 delayInSeconds = abi.decode(precompileData_, (uint256));
-        return scheduleFeesPerSecond * delayInSeconds + scheduleCallbackFees;
-    }
+    function getPrecompileFees(bytes memory precompileData_) public view returns (uint256) {
+        uint256 delayInSeconds;
+        if (precompileData_.length == 32) {
+            delayInSeconds = abi.decode(precompileData_, (uint256));
+        } else {
+            (delayInSeconds, ) = abi.decode(precompileData_, (uint256, uint256));
+        }
+        return scheduleFeesPerSecond * delayInSeconds + scheduleCallbackFees;
+    }
hardhat-scripts/deploy/1.deploy.ts (1)

100-105: Await getWatcherSigner; current code assigns a Promise to signer.

This breaks signer.address and downstream deployments.

-      const signer: Wallet = getWatcherSigner();
+      const signer: Wallet = await getWatcherSigner();
script/helpers/TransferRemainingCredits.s.sol (1)

31-37: Fix: calling transferFrom on the wrong contract (will always fail).

This calls transferFrom on appGateway, but transferFrom is on FeesManager. This should invoke the typed method on feesManager (and avoid fragile string signatures). Also skip zero-amount transfers.

Apply:

-        bytes memory data = abi.encodeWithSignature(
-            "transferFrom(address,address,uint256)",
-            appGateway,
-            newAppGateway,
-            availableFees
-        );
-        (bool success, ) = appGateway.call(data);
-        require(success, "Transfer failed");
+        require(availableFees > 0, "No available fees to transfer");
+        feesManager.transferFrom(appGateway, newAppGateway, availableFees);
hardhat-scripts/deploy/9.setupTransmitter.ts (2)

64-82: Top up only the delta to threshold, not the full threshold.

Depositing the full threshold every time underfunded over-allocates capital. Compute needed = threshold - current.

 export const checkAndDepositCredits = async (transmitter: string) => {
   console.log("Checking and depositing credits");
   const credits = await feesManagerContract
     .connect(transmitterSigner)
     .balanceOf(transmitter);
 
-  if (credits.lt(TRANSMITTER_CREDIT_THRESHOLD)) {
+  if (credits.lt(TRANSMITTER_CREDIT_THRESHOLD)) {
+    const needed = TRANSMITTER_CREDIT_THRESHOLD.sub(credits);
     console.log("Depositing credits for transmitter...");
     const tx = await feesManagerContract
       .connect(getWatcherSigner())
-      .wrap(transmitter, {
+      .wrap(transmitter, {
         ...(await overrides(EVMX_CHAIN_ID as ChainSlug)),
-        value: TRANSMITTER_CREDIT_THRESHOLD,
+        value: needed,
       });

84-101: Top up native only to the threshold.

Mirror the credit top-up logic for native balance.

 export const checkAndDepositNative = async (transmitter: string) => {
   console.log("Checking and depositing native");
   const nativeBalance = await transmitterSigner.provider!.getBalance(
     transmitter
   );
 
   if (nativeBalance.lt(TRANSMITTER_NATIVE_THRESHOLD)) {
+    const needed = TRANSMITTER_NATIVE_THRESHOLD.sub(nativeBalance);
     console.log("Depositing native for transmitter...");
     const tx = await getWatcherSigner().sendTransaction({
       to: transmitter,
-      value: TRANSMITTER_NATIVE_THRESHOLD,
+      value: needed,
       ...(await overrides(EVMX_CHAIN_ID as ChainSlug)),
     });
contracts/protocol/SocketBatcher.sol (1)

92-104: Don’t call syncOut when execute fails.

This should only advance batch state after a successful execute; calling syncOut on failure can desync chains.

Apply:

-        (bool success, bytes memory returnData) = socket__.execute{value: msg.value}(
+        (bool success, bytes memory returnData) = socket__.execute{value: msg.value}(
             execParams_.executeParams,
             TransmissionParams({
                 transmitterProof: execParams_.transmitterSignature,
                 socketFees: 0,
                 extraData: execParams_.executeParams.extraData,
                 refundAddress: execParams_.refundAddress
             })
         );
-
-        ICCTPSwitchboard(switchboard).syncOut(payloadId, cctpParams_.nextBatchRemoteChainSlugs);
-        return (success, returnData);
+        if (success) {
+            ICCTPSwitchboard(switchboard).syncOut(payloadId, cctpParams_.nextBatchRemoteChainSlugs);
+        }
+        return (success, returnData);
contracts/evmx/AuctionManager.sol (1)

165-169: Fix inverted OPEN check in _startAuction().

This currently reverts unless status is already OPEN. It should allow NOT_STARTED/RESTARTED and then mark OPEN.

-    function _startAuction(uint40 requestCount_) internal {
-        if (auctionStatus[requestCount_] != AuctionStatus.OPEN) revert AuctionNotOpen();
-        auctionStatus[requestCount_] = AuctionStatus.OPEN;
-        emit AuctionStarted(requestCount_);
-    }
+    function _startAuction(uint40 requestCount_) internal {
+        if (
+            auctionStatus[requestCount_] != AuctionStatus.NOT_STARTED &&
+            auctionStatus[requestCount_] != AuctionStatus.RESTARTED
+        ) revert AuctionNotOpen();
+        auctionStatus[requestCount_] = AuctionStatus.OPEN;
+        emit AuctionStarted(requestCount_);
+    }
test/SetupTest.t.sol (1)

1123-1128: ABI decode type mismatch for precompileData (6th tuple item).

You decode the 6th field as address, elsewhere as uint64. Mismatch will revert at runtime.

-                (address, Transaction, WriteFinality, uint256, uint256, address)
+                (address, Transaction, WriteFinality, uint256, uint256, uint64)
contracts/evmx/fees/Credit.sol (2)

241-251: isCreditSpendable ignores allowance size; only checks non-zero

This returns true for any non-zero allowance, then transferFrom can revert. It should require allowance(consumeFrom_, spender_) >= amount_ for non-watcher spenders. Also use available balance, not overridden balanceOf.

- if (!_isWatcher(spender_) && consumeFrom_ != spender_) {
-     if (allowance(consumeFrom_, spender_) == 0) return false;
- }
- return balanceOf(consumeFrom_) >= amount_;
+ if (!_isWatcher(spender_) && consumeFrom_ != spender_) {
+     if (allowance(consumeFrom_, spender_) < amount_) return false;
+ }
+ return availableBalanceOf(consumeFrom_) >= amount_;

329-352: withdrawCredits doesn’t lock maxFees_; race enables fee-evading spends

You require available >= credits_ + maxFees_ but only burn credits_. Without locking maxFees_, the caller can spend those before processing. This should block maxFees_ from the user, track it by request, and unblock/consume on settle/revert.

Suggested approach:

  • On enqueue: userBlockedCredits[consumeFrom] += maxFees_; requestBlockedCredits[requestId] = maxFees_;
  • On settle success: burn or transfer the blocked fees; on revert: userBlockedCredits[consumeFrom] -= requestBlockedCredits[requestId];
  • Emit events on block/unblock.
    Update _createRequest to return/capture requestId (or emit and react) so you can map blocked funds to the request.
♻️ Duplicate comments (6)
contracts/evmx/watcher/precompiles/WritePrecompile.sol (3)

306-311: Add tests that assert Solana payload packing order/discriminator/programId/accounts.

Digest integrity depends on exact packing. Tests should compute a reference digest and compare.

  • Assert order: programId || accounts[] || discriminator || borsh(args).
  • Include at least one multi-account, multi-arg case.

300-331: Remove hardcoded Solana socket; resolve from configuration and assert non-zero.

Hardcoding breaks correctness and upgrades. Use configured socket and revert if zero.

-        // bytes32 of Solana Socket address : 9vFEQ5e3xf4eo17WttfqmXmnqN3gUicrhFGppmmNwyqV
-        bytes32 hardcodedSocket = 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c;
-        return
-            DigestParams(
-                // watcherPrecompileConfig__.sockets(params_.payloadHeader.getChainSlug()), // TODO: this does not work, for some reason it returns 0x000.... address
-                hardcodedSocket,
+        bytes32 socket_ = configurations__().sockets(transaction_.chainSlug);
+        require(socket_ != bytes32(0), "socket not configured for chain");
+        return
+            DigestParams(
+                socket_,
                 toBytes32Format(transmitter_),
                 payloadParams_.payloadId,
                 deadline_,
                 payloadParams_.callType,
                 gasLimit_,
                 value_,
                 payloadPacked,
                 transaction_.target,
                 toBytes32Format(appGateway_),
                 prevBatchDigestHash_,
                 bytes("")
             );

263-288: Require a configured socket (non-zero) in EVM path to avoid corrupt digests.

Digest must include the real configured socket. A zero socket produces unverifiable digests.

-        return
-            DigestParams(
-                configurations__().sockets(transaction_.chainSlug),
+        bytes32 socket_ = configurations__().sockets(transaction_.chainSlug);
+        require(socket_ != bytes32(0), "socket not configured for chain");
+        return
+            DigestParams(
+                socket_,
                 toBytes32Format(transmitter_),
                 payloadParams_.payloadId,
                 deadline_,
                 payloadParams_.callType,
                 gasLimit_,
                 value_,
                 transaction_.payload,
                 transaction_.target,
                 toBytes32Format(appGateway_),
                 prevBatchDigestHash_,
                 bytes("")
             );
hardhat-scripts/deploy/3.configureChains.ts (1)

187-208: Normalize SWITCHBOARD_SOLANA and fix missing import; compare/set with the same canonical value.

This block reads the env unsafely, logs a misleading “reversed” hex, passes a raw string to requiredValue, and uses toBytes32Format without importing it. This should: (1) guard the env read, (2) normalize to a 0x-prefixed 32-byte hex once, (3) use that normalized value for both compare and setter args, (4) log the normalized hex, and (5) import toBytes32Format.

Apply:

@@
-import {
+import {
   DeployParams,
   getAddresses,
   getInstance,
   getSocketSigner,
   getWatcherSigner,
   overrides,
   storeAddresses,
   toBytes32FormatHexString,
+  toBytes32Format,
   updateContractSettings,
 } from "../utils";
@@
-  console.log("XXX Setting solana switchboard");
-  console.log("FAST_SWITCHBOARD_TYPE: ", FAST_SWITCHBOARD_TYPE);
-  const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA;
-  if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set");
-  console.log(
-    "solanaSwitchboard as bytes32 reversed: ",
-    Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")
-  );
+  console.log("Setting solana switchboard");
+  console.log("FAST_SWITCHBOARD_TYPE:", FAST_SWITCHBOARD_TYPE);
+  const solanaSwitchboardRaw = process.env.SWITCHBOARD_SOLANA;
+  if (!solanaSwitchboardRaw) throw new Error("SWITCHBOARD_SOLANA is not set");
+  const solanaSwitchboardHex = toBytes32FormatHexString(solanaSwitchboardRaw);
+  console.log("solanaSwitchboard (bytes32):", solanaSwitchboardHex);
   await updateContractSettings(
     EVMX_CHAIN_ID,
     Contracts.Configurations,
     "switchboards",
     [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE],
-    solanaSwitchboard,
+    solanaSwitchboardHex,
     "setSwitchboard",
     [
       ChainSlug.SOLANA_DEVNET,
       FAST_SWITCHBOARD_TYPE,
-      toBytes32Format(solanaSwitchboard),
+      toBytes32Format(solanaSwitchboardHex),
     ],
     signer
   );
hardhat-scripts/deploy/6.connect.ts (2)

158-189: Unsafe env read and inconsistent hex formatting for Solana POC.

.slice(2) before null-check can throw; manual Buffer concatenation produces non-normalized hex; align with bytes32 helpers.

-//TODO:GW: This is a temporary workaround for th Solana POC
+// TODO:GW: Temporary workaround for the Solana POC
@@
-const appGatewayAddress = process.env.APP_GATEWAY;
-if (!appGatewayAddress) throw new Error("APP_GATEWAY is not set");
-const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA!.slice(2); // remove 0x prefix for Buffer from conversion
-if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set");
-
-const solanaSwitchboardBytes32 = Buffer.from(solanaSwitchboard, "hex");
-const solanaAppGatewayId = ethers.utils.hexZeroPad(appGatewayAddress, 32);
+const appGatewayAddress = process.env.APP_GATEWAY;
+if (!appGatewayAddress) throw new Error("APP_GATEWAY is not set");
+const solanaSwitchboardRaw = process.env.SWITCHBOARD_SOLANA;
+if (!solanaSwitchboardRaw) throw new Error("SWITCHBOARD_SOLANA is not set");
+const solanaSwitchboardBytes32Hex = toBytes32FormatHexString(solanaSwitchboardRaw);
+const solanaAppGatewayId = toBytes32FormatHexString(appGatewayAddress);
@@
-console.log("SolanaAppGatewayId: ", solanaAppGatewayId);
-console.log(
-  "SolanaSwitchboardBytes32: ",
-  solanaSwitchboardBytes32.toString("hex")
-);
+console.log("SolanaAppGatewayId:", solanaAppGatewayId);
+console.log("SolanaSwitchboardBytes32:", solanaSwitchboardBytes32Hex);
@@
-  plugConfig: {
-    appGatewayId: solanaAppGatewayId,
-    switchboard: "0x" + solanaSwitchboardBytes32.toString("hex"),
-  },
+  plugConfig: {
+    appGatewayId: solanaAppGatewayId,
+    switchboard: solanaSwitchboardBytes32Hex,
+  },

1-1: ethers is used but not imported.

ethers.utils.hexZeroPad is referenced later; this will crash at runtime/compile. Either import ethers or replace with the existing bytes32 helper.

Fix by avoiding ethers:

-const solanaAppGatewayId = ethers.utils.hexZeroPad(appGatewayAddress, 32);
+const solanaAppGatewayId = toBytes32FormatHexString(appGatewayAddress);

Comment on lines 266 to 267
IERC20(address(feesManager__())).transferFrom(from_, to_, watcherFees);
}
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

Revisit fee movement semantics in deductScheduleFees().

Switching to IERC20(address(feesManager__())).transferFrom(from_, to_, watcherFees) moves credits to to_ (often address(this) or the same transmitter), not to the watcher/escrow. This should reserve/pay schedule fees in the same place the watcher expects (previously via feesManager__().transferCredits(...)). As-is, fees risk sitting on this contract and not being consumed.

Run to trace fee flow and confirm no stranded balances:

Expected: either (a) watcher consumes from AuctionManager balance, or (b) credits are moved to a watcher-owned escrow. If neither, revert to a transferCredits-style reservation or route to_ to the correct escrow.


🏁 Script executed:

#!/bin/bash
rg -nP -C3 'deductScheduleFees\(|transferCredits\(|feesManager__\(\).*transferFrom\(|getPrecompileFees\('
rg -nP -C3 '\bwatcher__\(\).*consume|withdraw|redeem|settle|collect'
rg -nP -C3 '\bFeesManager\b|\bIFeesManager\b|\bsusdc\b'

Length of output: 71720


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== AuctionManager.deductScheduleFees (context) ==="
nl -ba contracts/evmx/AuctionManager.sol | sed -n '250,276p'

echo
echo "=== RequestHandler - fee collection (around transferFrom) ==="
nl -ba contracts/evmx/watcher/RequestHandler.sol | sed -n '316,330p'

echo
echo "=== RequestHandler._settleRequest (context) ==="
nl -ba contracts/evmx/watcher/RequestHandler.sol | sed -n '412,428p'

echo
echo "=== Search for unblock/block/assign functions in fees/ directory ==="
rg -n 'unblockAndAssignCredits|blockAndAssignCredits|blockCredits|assignCredits|unblockCredits|consumeCredits|transferCredits' contracts/evmx -S || true

echo
echo "=== Show FeesManager.sol head (to inspect token-like behavior) ==="
nl -ba contracts/evmx/fees/FeesManager.sol | sed -n '1,260p'

echo
echo "=== Show Credit.sol head (mint/burn/withdraw semantics) ==="
nl -ba contracts/evmx/fees/Credit.sol | sed -n '1,420p'

Length of output: 92


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== AuctionManager.deductScheduleFees (lines 256-272) ==="
sed -n '256,272p' contracts/evmx/AuctionManager.sol || true

echo
echo "=== RequestHandler - fee collection (lines 316-330) ==="
sed -n '316,330p' contracts/evmx/watcher/RequestHandler.sol || true

echo
echo "=== RequestHandler._settleRequest (lines 412-428) ==="
sed -n '412,428p' contracts/evmx/watcher/RequestHandler.sol || true

echo
echo "=== Search for unblockAndAssignCredits definition ==="
rg -n 'unblockAndAssignCredits' -S || true

echo
echo "=== Show FeesManager.sol (first 420 lines) ==="
sed -n '1,420p' contracts/evmx/fees/FeesManager.sol || true

echo
echo "=== Show Credit.sol (first 420 lines) ==="
sed -n '1,420p' contracts/evmx/fees/Credit.sol || true

echo
echo "=== Show IFeesManager.sol ==="
sed -n '1,240p' contracts/evmx/interfaces/IFeesManager.sol || true

Length of output: 24720


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find queue definitions ==="
rg -n "function queue\(" -S || true

echo
echo "=== Print Watcher.queue implementation ==="
sed -n '1,320p' contracts/evmx/watcher/Watcher.sol | sed -n '1,320p' || true

Length of output: 9495


Critical: preserve fee reservation semantics — don't transfer sUSDC into AuctionManager; reserve via FeesManager/RequestHandler.

deductScheduleFees currently does IERC20(address(feesManager__())).transferFrom(from_, to_, watcherFees) which moves credits to to_ (often address(this) or the transmitter) instead of reserving them via FeesManager.blockCredits; RequestHandler later calls unblockAndAssignCredits which reads requestBlockedCredits (but blockCredits isn't being used) — fees can be left stranded on contracts and never consumed.

  • This should NOT use ERC20.transferFrom(...) to move fees into AuctionManager. Move fee reservation to the RequestHandler so FeesManager.blockCredits(requestCount, consumeFrom, amount) is called at request creation (onlyRequestHandler is enforced).
  • OR, if immediate transfer remains necessary, this should transfer to the watcher/fees-escrow that the watcher actually consumes (not to AuctionManager), and ensure unblock/assignment logic is consistent.
  • Add unit tests that assert no sUSDC remains on AuctionManager/RequestHandler after schedule/expire flows and that unblockAndAssignCredits actually assigns funds to the transmitter.

Locations: contracts/evmx/AuctionManager.sol (deductScheduleFees, ~lines 260-266); contracts/evmx/watcher/RequestHandler.sol (fee collect transferFrom ~lines 316-325 and unblockAndAssignCredits call ~412-421); contracts/evmx/fees/FeesManager.sol (blockCredits / unblockAndAssignCredits).

🤖 Prompt for AI Agents
In contracts/evmx/AuctionManager.sol around lines 266 (deductScheduleFees) the
code currently calls IERC20(address(feesManager__())).transferFrom(...), which
moves sUSDC into AuctionManager instead of reserving it; change the flow so
AuctionManager does NOT call ERC20.transferFrom here but instead delegates fee
reservation to the RequestHandler/FeesManager at request creation: remove the
transferFrom and call the RequestHandler-only entry (or emit the params) so
RequestHandler calls FeesManager.blockCredits(requestCount, consumeFrom, amount)
under the onlyRequestHandler guard; ensure FeesManager.blockCredits updates
requestBlockedCredits and unblockAndAssignCredits later consumes those blocked
credits; alternatively, if immediate token movement is required, transfer tokens
into the designated fees-escrow/watcher-escrow contract (not AuctionManager) and
wire FeesManager.unblockAndAssignCredits to read from that escrow; finally add
unit tests that assert no sUSDC balance remains on AuctionManager/RequestHandler
after schedule/expire flows and that unblockAndAssignCredits assigns funds to
the transmitter.

Comment on lines 129 to 136
function setSusdcToken(uint32 chainSlug_, bytes32 susdcToken_) external onlyOwner {
forwarderAddresses[susdcToken][chainSlug_] = asyncDeployer__().getOrDeployForwarderContract(
susdcToken_,
chainSlug_
);
_setValidPlug(true, chainSlug_, susdcToken);
emit SusdcTokenSet(chainSlug_, susdcToken_);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix setSusdcToken: state not updated; wrong key used for forwarder lookup

This writes into forwarderAddresses[susdcToken][chainSlug_] but never assigns susdcToken = susdcToken_. Calls to burn will resolve the forwarder under the old/zero key. Update state and use the freshly set key.

 function setSusdcToken(uint32 chainSlug_, bytes32 susdcToken_) external onlyOwner {
-    forwarderAddresses[susdcToken][chainSlug_] = asyncDeployer__().getOrDeployForwarderContract(
-        susdcToken_,
-        chainSlug_
-    );
-    _setValidPlug(true, chainSlug_, susdcToken);
-    emit SusdcTokenSet(chainSlug_, susdcToken_);
+    susdcToken = susdcToken_;
+    forwarderAddresses[susdcToken][chainSlug_] =
+        asyncDeployer__().getOrDeployForwarderContract(susdcToken_, chainSlug_);
+    _setValidPlug(true, chainSlug_, susdcToken);
+    emit SusdcTokenSet(chainSlug_, susdcToken_);
 }
📝 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 setSusdcToken(uint32 chainSlug_, bytes32 susdcToken_) external onlyOwner {
forwarderAddresses[susdcToken][chainSlug_] = asyncDeployer__().getOrDeployForwarderContract(
susdcToken_,
chainSlug_
);
_setValidPlug(true, chainSlug_, susdcToken);
emit SusdcTokenSet(chainSlug_, susdcToken_);
}
function setSusdcToken(uint32 chainSlug_, bytes32 susdcToken_) external onlyOwner {
susdcToken = susdcToken_;
forwarderAddresses[susdcToken][chainSlug_] = asyncDeployer__().getOrDeployForwarderContract(
susdcToken_,
chainSlug_
);
_setValidPlug(true, chainSlug_, susdcToken);
emit SusdcTokenSet(chainSlug_, susdcToken);
}
🤖 Prompt for AI Agents
In contracts/evmx/fees/Credit.sol around lines 129 to 136, the function
setSusdcToken writes the forwarder address using the old susdcToken key and
never updates the contract state; to fix it assign the state variable susdcToken
= susdcToken_ first, then store the forwarder under
forwarderAddresses[susdcToken_][chainSlug_] (or use the freshly set susdcToken)
by calling asyncDeployer__().getOrDeployForwarderContract(susdcToken_,
chainSlug_), call _setValidPlug(true, chainSlug_, susdcToken_) using the new key
(susdcToken_ or the updated state), and emit SusdcTokenSet(chainSlug_,
susdcToken_) with the updated value so subsequent burn/lookups use the correct
key.

Comment on lines 156 to 160
tokenOnChainBalances[chainSlug_][token_] += creditAmount_ + nativeAmount_;

UserCredits storage userCredit = userCredits[depositTo_];
userCredit.totalCredits += creditAmount_;
// Mint tokens to the user
_mint(depositTo_, creditAmount_);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Deposit double-mints on native withdraw failure; wrong credit math

On !success, it mints creditAmount_ again and then sets nativeAmount_ = 0 before adding it to creditAmount_ (no-op). This should mint nativeAmount_ and convert it to credits.

-    if (!success) {
-        _mint(depositTo_, creditAmount_);
-        nativeAmount_ = 0;
-        creditAmount_ += nativeAmount_;
-    }
+    if (!success) {
+        // convert native to credits instead of duplicating the credit mint
+        _mint(depositTo_, nativeAmount_);
+        creditAmount_ += nativeAmount_;
+        nativeAmount_ = 0;
+    }

Also applies to: 165-169

Comment on lines 172 to 180
if (data_.length > 0)
IReceiver(depositTo_).onTransfer(
chainSlug_,
token_,
creditAmount_,
nativeAmount_,
data_
);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

onTransfer callback can brick deposits; wrap in try/catch

A failing IReceiver.onTransfer reverts the entire deposit (even after mint). This should not brick credits. Wrap in try/catch and emit a diagnostic event.

- if (data_.length > 0)
-     IReceiver(depositTo_).onTransfer(chainSlug_, token_, creditAmount_, nativeAmount_, data_);
+ if (data_.length > 0) {
+     try IReceiver(depositTo_).onTransfer(chainSlug_, token_, creditAmount_, nativeAmount_, data_) {
+     } catch {
+         // optionally emit an event e.g., OnTransferCallbackFailed(depositTo_, chainSlug_, token_)
+     }
+ }
📝 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 (data_.length > 0)
IReceiver(depositTo_).onTransfer(
chainSlug_,
token_,
creditAmount_,
nativeAmount_,
data_
);
if (data_.length > 0) {
try IReceiver(depositTo_).onTransfer(
chainSlug_,
token_,
creditAmount_,
nativeAmount_,
data_
) {
} catch {
// optionally emit an event e.g., OnTransferCallbackFailed(depositTo_, chainSlug_, token_)
}
}
🤖 Prompt for AI Agents
In contracts/evmx/fees/Credit.sol around lines 172-180, the external call to
IReceiver(depositTo_).onTransfer(...) can revert and therefore undo the deposit;
wrap this call in a try/catch so a failing receiver does not revert the whole
function, and emit a diagnostic event in the catch with relevant details
(receiver address, chainSlug_, token_, creditAmount_, nativeAmount_, and
optionally the revert reason or bytes data) so failures are observable while
allowing the deposit to complete.

Comment on lines 220 to 223
/// @notice Override balanceOf to return available (unblocked) credits
function balanceOf(address account) public view override returns (uint256) {
return super.balanceOf(account) - userBlockedCredits[account];
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Overriding balanceOf breaks ERC-20 invariants and can underflow

Returning “available” (minus blocked) makes sum of balances != totalSupply and can underflow if userBlockedCredits[account] > super.balanceOf(account), reverting view calls. This should expose availability via a separate accessor and keep ERC-20 semantics intact.

-/// @notice Override balanceOf to return available (unblocked) credits
-function balanceOf(address account) public view override returns (uint256) {
-    return super.balanceOf(account) - userBlockedCredits[account];
-}
+/// @notice Available (unblocked) credits
+function availableBalanceOf(address account) public view returns (uint256) {
+    uint256 bal = super.balanceOf(account);
+    uint256 blocked = userBlockedCredits[account];
+    return blocked >= bal ? 0 : bal - blocked;
+}

Follow-ups within this file:

  • Use availableBalanceOf in spendability checks and preconditions.
  • Restore base ERC20.balanceOf behavior. Update tests accordingly.
📝 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
/// @notice Override balanceOf to return available (unblocked) credits
function balanceOf(address account) public view override returns (uint256) {
return super.balanceOf(account) - userBlockedCredits[account];
}
/// @notice Available (unblocked) credits
function availableBalanceOf(address account) public view returns (uint256) {
uint256 bal = super.balanceOf(account);
uint256 blocked = userBlockedCredits[account];
return blocked >= bal ? 0 : bal - blocked;
}

Comment on lines 154 to 166
function testFuzzLargeProofs(bytes32 digest, uint256 proofSize) public {
vm.assume(digest != bytes32(0));
vm.assume(proofSize >= 65 && proofSize <= 1000); // Reasonable proof sizes

// Create a large proof
bytes memory largeProof = new bytes(proofSize);
for (uint256 i = 0; i < proofSize; i++) {
largeProof[i] = bytes1(uint8(i % 256));
}

vm.expectRevert(InvalidSignature.selector);
fastSwitchboard.attest(digest, largeProof);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fuzz test is over-constrained; 65-byte proofs won’t always hit InvalidSignature.

For 65 bytes, ECDSA parsing succeeds and the revert should be WatcherNotFound. Branch by size.

-        vm.expectRevert(InvalidSignature.selector);
-        fastSwitchboard.attest(digest, largeProof);
+        if (proofSize == 65) {
+            vm.expectRevert(FastSwitchboard.WatcherNotFound.selector);
+        } else {
+            vm.expectRevert(FastSwitchboard.InvalidSignature.selector);
+        }
+        fastSwitchboard.attest(digest, largeProof);
📝 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 testFuzzLargeProofs(bytes32 digest, uint256 proofSize) public {
vm.assume(digest != bytes32(0));
vm.assume(proofSize >= 65 && proofSize <= 1000); // Reasonable proof sizes
// Create a large proof
bytes memory largeProof = new bytes(proofSize);
for (uint256 i = 0; i < proofSize; i++) {
largeProof[i] = bytes1(uint8(i % 256));
}
vm.expectRevert(InvalidSignature.selector);
fastSwitchboard.attest(digest, largeProof);
}
function testFuzzLargeProofs(bytes32 digest, uint256 proofSize) public {
vm.assume(digest != bytes32(0));
vm.assume(proofSize >= 65 && proofSize <= 1000); // Reasonable proof sizes
// Create a large proof
bytes memory largeProof = new bytes(proofSize);
for (uint256 i = 0; i < proofSize; i++) {
largeProof[i] = bytes1(uint8(i % 256));
}
if (proofSize == 65) {
vm.expectRevert(FastSwitchboard.WatcherNotFound.selector);
} else {
vm.expectRevert(FastSwitchboard.InvalidSignature.selector);
}
fastSwitchboard.attest(digest, largeProof);
}
🤖 Prompt for AI Agents
In test/protocol/switchboards/FastSwitchboardTest.t.sol around lines 154 to 166,
the fuzz test assumes all proof sizes between 65 and 1000 should revert with
InvalidSignature but a 65-byte proof parses as a valid ECDSA signature and will
instead revert with WatcherNotFound; change the assertion to branch on
proofSize: when proofSize == 65 expectRevert(WatcherNotFound.selector) (or the
WatcherNotFound error) and for all other sizes
expectRevert(InvalidSignature.selector), keeping the same proof generation and
size assumptions.

Comment on lines 164 to 166
vm.expectRevert(InvalidSignature.selector);
fastSwitchboard.attest(digest, largeProof);
}
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 the correct error type qualifier for expectRevert.

InvalidSignature must be referenced from the contract that defines it.

Option A (if FastSwitchboard declares the error):

-        vm.expectRevert(InvalidSignature.selector);
+        vm.expectRevert(FastSwitchboard.InvalidSignature.selector);

Option B (if it’s from a library, e.g., ECDSA):

-        vm.expectRevert(InvalidSignature.selector);
+        vm.expectRevert(ECDSA.InvalidSignature.selector);
📝 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
vm.expectRevert(InvalidSignature.selector);
fastSwitchboard.attest(digest, largeProof);
}
vm.expectRevert(FastSwitchboard.InvalidSignature.selector);
fastSwitchboard.attest(digest, largeProof);
}
🤖 Prompt for AI Agents
In test/protocol/switchboards/FastSwitchboardTest.t.sol around lines 164 to 166,
the expectRevert call references InvalidSignature without its defining
contract/library qualifier; update the expectRevert to use the correct scoped
error selector (e.g. FastSwitchboard.InvalidSignature.selector if the error is
declared in FastSwitchboard, or ECDSA.InvalidSignature.selector if declared in a
library), and ensure any necessary contract/library name is imported or visible
in the test scope.

Comment on lines 1 to 3
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

File will not be picked up by Foundry as a test

The filename includes a space before .sol and does not end with .t.sol. Foundry won’t run these tests.

Rename to:

  • test/protocol/switchboards/MessageSwitchboardTest.t.sol
    And delete the stray copy to avoid duplicate compilation.
🤖 Prompt for AI Agents
In test/protocol/switchboards/MessageSwitchboardTest.t copy.sol around lines
1-3, the filename contains a space and does not end with .t.sol so Foundry will
not detect it as a test; rename the file to
test/protocol/switchboards/MessageSwitchboardTest.t.sol (removing the space and
adding the .t.sol suffix) and remove the stray "copy" file to avoid duplicate
compilation artifacts.

Comment on lines 661 to 667
function approve(address appGateway_, address user_) internal {
bool approval = feesManager.isApproved(user_, appGateway_);
if (approval) return;

AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1);
approvals[0] = AppGatewayApprovals({appGateway: appGateway_, approval: true});

hoax(user_);
feesManager.approveAppGateways(approvals);
feesManager.approve(appGateway_, true);

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

Signature/order of isApproved appears inconsistent with interface.

Here you call isApproved(user_, appGateway_), but IFeesManager declares isApproved(appGateway_, user_). Confirm and standardize across codebase to avoid subtle bugs.

Run to verify implementation signature and call sites:


🏁 Script executed:

#!/bin/bash
# Check definition
rg -nP 'function\s+isApproved\s*\(' contracts --type=sol -C2
# Check call sites
rg -nP '\bisApproved\s*\(' --type=sol -C3

Length of output: 55


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)" >&2
echo "--- list first 200 .sol files ---" >&2
find . -type f -iname '*.sol' -print | sed -n '1,200p' || true

echo "--- search for IFeesManager/interface and isApproved definitions/usages ---" >&2
if command -v rg >/dev/null 2>&1; then
  rg -n --hidden --no-ignore -S -P "interface\s+IFeesManager\b" -C4 || true
  rg -n --hidden --no-ignore -S -P "contract\s+IFeesManager\b" -C4 || true
  rg -n --hidden --no-ignore -S -P "function\s+isApproved\s*\(" -C4 || true
  rg -n --hidden --no-ignore -S -P "\bisApproved\s*\(" -C4 || true
  rg -n --hidden --no-ignore -S -P "feesManager\b" -C3 || true
else
  echo "rg not available, falling back to grep" >&2
  grep -RIn --exclude-dir=node_modules -E "interface\s+IFeesManager\b" . || true
  grep -RIn --exclude-dir=node_modules -E "contract\s+IFeesManager\b" . || true
  grep -RIn --exclude-dir=node_modules -E "function\s+isApproved\s*\(" . || true
  grep -RIn --exclude-dir=node_modules -E "\bisApproved\s*\(" . || true
  grep -RIn --exclude-dir=node_modules -E "feesManager\b" . || true
fi

# show the test file snippet if present
echo "--- show test/SetupTest.t.sol around reported lines if file exists ---" >&2
file=$(find . -type f -iname 'SetupTest.t.sol' -print -quit || true)
if [ -n "$file" ]; then
  echo "Found: $file" >&2
  sed -n '640,680p' "$file" || true
else
  echo "SetupTest.t.sol not found by that name; searching for files under test/ with 'SetupTest' in name..." >&2
  find . -type f -path '*/test/*' -iname '*SetupTest*.sol' -print -exec sed -n '640,680p' {} \; || true
fi

Length of output: 39646


Standardize isApproved parameter order (interface vs implementation & callers)

IFeesManager declares isApproved(address appGateway_, address user_) (contracts/evmx/interfaces/IFeesManager.sol:47) but Credit.sol implements isApproved(address user_, address appGateway_) (contracts/evmx/fees/Credit.sol:138) and tests/scripts call isApproved(user_, appGateway_) (test/SetupTest.t.sol:661–672, 677–700; hardhat-scripts/deploy/9.setupTransmitter.ts:43–45). This should be fixed by updating the interface to isApproved(address user_, address appGateway_) (or aligning the implementation/callers to the intended order), update any ABI/TS callers, and run tests.

🤖 Prompt for AI Agents
In test/SetupTest.t.sol around lines 661 to 667, the test calls
isApproved(user_, appGateway_) but IFeesManager declares isApproved(address
appGateway_, address user_) while Credit.sol implements isApproved(address
user_, address appGateway_), causing mismatched parameter order across
interface, implementation and callers; fix by standardizing to
isApproved(address user_, address appGateway_) in the IFeesManager interface (or
alternatively change implementation/callers to match the interface), then update
any ABI/TypeScript callers (hardhat-scripts/deploy/9.setupTransmitter.ts and
other tests) to the chosen parameter order, regenerate ABIs/TS types if used,
and run the full test suite to verify.

* @dev Tests for IdUtils utility functions
*/
contract IdUtilsTest is Test {
function testCreatePayloadId() public pure {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix: Tests declared pure will not compile (assert/log/cheatcodes). Remove pure.

These tests call assertEq, fail, and vm.* which are not allowed in pure functions. This should drop pure from all test function signatures to avoid compilation errors.

Apply:

-    function testCreatePayloadId() public pure {
+    function testCreatePayloadId() public {
-    function testCreatePayloadIdWithZeroValues() public pure {
+    function testCreatePayloadIdWithZeroValues() public {
-    function testCreatePayloadIdWithMaxValues() public pure {
+    function testCreatePayloadIdWithMaxValues() public {
-    ) public pure {
+    ) public {
-    function testToBytes32Format() public pure {
+    function testToBytes32Format() public {
-    function testToBytes32FormatWithZeroAddress() public pure {
+    function testToBytes32FormatWithZeroAddress() public {
-    function testFromBytes32Format() public pure {
+    function testFromBytes32Format() public {
-    function testFromBytes32FormatWithZeroAddress() public pure {
+    function testFromBytes32FormatWithZeroAddress() public {
-    function testFromBytes32FormatWithMaxValidAddress() public pure {
+    function testFromBytes32FormatWithMaxValidAddress() public {
-    function testConvertersRoundTrip() public pure {
+    function testConvertersRoundTrip() public {
-    function testConvertersFuzz(address addr) public pure {
+    function testConvertersFuzz(address addr) public {

Also applies to: 30-30, 36-36, 57-57, 76-76, 83-83, 89-89, 98-98, 122-122, 131-131, 140-140

🤖 Prompt for AI Agents
In test/Utils.t.sol around lines 13, 30, 36, 57, 76, 83, 89, 98, 122, 131, and
140, the test functions are declared `pure` but call assertions, logs, or vm
cheatcodes which are not allowed in `pure` functions; remove the `pure` keyword
from each affected function signature so they are non-pure (e.g., change
`function testX() public pure` to `function testX() public`) to allow
assertions, fail(), and vm.* calls to compile and run.

@gwalen gwalen changed the base branch from dev to fees-deposit-hook September 27, 2025 07:20
@gwalen gwalen closed this Sep 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant