@crossecute/protocol 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +25 -1
  2. package/abi/Blake2b256.json +1 -0
  3. package/abi/Blake2bScheme.json +1 -0
  4. package/abi/ChainRegistry.json +1 -0
  5. package/abi/Commitment.json +1 -0
  6. package/abi/Conduits.json +1 -0
  7. package/abi/DivergentConduits.json +1 -0
  8. package/abi/DivergentTransceiver.json +1 -0
  9. package/abi/DivergentTransceiverExtension.json +1 -0
  10. package/abi/Envelope.json +1 -0
  11. package/abi/Erc7930.json +1 -0
  12. package/abi/Executor.json +1 -0
  13. package/abi/IBootstrapAllowlist.json +1 -0
  14. package/abi/ICancel.json +1 -0
  15. package/abi/IChainRegistryRefs.json +1 -0
  16. package/abi/ICommitFinalize.json +1 -0
  17. package/abi/ICommitmentScheme.json +1 -0
  18. package/abi/IConduitReceiverReport.json +1 -0
  19. package/abi/IConduitTransceiver.json +1 -0
  20. package/abi/IERC7786GatewaySource.json +1 -0
  21. package/abi/IERC7786Recipient.json +1 -0
  22. package/abi/IExecute.json +1 -0
  23. package/abi/ILzOftConduitFactory.json +1 -0
  24. package/abi/IProviderIdTable.json +1 -0
  25. package/abi/IProxy.json +1 -0
  26. package/abi/IReceiverInit.json +1 -0
  27. package/abi/IRefValidator.json +1 -0
  28. package/abi/IReportFloat.json +1 -0
  29. package/abi/ITransmitterInit.json +1 -0
  30. package/abi/IVmDeriver.json +1 -0
  31. package/abi/KeccakScheme.json +1 -0
  32. package/abi/Move.json +1 -0
  33. package/abi/MoveValidator.json +1 -0
  34. package/abi/OpStackTransmitterBase.json +1 -0
  35. package/abi/OutboundBase.json +1 -0
  36. package/abi/OwnableTransmitter.json +1 -0
  37. package/abi/ProviderAddress.json +1 -0
  38. package/abi/ProviderAttribute.json +1 -0
  39. package/abi/ProviderChainId.json +1 -0
  40. package/abi/ProviderReceiver.json +1 -0
  41. package/abi/ProviderTransceiver.json +1 -0
  42. package/abi/Proxy.json +1 -0
  43. package/abi/ProxyDeployer.json +1 -0
  44. package/abi/ProxyInitCode.json +1 -0
  45. package/abi/ReceiverBase.json +1 -0
  46. package/abi/Roles.json +1 -0
  47. package/abi/RolesEnumerable.json +1 -0
  48. package/abi/SchemeFold.json +1 -0
  49. package/abi/Sha256Scheme.json +1 -0
  50. package/abi/StarknetDerive.json +1 -0
  51. package/abi/StarknetValidator.json +1 -0
  52. package/abi/SuiDerive.json +1 -0
  53. package/abi/TransceiverBase.json +1 -0
  54. package/abi/TransceiverCore.json +1 -0
  55. package/abi/TransceiverExtension.json +1 -0
  56. package/abi/TransmitterBase.json +1 -0
  57. package/abi/Treasury.json +1 -0
  58. package/abi/TronConduits.json +1 -0
  59. package/abi/TronTransceiver.json +1 -0
  60. package/abi/TronTransceiverExtension.json +1 -0
  61. package/abi/VmDeriver.json +1 -0
  62. package/abi/ZkSyncConduits.json +1 -0
  63. package/abi/ZkSyncTransceiver.json +1 -0
  64. package/abi/ZkSyncTransceiverExtension.json +1 -0
  65. package/abi/index.d.ts +63 -0
  66. package/abi/index.js +63 -0
  67. package/contracts/addressing/ChainKey.sol +45 -0
  68. package/contracts/addressing/ChainType.sol +50 -0
  69. package/contracts/addressing/Erc7930.sol +209 -0
  70. package/contracts/addressing/Move.sol +212 -0
  71. package/contracts/conduit/Conduits.sol +43 -0
  72. package/contracts/conduit/Proxy.sol +144 -0
  73. package/contracts/conduit/ProxyDeployer.sol +42 -0
  74. package/contracts/conduit/ProxyInitCode.sol +47 -0
  75. package/contracts/derivation/AddressDerive.sol +326 -0
  76. package/contracts/derivation/BitcoinDerive.sol +33 -0
  77. package/contracts/derivation/Blake2b256.sol +122 -0
  78. package/contracts/derivation/IVmDeriver.sol +35 -0
  79. package/contracts/derivation/StarknetDerive.sol +93 -0
  80. package/contracts/derivation/SuiDerive.sol +102 -0
  81. package/contracts/derivation/VmDeriver.sol +291 -0
  82. package/contracts/messaging/Call.sol +43 -0
  83. package/contracts/messaging/Commitment.sol +131 -0
  84. package/contracts/messaging/DeliveryGas.sol +18 -0
  85. package/contracts/messaging/Envelope.sol +123 -0
  86. package/contracts/messaging/Executor.sol +52 -0
  87. package/contracts/messaging/IErc7786.sol +68 -0
  88. package/contracts/messaging/Payload.sol +90 -0
  89. package/contracts/messaging/Roles.sol +113 -0
  90. package/contracts/messaging/inbound/ReceiverBase.sol +359 -0
  91. package/contracts/messaging/outbound/OutboundBase.sol +221 -0
  92. package/contracts/messaging/outbound/OwnableTransmitter.sol +23 -0
  93. package/contracts/messaging/outbound/TransmitterBase.sol +604 -0
  94. package/contracts/messaging/transceiver/DivergentConduits.sol +92 -0
  95. package/contracts/messaging/transceiver/DivergentTransceiver.sol +97 -0
  96. package/contracts/messaging/transceiver/IBootstrapAllowlist.sol +8 -0
  97. package/contracts/messaging/transceiver/TransceiverBase.sol +541 -0
  98. package/contracts/messaging/transceiver/TransceiverCore.sol +307 -0
  99. package/contracts/messaging/transceiver/TransceiverExtension.sol +278 -0
  100. package/contracts/protocols/ProviderAddress.sol +32 -0
  101. package/contracts/protocols/ProviderAttribute.sol +65 -0
  102. package/contracts/protocols/ProviderChainId.sol +115 -0
  103. package/contracts/protocols/ProviderReceiver.sol +24 -0
  104. package/contracts/protocols/ProviderTransceiver.sol +48 -0
  105. package/contracts/protocols/layerzero/ILzOftConduitFactory.sol +10 -0
  106. package/contracts/protocols/op-stack/OpStackTransmitterBase.sol +11 -0
  107. package/contracts/registry/ChainRegistry.sol +667 -0
  108. package/contracts/registry/IChainRegistryRefs.sol +81 -0
  109. package/contracts/registry/ICommitmentScheme.sol +46 -0
  110. package/contracts/registry/IRefValidator.sol +13 -0
  111. package/contracts/registry/Provenance.sol +25 -0
  112. package/contracts/schemes/Blake2bScheme.sol +18 -0
  113. package/contracts/schemes/KeccakScheme.sol +19 -0
  114. package/contracts/schemes/Sha256Scheme.sol +15 -0
  115. package/contracts/treasury/Treasury.sol +76 -0
  116. package/contracts/validators/MoveValidator.sol +24 -0
  117. package/contracts/validators/StarknetValidator.sol +20 -0
  118. package/package.json +34 -3
@@ -0,0 +1,541 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {
5
+ TransceiverCore,
6
+ TransceiverConfig,
7
+ ITransmitterInit,
8
+ IConduitReceiverReport
9
+ } from "./TransceiverCore.sol";
10
+ import {IBootstrapAllowlist} from "./IBootstrapAllowlist.sol";
11
+ import {TransceiverExtension} from "./TransceiverExtension.sol";
12
+ import {IReceiverInit} from "../inbound/ReceiverBase.sol";
13
+ import {Call} from "../Call.sol";
14
+ import {Envelope} from "../Envelope.sol";
15
+ import {Conduits} from "../../conduit/Conduits.sol";
16
+ import {ProxyInitCode} from "../../conduit/ProxyInitCode.sol";
17
+ import {Erc7930} from "../../addressing/Erc7930.sol";
18
+ import {Provenance} from "../../registry/Provenance.sol";
19
+ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
20
+
21
+ /// @title TransceiverBase
22
+ /// @notice One transceiver per chain per provider. It creates transmitters for conduits homed
23
+ /// here and receivers for conduits homed on any configured origin, and it sends and
24
+ /// accepts both bootstraps and receiver reports.
25
+ ///
26
+ /// @dev A conduit's CREATE2 salt is `(owner, salt, homeChainKey)` and nothing else, so one
27
+ /// transmitter has one receiver per destination, at an address fixed before the first
28
+ /// message, and the transmitter a conduit has here never collides with the receiver a
29
+ /// conduit homed elsewhere has here. A receiver is made only by an authenticated bootstrap.
30
+ ///
31
+ /// @dev Runs no payload of its own: an inbound message is an `Envelope`, acted on in
32
+ /// `_handleInbound`, and a bootstrap creates the conduit and runs its payload in the same
33
+ /// delivery. A first payload that should wait carries a call to the new receiver's own
34
+ /// `commit`. No approvals are held here, so no origin can approve or cancel on another's
35
+ /// behalf.
36
+ ///
37
+ /// @dev Owned by the governor's conduit on this chain, derived at initialization
38
+ /// rather than typed, and configured by payloads the governor sends from its home. The owner
39
+ /// configures and can move no money. `GATEWAY_ROLE` is fixed at initialization with no
40
+ /// revoke path, since a transceiver's transports serve every conduit on its chain; a
41
+ /// compromised transport means a new transceiver.
42
+ ///
43
+ /// @dev Holds what a bootstrap, a delivery, and a conduit's send reach. Initialization, owner
44
+ /// configuration, and views no contract calls live in `extension`, reached through
45
+ /// `fallback`, which keeps every binding under EIP-170 (#70). The extension is an
46
+ /// implementation immutable, so it changes only with a new transceiver, as this code does.
47
+ abstract contract TransceiverBase is TransceiverCore {
48
+ /// @notice The `TransceiverExtension` every selector not declared here runs in.
49
+ address public immutable extension;
50
+
51
+ /// The contract `_deployConduit` reads `Proxy`'s initcode from (`ProxyInitCode`),
52
+ /// written by this implementation's constructor. Zero on zkSync, which deploys by hash.
53
+ address internal immutable _proxyInitCode;
54
+
55
+ error NoExtension();
56
+ /// @dev The extension derives conduits, or names this chain, differently from this contract.
57
+ error ExtensionMismatch();
58
+
59
+ constructor(address extension_) {
60
+ if (extension_.code.length == 0) revert NoExtension();
61
+ extension = extension_;
62
+ _proxyInitCode = _storeProxyInitCode();
63
+ }
64
+
65
+ /// @notice Write `Proxy`'s initcode for `_deployConduit` to read.
66
+ /// @dev A variant whose `_deployConduit` does not read it overrides this to store nothing.
67
+ function _storeProxyInitCode() internal virtual returns (address) {
68
+ return ProxyInitCode.store();
69
+ }
70
+
71
+ /// @notice Run any other selector in the extension, against this contract's storage.
72
+ fallback() external payable {
73
+ address target = extension;
74
+ assembly {
75
+ calldatacopy(0, 0, calldatasize())
76
+ let ok := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
77
+ returndatacopy(0, 0, returndatasize())
78
+ if iszero(ok) { revert(0, returndatasize()) }
79
+ return(0, returndatasize())
80
+ }
81
+ }
82
+
83
+ /// @notice Accept the float reports are paid from.
84
+ receive() external payable {}
85
+
86
+ /* ================================ initialization ============================== */
87
+
88
+ /// @notice For a transceiver that derives conduit addresses Ethereum's way.
89
+ function __TransceiverBase_init(TransceiverConfig memory c) internal onlyInitializing {
90
+ __TransceiverBase_init(c, false);
91
+ }
92
+
93
+ /// @notice Configure and grant the gateways, in the extension.
94
+ ///
95
+ /// @dev `addressesDiverge_` is the contract's own fact: `DivergentTransceiver` passes true,
96
+ /// having set its derivation inputs first, since the owner is derived with them.
97
+ ///
98
+ /// @dev A self-call, so the extension can refuse any caller but this contract
99
+ /// (`TransceiverExtension.initializeBase`). The owner it derived must be the one this
100
+ /// contract derives: an extension of another chain type would arm conduits at
101
+ /// addresses this contract does not create.
102
+ ///
103
+ /// @dev A transceiver decides which payloads are authentic, so a live upgrade key would be
104
+ /// a standing ability to forge any message. There is none: a transceiver is a
105
+ /// `Proxy` that `ProxyDeployer` arms with this initializer and locks in one
106
+ /// call, and neither this contract nor its extension has an upgrade function. A bug
107
+ /// in either is fixed only by redeploying, which re-derives every conduit.
108
+ function __TransceiverBase_init(TransceiverConfig memory c, bool addressesDiverge_) internal onlyInitializing {
109
+ if (TransceiverCore(extension).localChainKey() != localChainKey) revert ExtensionMismatch();
110
+ TransceiverExtension(address(this)).initializeBase(c, addressesDiverge_);
111
+ if (owner() != _predictConduit(c.governorOwner, c.governorSalt, keccak256(c.governorHome))) {
112
+ revert ExtensionMismatch();
113
+ }
114
+ }
115
+
116
+ /* ============================== conduit manufacture ============================ */
117
+
118
+ /// @notice Where `(owner, salt)`'s receiver will sit on `chainKey`, for a conduit homed on
119
+ /// this chain, where `chainKey` derives addresses with Ethereum's CREATE2.
120
+ /// @dev Ethereum's CREATE2 over this provider's transceiver there, which is the counterpart
121
+ /// on that chain. It equals the conduit's own address only when this chain also uses
122
+ /// that formula and the same deployer deployed both transceivers, which is why a
123
+ /// transmitter records this rather than `address(this)`.
124
+ ///
125
+ /// @dev The initcode hash is the destination's, not this compiler's: on zkSync and Tron
126
+ /// `PROXY_INIT_CODE_HASH` is the local compiler's and not what an EVM destination
127
+ /// deploys (#30), so a diverging transceiver takes it from the provider's deployment
128
+ /// record, which R8.4 holds equal to solc's.
129
+ function predictReceiver(bytes32 chainKey, address owner, bytes32 salt) public view returns (bytes memory) {
130
+ return abi.encodePacked(_conduitUnder(_evmCounterpartOn(chainKey), owner, salt, localChainKey));
131
+ }
132
+
133
+ /// @dev Where `(owner, salt)`'s conduit homed on `homeChainKey` sits under the transceiver at
134
+ /// `transceiver_`, with the initcode hash that chain's compiler produces.
135
+ function _conduitUnder(address transceiver_, address owner, bytes32 salt, bytes32 homeChainKey)
136
+ private
137
+ view
138
+ returns (address)
139
+ {
140
+ bytes32 initCodeHash = PROXY_INIT_CODE_HASH;
141
+ if (_addressesDiverge) {
142
+ initCodeHash = _chainRegistry.providerDeployment(_messageProvider).proxyInitCodeHash;
143
+ if (initCodeHash == bytes32(0)) revert NoProviderDeployment();
144
+ }
145
+ return Create2.computeAddress(_conduitSalt(owner, salt, homeChainKey), initCodeHash, transceiver_);
146
+ }
147
+
148
+ /// @notice Create the caller's transmitter.
149
+ /// @dev The owner is `msg.sender`, never an argument, so no one can claim the address
150
+ /// another party will occupy on every chain.
151
+ /// @param salt Chosen by the caller; `bytes32(0)` suits an owner who wants one conduit.
152
+ /// @return conduit The transmitter, at the address its receivers occupy on parity chains.
153
+ function createTransmitter(bytes32 salt) external returns (address conduit) {
154
+ conduit = _createConduit(msg.sender, salt, localChainKey, address(0), new Call[](0));
155
+ }
156
+
157
+ /// @notice Deploy the proxy at `salt`, and return where it actually landed.
158
+ /// @dev Separate from the prediction because zkSync diverges in both: a different formula,
159
+ /// and deployment through a system contract against a pre-published bytecode hash, so
160
+ /// a variant there emits `new Proxy{salt: s}()`. Tron diverges only in the formula.
161
+ function _deployConduit(bytes32 salt) internal virtual returns (address deployed) {
162
+ return Conduits.deploy(salt, _proxyInitCode);
163
+ }
164
+
165
+ /// @notice Deploy an owner's conduit and arm it: a transmitter if it is homed here, a
166
+ /// receiver otherwise, through the same argument-free proxy.
167
+ ///
168
+ /// @dev The checks around the deployment are `Conduits`', shared with every other
169
+ /// conduit creator. The prediction it holds the deployment to is the one every published
170
+ /// address comes from: a transmitter's recorded counterpart, a receiver's
171
+ /// `sourceTransmitter`, the conduit a report is forwarded to.
172
+ /// @param sourceTransmitter The transmitter a receiver will answer to; zero for a
173
+ /// transmitter, which answers to its owner.
174
+ function _createConduit(
175
+ address owner,
176
+ bytes32 salt,
177
+ bytes32 homeChainKey,
178
+ address sourceTransmitter,
179
+ Call[] memory calls
180
+ ) internal returns (address conduit) {
181
+ if (owner == address(0)) revert ZeroOwner();
182
+
183
+ address implementation = _conduitImplementation(homeChainKey);
184
+ if (implementation == address(0)) revert NoConduitImplementation();
185
+
186
+ conduit = _predictConduit(owner, salt, homeChainKey);
187
+ Conduits.requireVacant(conduit, owner, salt);
188
+
189
+ address deployed = _deployConduit(_conduitSalt(owner, salt, homeChainKey));
190
+ Conduits.requireLanded(conduit, deployed);
191
+
192
+ Conduits.arm(conduit, implementation, _conduitInitializer(owner, salt, homeChainKey, sourceTransmitter, calls));
193
+
194
+ emit ConduitCreated(owner, conduit, salt, homeChainKey);
195
+ }
196
+
197
+ /// @notice The logic a conduit homed on `homeChainKey` is armed with.
198
+ function _conduitImplementation(bytes32 homeChainKey) internal view virtual returns (address) {
199
+ return homeChainKey == localChainKey ? _transmitterImplementation : _receiverImplementation;
200
+ }
201
+
202
+ /// @notice The initializer that logic is armed with, run by delegatecall inside the
203
+ /// upgrade so the conduit is never live and uninitialized.
204
+ /// @dev The proxy locks in the same call, and a conduit's own configuration is gated on
205
+ /// its owner, not this contract, so all provider setup must be in this calldata. A
206
+ /// transmitter takes its salt, since a conduit cannot recover it from its own
207
+ /// address; a receiver answers to the transmitter its bootstrap carried, on its home,
208
+ /// named here by chainKey as an ERC-7786 gateway reports it.
209
+ function _conduitInitializer(
210
+ address owner,
211
+ bytes32 salt,
212
+ bytes32 homeChainKey,
213
+ address sourceTransmitter,
214
+ Call[] memory calls
215
+ ) internal view virtual returns (bytes memory) {
216
+ if (homeChainKey == localChainKey) {
217
+ return abi.encodeCall(ITransmitterInit.initialize, (owner, address(this), salt));
218
+ }
219
+ return abi.encodeCall(IReceiverInit.initialize, (sourceTransmitter, homeChainKey, calls));
220
+ }
221
+
222
+ /* =================================== bootstrap ================================= */
223
+
224
+ /// @notice Stand a conduit up on a chain that has none, and carry its payload.
225
+ ///
226
+ /// @dev Callable only by the conduit `(owner, salt)` resolves to when homed on this chain,
227
+ /// which is what lets the owner and salt travel in the message without a caller
228
+ /// claiming another identity. A conduit homed elsewhere bootstraps from its own home.
229
+ ///
230
+ /// @dev `_minCounterpartProvenance` applies here through `_requireRoutable`, as it does to
231
+ /// every message this transceiver sends to or accepts from a chain (`_counterpartOn`).
232
+ /// A conduit's later sends leave from its transmitter, which does not read it.
233
+ function bootstrap(
234
+ bytes32 destinationChainKey,
235
+ address owner,
236
+ bytes32 salt,
237
+ Call[] calldata calls,
238
+ bytes[] calldata attributes
239
+ ) external payable {
240
+ bytes32 transmitter = _bootstrapCaller(owner, salt);
241
+ _sendBootstrap(
242
+ destinationChainKey, owner, salt, _bootstrapEnvelope(owner, salt, transmitter, calls), attributes
243
+ );
244
+ }
245
+
246
+ /// @notice `bootstrap`, for a destination whose calls this chain cannot express.
247
+ /// @dev Which form a caller may use depends on the destination's chain type, enforced by
248
+ /// the transmitter: this contract sees only a chainKey.
249
+ function bootstrapElements(
250
+ bytes32 destinationChainKey,
251
+ address owner,
252
+ bytes32 salt,
253
+ bytes[] calldata elements,
254
+ bytes[] calldata attributes
255
+ ) external payable {
256
+ bytes32 transmitter = _bootstrapCaller(owner, salt);
257
+ _sendBootstrap(
258
+ destinationChainKey,
259
+ owner,
260
+ salt,
261
+ Envelope.encodeBootstrapElements(owner, salt, transmitter, elements),
262
+ attributes
263
+ );
264
+ }
265
+
266
+ /// @notice Empty: this transceiver sends a conduit's bootstrap itself, through `bootstrap`.
267
+ function conduitBootstrap(bytes calldata, address, bytes32, bytes calldata)
268
+ external
269
+ pure
270
+ returns (bytes memory recipient, bytes memory message)
271
+ {
272
+ return ("", "");
273
+ }
274
+
275
+ /// @notice What `bootstrap` would cost, before anything is spent.
276
+ /// @dev Applies `_requireRoutable`, so it fails wherever the send would, but not
277
+ /// `bootstrap`'s caller check: a quote is taken before the conduit exists.
278
+ function quoteBootstrap(
279
+ bytes32 destinationChainKey,
280
+ address owner,
281
+ bytes32 salt,
282
+ Call[] calldata calls,
283
+ bytes[] calldata attributes
284
+ ) external view returns (uint256 nativeFee) {
285
+ return _quoteBootstrap(
286
+ destinationChainKey,
287
+ _bootstrapEnvelope(owner, salt, _transmitterWord(_allowedConduit(owner, salt)), calls),
288
+ attributes
289
+ );
290
+ }
291
+
292
+ /// @notice `quoteBootstrap`, for a destination whose calls this chain cannot express.
293
+ function quoteBootstrapElements(
294
+ bytes32 destinationChainKey,
295
+ address owner,
296
+ bytes32 salt,
297
+ bytes[] calldata elements,
298
+ bytes[] calldata attributes
299
+ ) external view returns (uint256 nativeFee) {
300
+ return _quoteBootstrap(
301
+ destinationChainKey,
302
+ Envelope.encodeBootstrapElements(owner, salt, _transmitterWord(_allowedConduit(owner, salt)), elements),
303
+ attributes
304
+ );
305
+ }
306
+
307
+ /// @notice The envelope `bootstrap` sends and `quoteBootstrap` prices, built in one place so
308
+ /// the two cannot drift.
309
+ function _bootstrapEnvelope(address owner, bytes32 salt, bytes32 transmitter, Call[] calldata calls)
310
+ private
311
+ pure
312
+ returns (bytes memory)
313
+ {
314
+ return Envelope.encodeBootstrap(owner, salt, transmitter, calls);
315
+ }
316
+
317
+ /// @notice Refuse any caller but the conduit `(owner, salt)` resolves to here, and return
318
+ /// it as the bootstrap carries it.
319
+ function _bootstrapCaller(address owner, bytes32 salt) private view returns (bytes32) {
320
+ address conduit = _allowedConduit(owner, salt);
321
+ if (conduit != msg.sender) revert NotTheConduit(owner, salt, msg.sender);
322
+ return _transmitterWord(conduit);
323
+ }
324
+
325
+ /// @notice The conduit `(owner, salt)` resolves to here, refused unless the allowlist, where
326
+ /// one is set, allows it.
327
+ /// @dev Shared by `bootstrap` and its quotes so they refuse the same conduits.
328
+ function _allowedConduit(address owner, bytes32 salt) private view returns (address conduit) {
329
+ conduit = _localConduit(owner, salt);
330
+ IBootstrapAllowlist allowlist = _bootstrapAllowlist;
331
+ if (address(allowlist) != address(0) && !allowlist.isAllowed(conduit)) revert NotAllowed(conduit);
332
+ }
333
+
334
+ /// @dev One send path for both envelope forms. The provenance bar lives inside
335
+ /// `_requireRoutable`: an under-graded counterpart, or an unconfigured route, reverts
336
+ /// before anything crosses.
337
+ function _sendBootstrap(
338
+ bytes32 destinationChainKey,
339
+ address owner,
340
+ bytes32 salt,
341
+ bytes memory envelope,
342
+ bytes[] calldata attributes
343
+ ) private {
344
+ _requireBootstrappable(destinationChainKey);
345
+ emit BootstrapSent(destinationChainKey, owner, salt);
346
+ _sendMessage(_recipientOn(destinationChainKey), envelope, attributes, _bootstrapSendValue(destinationChainKey));
347
+ }
348
+
349
+ /// @dev The quote of `_sendBootstrap`, over the same envelope, plus the destination's fee.
350
+ function _quoteBootstrap(bytes32 destinationChainKey, bytes memory envelope, bytes[] calldata attributes)
351
+ private
352
+ view
353
+ returns (uint256)
354
+ {
355
+ _requireBootstrappable(destinationChainKey);
356
+ return
357
+ _quoteMessage(_recipientOn(destinationChainKey), envelope, attributes) + _bootstrapFee[destinationChainKey];
358
+ }
359
+
360
+ /// @notice Refuse a bootstrap that cannot be sent, or that would cost the destination's float
361
+ /// more than it paid.
362
+ /// @dev A reporting destination pays the report from its float, so a free bootstrap there
363
+ /// would let anyone drain it. The fee's size is governance; zero is refused here.
364
+ function _requireBootstrappable(bytes32 destinationChainKey) private view {
365
+ _requireRoutable(destinationChainKey);
366
+ if (!isLive(destinationChainKey)) revert DestinationNotLive(destinationChainKey);
367
+ if (_bootstrapFee[destinationChainKey] == 0 && reportsReceiver(destinationChainKey)) {
368
+ revert NoBootstrapFee(destinationChainKey);
369
+ }
370
+ }
371
+
372
+ /// @notice An EVM transmitter as a bootstrap carries it: left-padded to a word.
373
+ function _transmitterWord(address transmitter) private pure returns (bytes32) {
374
+ return bytes32(uint256(uint160(transmitter)));
375
+ }
376
+
377
+ /// @notice Take the destination's fee off the top of `msg.value`, forward it, and return
378
+ /// what is left for the send.
379
+ /// @dev Paid before the send, which reaches a provider endpoint and arbitrary code beyond
380
+ /// it; a later revert unwinds the payment too. `call` rather than `transfer`, so a
381
+ /// treasury contract is not held to the 2300-gas stipend.
382
+ function _bootstrapSendValue(bytes32 chainKey) internal returns (uint256) {
383
+ uint256 fee = _bootstrapFee[chainKey];
384
+ if (msg.value < fee) revert InsufficientBootstrapFee(fee, msg.value);
385
+ if (fee == 0) return msg.value;
386
+
387
+ address to = _treasury;
388
+ emit BootstrapFeePaid(chainKey, to, fee);
389
+
390
+ // forge-lint: disable-next-line(arbitrary-send-eth) the write-once treasury
391
+ (bool ok,) = to.call{value: fee}("");
392
+ if (!ok) revert FeeTransferFailed(to, fee);
393
+ return msg.value - fee;
394
+ }
395
+
396
+ /* =================================== inbound =================================== */
397
+
398
+ /// @notice The funnel every binding routes an arriving message into.
399
+ ///
400
+ /// @dev Authentication runs here, before anything is decoded, so a binding only translates
401
+ /// its SDK's callback into these arguments.
402
+ ///
403
+ /// @param route How the source chain is named, as the provider reported it.
404
+ /// @param sender The counterpart's address on that chain, in that chain's own format.
405
+ /// @param message The body: see `Envelope`.
406
+ function _onInbound(bytes memory route, bytes memory sender, bytes calldata message) internal {
407
+ bytes32 chainKey = _authenticateOrigin(route, sender);
408
+ _handleInbound(chainKey, message);
409
+ }
410
+
411
+ /// @notice Establish which chain this came from, and refuse it if the sender is not that
412
+ /// chain's counterpart.
413
+ /// @dev The route names the chain; the sender must be its counterpart at this
414
+ /// transceiver's provenance bar. An unknown route reverts in `chainKeyOfRoute`.
415
+ function _authenticateOrigin(bytes memory route, bytes memory sender)
416
+ internal
417
+ view
418
+ virtual
419
+ returns (bytes32 chainKey)
420
+ {
421
+ chainKey = chainKeyOfRoute(route);
422
+ if (keccak256(sender) != keccak256(_counterpartOn(chainKey))) {
423
+ revert NotCounterpart(chainKey);
424
+ }
425
+ }
426
+
427
+ /// @notice Act on an authenticated message.
428
+ /// @dev A report is for a conduit homed here. A bootstrap creates a receiver homed on the
429
+ /// authenticated origin, never on a chain the message states. Each decoder checks the
430
+ /// `Envelope` kind, so a wrong shape is refused by name rather than misread.
431
+ function _handleInbound(bytes32 origin, bytes calldata message) internal virtual {
432
+ if (Envelope.kindOf(message) == Envelope.RECEIVER_REPORT) {
433
+ (address reportOwner, bytes32 reportSalt, bytes memory interop) = Envelope.decodeReceiverReport(message);
434
+ _onDestinationReceiver(origin, reportOwner, reportSalt, interop);
435
+ return;
436
+ }
437
+
438
+ (address owner, bytes32 salt, bytes32 transmitter, Call[] memory calls) = Envelope.decodeBootstrap(message);
439
+ if (uint256(transmitter) >> 160 != 0) revert SourceTransmitterNotEvm(transmitter);
440
+ _bootstrapInbound(owner, salt, origin, address(uint160(uint256(transmitter))), calls);
441
+ }
442
+
443
+ /// @notice Stand up the receiver of a conduit homed on `home`, and run its first payload.
444
+ ///
445
+ /// @dev Never deferred: the receiver is created and its payload run in the delivery that
446
+ /// carries them, reachable only from an authenticated `_onInbound`.
447
+ ///
448
+ /// @dev Where this chain diverges, the home cannot derive the receiver, so it is reported.
449
+ /// Otherwise, if the home is `Predetermined`, the carried transmitter must be the one the
450
+ /// home's transceiver, as the registry records it, derives for this conduit; a home graded
451
+ /// below that (zkSync, Tron) has its transmitter elsewhere. The receiver sits at the
452
+ /// transmitter's address only when both chains' transceivers share a deployer.
453
+ function _bootstrapInbound(
454
+ address owner,
455
+ bytes32 salt,
456
+ bytes32 home,
457
+ address sourceTransmitter,
458
+ Call[] memory calls
459
+ ) internal {
460
+ address receiver = _createConduit(owner, salt, home, sourceTransmitter, calls);
461
+ if (_addressesDiverge) {
462
+ _reportReceiver(home, owner, salt, receiver);
463
+ } else if (_chainRegistry.provenanceFor(home) == Provenance.Predetermined) {
464
+ address expected = _conduitUnder(_evmCounterpartOn(home), owner, salt, home);
465
+ if (sourceTransmitter != expected) revert SourceTransmitterMismatch(expected, sourceTransmitter);
466
+ }
467
+ }
468
+
469
+ /* ================================== the report ================================= */
470
+
471
+ /// @notice Tell the conduit's home where its receiver landed.
472
+ /// @dev Paid from this contract's float, since it is nested in a delivery where
473
+ /// `msg.value` is zero. A failed send reverts the conduit creation with it, which
474
+ /// keeps the bootstrap retryable once the float is topped up.
475
+ function _reportReceiver(bytes32 home, address owner, bytes32 salt, address receiver) internal {
476
+ emit ReceiverReported(home, owner, salt, receiver);
477
+
478
+ bytes memory recipient = _recipientOn(home);
479
+ bytes memory payload = _reportPayload(owner, salt, receiver);
480
+
481
+ _reporting = true;
482
+ _sendMessage(recipient, payload, new bytes[](0), _quoteMessage(recipient, payload, new bytes[](0)));
483
+ _reporting = false;
484
+ }
485
+
486
+ /// @notice A destination reports where it created the receiver of a conduit homed here.
487
+ ///
488
+ /// @dev For chains this one cannot derive: Starknet's Pedersen derivation, and zkSync's and
489
+ /// Tron's CREATE2 formulas. Internal, so reachable only from an authenticated
490
+ /// `_onInbound`.
491
+ ///
492
+ /// @dev Written to the conduit, not the registry, because the transmitter is what
493
+ /// addresses that receiver and the registry is out of the send path. The registry
494
+ /// still decides which chains may report (`requiresReceiverCallback`), so a chain this
495
+ /// one derives cannot replace a `Predetermined` fact with a reported one.
496
+ ///
497
+ /// @dev The destination cannot choose the conduit: `chainKey` is authenticated and the
498
+ /// conduit is `_localConduit` of the stated pair, the derivation `bootstrap` checked the
499
+ /// caller against. The conduit refuses a second report.
500
+ ///
501
+ /// @dev The reported address must be on the reporting chain. An authenticated transceiver
502
+ /// can still report a wrong address on its own chain; the conduit keeps the first
503
+ /// one, so a compromised transceiver costs only its own chain.
504
+ /// @param interop Canonical ERC-7930 bytes for the receiver on the destination.
505
+ function _onDestinationReceiver(bytes32 chainKey, address owner, bytes32 salt, bytes memory interop) internal {
506
+ if (address(_chainRegistry) == address(0)) revert NoChainRegistry();
507
+ if (owner == address(0)) revert ZeroOwner();
508
+ if (!_chainRegistry.requiresReceiverCallback(chainKey)) {
509
+ revert ChainDoesNotReport(chainKey);
510
+ }
511
+
512
+ bytes32 reported = Erc7930.chainKey(interop);
513
+ if (reported != chainKey) revert ReportedChainMismatch(chainKey, reported);
514
+
515
+ address conduit = _localConduit(owner, salt);
516
+ IConduitReceiverReport(conduit).onDestinationReceiverReported(chainKey, Erc7930.parseStrict(interop).addr);
517
+ emit DestinationReceiverReported(chainKey, owner, salt, conduit);
518
+ }
519
+
520
+ /// @notice Whether sends may reach this provider's transceiver on `chainKey`: the registry has
521
+ /// marked it live.
522
+ /// @dev Asked by a transmitter, which holds no registry, before every send and quote. With no
523
+ /// registry it answers false.
524
+ function isLive(bytes32 chainKey) public view returns (bool) {
525
+ if (address(_chainRegistry) == address(0)) return false;
526
+ return _chainRegistry.isLive(chainKey, _messageProvider);
527
+ }
528
+
529
+ /// @notice Whether `chainKey` reports its receiver address back, rather than this
530
+ /// transceiver deriving it.
531
+ /// @dev True exactly where this contract cannot recompute an address (zkSync, Tron, every
532
+ /// non-EVM VM), from the same registry answer `_onDestinationReceiver` enforces. With
533
+ /// no registry it answers false; `_requireRoutable` then refuses the bootstrap. This
534
+ /// chain is refused first: a transmitter's bootstrap asks this before anything else,
535
+ /// and the registry need not list its own chain.
536
+ function reportsReceiver(bytes32 chainKey) public view returns (bool) {
537
+ if (chainKey == localChainKey) revert IsLocalChain(chainKey);
538
+ if (address(_chainRegistry) == address(0)) return false;
539
+ return _chainRegistry.requiresReceiverCallback(chainKey);
540
+ }
541
+ }