A-5 immutable
A-5 immutable
A-1 は「動いているコードが公開ソースと一致するか」を確かめました。ですがその比較では、コンストラクタで焼き込まれる immutable 値(LayerZero エンドポイント等の参照アドレス)を正規化して(ゼロに伏せて)一致を取っています。つまり「正しいコードを、誤った/悪意あるコンストラクタ引数でデプロイする」余地は A-1 だけでは塞げません。この項目では、live V1 テストネットの各契約に焼き込まれた immutable の実際の値を読み出し、正当なアドレスを指しているかを確かめます。とくに最重要なのは、すべての OApp が指す LayerZero エンドポイント――プロトコルの信頼の根(trust root)です。
A-1 は「機械の設計図が公開版と同じか」を確かめました。でも機械は工場出荷時に、設計図には書かれていない具体的な部品(参照先アドレス)が差し込まれます。A-1 はその部品穴にカバーをかけたまま形を比べていました。A-5 はカバーを外し、差し込まれた部品が一つひとつ本物かを確かめます。
→
でも部品は出荷時に挿入immutable
→
部品が本物か確認A-5
いちばん大事な部品が、すべての契約が外部とやり取りする窓口=LayerZero エンドポイントです。ここが偽物を指していたら、他の確認はすべて静かに無効になります。結果、エンドポイントは 8/8 すべて正規。さらに内部配線(参照先アドレス)を全数読み出しても、すべて正しい相手を指しており、ズレ(ドリフト)は1件もありませんでした。
1. 主張(検証対象)
live V1 の各契約に焼き込まれた security-critical な immutable が、すべて期待される正当なアドレスを指していること。とりわけ、全 OApp の LayerZero エンドポイントが正規の EndpointV2(0x6EDCE…f10f)であり、内部配線(CawActions.cawProfile/externalSelf、Relay.cawActions、ProfileL2.layer1EndpointId 等)が live のアドレス集合と整合すること。
2. 検証方法
immutable を全列挙
ソースから immutable を全部洗い出す(自動生成 getter で読み出せる)。V1 の CawActions は eip712DomainHash/cawProfile/externalSelf の3つのみで、後の版にある capOracle/erc1271Sibling/zkVerifier は V1 には存在しません(より薄い実装)。OApp 群(CawProfile・CawProfileL2・Archive・Relay)は endpoint を持ちます。
期待値を確定
エンドポイント=各チェーンの正規 EndpointV2、内部配線=live のアドレス集合、設定値=deploy 設定(L1 eid=40161)。記憶ではなく、production ノードが実際に使うアドレス集合(ノードの addresses.ts)とソースから機械生成。
オンチェーンで全数照合
3チェーン(Sepolia/Base Sepolia/Arbitrum Sepolia)の各 getter を読み出し、期待値と突き合わせる。自己参照などのズレが無いかを1件ずつ確認。
3. 判定
CawActions の cawProfile/externalSelf、Relay の cawActions、ProfileL2 の layer1EndpointId など内部配線はすべて live のアドレスと整合し、3チェーンとも自己参照などのズレはありません。- 一致 LayerZero エンドポイント 8/8(CawProfile@L1・CawProfileL2×3・CawActionsArchive×2・CawChallengeRelay×2)がすべて正規 EndpointV2
0x6EDCE…f10f。信頼の根は固定されています。 - 一致 内部配線:
CawActions.cawProfile=各チェーンの CawProfileL2、externalSelf=自分自身、Relay.cawActions=各チェーンの CawActions、ProfileL2.layer1EndpointId=40161。すべて整合。 - 一致 L1 ミラー(
CawProfileL2_L1)はbypassLZ=true・cawProfile=L1 の CawProfile。設計どおり(L1 は LayerZero を介さず直接呼ぶ co-deploy 構成)。
4. receipt(公開証明)
信頼の根の確認は、各 OApp の endpoint() を読むだけで再現できます。8つすべて同じ正規エンドポイントを返します。
信頼の根:各 OApp の endpoint() を読むと、8つすべてが正規 EndpointV2 0x6EDCE…f10f を返します。
内部配線も同様に、getter を読むだけで「正しい相手を指している」ことを確認できます。
内部配線:CawActions.cawProfile()→0x0c3e…6b11d(CawProfileL2@L2)、externalSelf()→0x5674…06af(自分自身)、L1 ミラーは bypassLZ()→true。いずれも期待どおりで、再現コマンドは下のツール(ethers v6/curl)に用意しています。
5. 注記・限界
この項目(A-5)が扱うのは、信頼モデルの中心にある security-critical な immutable(LZ エンドポイント・内部配線・設定値)です。周辺の経済系コントラクト(BuyAndBurn/Minter/Marketplace)が持つ外部 DeFi アドレス(WETH/router/pair)は、テストネットでは中心から外れるため今回の必須照合からは外しており、「外部・低優先」として扱っています。なお eip712DomainHash はアドレスではなく構築時に計算される bytes32 で、その正しさは A-1(構築コードがソースと一致)から従います。
だからこそ、見ているのが本番の地かどうかを、まず疑う。
A-1 confirmed that the running code matches the public source. But that comparison normalizes (zeroes out) the immutable values baked in by the constructor — the reference addresses such as the LayerZero endpoint. So “deploy the correct code with wrong or malicious constructor arguments” is a gap A-1 alone cannot close. Here we read the actual values of those immutables on the live V1 testnet and check they point to legitimate addresses. The most important one is the LayerZero endpoint every OApp points to — the protocol’s trust root.
A-1 checked whether “the machine’s blueprint matches the public one.” But at the factory, the machine gets specific parts (reference addresses) slotted in that the blueprint doesn’t fix. A-1 compared shapes with a cover left over those part-slots. A-5 lifts the cover and checks each slotted part is the genuine one.
→
but parts slotted at the factoryimmutable
→
check each part is genuineA-5
The most important part is the window every contract uses to talk to the outside — the LayerZero endpoint. If that pointed at a fake, every other check would be quietly voided. It checked out: the endpoint is genuine on all 8 OApps. Reading the internal wiring in full as well, every reference points at the right counterpart — there was not a single drift.
1. Claim under test
That every security-critical immutable baked into the live V1 contracts points to its expected, legitimate address. In particular, that every OApp’s LayerZero endpoint is the canonical EndpointV2 (0x6EDCE…f10f), and that the internal wiring (CawActions.cawProfile / externalSelf, Relay.cawActions, ProfileL2.layer1EndpointId, etc.) is consistent with the live address set.
2. How we checked
Enumerate every immutable
From the source, list every immutable (each has an auto-generated getter, so it is readable). The V1 CawActions has only three — eip712DomainHash, cawProfile, externalSelf; the capOracle / erc1271Sibling / zkVerifier immutables of later versions do not exist in V1 (a leaner build). The OApps (CawProfile, CawProfileL2, Archive, Relay) carry endpoint.
Fix the expected values
Endpoint = the chain’s canonical EndpointV2; internal wiring = the live address set; config = the deploy config (L1 eid = 40161). Generated from the address set the production nodes actually use (the node’s addresses.ts) and the source, not from memory.
Reconcile on-chain, in full
Read each getter on all three chains (Sepolia / Base Sepolia / Arbitrum Sepolia) and compare to expected, checking each value for self-reference or any other drift.
3. Verdict
CawActions.cawProfile / externalSelf, Relay.cawActions, ProfileL2.layer1EndpointId — is consistent with the live addresses on all three chains, with no self-references or other drift.- match LayerZero endpoint 8/8 (CawProfile@L1, CawProfileL2×3, CawActionsArchive×2, CawChallengeRelay×2) all the canonical EndpointV2
0x6EDCE…f10f. The trust root is fixed. - match Internal wiring:
CawActions.cawProfile= each chain’s CawProfileL2,externalSelf= self,Relay.cawActions= each chain’s CawActions,ProfileL2.layer1EndpointId= 40161. All consistent. - match The L1 mirror (
CawProfileL2_L1) hasbypassLZ=trueandcawProfile= the L1 CawProfile — as designed (L1 hosts a co-deployed mirror that is called directly, bypassing LayerZero).
4. Receipt (public proof)
The trust-root check reproduces by simply reading each OApp’s endpoint(). All eight return the same canonical endpoint.
Trust root: reading endpoint() on each OApp, all eight return the canonical EndpointV2 0x6EDCE…f10f.
The internal wiring reproduces the same way — each getter shows it points at the right counterpart.
Internal wiring: CawActions.cawProfile()→0x0c3e…6b11d (CawProfileL2@L2), externalSelf()→0x5674…06af (itself); the L1 mirror has bypassLZ()→true — all as expected. Reproduce with the tool below (ethers v6 / curl).
5. Notes & limits
This item (A-5) covers the security-critical immutables at the center of the trust model (the LZ endpoint, internal wiring, config values). The external DeFi addresses (WETH, router, pair) on the peripheral economic contracts (BuyAndBurn, Minter, Marketplace) sit off-center on testnet, so they are not part of the required reconciliation here and are treated as “external / low-priority.” Note that eip712DomainHash is not an address but a constructor-computed bytes32, and its correctness follows from A-1 (the constructor code matches source).
Which is why the first thing to doubt is whether you are looking at the live ground at all.






