Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Offchain security review · July 2026

Console EVM Updates

for LayerZero

Guardian's review of Console EVM Updates for LayerZero, published July 2026. The report records 4 findings, including 1 low and 3 informational.

Published
Review window
July 16 to 21, 2026
Language
Solidity
Chains
Ethereum, Solana, Stellar, Canton
Sector
Cross-chain
  • 0 Critical
  • 0 High
  • 0 Medium
  • 1 Low
  • 3 Informational

3 resolved · 1 acknowledged

Scope

67 files in scope · 3,013 nSLOC
FilenSLOCLines
apps/oapp-app/contracts/evm/non-upgradeable-next/contracts/oapp/libs/OptionsBuilder.sol74181
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/alt/OAppAltUpgradeable.sol2048
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/messaging-channel/OAppMessagingChannelBaseUpgradeable.sol1976
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/messaging-channel/OAppMessagingChannelRBACUpgradeable.sol2770
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/msg-inspection/OAppMsgInspectionBaseUpgradeable.sol2667
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/msg-inspection/OAppMsgInspectionRBACUpgradeable.sol1134
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/OAppCoreBaseUpgradeable.sol47114
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/OAppCoreRBACUpgradeable.sol1971
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/OAppReceiverUpgradeable.sol26118
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/OAppSenderUpgradeable.sol44130
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/OAppUpgradeable.sol1245
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/options-type-3/OAppOptionsType3BaseUpgradeable.sol50122
apps/oapp-app/contracts/evm/upgradeable-next/contracts/oapp/options-type-3/OAppOptionsType3RBACUpgradeable.sol1134
apps/oft-app/contracts/evm/non-upgradeable-next/contracts/libs/OFTComposeMsgCodec.sol3185
apps/oft-app/contracts/evm/non-upgradeable-next/contracts/libs/OFTMsgCodec.sol2978
apps/oft-app/contracts/evm/non-upgradeable-next/contracts/utils/OFTDecimalUtils.sol3388
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/alt/OFTBurnMintExtendedRBACAltUpgradeable.sol2852
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/alt/OFTBurnSelfMintExtendedRBACAltUpgradeable.sol2650
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/alt/OFTLockUnlockExtendedRBACAltUpgradeable.sol1433
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/OFTBurnMintExtendedRBACUpgradeable.sol63162
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/OFTBurnSelfMintExtendedRBACUpgradeable.sol3477
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/OFTCoreExtendedRBACUpgradeable.sol96170
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/OFTLockUnlockExtendedRBACUpgradeable.sol4393
apps/oft-app/contracts/evm/upgradeable-next/contracts/extended/OFTNativeExtendedRBACUpgradeable.sol57134
apps/oft-app/contracts/evm/upgradeable-next/contracts/oft/OFTCoreBaseUpgradeable.sol122344
apps/oft-app/contracts/evm/upgradeable-next/contracts/oft/OFTCoreRBACUpgradeable.sol2152
apps/project-types/console-oft-next-app/contracts/evm/contracts/alt/OFTBurnMintAlt.sol2333
apps/project-types/console-oft-next-app/contracts/evm/contracts/alt/OFTBurnSelfMintAlt.sol2131
apps/project-types/console-oft-next-app/contracts/evm/contracts/alt/OFTLockUnlockAlt.sol919
apps/project-types/console-oft-next-app/contracts/evm/contracts/ERC20Plus.sol49118
apps/project-types/console-oft-next-app/contracts/evm/contracts/OFTBurnMint.sol2345
apps/project-types/console-oft-next-app/contracts/evm/contracts/OFTBurnSelfMint.sol2145
apps/project-types/console-oft-next-app/contracts/evm/contracts/OFTLockUnlock.sol919
apps/project-types/console-oft-next-app/contracts/evm/contracts/OFTNative.sol919
apps/project-types/nexus-next-app/contracts/evm/contracts/extensions/OFTRegistryBaseUpgradeable.sol89189
apps/project-types/nexus-next-app/contracts/evm/contracts/extensions/OFTRegistryRBACUpgradeable.sol1650
apps/project-types/nexus-next-app/contracts/evm/contracts/extensions/TokenScalesBaseUpgradeable.sol41107
apps/project-types/nexus-next-app/contracts/evm/contracts/libs/NexusMsgCodec.sol35111
apps/project-types/nexus-next-app/contracts/evm/contracts/modules/NexusFeeConfigModule.sol83172
apps/project-types/nexus-next-app/contracts/evm/contracts/modules/NexusModule.sol3492
apps/project-types/nexus-next-app/contracts/evm/contracts/modules/NexusPauseModule.sol98197
apps/project-types/nexus-next-app/contracts/evm/contracts/modules/NexusRateLimiterModule.sol54121
apps/project-types/nexus-next-app/contracts/evm/contracts/Nexus.sol274574
apps/project-types/nexus-next-app/contracts/evm/contracts/NexusAlt.sol1746
apps/project-types/nexus-next-app/contracts/evm/contracts/NexusERC20.sol74168
apps/project-types/nexus-next-app/contracts/evm/contracts/NexusERC20Guard.sol2247
apps/project-types/nexus-next-app/contracts/evm/contracts/NexusOFT.sol80167
apps/project-types/nexus-next-app/contracts/evm/contracts/NexusOFTAlt.sol3873
apps/proxy-app/contracts/evm/proxy-v5-next-evm/contracts/ProxyAdmin2Step.sol1739
apps/proxy-app/contracts/evm/proxy-v5-next-evm/contracts/TransparentUpgradeableProxy2Step.sol31109
contracts/common/utils/evm/upgradeable-next/contracts/access/AccessControl2StepUpgradeable.sol68144
contracts/common/utils/evm/upgradeable-next/contracts/allowlist/AllowlistBaseUpgradeable.sol94184
contracts/common/utils/evm/upgradeable-next/contracts/allowlist/AllowlistRBACUpgradeable.sol1955
contracts/common/utils/evm/upgradeable-next/contracts/credit-redirect/CreditRedirectBaseUpgradeable.sol45103
contracts/common/utils/evm/upgradeable-next/contracts/credit-redirect/CreditRedirectRBACUpgradeable.sol1135
contracts/common/utils/evm/upgradeable-next/contracts/fee-accounting/FeeHandlerBaseUpgradeable.sol2969
contracts/common/utils/evm/upgradeable-next/contracts/fee-accounting/FeeHandlerRBACUpgradeable.sol1134
contracts/common/utils/evm/upgradeable-next/contracts/fee-config/FeeConfigBaseUpgradeable.sol54125
contracts/common/utils/evm/upgradeable-next/contracts/fee-config/FeeConfigRBACUpgradeable.sol1544
contracts/common/utils/evm/upgradeable-next/contracts/libs/EnumerableSetPagination.sol3669
contracts/common/utils/evm/upgradeable-next/contracts/pause-by-id/PauseByIDBaseUpgradeable.sol55127
contracts/common/utils/evm/upgradeable-next/contracts/pause-by-id/PauseByIDRBACUpgradeable.sol3074
contracts/common/utils/evm/upgradeable-next/contracts/pause/PauseBaseUpgradeable.sol43107
contracts/common/utils/evm/upgradeable-next/contracts/pause/PauseRBACUpgradeable.sol1647
contracts/common/utils/evm/upgradeable-next/contracts/rate-limiter/libs/RateLimiterUtils.sol3670
contracts/common/utils/evm/upgradeable-next/contracts/rate-limiter/RateLimiterBaseUpgradeable.sol272590
contracts/common/utils/evm/upgradeable-next/contracts/rate-limiter/RateLimiterRBACUpgradeable.sol2981

Findings 4

  1. L-01 Low OFT interface ID omits receiver functions Unexpected Behavior Resolved
    Location
    apps/oapp-app/contracts/evm/non-upgradeable-next/contracts/interfaces/IOAppExtended.sol:23-28

    Description

    OAPP_EXTENDED_INTERFACE_ID is intended to flatten the complete OApp interface tree. However, it includes type(IOAppReceiver).interfaceId without separately including its parent, ILayerZeroReceiver. Solidity calculates an interface ID using only functions declared directly by that interface. Consequently, type(IOAppReceiver).interfaceId includes isComposeMsgSender but excludes the inherited allowInitializePath, nextNonce, and lzReceive functions.

    /// @dev Flatten all leaf interface IDs.
    bytes4 constant OAPP_EXTENDED_INTERFACE_ID = type(IOAppReceiver).interfaceId ^
        type(IOAppMessagingChannel).interfaceId ^
        type(IOAppCore).interfaceId ^
        type(IOAppOptionsType3).interfaceId ^
        type(IOAppMsgInspection).interfaceId;
    

    The incomplete OApp ID propagates into OFT_EXTENDED_INTERFACE_ID. The contract currently returns 0x97b6b900, whereas the complete flattened interface ID is 0x06fbb406. The difference is exactly the omitted ILayerZeroReceiver interface ID. Integrations that independently calculate the identifier from the complete ABI may reject or misidentify compatible OFT deployments.

    Recommendation

    Include type(ILayerZeroReceiver).interfaceId when calculating OAPP_EXTENDED_INTERFACE_ID.

  2. I-01 Informational Divergent guards can block Nexus credits Unexpected Behavior Acknowledged
    Location
    https://github.com/GuardianOrg/layerzero-console-oft-nexus-next-audit-scope-2026-07-15-team1-1784123035999/blob/6113ab299c04457e8fcc539e6214ba6b218c34c2/contracts/common/utils/evm/upgradeable-next/contracts/credit-redirect/CreditRedirectBaseUpgradeable.sol#L63-L84 https://github.com/GuardianOrg/layerzero-console-oft-nexus-next-audit-scope-2026-07-15-team1-1784123035999/blob/6113ab299c04457e8fcc539e6214ba6b218c34c2/apps/project-types/nexus-next-app/contracts/evm/contracts/Nexus.sol#L465-L480 https://github.com/GuardianOrg/layerzero-console-oft-nexus-next-audit-scope-2026-07-15-team1-1784123035999/blob/6113ab299c04457e8fcc539e6214ba6b218c34c2/apps/project-types/nexus-next-app/contracts/evm/contracts/NexusERC20.sol#L82-L114

    Description

    Nexus processes inbound transfers for multiple registered tokens using a single global CreditRedirectStorage configuration. However, each registered NexusERC20 stores an independent guard pointer that its administrator can replace through setGuard. When an inbound message is executed, Nexus first determines the effective recipient using the global allowlist.

            if (!_isAllowlisted(_to)) {
                /// @dev `escrow` is guaranteed to be non-zero here.
                address escrow = _getCreditRedirectStorage().escrow;
                emit CreditRedirected(_to, escrow, _amountLD);
                return escrow;
            }
            return _to;
    

    If the global allowlist considers the intended recipient eligible, _redirectCredit returns the original recipient instead of the escrow address. Next, Nexus invokes the registered NexusERC20’s mint function. Before minting, NexusERC20 performs another validation using that token’s current guard. If the global allowlist and the token’s current guard contain different policies, the global allowlist may accept a recipient that the token guard rejects. In that case, Nexus does not redirect the credit to escrow because _redirectCredit returns the original recipient, but the subsequent guard check reverts the mint. Consequently, the sender’s tokens have already been burned on the source chain and remain temporarily unavailable while the destination message is pending.

    Although the intended deployment uses one shared guard for all Nexus tokens, this relationship is not enforced during token registration, credit redirect configuration, or subsequent guard replacement.

    Recommendation

    Consider enforcing that Nexus’s credit-redirect allowlist remains compatible with every registered NexusERC20 guard.

  3. I-02 Informational Empty init data leaves proxy unprotected Informational Resolved
    Location
    apps/proxy-app/contracts/evm/proxy-v5-next-evm/contracts/TransparentUpgradeableProxy2Step.sol:70-74

    Description

    TransparentUpgradeableProxy2Step forwards the caller-provided initialization data to the inherited ERC1967Proxy constructor. The pinned OpenZeppelin version permits this data to be empty, in which case the implementation is installed without initializing the proxy’s storage. If the implementation exposes an external initializer, it remains callable through the proxy. Because non-admin calls are delegated to the implementation, anyone can invoke the initializer before the legitimate administrator and assign themselves application ownership, roles, or other privileged configuration.

    Recommendation

    Require the proxy to be initialized atomically during deployment using the expected encoded initializer call. Consider upgrading to OpenZeppelin Contracts v5.6 or later, which rejects empty initialization data by default.

  4. I-03 Informational Burn docs omit nonce eligibility Documentation Resolved
    Location
    apps/oapp-app/contracts/evm/non-upgradeable-next/contracts/interfaces/IOAppMessagingChannel.sol:31-38

    Description

    The interface describes burn as removing a verified inbound nonce, implying that any verified packet can be burned with the correct payload hash. However, the Endpoint additionally requires the nonce to be at or below lazyInboundNonce.

            if (curPayloadHash == EMPTY_PAYLOAD_HASH || _nonce > lazyInboundNonce[_oapp][_srcEid][_sender])
                revert Errors.LZ_InvalidNonce(_nonce);
    

    Because verification does not advance lazyInboundNonce, burning a freshly verified packet will revert while its nonce remains above the lazy checkpoint. The incomplete documentation may cause operators to submit reverting recovery transactions or misunderstand when burn is applicable.

    Recommendation

    Document that burn requires an exact, nonempty stored payload hash and _nonce <= lazyInboundNonce.

More from LayerZero

All 7 reports
  1. Canton VER Updates

    169 findings2 critical · 12 high 169 findings: 2 critical, 12 high, 36 medium, 54 low, 65 informational
  2. Solana Console

    42 findings 42 findings: 6 low, 36 informational
  3. Solana OApp

    54 findings 54 findings: 1 medium, 9 low, 44 informational
  4. OneSig on Stellar

    7 findings 7 findings: 4 low, 3 informational

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote