The whole request

The home-chain end of a LayerZero V2 bridge for the existing ERC-20 Zero To One (ZTO) at 0xd782bdea4ef02a0bd391eb9089470c8080f0a68e on Ethereum: an OFT adapter that locks ZTO on send and releases it on receive. LayerZero EndpointV2 on this chain: 0x1a44076050125825900e736c501f859c50fE728c (endpoint id 30101). It will be paired with one OFT on Robinhood (endpoint id 30416).

The owner and LayerZero delegate is 0xcecc29b037f5064fcdf45a5c318f132ef76aa551, set in the constructor; only the owner can set a peer. Use LayerZero V2 OApp/OFT semantics with 6 shared decimals. Put the LayerZero code the contract needs under src/ and add nothing under lib/.

No fees, no pause, no upgrade path, no rescue function. The constructor must not revert when the endpoint and token addresses hold no code, because the launch verifier's protected-invariant harness deploys the contract in a fresh EVM before any test can place mocks there (an earlier build was parked with "application constructor failed" in setUp for exactly this).

So the constructor makes no unguarded external call: register the LayerZero delegate only when the endpoint address has code, and expose an owner-only setDelegate for the other case; never read decimals from another contract in the constructor (the token has 18 decimals, write that as a constant). On the real chain the delegate must still be set by the constructor, and a test must prove both cases.

Published · Contracts

app
ZTOAdapter 0x68d443f419064b11cc427b9b72faa6c1cedf6378
github
identity-md-launches/launch-778-home-chain-end-layerzero-v2

Work

  1. Posted11 minto the first attempt
  2. Build contract projectAgent #113786 files changed

    Implemented ZTOAdapter with lock/release behavior, six shared decimals, owner-only Robinhood peer configuration, and both constructor cases tested.

    LayerZero dependencies are vendored under src/; nothing was added under lib/.

    Validation passed: forge build, forge test (39 tests, including 256 fuzz runs), and forge fmt --check.

    Documentation covers assumptions, deployment, and operations. Robinhood peer and messaging configuration remain deployment responsibilities.

    ran oncodex · gpt-6-astra · 6 turns · 10m 33s · 85.6K in · 27.3K out · 858.1K cached
    submissione458e88f6b733f3ce949467b3a0226fb53f5eb6e2742f7a580b57dd4d6f2d4eb
    device393ade31b99d5d5b676eaf16a3b75da1ab480964562b9cfec1a37e8d212268ba
    started from0243d7da4a4337ae8b16bcdf15bb4ead736fd68f
    bundle1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb · 145 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 86 files
    .gitignoreDEPENDENCIES.jsonREADME.mddocs/SECURITY.mddocs/VENDORED.mdfoundry.tomlsrc/ZTOAdapter.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/LICENSE-LZBL-1.2src/vendor/@layerzerolabs/lz-evm-protocol-v2/LICENSE-MITsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.solsrc/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.solsrc/vendor/@layerzerolabs/oapp-evm/LICENSE-MITsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OApp.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppMsgInspector.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IOAppPreCrimeSimulator.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.solsrc/vendor/@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.solsrc/vendor/@layerzerolabs/oft-evm/LICENSE-MITsrc/vendor/@layerzerolabs/oft-evm/contracts/OFT.solsrc/vendor/@layerzerolabs/oft-evm/contracts/OFTAdapter.solsrc/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.solsrc/vendor/@layerzerolabs/oft-evm/contracts/interfaces/IOFT.solsrc/vendor/@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.solsrc/vendor/@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.solsrc/vendor/@openzeppelin/contracts/LICENSEsrc/vendor/@openzeppelin/contracts/access/Ownable.solsrc/vendor/@openzeppelin/contracts/interfaces/draft-IERC6093.solsrc/vendor/@openzeppelin/contracts/token/ERC20/ERC20.solsrc/vendor/@openzeppelin/contracts/token/ERC20/IERC20.solsrc/vendor/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.solsrc/vendor/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.solsrc/vendor/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.solsrc/vendor/@openzeppelin/contracts/utils/Address.solsrc/vendor/@openzeppelin/contracts/utils/Context.solsrc/vendor/@openzeppelin/contracts/utils/introspection/IERC165.soltest/ZTOAdapter.t.soltest/ZTOAdapterDeployment.t.soltest/mocks/EndpointV2Mock.soltest/mocks/MockZTO.soltest/mocks/RobinhoodOFTMock.soltest/vendor/forge-std/Base.soltest/vendor/forge-std/LICENSE-APACHEtest/vendor/forge-std/LICENSE-MITtest/vendor/forge-std/Script.soltest/vendor/forge-std/StdAssertions.soltest/vendor/forge-std/StdChains.soltest/vendor/forge-std/StdCheats.soltest/vendor/forge-std/StdConstants.soltest/vendor/forge-std/StdError.soltest/vendor/forge-std/StdInvariant.soltest/vendor/forge-std/StdJson.soltest/vendor/forge-std/StdMath.soltest/vendor/forge-std/StdStorage.soltest/vendor/forge-std/StdStyle.soltest/vendor/forge-std/StdToml.soltest/vendor/forge-std/StdUtils.soltest/vendor/forge-std/Test.soltest/vendor/forge-std/Vm.soltest/vendor/forge-std/console.soltest/vendor/forge-std/console2.soltest/vendor/forge-std/interfaces/IERC1155.soltest/vendor/forge-std/interfaces/IERC165.soltest/vendor/forge-std/interfaces/IERC20.soltest/vendor/forge-std/interfaces/IERC4626.soltest/vendor/forge-std/interfaces/IERC6909.soltest/vendor/forge-std/interfaces/IERC721.soltest/vendor/forge-std/interfaces/IERC7540.soltest/vendor/forge-std/interfaces/IERC7575.soltest/vendor/forge-std/interfaces/IMulticall3.soltest/vendor/forge-std/safeconsole.sol
  3. ManifestAgent #17011 file changed

    Created launch.json for ZTOAdapter with no constructor arguments, matching the accepted implementation.

    Schema and ABI checks passed. forge build succeeded; all 40 tests passed, including the protected deployment check and both endpoint-code cases.

    Only launch.json is included in the submission changes.

    ran oncodex · gpt-6-astra · 3 turns · 1m 27s · 20.9K in · 3K out · 177.9K cached
    submission054d85e17fd5b0e92e8c3033dcc51b3857b26f1f23c9f40c2dde8c54ea889825
    device54f31a82977e83fd994796c9d1f646ef46c7a7b5936143e1d9c52b6ad973405e
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundle214d0d647a271128b0ed30a61b77cdc65c233bdd9a73e4fedf1fdfb1b50d8329 · 146 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    changed · 1 file
    launch.json
    may write
    launch.json
  4. Write foundry testsAgent #6252 files changed

    Added failure-path, boundary, fuzz, and multi-actor custody invariant tests under test/.

    Validation passed offline: forge build and all 53 tests, including 32,768 random invariant calls with no unexpected reverts.

    No reproducible contract defects found. Tests use local token and endpoint mocks.

    ran oncodex · gpt-6-astra · 6 turns · 5m 44s · 73K in · 16.4K out · 762.4K cached
    submission397265d1855b63f421c1cb1c96efd8b9f8b43a44f069082f7891675fb6c9cc44
    device4fcac33561ba733dbf599f84ceb3e26439db174cc13b33fe91b78df7af0e0e3e
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundle30377091cb7ad02419d7a50c051053d6cb12627e094532e0458ab810e2d1077f · 152 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    changed · 2 files
    test/ZTOAdapter.t.soltest/ZTOAdapterInvariant.t.sol
    may write
    testtest/**
  5. Audit mathAgent #595found 1 low, 2 info

    The review is complete. The findings file is written and valid, the scratch test directory is removed, and the working tree holds no new files beyond the ignored findings file and build artefacts.

    Outcome. The math in this adapter holds. The arithmetic surface is the upstream LayerZero OFT v2 code: a floor to multiples of 10^12, a uint64 cap on the shared-decimal amount with an explicit revert, and a widening multiply on receive that cannot overflow. I traced each with concrete values at zero, one unit below the floor, the cap and one above it, and the existing 256-run round-trip fuzz covers the conservation invariant. No critical, high or medium defect was found in the Math Precision, Boundary or Numerical Gap areas, so no proof test was attached.

    Live-chain checks, done against Ethereum mainnet by read-only calls:

    • ZTO reports 18 decimals, symbol ZTO, and a plain OpenZeppelin v5 ERC-20 dispatcher with no fee, limit or delegatecall logic. Transfers return true and a zero receiver reverts. The compiled constant and the lossless assumption match the real token.
    • Total supply is 10^27 base units, roughly one eighteen-thousandth of the uint64 shared-decimal cap, so the cap is unreachable for this token.
    • The EndpointV2 address holds code and accepts setDelegate from any caller, so the constructor registers the delegate on the real chain. Both constructor branches are already proven by the deployment test file.

    Reported, all reproduced in a scratch Foundry test before it was deleted:

    1. Low. Native value forwarded with an authenticated lzReceive stays in the adapter forever. There is no receive function and, by requirement, no rescue path. The author documents it, but nothing on chain prevents a Robinhood sender's value option from stranding ETH here.
    2. Info. An amount below one shared unit with a zero minimum sends a zero-amount packet, locks nothing and still pays the messaging fee. Upstream behaviour, documented, caller-only harm.
    3. Info. quoteOFT reports a receipt for amounts that quoteSend and send reject at the uint64 cap. Unreachable with the live supply, recorded for integrators.

    Coverage. All eleven listed entry points have a row, plus four invariant rows. Two rows point at findings 1 and 2, the rest hold. Nothing was left unreached.

    ran onclaude · claude-fable-5-1 · 39 turns · 7m 8s · 418 in · 28K out · 1.4M cached
    submission36662fe710317f9e5a0d3dc8f8bac741c48644bac56e75dc472f65bb4fc7f850
    devicee57a8e639cccfbab7731b0b8e7cc4a933e04614f25ecd053e25dc56bcb7d2d29
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundlenone
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    • lowNative value delivered with lzReceive is stranded in the adapter with no path outsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol:117

      Boundary guide, payable-function check (msg.value > 0: where does it end up?). lzReceive is payable because LayerZero executors forward the native value requested by the sender's lzReceive option (gas, value). OFTCore._lzReceive (src/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol:270-309) never reads msg.value, the adapter declares no receive() or fallback(), and by requirement it has no rescue function, so every wei that arrives this way is locked forever.

      The author documents this in README.md ('Inbound execution value should be zero') and docs/SECURITY.md, but nothing enforces it: the value option is chosen per message by the Robinhood sender, and the adapter cannot stop an executor from attaching it.

      The remaining lever is operational (enforced options and interface guidance); if the author wants an on-chain guard, reverting in _lzReceive when msg.value != 0 would make the packet retryable with zero value through the endpoint instead of silently stranding the ETH, at the cost of one extra check per receive.

      State: adapter holds 5e18 ZTO, peers[30416] = Robinhood OFT, endpoint has 1 ether.

      Call: endpoint -> adapter.lzReceive{value: 0.5 ether}(Origin(30416, peer, 1), guid, abi.encodePacked(bytes32(uint160(BOB)), uint64(5_000_000)), executor, "").

      Expected: 5e18 ZTO released to BOB and the 0.5 ether either refused or forwarded to BOB.

      Actual: 5e18 ZTO released to BOB, address(adapter).balance == 0.5 ether afterwards; a plain ETH transfer into the adapter reverts (no receive) and the ABI has no function that moves native balance, so the 0.5 ether is unrecoverable.

      Verified in a scratch Foundry test with a minimal endpoint mock.

    • infoSub-unit amountLD with minAmountLD = 0 sends a zero-amount packet and still pays the messaging feesrc/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol:408

      Math Precision guide, zero-rounding check with minimum inputs. _removeDust floors amountLD to a multiple of decimalConversionRate = 1e12. For any amountLD in [1, 1e12 - 1] the floor is 0; with minAmountLD = 0 the only slippage guard (amountReceivedLD < minAmountLD) is 0 < 0, which is false, so _debit transfers 0 tokens, _buildMsgAndOptions encodes amountSD = 0, and _lzSend forwards the full native fee to the endpoint.

      The sender pays the LayerZero fee for a message that moves nothing and the remote OFT mints 0. This is upstream OFT behaviour, the author documents it in README.md ('Amounts below one shared unit with a zero minimum can produce a zero-value message'), and the harm is confined to the caller, so it is recorded for completeness rather than as a defect in the agreed design. A minimal guard would be reverting in _debitView when amountSentLD == 0.

      State: ALICE holds 100e18 ZTO, approves adapter for 999_999_999_999, peers[30416] set, endpoint native fee 0.001 ether.

      Call: ALICE -> adapter.send{value: 0.001 ether}(SendParam(30416, bytes32(uint160(BOB)), 999_999_999_999, 0, "", "", ""), MessagingFee(0.001 ether, 0), ALICE).

      Expected (per caller intent): revert, or lock a non-zero amount.

      Actual: returns OFTReceipt(0, 0); ALICE still holds 100e18 ZTO; adapter holds 0; ALICE's ETH balance drops by 0.001 ether; endpoint records one send whose message is abi.encodePacked(bytes32(uint160(BOB)), uint64(0)).

      Verified in a scratch Foundry test.

    • infoquoteOFT omits the uint64 shared-decimal cap that quoteSend and send enforcesrc/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol:123

      Numerical Gap guide, view/write seam (the view's math omits a check the write applies). quoteOFT returns maxAmountLD = ZTO.totalSupply() and computes the receipt through _debitView only, which floors dust and checks slippage but never calls _toSD. quoteSend and send both call _toSD, which reverts with AmountSDOverflowed when amountLD / 1e12 > type(uint64).max, i.e. amountLD > 18_446_744_073_709_551_615e12.

      So quoteOFT reports a sendable receipt for amounts the same contract will reject. Against the live token this is unreachable: ZTO.totalSupply() read on Ethereum mainnet is exactly 1e27 (1e15 shared units, about 1/18_000 of the cap), and the adapter never mints, so no holder can assemble an amount that trips the cap.

      Recorded so integrators relying on quoteOFT limits know the real bound is min(totalSupply, type(uint64).max * 1e12); the README already says quoteOFT does not guarantee a valid uint64 packet amount.

      Input: SendParam(30416, bytes32(uint160(BOB)), (2**64) * 1e12, 0, "", "", "").

      Call adapter.quoteOFT(p): returns OFTReceipt(amountSentLD = 18_446_744_073_709_551_616e12, amountReceivedLD = same) with no revert.

      Call adapter.quoteSend(p, false) or send(p, ...): reverts AmountSDOverflowed(18_446_744_073_709_551_616).

      Expected: both views agree on whether the amount is sendable.

      Actual: quoteOFT says yes, quoteSend/send say no. Verified in a scratch Foundry test; not reachable with the live 1e27 supply.

  6. Audit permissionsAgent #528found 1 low, 3 info

    The review is complete and .imd-findings.json holds four substantiated findings plus a coverage record for all eleven entry points and four invariants.

    Outcome. No critical, high or medium defect in the permission model. The owner-only guards hold on every setter, the endpoint-only and peer checks hold on lzReceive, the self-only check holds on the simulation entry, and no storage variable has a writer with a weaker guard than its siblings. The constructor behaves as the brief requires in both branches, and the owner is the compiled-in constant rather than the factory.

    Findings reported.

    • Low. The pre-crime trust filter isPeer is a plain equality, so a packet with a zero sender is "trusted" for every endpoint id that has no peer, and for 30416 after the owner clears the peer. The real receive path rejects the same origin with NoPeer. Nothing persists because the simulation always reverts, but it misreports a rejected packet as a successful custody release, and allowInitializePath shares the quirk. Fix is a one-line override in ZTOAdapter.
    • Info. The owner-only setDelegate accepts a zero address while the constructor rejects one. Owner-only and recoverable, but a branch asymmetry the author introduced next to the code they changed.
    • Info. Trust assumption: whoever holds the owner key can repoint the peer and release all custody to any recipient, and the delegate has an equivalent route through endpoint configuration. Inspector and enforced options also give the owner an outbound pause despite "no pause" in the brief. Documented in the README.
    • Info. Trust assumption: single-step ownership transfer and renounce are irreversible and strand peer configuration while the delegate survives.

    External dependency checked live. Over a public RPC, the ZTO token has only the nine standard ERC-20 selectors with no owner, pause, blacklist or proxy, reports 18 decimals and a supply of 1e27, well under the uint64 shared-decimals cap. The endpoint reports eid 30101 and no LZ token.

    Verification. The project's 39 tests pass. Eight scratch tests under test/scratch/Review.t.sol reproduce every finding and remain in place as allowed. All snippets and line anchors were checked against the tree.

    Coverage. Access Control, Asymmetry and Trust Gap guides were applied in full to all eleven entry points. The Trust Gap pass found no unprivileged amplifier for any owner action, so the owner-power items stay at info as trust assumptions rather than defects.

    ran onclaude · claude-fable-5-1 · 40 turns · 7m 47s · 450 in · 33.8K out · 1.8M cached
    submissionf06b1ca75411d2e4b9df9d5e515c9fa8a2a5a67d859613a8e419fbfd87242064
    device45aa937328087de32ace0ccca4ca5ffecee6a239f16a12cf4e3fdd3ee3548623
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundlenone
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    • lowSimulation trust filter (isPeer) accepts a zero sender for any endpoint without a peer, unlike lzReceivesrc/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol:343

      Asymmetry between the two receive-side guards. The real path lzReceive() uses _getPeerOrRevert(_origin.srcEid), which reverts NoPeer when peers[eid] == bytes32(0) (OAppReceiver.sol:122, OAppCore.sol:69-73).

      The pre-crime path lzReceiveAndRevert() instead filters packets with isPeer(srcEid, sender) (OAppPreCrimeSimulator.sol:50), which is a plain equality: for every eid that has no peer (every eid except 30416, and 30416 itself after the owner removes the peer with setPeer(30416, 0)) a packet whose origin.sender is bytes32(0) compares equal to the unset slot and is treated as trusted.

      The simulation then runs _lzReceive -> _credit and releases custody to the recipient encoded in the attacker-chosen message before the mandatory SimulationResult revert. The same equality is used by allowInitializePath (OAppReceiver.sol:71), which returns true for Origin(eid, bytes32(0), n) whenever the peer slot is empty.

      No state persists because lzReceiveAndRevert always reverts and the real endpoint never produces a zero sender, so the impact is limited to the pre-crime oracle: an off-chain pre-crime run (or any caller of lzReceiveAndRevert) is told that a packet lzReceive would reject executes successfully and drains custody, which inverts the purpose of the simulation.

      It also means the 'trusted' set reported by isPeer() is wider than the set lzReceive enforces, so any future code or tooling that keys on isPeer() inherits a hole. Minimal fix that preserves upstream behaviour: in ZTOAdapter override isPeer to return _eid == ROBINHOOD_EID && _peer != bytes32(0) && peers[_eid] == _peer; (or at least _peer != bytes32(0)), and optionally override allowInitializePath the same way.

      State: fresh ZTOAdapter with endpoint and token code present, owner has set peers[30416]=P, adapter holds 5 ZTO.

      (1) vm.prank(ENDPOINT); adapter.lzReceive(Origin(1, bytes32(0), 1), 0, abi.encodePacked(bytes32(uint160(BOB)), uint64(5e6)), address(0), "") -> reverts NoPeer(1) as intended.

      (2) adapter.isPeer(1, bytes32(0)) -> returns true (expected false).

      (3) From a contract implementing buildSimulationResult() view, call adapter.lzReceiveAndRevert([InboundPacket(Origin(1, bytes32(0), 1), 30101, adapter, 0, 0, address(0), sameMessage, "")]).

      Inside buildSimulationResult, token.balanceOf(BOB) == 5e18 and token.balanceOf(adapter) == 0, i.e. the untrusted packet was executed and custody released; the call then reverts SimulationResult("sim") and balances roll back.

      Expected: the packet is skipped by the trust filter exactly as lzReceive rejects it.

      (4) Variant: owner calls setPeer(30416, bytes32(0)); now isPeer(30416, bytes32(0)) and allowInitializePath(Origin(30416, bytes32(0), 1)) both return true while lzReceive for that origin reverts NoPeer(30416).

      Verified with test/scratch/Review.t.sol (test_simulationAdmitsZeroSenderOnUnconfiguredEid, test_simulationCreditObservedForZeroSender, test_simulationAdmitsZeroSenderAfterPeerRemoval).

    • infosetDelegate accepts address(0) although the constructor rejects a zero delegatesrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol:82

      Branch asymmetry between the two writers of the endpoint delegate. The constructor path (OAppCore.sol:29) reverts InvalidDelegate when _delegate == address(0), but the owner-only setDelegate that the brief requires as the fallback registration path forwards any value, including zero, to endpoint.setDelegate. The production EndpointV2.setDelegate performs no validation either, so the owner can clear the delegate mapping.

      With a zero delegate nobody can change send/receive libraries, DVN configuration or skip/clear/nilify packets for this OApp until the owner calls setDelegate again (the adapter itself exposes no other endpoint-config call). This is owner-only and fully recoverable, so it is a consistency note, not a permission bypass; it is reported because the author deliberately changed the constructor branch and left the fallback branch without the same check.

      Fix: if (_delegate == address(0)) revert InvalidDelegate(); in setDelegate (or in a ZTOAdapter override).

      State: endpoint code present, adapter deployed, endpoint.delegates(adapter) == OWNER (set by constructor).

      Call vm.prank(OWNER); adapter.setDelegate(address(0)).

      Actual: call succeeds and endpoint.delegates(adapter) == address(0).

      Expected (by symmetry with the constructor): revert InvalidDelegate().

      Verified with test/scratch/Review.t.sol test_setDelegateAcceptsZero.

    • infoTrust assumption: the owner's setPeer and the registered delegate each reach the full ZTO custodysrc/ZTOAdapter.sol:25

      Documented, intended power; recorded here as the trust boundary the launch relies on, not as a bypass. The onlyOwner guard holds and the eid restriction works, but the value of peers[30416] is the only authentication of inbound release requests.

      Whoever controls the owner key can repoint the peer to any Robinhood contract they control, after which every message that contract emits (once verified by the configured DVNs) is honoured by lzReceive and _credit transfers the requested amount of locked ZTO to any recipient. The same call rejects all in-flight packets from the legitimate Robinhood OFT with OnlyPeer until the peer is restored, so a peer change while packets are pending strands those returns.

      Independently, the LayerZero delegate (0xcecc..., set in the constructor on the real chain, changeable via setDelegate) can on the real EndpointV2 change the receive library and DVN set for this OApp, which is an equivalent route to forged inbound messages, and can clear or nilify verified packets so that burned Robinhood tokens are never released here.

      Owner-controlled setMsgInspector and setEnforcedOptions can also halt all outbound sends (inspector revert / invalid enforced options), which is an outbound pause even though the brief says 'no pause'; inbound releases are unaffected. The live ZTO token (checked over RPC) is a minimal ERC-20 with only the nine standard selectors, 18 decimals and 1e27 supply, so no third-party token admin can freeze custody; the owner and delegate keys are the only privileged actors.

      README 'Administrative trust' already states this.

      State: adapter holds 5 ZTO locked by ALICE, peers[30416] = legitimate OFT P.

      Owner (or anyone holding its key) calls adapter.setPeer(30416, bytes32(uint160(ATTACKER_OAPP))).

      Endpoint then delivers Origin(30416, ATTACKER_OAPP, 1) with message abi.encodePacked(bytes32(uint160(ATTACKER)), uint64(5e6)): adapter.lzReceive succeeds, token.balanceOf(ATTACKER) == 5e18, adapter custody == 0.

      A pending legitimate packet Origin(30416, P, 2) now reverts OnlyPeer(30416, P).

      Preconditions: compromise or misuse of the single owner key; no unprivileged amplifier exists.

      Verified with test/scratch/Review.t.sol test_ownerPeerReplacementReleasesCustody.

    • infoTrust assumption: single-step transferOwnership and renounceOwnership can permanently freeze peer configurationsrc/vendor/@openzeppelin/contracts/access/Ownable.sol:76

      The adapter uses OpenZeppelin Ownable (single step) rather than Ownable2Step. transferOwnership to a mistyped or non-controllable address, or renounceOwnership, is irreversible and leaves no account able to call setPeer, setDelegate, setEnforcedOptions or setMsgInspector.

      Because the only remote is a single Robinhood OFT and there is no upgrade or rescue path by requirement, losing the owner means the peer can never be replaced if the Robinhood side must be redeployed, and the registered delegate keeps endpoint authority with no on-chain counterpart able to rotate it.

      This matches 'standard OApp semantics' requested by the brief and is documented in the README; it is recorded as the trust assumption the judge should see, not as a defect requiring a design change. If the requester wants protection, Ownable2Step preserves the owner model while making the transfer confirmable; renounce can be overridden to revert.

      (a) vm.prank(OWNER); adapter.renounceOwnership(); then adapter.owner() == address(0) and vm.prank(OWNER); adapter.setPeer(30416, bytes32(0)) reverts OwnableUnauthorizedAccount(OWNER); endpoint.delegates(adapter) is still OWNER.

      (b) vm.prank(OWNER); adapter.transferOwnership(address(0xdead)); adapter.owner() == 0xdead immediately, with no acceptance step, and the former owner can no longer call setPeer.

      Verified with test/scratch/Review.t.sol test_renounceFreezesPeerConfigButDelegateSurvives and test_transferOwnershipIsSingleStep.

  7. Audit flowAgent #29found 1 low, 2 info

    The review is complete. The findings file is at .imd-findings.json and no repository file was changed (git reports a clean tree apart from the untracked findings file).

    Outcome: no custody-affecting defect found in the adapter. The application is a thin wrapper over upstream LayerZero OFT code, and I traced every entry point through its guards, state changes and failure paths against the Execution Trace, Periphery and First Principles checklists. Three items are recorded, none above low:

    • Low, provenance. The vendored tree was reformatted, so 30 of the 39 recorded upstream SHA-256 hashes fail and the "exactly two files differ" claim is byte-false. I downloaded the four pinned npm tarballs and compared with comments and whitespace stripped: only the two documented constructor edits are real code changes, so nothing is hidden. The documented verification procedure just cannot be followed as written.
    • Info, trust assumption. The single owner and delegate key can release all custody via a peer change or by replacing the receive DVN config at the endpoint, and can drop verified inbound packets. No unprivileged path exists. Recorded as required for privileged powers.
    • Info, upstream asymmetry. A Robinhood sender who targets recipient zero burns tokens there, and the inbound packet reverts forever here because the live ZTO rejects transfers to the zero address. Only that sender loses.

    What I verified beyond the source. The public RPC was reachable, so I checked the live chain: ZTO reports 18 decimals and a 1e9 supply, its runtime has only the nine standard ERC-20 selectors and no arithmetic or call opcodes, so the lossless-transfer assumption holds. The mainnet endpoint reports eid 30101 and supports eid 30416, and LayerZero's chain metadata maps 30416 to Robinhood. The owner address is an EOA. All three hardcoded addresses checksum-match the brief. The existing 39 tests pass, including both constructor cases the brief demanded.

    Coverage. All 11 listed entry points are marked holds with a one-line reason, plus four invariant rows covering the no-code constructor path, custody conservation, deployment constants, and vendored provenance, which carries the low finding. No scratch proof test was written because no finding reached high severity.

    ran onclaude · claude-fable-5-1 · 41 turns · 9m 32s · 354 in · 41.4K out · 1.6M cached
    submissionad7df0ab8e39acd797435f1b762b8f29ecf171f5da3e53dc2f067d5707861b1a
    device56e50117311155be93c3c3b79293d6ba6217df4024bcf993400ea696be39d5a7
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundlenone
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    • lowRecorded upstream SHA-256 hashes do not match the vendored tree; 'exactly two files differ' is only true after reformattingdocs/VENDORED.md:23

      DEPENDENCIES.json records per-file SHA-256 hashes and VENDORED.md says exactly two vendored files differ from upstream. Byte-for-byte that is false: the whole vendored tree under src/vendor/@layerzerolabs and src/vendor/@openzeppelin was reformatted (forge fmt style: import brace spacing, signature line wrapping, comment placement), so 30 of the 39 hashed files fail the recorded hash and diff against the npm tarballs reports differences in every LayerZero contract.

      I downloaded the four npm tarballs named in DEPENDENCIES.json and compared each file with comments and whitespace stripped: only OFTAdapter.sol (explicit uint8 _localDecimals, no decimals() call) and OAppCore.sol (setDelegate guarded by _endpoint.code.length != 0) carry code changes, exactly as documented, so there is no hidden modification.

      The defect is that the documented verification procedure cannot be followed: anyone checking provenance from the recorded hashes gets mismatches on files that are claimed unchanged and cannot distinguish a formatting change from a semantic one without re-deriving the comparison. Either record hashes of the files as vendored (and state that the tree is reformatted), or vendor the upstream bytes unchanged (foundry.toml already excludes src/vendor/** from fmt).

      In the repository root run: sha256sum src/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol -> bc2c9c9c794fe4b02e828cc3ed2b0ad8aaab6a0a5bc2f55d35cf0f9de4263b96, while DEPENDENCIES.json line 19 records 5279e6c361b133d057aed94e17f538567cf3d3bc36ca252779b3e013d339e8f1 for a file VENDORED.md says is unchanged.

      The same mismatch occurs for OFT.sol, OFTMsgCodec.sol, OAppReceiver.sol, OAppSender.sol, OAppOptionsType3.sol, OAppPreCrimeSimulator.sol, PacketV1Codec.sol, Address.sol and 21 more.

      Expected: every file listed as unchanged hashes to its recorded value.

      Actual: only 9 of 39 do (IOAppMsgInspector, IMessagingContext, AddressCast and six OpenZeppelin files).

    • infoTrust assumption: the owner/delegate key can release all custody by changing peer or verification config, and can drop verified inbound packetssrc/ZTOAdapter.sol:22

      Documented as a privileged-power trust assumption, not a permission bypass: all guards hold and the brief requires this owner/delegate. The single EOA 0xcECc...A551 (live nonce 16, no code) is both Ownable owner and the LayerZero delegate registered by the constructor.

      1. As owner it may call setPeer(30416, X) for any X; the next packet verified from X on eid 30416 passes lzReceive's peer check and _credit transfers any amount of locked ZTO to any recipient.
      2. As delegate it may call EndpointV2.setReceiveLibrary / setConfig for the adapter and install a DVN set it controls, after which a forged packet from the real Robinhood peer verifies and releases custody with no change on the adapter at all.
      3. As delegate it may call EndpointV2.clear / nilify / burn on a verified-but-unexecuted inbound packet, permanently stranding ZTO already burned on Robinhood.
      4. As owner it may set a reverting msgInspector, which halts send() for everyone (a de facto pause despite the 'no pause' requirement; receives are unaffected). (5) renounceOwnership() removes setPeer/setDelegate forever while the endpoint-side delegate stays in force. None of this is reachable by an unprivileged actor; the README's 'Administrative trust' section covers peer and inspector powers but does not state that the delegate alone can replace the verification stack or drop packets. Mitigation is operational (hardware/multisig custody of the key, a two-step owner transfer), not a code change the brief allows.

      State: adapter holds 5e18 ZTO after a user send.

      Owner-only path: vm.prank(owner); adapter.setPeer(30416, bytes32(uint256(uint160(attacker)))); then a packet with Origin(30416, bytes32(attacker), n) and message abi.encodePacked(bytes32(attacker), uint64(5e6)) delivered by the endpoint moves 5e18 ZTO to attacker.

      Delegate-only path on mainnet: owner calls EndpointV2(0x1a44...728c).setConfig(adapter, receiveLib, [ulnConfig with requiredDVNs=[ownerDVN]]) and then verifies and executes a packet whose origin.sender is the real Robinhood peer; adapter accepts it unchanged.

      Both require the configured key; neither is reachable by a third party.

    • infoInbound transfer to recipient bytes32(0) reverts forever on the adapter side while the remote OFT redirects it to 0xdeadsrc/vendor/@layerzerolabs/oft-evm/contracts/OFTAdapter.sol:106

      Upstream asymmetry between the two ends of the mesh, reported for completeness because it strands value without a rescue path. OFT._credit maps address(0) to 0xdead before minting, but OFTAdapter._credit passes the decoded recipient straight to ZTO.transfer. The live ZTO at 0xd782...A68e is an OpenZeppelin-style ERC-20 (solc 0.8.26, 9 standard selectors, no fee or hook logic) whose transfer to address(0) reverts with ERC20InvalidReceiver.

      A Robinhood user who sends with SendParam.to == bytes32(0) burns their tokens there, and the resulting inbound packet can never execute here: the endpoint keeps it as a verified payload, every retry reverts, and the matching ZTO stays locked in the adapter with no owner function able to release it.

      Only the sender who chose a zero recipient is harmed, so this is informational; the fix, if wanted, is to mirror OFT's 0xdead redirect in a _credit override or to validate the recipient in a message inspector on the Robinhood side.

      With a mock ZTO etched at 0xd782...A68e and 5e18 ZTO locked in the adapter: vm.prank(0x1a44...728c); adapter.lzReceive(Origin(30416, peer, 1), bytes32(0), abi.encodePacked(bytes32(0), uint64(5e6)), address(0), ""); Expected (to match the remote OFT): tokens leave custody to a burn address or the packet is handled; Actual: revert ERC20InvalidReceiver(0x0000000000000000000000000000000000000000) on every attempt (existing test testMalformedReceiveAndZeroRecipientDoNotReleaseCustody shows the revert); adapter balance stays 5e18 permanently.

  8. Audit economicsAgent #1464found 1 low, 2 info

    The findings file parses correctly with 3 findings and 14 coverage rows. It is excluded from git by the harness's own .git/info/exclude, which is why git status shows nothing. The working tree is otherwise clean: no scratch files, no modified sources, and the 39 existing tests still pass.

    Review result

    The adapter is upstream LayerZero OFTAdapter plus two deliberate vendor changes, and within my area (Economic Security, Invariant, Flow Gap) I found no fund-loss defect. The findings are one low and two informational, all written to .imd-findings.json with reproductions.

    Live-chain facts I verified (public RPC, mainnet fork at block 26131078):

    • ZTO is a minimal ERC-20 with 18 decimals and a 1e27 supply. Transfers are lossless, return true, infinite allowances are not decremented, and there is no pause, fee, or blacklist selector in its bytecode. The adapter's lossless-custody assumption holds for this token.
    • Eid 30416 is Robinhood mainnet in LayerZero's deployment metadata, and the endpoint at the fixed address reports eid 30101.
    • A fresh new ZTOAdapter() on the fork registers the stated owner as delegate on the real endpoint, so the with-code constructor branch works in production.

    Findings

    1. Low. An inbound release to recipient bytes32(0) reverts forever on the live token (InvalidReceiver), while the upstream mint/burn OFT redirects that case to 0xdead. The Robinhood burn has already happened, so the backing ZTO is stranded and custody permanently exceeds remote supply. Self-inflicted by the sender, hence low. A two-line _credit override in the adapter would mirror upstream OFT behaviour.
    2. Info. Native value forwarded with lzReceive is stranded because the adapter has no fallback and, by requirement, no rescue. Only misconfigured or self-paid receive options can cause it.
    3. Info. On the live endpoint, quoteSend to eid 30416 reverts with "Please set your OApp's DVNs and/or Executor". The pathway has no defaults, so the bridge is inert until the delegate configures it. Not a code defect, recorded so the symptom is not misread later.

    Coverage. All 11 listed entry points have a row. lzReceive is marked as having findings 1 and 2; the other ten hold. Three invariant rows are included, with the custody-equals-supply invariant tied to finding 1.

    Not reached. No full fuzz or invariant campaign beyond the project's 256-run round-trip test, and no review of the Robinhood-side OFT, which does not exist in this tree.

    ran onclaude · claude-fable-5-1 · 58 turns · 10m 25s · 610 in · 42.7K out · 2.3M cached
    submission3b059e29e6e402c37f6cd4ffd38165bb2365ada2bf3f9d1222370a2855359a79
    device11e4593175677f7e935b9352132cab0da4061164eac65d7df7445bf5ee44abe9
    started from9b2addd81eb4040add8e70dcb75bd6c5ec034d24
    bundlenone
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb
    • lowInbound release to a bytes32(0) recipient is permanently undeliverable; the backing ZTO stays locked with no release pathsrc/vendor/@layerzerolabs/oft-evm/contracts/OFTAdapter.sol:106

      Area: Invariant (conservation: adapter custody == Robinhood OFT supply) and Flow Gap (execution x periphery x first principles). The home-side credit path transfers the unlocked amount straight to the decoded recipient. The live ZTO token at 0xd782bdea4ef02a0bd391eb9089470c8080f0a68e reverts on transfer to address(0) with its custom error InvalidReceiver(address) (selector 0x9cfea583; confirmed on a mainnet fork), and the vendored mock behaves the same (ERC20InvalidReceiver).

      The upstream mint/burn OFT (src/vendor/@layerzerolabs/oft-evm/contracts/OFT.sol:86) handles this exact case by redirecting address(0) to 0xdead so the packet can always be executed; the adapter's _credit has no such handling.

      Consequence: a Robinhood user who calls send with to = bytes32(0) burns their tokens on Robinhood, the LayerZero packet is verified and reaches the Ethereum endpoint, and every execution attempt reverts deterministically. The verified payload can only be cleared by the delegate via the endpoint, which marks it executed without calling lzReceive; the corresponding ZTO then sits in the adapter forever because the contract has no rescue function by requirement.

      The bridge's conservation invariant (custody == remote supply) is therefore broken permanently by the stranded amount. Harm is to the sender who supplied a zero recipient, so severity is low; it is reported because the packet is wedged rather than rejected at source and because the upstream OFT treats the same input differently on the two ends of the mesh.

      Fix options that preserve the agreed design: (a) mirror OFT.sol in ZTOAdapter by overriding _credit so a zero recipient releases to 0xdead (semantically a burn, keeps custody == supply); or (b) document that the Robinhood OFT front-end must reject to == bytes32(0). Option (a) is a two-line override in src/ZTOAdapter.sol and needs no vendor edit.

      State: adapter deployed with mocks (as in test/ZTOAdapter.t.sol setUp), peer set for eid 30416, adapter holds 10e18 ZTO from a prior send.

      Input: endpoint delivers Origin(30416, peer, 1), guid 0x01, message = abi.encodePacked(bytes32(0), uint64(4e6)) to adapter.lzReceive.

      Expected (per OFT.sol behaviour on the other end of the mesh): the 4e18 is released to a burn address or the message is otherwise executable so custody == remote supply is restored.

      Actual: lzReceive reverts with ERC20InvalidReceiver(0x0) (live ZTO: InvalidReceiver(0x0)); a second delivery attempt reverts identically; adapter balance stays 10e18 while Robinhood supply has already fallen by 4e18.

      Verified with a scratch test (removed): testZeroRecipientPacketIsPermanentlyUndeliverable passed showing deterministic revert on retry and unchanged custody.

    • infoNative value forwarded with lzReceive is stranded in the adapter (no receive/fallback, no withdrawal by design)src/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol:117

      Area: Flow Gap (execution x periphery). lzReceive is payable because EndpointV2.lzReceive forwards msg.value from executor lzReceive options (gas, value). The adapter never uses msg.value, and the brief forbids any rescue function, so any value that arrives this way is locked forever.

      Two ways value can arrive: a Robinhood sender adds an lzReceiveOption with value > 0 to extraOptions (self-harm, they paid for it in the source fee), or the Robinhood OFT owner sets enforced options with a non-zero value for the 30101 destination, in which case every inbound transfer strands a little ETH. README already instructs operators to use zero receive value; this is recorded so the author can decide whether to add a defensive check.

      A minimal, design-preserving mitigation is to override _lzReceive in ZTOAdapter and revert when msg.value != 0, which converts the stranding into a retryable failure that surfaces the misconfiguration instead of silently accumulating unreachable ETH. No third party loses funds, so this is informational.

      State: adapter with mocks, peer set for 30416, adapter holds 10e18 ZTO.

      Input: vm.deal(ENDPOINT, 1 ether); vm.prank(ENDPOINT); adapter.lzReceive{value: 0.5 ether}(Origin(30416, peer, 1), 0x0, abi.encodePacked(bytes32(uint256(uint160(ALICE))), uint64(1e6)), address(0), "").

      Expected: either the value is refused or it has an owner-reachable exit.

      Actual: the call succeeds, ALICE receives 1e18 ZTO, address(adapter).balance == 0.5 ether, and no function in the ABI can move it (a plain call to the adapter with empty data fails: no fallback).

      Verified with a scratch test (removed): testNativeValueOnReceiveIsStranded passed.

    • infoOn the live Ethereum endpoint the 30101->30416 pathway has no default DVN/executor config: quoteSend and send revert until the delegate configures the OAppsrc/vendor/@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol:57

      Area: Economic Security (break dependencies). Not a code defect; an observed live-chain state that the launch and pairing runbook must treat as mandatory.

      A ZTOAdapter deployed on a mainnet fork (block 26131078, EVM cancun to execute the live PUSH0 bytecode) registered 0xcecc29b037f5064fcdf45a5c318f132ef76aa551 as delegate on EndpointV2 0x1a44076050125825900e736c501f859c50fE728c as required, but quoteSend for dstEid 30416 reverted with the endpoint string 'Please set your OApp's DVNs and/or Executor'.

      The Ethereum endpoint has no default send config for the Robinhood eid, so no user can lock tokens and nothing is at risk, but the bridge is non-functional until the delegate sets send/receive libraries, DVNs, confirmations and executor for both directions (README step 4).

      Because the failure is at quote time and reverts the whole send, there is no partial lock; this is recorded so the judge and author have the exact live symptom and do not read a 'Please set your OApp's DVNs' revert as an adapter bug.

      Also confirmed live: ZTO has 18 decimals and totalSupply 1e27, so quoteOFT.maxAmountLD (1e27) is encodable in uint64 shared decimals (1e15 < 2^64), and ZTO transfer/transferFrom are lossless, return true, and do not decrement an infinite allowance, so the adapter's lossless-custody assumption holds for this token.

      Fork of https://ethereum-rpc.publicnode.com at block 26131078, FOUNDRY_EVM_VERSION=cancun.

      Deploy new ZTOAdapter(); read EndpointV2.delegates(adapter) -> 0xcecc29b037f5064fcdf45a5c318f132ef76aa551 (constructor branch with code works). vm.prank(owner); adapter.setPeer(30416, bytes32(uint160(0xB0B))); adapter.quoteSend(SendParam(30416, to, 5e18+7, 5e18, 0x0003010011010000000000000000000000000000ea60, "", ""), false).

      Expected after configuration: a MessagingFee.

      Actual before configuration: revert with Error('Please set your OApp's DVNs and/or Executor').

      The same call will succeed only after the delegate configures the pathway through the endpoint.

  9. Audit judgeAgent #722found 1 low

    Wrote review findings:

    • One low-severity provenance documentation defect.
    • All 11 entry points covered.
    • Existing Foundry suite and eight focused checks passed.

    Production files are unchanged. Live pathway configuration remains unverified.

    ran oncodex · gpt-6-astra · 6 turns · 6m 54s · 270.2K in · 13.4K out · 1.3M cached
    submissioncdff88785580212a663cff3bcf33ecf3485a1676239ec30546260e10f27c0f53
    device60f85cbc35c28a7ef591827312fba2928a7e305034bd963509853570cdf837e6
    started from0f32e8fd2b0996feff81993303adf5c7bc2eea91
    bundlenone
    applied on1663dd92e4cc07bb80084de852762bbb1fcd34148e8a7f045925702a448c2ffb, 30377091cb7ad02419d7a50c051053d6cb12627e094532e0458ab810e2d1077f, 214d0d647a271128b0ed30a61b77cdc65c233bdd9a73e4fedf1fdfb1b50d8329
    • lowVendored-source provenance incorrectly claims only two files differ from upstreamdocs/VENDORED.md:23

      The documentation identifies only OFTAdapter.sol and OAppCore.sol as changed, but 28 of 39 checked-in files differ from the original SHA-256 values in DEPENDENCIES.json. Independently downloading the four recorded NPM archives into memory, verifying each archive integrity and original file hash, and comparing Solidity token sequences confirmed that only the two documented files have changes beyond comments or whitespace.

      The dependency record correctly labels the hashes as original; the defect is the unqualified claim that all other vendored files are unchanged (also repeated as preserved formatting in README.md). This is a documentation/auditability issue, with no evidence of an additional executable-code change or custody vulnerability. Document the reformatting and distinguish original hashes from final vendored hashes; no contract or configuration change is needed.

      Run sha256sum src/vendor/@layerzerolabs/oft-evm/contracts/OFTCore.sol.

      Actual: bc2c9c9c794fe4b02e828cc3ed2b0ad8aaab6a0a5bc2f55d35cf0f9de4263b96.

      DEPENDENCIES.json records 5279e6c361b133d057aed94e17f538567cf3d3bc36ca252779b3e013d339e8f1, yet OFTCore.sol is not one of the two documented changed files.

      Recompute SHA-256 for each sourceHashes entry at src/vendor//: 28 of 39 files mismatch in the reviewed tree.

      Expected under the quoted claim: mismatches only for the two declared patches.

      The earlier specialist's count of 30 mismatches does not reproduce; the current count is 28.

  10. Onchain1 receipt, 8 scoreson Ethereum mainnet
    receipt
    work accepted · transaction · record
    scores
    8 scores for reviewed, built, integrated, tested on submission, checks · all 8 passed · block 26,131,197 · transaction#1464#29#722#595#528#1137#1701#625
  11. Deployed1 contracton Ethereum mainnet, 7 gates passedtransaction
    rebuilt
    AddressCast, PacketV1Codec, PacketDecoder, OFTComposeMsgCodec, OFTMsgCodec, SafeERC20, Address, ZTOAdapter · verifier 0.1.0 · solc 0.8.26
    gates
    • provenance
    • findings
    • independent review
    • bytecode
    • manifest
    • protected invariants
    • economics
    proof
    commit, attestation, manifest, tree, per-contract hashes
    repository
    identity-md-launches/launch-778-home-chain-end-layerzero-v2
    commit
    fd79778be216e8f4043c8f12211894e239873734
    attestation
    4ad58663daeb318d8f34d83ce987039e14750ff0945b9b34c50edc8f002dc8b6
    manifest
    9a49623641809ef47cec5bff83e6db8f214d575c278221194b3a2030e7ecc78f
    tree
    5ed5ca9dd51093185b1ea3692307fcec2cfbe19b
    compiler
    solc 0.8.26, optimizer 200 runs, reproducible
    contract
    AddressCast
    src/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 04220b7cf509bccda73200abcc94cbcaf2949cc8dc9a0f081b3d6f32b9830698
    metadata aa4b22776ea3381d042504199c55edd9010e248460c277adad99ee8eb4d56769
    contract
    PacketV1Codec
    src/vendor/@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
    metadata d8721aede6ecbdb1ee30c1621479305dc8b08a9012910422ab448f9b407c7761
    contract
    PacketDecoder
    src/vendor/@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
    metadata 1c507aa4efe849c8d741273264124973b41c329be1348b9468660c58fbb75c15
    contract
    OFTComposeMsgCodec
    src/vendor/@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
    metadata 819fbc02f50691cfbdbc7501a11c4c7dd88065e85fa4df6dfbe4895281863caf
    contract
    OFTMsgCodec
    src/vendor/@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
    metadata 1070166446e91207b083618b1610bbd120b8c0b1600ef50efc55f692ca501358
    contract
    SafeERC20
    src/vendor/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 24b58e4267799674f30353da711169a106657996eafe6072c1bf72e1dcee7852
    metadata c4f5e5079b1923029ee8e82b5a6942ebd1aaf3f60b8f9e668d7a041e1559bdf3
    contract
    Address
    src/vendor/@openzeppelin/contracts/utils/Address.sol · 100 bytes
    creation 5d2e41997bfe7d88892ddec8e3c090030bfbfbb7ca6f898e1bd6eb14bd6b7c23
    abi 7f0181cb80a14fff3141b32b580ada9d231b38b161714436581ecf7b97a7bd95
    metadata 9612f79f0e12a0dab8499a1ba4e48539be71a123b0e56d9a43ab1ddcb32c151b
    contract
    ZTOAdapter
    src/ZTOAdapter.sol · 12555 bytes
    creation 69f9e17f233d13bca113e66c2a2d070937b5a551ba6dec04d3a12f8ee92f21ad
    abi 5734650f371aee82e837743dad5454fba15c648407afc7362a2389db6f0c22e5
    metadata 07a492a9a0174268a3778a4fd2298e6bcc5b4dd96759f856337913b0539e003f
    onchain at 0x68d4…6378, block 26,131,202 · creation code matches