@crossecute/protocol 0.0.0 → 0.1.1

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,604 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {OutboundBase} from "./OutboundBase.sol";
5
+ import {RolesEnumerable} from "../Roles.sol";
6
+ import {ICommitFinalize, ICancel} from "../inbound/ReceiverBase.sol";
7
+ import {Executor} from "../Executor.sol";
8
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
9
+ import {ChainKey} from "../../addressing/ChainKey.sol";
10
+ import {Commitment} from "../Commitment.sol";
11
+ import {Payload} from "../Payload.sol";
12
+ import {Call} from "../Call.sol";
13
+ import {Erc7930} from "../../addressing/Erc7930.sol";
14
+ import {IERC7786GatewaySource} from "../IErc7786.sol";
15
+ import {DeliveryGas} from "../DeliveryGas.sol";
16
+
17
+ /// @title IConduitTransceiver
18
+ /// @notice Everything a conduit needs from the transceiver whose address it stores.
19
+ interface IConduitTransceiver {
20
+ function treasury() external view returns (address);
21
+
22
+ function bootstrap(
23
+ bytes32 destinationChainKey,
24
+ address owner,
25
+ bytes32 salt,
26
+ Call[] calldata calls,
27
+ bytes[] calldata attributes
28
+ ) external payable;
29
+
30
+ function bootstrapElements(
31
+ bytes32 destinationChainKey,
32
+ address owner,
33
+ bytes32 salt,
34
+ bytes[] calldata elements,
35
+ bytes[] calldata attributes
36
+ ) external payable;
37
+
38
+ function quoteBootstrap(
39
+ bytes32 destinationChainKey,
40
+ address owner,
41
+ bytes32 salt,
42
+ Call[] calldata calls,
43
+ bytes[] calldata attributes
44
+ ) external view returns (uint256);
45
+
46
+ function quoteBootstrapElements(
47
+ bytes32 destinationChainKey,
48
+ address owner,
49
+ bytes32 salt,
50
+ bytes[] calldata elements,
51
+ bytes[] calldata attributes
52
+ ) external view returns (uint256);
53
+
54
+ /// @notice The message the calling conduit sends itself, through its own transport, to stand
55
+ /// up on the chain `route` names. An empty `recipient` means this transceiver
56
+ /// carries the bootstrap, through `bootstrap` or `bootstrapElements`.
57
+ function conduitBootstrap(bytes calldata route, address owner, bytes32 salt, bytes calldata body)
58
+ external
59
+ view
60
+ returns (bytes memory recipient, bytes memory message);
61
+
62
+ /// @notice Whether a destination reports its receiver address back, rather than this
63
+ /// chain deriving it. False wherever Ethereum's CREATE2 holds; true on zkSync, Tron, and
64
+ /// every non-EVM VM. Asked here because a conduit holds no registry.
65
+ function reportsReceiver(bytes32 chainKey) external view returns (bool);
66
+
67
+ /// @notice The chain identifier the governor configured a destination under, including one
68
+ /// this conduit has not bootstrapped yet.
69
+ function routeTo(bytes32 chainKey) external view returns (bytes memory);
70
+
71
+ /// @notice Where this conduit's receiver will sit on a chain that does not report it.
72
+ function predictReceiver(bytes32 chainKey, address owner, bytes32 salt) external view returns (bytes memory);
73
+ }
74
+
75
+ /// @title TransmitterBase
76
+ /// @notice The per-user conduit on the home chain, and the source-side entry point. One
77
+ /// transmitter per protocol per user, routing to every destination.
78
+ ///
79
+ /// @dev The destination is a parameter, not state: one transmitter fans out to every chain,
80
+ /// with one receiver per destination.
81
+ ///
82
+ /// @dev Ownership is declared through `_checkOwner` and the modifier is
83
+ /// `onlyConduitOwner`, so a provider SDK that brings its own `onlyOwner` does not
84
+ /// collide. Every binding answers through `OwnableTransmitter`, whose inherited
85
+ /// `renounceOwnership` bricks the conduit, since every entry point is owner-gated. Left
86
+ /// available on purpose, so an owner can retire a conduit for good.
87
+ ///
88
+ /// @dev No registry pointer: chainKeys derive purely and the transceiver does the directory
89
+ /// lookups.
90
+ ///
91
+ /// @dev Commitments are hashed with the destination's chainKey, not this chain's:
92
+ /// `Commitment.hashCalls` seeds with the chain the receiver recomputes on.
93
+ abstract contract TransmitterBase is Initializable, OutboundBase, RolesEnumerable, Executor, IERC7786GatewaySource {
94
+ /// The local transceiver for this protocol, which carries every message out.
95
+ address public transceiver;
96
+ /// The caller-chosen half of this conduit's CREATE2 salt, stored because `bootstrap` must
97
+ /// state it and an address cannot be reversed into its salt.
98
+ bytes32 public conduitSalt;
99
+ /// The owner at creation, fixed for the conduit's life. It is the identity the CREATE2
100
+ /// salt and every receiver address derive from, so bootstrap and prediction use it and not
101
+ /// the current owner, which can change without moving any address.
102
+ address public deployer;
103
+
104
+ event TransmitterConfigured(address indexed owner, address indexed transceiver);
105
+ event DestinationBootstrapped(bytes32 indexed destinationChainKey);
106
+ /// @dev A destination reported an address this conduit could not derive. Distinct from
107
+ /// `CounterpartSet`, which also fires for the derived address at bootstrap.
108
+ event DestinationReceiverReported(bytes32 indexed destinationChainKey, bytes receiver);
109
+ /// @dev An execution the owner drove directly, as distinct from one a commitment discharged.
110
+ event Executed(address indexed caller, uint256 callCount);
111
+ event CrossecuteFeePaid(address indexed treasury, uint256 amount);
112
+
113
+ error FeeTransferFailed(address treasury, uint256 amount);
114
+
115
+ error NoTransceiver();
116
+ /// @dev The recipient is not the receiver this conduit recorded for that chain.
117
+ error RecipientIsNotThisConduit(bytes recipient);
118
+ /// @dev No receiver known on that chain, so a payload would fail on delivery after the fee
119
+ /// is spent.
120
+ error NotBootstrapped(bytes32 destinationChainKey);
121
+ /// @dev `Proxy` arms once, so a second bootstrap would pay a fee to revert on arrival.
122
+ error AlreadyBootstrapped(bytes32 destinationChainKey);
123
+ /// @dev Something that is not this conduit's transceiver tried to report a receiver.
124
+ error NotTransceiver(address caller);
125
+ /// @dev A receiver's address is reported once; a second report, replayed or hostile, is
126
+ /// refused. See `_receiverPinned`.
127
+ error ReceiverAlreadyReported(bytes32 destinationChainKey);
128
+ /// @dev `Call[]` is what an EVM receiver executes and nothing else decodes it.
129
+ error TypedPayloadToNonEvmDestination();
130
+ /// @dev This conduit's balance, including the call's `msg.value`, cannot cover the fee.
131
+ error InsufficientBalance(uint256 fee, uint256 balance);
132
+
133
+ /// @notice Whether a bootstrap to `destinationChainKey` has been dispatched.
134
+ ///
135
+ /// @dev Dispatched, not landed: on a parity chain no report ever comes back, so nothing
136
+ /// here could wait for confirmation. Delivery is retryable at the provider, so a
137
+ /// bootstrap that reverts on arrival is pending, not lost. See
138
+ /// [Failure handling](../../../../../docs/message-flow.md#failure-handling).
139
+ ///
140
+ /// @dev A permanently undeliverable bootstrap (a wrong route, a destination that never
141
+ /// accepts it) blocks any retry from here. Both causes are write-once transceiver
142
+ /// misconfiguration that strands the conduit anyway.
143
+ function isBootstrapped(bytes32 destinationChainKey) public view returns (bool) {
144
+ return _bootstrapDispatched[destinationChainKey];
145
+ }
146
+
147
+ /// @notice Whether this conduit has been stood up on an EVM chain, by plain chain id.
148
+ function isBootstrappedOn(uint256 destinationChainId) external view returns (bool) {
149
+ return _bootstrapDispatched[ChainKey.forEvm(destinationChainId)];
150
+ }
151
+
152
+ /// @notice Whether this conduit can be sent to on `destinationChainKey` yet.
153
+ /// @dev Equals `isBootstrapped` where the receiver's address is known at dispatch. On
154
+ /// zkSync, Tron, and every non-EVM VM it stays false until the destination's report lands.
155
+ function isReachable(bytes32 destinationChainKey) external view returns (bool) {
156
+ return hasCounterpart(destinationChainKey);
157
+ }
158
+
159
+ /// @notice This conduit's receiver on `destinationChainKey`, in that chain's own format.
160
+ function destinationReceiverOn(bytes32 destinationChainKey) external view returns (bytes memory) {
161
+ return counterpartOn(destinationChainKey);
162
+ }
163
+
164
+ /// Destinations whose receiver address has been reported and is now fixed.
165
+ ///
166
+ /// @dev Single-shot, with no owner override: the receiver address decides where a payload
167
+ /// lands. A wrong report is permanent for that destination, which requires the
168
+ /// transceiver on that chain to be compromised or misbuilt, losing the chain either way.
169
+ mapping(bytes32 destinationChainKey => bool) private _receiverPinned;
170
+
171
+ /// @notice Destinations this conduit has dispatched a bootstrap to.
172
+ /// @dev Separate from the counterpart table: on a reporting chain a bootstrap is recorded,
173
+ /// so a second cannot be paid for, while the destination stays unreachable until its
174
+ /// receiver is known.
175
+ mapping(bytes32 destinationChainKey => bool) private _bootstrapDispatched;
176
+
177
+ /// @notice The transceiver reports where the destination actually created this conduit's
178
+ /// receiver.
179
+ ///
180
+ /// @dev Recorded as the counterpart `sendMessage` checks against, since the registry is
181
+ /// off the send path.
182
+ ///
183
+ /// @dev The transceiver authenticated the origin chain and derived this conduit from the
184
+ /// report's `(owner, salt)`, so it cannot aim a report at a conduit the reporting
185
+ /// chain did not name. It can report a wrong address for a real conduit on its own
186
+ /// chain, which is permanent (see `_receiverPinned`).
187
+ function onDestinationReceiverReported(bytes32 destinationChainKey, bytes calldata receiver) external {
188
+ if (msg.sender != transceiver) revert NotTransceiver(msg.sender);
189
+ // The dispatch is what a report answers: on a reporting chain there is no
190
+ // counterpart until it arrives.
191
+ if (!_bootstrapDispatched[destinationChainKey]) {
192
+ revert NotBootstrapped(destinationChainKey);
193
+ }
194
+ if (_receiverPinned[destinationChainKey]) {
195
+ revert ReceiverAlreadyReported(destinationChainKey);
196
+ }
197
+
198
+ _receiverPinned[destinationChainKey] = true;
199
+ _setCounterpart(destinationChainKey, receiver);
200
+ emit DestinationReceiverReported(destinationChainKey, receiver);
201
+ }
202
+
203
+ /// @notice Whether a receiver report for this destination has been accepted.
204
+ function isReceiverPinned(bytes32 destinationChainKey) external view returns (bool) {
205
+ return _receiverPinned[destinationChainKey];
206
+ }
207
+
208
+ function _requireBootstrapped(bytes32 chainKey) private view {
209
+ if (!hasCounterpart(chainKey)) revert NotBootstrapped(chainKey);
210
+ }
211
+
212
+ function _requireNotBootstrapped(bytes32 chainKey) private view {
213
+ if (_bootstrapDispatched[chainKey]) revert AlreadyBootstrapped(chainKey);
214
+ }
215
+
216
+ /// @notice Reverts unless the caller is the owner. Declared, not implemented.
217
+ function _checkOwner() internal view virtual;
218
+
219
+ modifier onlyConduitOwner() {
220
+ _checkOwner();
221
+ _;
222
+ }
223
+
224
+ /* ================================== send =================================== */
225
+
226
+ /// @notice Put a payload on the wire for this conduit's receiver on another chain.
227
+ ///
228
+ /// @dev The ERC-7786 source entry point, and the only send: the recipient is an ERC-7930
229
+ /// address carrying its own chain.
230
+ ///
231
+ /// @dev A built `bytes` payload cannot be asked whether it holds `Call[]` or opaque
232
+ /// elements, so pairing it with the right destination type is the caller's job;
233
+ /// `payloadForCalls` and `payloadForElements` build it. `bootstrap` decodes its body
234
+ /// in the destination's form instead.
235
+ ///
236
+ /// @dev The recipient must be this conduit's recorded receiver on that chain; see
237
+ /// `_requireOwnRecipient`.
238
+ ///
239
+ /// @dev Priced here, in the same call, and paid from this conduit's balance, so the owner
240
+ /// approves a payload rather than a payload and a price. `msg.value` only tops the
241
+ /// balance up.
242
+ ///
243
+ /// @return sendId Zero when the gateway has taken the message. A binding that returns
244
+ /// non-zero has a second step to perform and says so in its own NatSpec.
245
+ function sendMessage(bytes calldata recipient, bytes calldata payload, bytes[] calldata attributes)
246
+ external
247
+ payable
248
+ onlyConduitOwner
249
+ returns (bytes32 sendId)
250
+ {
251
+ (uint256 providerFee, uint256 crossecuteFee) = quoteMessageBreakdown(recipient, payload, attributes);
252
+ uint256 fee = providerFee + crossecuteFee;
253
+ _requireFunded(fee);
254
+ _payCrossecuteFee(crossecuteFee);
255
+
256
+ emit MessageSent(
257
+ bytes32(0), Erc7930.encodeEvm(block.chainid, address(this)), recipient, payload, fee, attributes
258
+ );
259
+ return _sendMessage(recipient, payload, attributes, providerFee);
260
+ }
261
+
262
+ /// @dev Collected outside the owner-controlled conduit and the transceiver's report float.
263
+ /// A provider revert unwinds this payment with the rest of the send.
264
+ function _payCrossecuteFee(uint256 fee) private {
265
+ if (fee == 0) return;
266
+ address treasury = IConduitTransceiver(transceiver).treasury();
267
+ if (treasury == address(0)) revert FeeTransferFailed(treasury, fee);
268
+ emit CrossecuteFeePaid(treasury, fee);
269
+ // forge-lint: disable-next-line(arbitrary-send-eth) the transceiver's write-once treasury
270
+ (bool ok,) = treasury.call{value: fee}("");
271
+ if (!ok) revert FeeTransferFailed(treasury, fee);
272
+ }
273
+
274
+ function _crossecuteFee(uint256 providerFee) internal pure virtual returns (uint256) {
275
+ return providerFee / 10;
276
+ }
277
+
278
+ /// @notice Whether this conduit understands a per-send attribute. Required by ERC-7786
279
+ /// and answered by the binding; none here.
280
+ function supportsAttribute(bytes4) external view virtual returns (bool) {
281
+ return false;
282
+ }
283
+
284
+ /// @notice The recipient `sendMessage` takes for this conduit on `destinationChainId`: its
285
+ /// recorded receiver there, as an ERC-7930 address. Reverts until the receiver is
286
+ /// known.
287
+ /// @dev Not this conduit's own address: the two differ wherever this chain or the
288
+ /// destination derives addresses differently from Ethereum.
289
+ function recipientOn(uint256 destinationChainId) public view returns (bytes memory) {
290
+ bytes32 chainKey = ChainKey.forEvm(destinationChainId);
291
+ _requireBootstrapped(chainKey);
292
+ return _recipientOn(chainKey);
293
+ }
294
+
295
+ /// @notice The ERC-7930 chain identifier for an EVM chain: `bootstrapPayload`'s first
296
+ /// argument, and the value a route is configured under.
297
+ /// @dev `Erc7930` is internal-only, so off-chain callers need this builder to reach the
298
+ /// `bytes` entry points. It names a chain, where `recipientOn` names a conduit on one.
299
+ function chainIdentifierFor(uint256 destinationChainId) public pure returns (bytes memory) {
300
+ return _evmIdentifier(destinationChainId);
301
+ }
302
+
303
+ /// @notice The wire bytes for an EVM destination, which decodes `Call[]`.
304
+ function payloadForCalls(Call[] calldata calls) public pure returns (bytes memory) {
305
+ return Payload.encodeCalls(calls);
306
+ }
307
+
308
+ /// @notice The wire bytes for a destination whose calls this chain cannot express.
309
+ function payloadForElements(bytes[] calldata elements) public pure returns (bytes memory) {
310
+ return Payload.encodeElements(elements);
311
+ }
312
+
313
+ /// @notice The recipient must be the receiver this conduit recorded for that chain.
314
+ /// Returns the chainKey so nothing parses twice.
315
+ ///
316
+ /// @dev Compares against the stored counterpart, not `address(this)`: the receiver is at
317
+ /// this address only where Ethereum's CREATE2 holds, not on zkSync, Tron, or a non-EVM
318
+ /// chain. The whole recipient is compared, so the right conduit on the wrong chain is
319
+ /// refused too.
320
+ ///
321
+ /// @dev Checks bootstrap first so an unreached destination reverts `NotBootstrapped`
322
+ /// rather than `NoRouteFor` from `_recipientOn`. A stored receiver means the bootstrap
323
+ /// passed the registry's live check, which is never undone, so sends are not checked again.
324
+ function _requireOwnRecipient(bytes calldata recipient) private view returns (bytes32 chainKey) {
325
+ if (recipient.length == 0) revert NoDestination();
326
+
327
+ chainKey = ChainKey.fromIdentifier(recipient);
328
+ _requireBootstrapped(chainKey);
329
+
330
+ if (keccak256(recipient) != keccak256(_recipientOn(chainKey))) {
331
+ revert RecipientIsNotThisConduit(recipient);
332
+ }
333
+ }
334
+
335
+ /* ================================= bootstrap =============================== */
336
+
337
+ /// @notice Stand this conduit up on a chain that has none, and run a payload there.
338
+ ///
339
+ /// @dev Path B: with no receiver yet, the message goes to the transceiver on that chain.
340
+ /// It passes the owner and salt, from which the destination derives this conduit's
341
+ /// address; the transceiver checks they resolve to `msg.sender`. Where the transceiver
342
+ /// answers `conduitBootstrap` instead, this conduit sends that message through its own
343
+ /// transport, as `sendMessage` would.
344
+ ///
345
+ /// @dev `payload` is `bootstrapPayload(destinationChainIdentifier, body)`. The body is
346
+ /// decoded in the one form the destination's chain type implies (see `Payload`), so
347
+ /// what reaches the transceiver is always in that form.
348
+ ///
349
+ /// @dev Pays `_quoteBootstrap` from this conduit's balance, as `sendMessage` does. `quoteBootstrap` takes the same arguments, for
350
+ /// funding the balance ahead of time. Pass empty `attributes` for the gateway's default.
351
+ function bootstrap(bytes calldata payload, bytes[] calldata attributes) external payable onlyConduitOwner {
352
+ (bytes32 chainKey, bytes memory route, bool typed, bytes memory body) = _decodeBootstrap(payload);
353
+ _markBootstrapped(chainKey, route);
354
+ _sendBootstrap(chainKey, route, typed, body, attributes);
355
+ }
356
+
357
+ /// @notice The payload `bootstrap` takes: the destination's ERC-7930 chain identifier, and
358
+ /// the body its receiver runs on creation, built by `payloadForCalls` for an EVM
359
+ /// chain and `payloadForElements` for any other.
360
+ function bootstrapPayload(bytes calldata destinationChainIdentifier, bytes calldata body)
361
+ public
362
+ pure
363
+ returns (bytes memory)
364
+ {
365
+ return Payload.encodeBootstrap(destinationChainIdentifier, body);
366
+ }
367
+
368
+ /* ================================== quote ================================== */
369
+
370
+ /// @notice What `sendMessage` would cost, in this chain's native currency.
371
+ /// @dev Takes `sendMessage`'s arguments and applies its checks, so a quote never succeeds
372
+ /// for a send that would be refused. Ungated, so the balance can be sized off-chain.
373
+ function quoteMessage(bytes calldata recipient, bytes calldata payload, bytes[] calldata attributes)
374
+ external
375
+ view
376
+ override
377
+ returns (uint256 nativeFee)
378
+ {
379
+ (uint256 providerFee, uint256 crossecuteFee) = quoteMessageBreakdown(recipient, payload, attributes);
380
+ return providerFee + crossecuteFee;
381
+ }
382
+
383
+ /// @notice The provider quote and Crossecute fee, in source-chain native currency.
384
+ /// @dev Applies the same recipient and payload checks as `sendMessage`.
385
+ function quoteMessageBreakdown(bytes calldata recipient, bytes calldata payload, bytes[] calldata attributes)
386
+ public
387
+ view
388
+ returns (uint256 providerFee, uint256 crossecuteFee)
389
+ {
390
+ _requireOwnRecipient(recipient);
391
+ if (payload.length == 0) revert EmptyPayload();
392
+ providerFee = _quoteMessage(recipient, payload, attributes);
393
+ crossecuteFee = _crossecuteFee(providerFee);
394
+ }
395
+
396
+ /// @notice What `bootstrap` would cost, in this chain's native currency.
397
+ /// @dev Decoded, checked and priced as `bootstrap` does it, through `_quoteBootstrap`. The
398
+ /// `(owner, salt)` check is not applied: a quote spends nothing and may be taken before
399
+ /// the conduit exists.
400
+ function quoteBootstrap(bytes calldata payload, bytes[] calldata attributes)
401
+ external
402
+ view
403
+ returns (uint256 nativeFee)
404
+ {
405
+ (bytes32 chainKey, bytes memory route, bool typed, bytes memory body) = _decodeBootstrap(payload);
406
+ (nativeFee,,) = _quoteBootstrap(chainKey, route, typed, body, attributes);
407
+ }
408
+
409
+ /* =============================== shared code =============================== */
410
+
411
+ /// @dev A `uint256` chain id is an `eip155` reference by construction.
412
+ function _evmIdentifier(uint256 chainId) private pure returns (bytes memory) {
413
+ if (chainId == 0) revert NoDestination();
414
+ return Erc7930.encodeEvmChain(chainId);
415
+ }
416
+
417
+ /// @dev Everything `bootstrap` and `quoteBootstrap` read from a payload and check before
418
+ /// pricing it. The identifier is reduced to the bare chain identifier the route table
419
+ /// holds, so a conduit envelope names the same chain. `typed` is whether the
420
+ /// destination decodes `Call[]`.
421
+ function _decodeBootstrap(bytes calldata payload)
422
+ private
423
+ view
424
+ returns (bytes32 chainKey, bytes memory route, bool typed, bytes memory body)
425
+ {
426
+ if (transceiver == address(0)) revert NoTransceiver();
427
+ bytes memory identifier;
428
+ (identifier, body) = Payload.decodeBootstrap(payload);
429
+ if (identifier.length == 0) revert NoDestination();
430
+ if (body.length == 0) revert EmptyPayload();
431
+
432
+ Erc7930.Interop memory io = Erc7930.parseStrict(identifier);
433
+ route = Erc7930.encodeChainId(io.chainType, io.chainRef);
434
+ typed = io.chainType == Erc7930.CT_EIP155;
435
+ chainKey = keccak256(route);
436
+ _requireNotBootstrapped(chainKey);
437
+ }
438
+
439
+ /// @notice What the bootstrap to `chainKey` costs, as `bootstrap` and `quoteBootstrap` both
440
+ /// price it, and the message this conduit sends when the transceiver hands one back.
441
+ /// `body` is `Call[]` when `typed`, opaque elements otherwise.
442
+ /// @dev Runs after `_markBootstrapped` on a send and without it on a quote, so nothing it
443
+ /// reaches may read what that records.
444
+ function _quoteBootstrap(
445
+ bytes32 chainKey,
446
+ bytes memory route,
447
+ bool typed,
448
+ bytes memory body,
449
+ bytes[] calldata attributes
450
+ ) private view returns (uint256 fee, bytes memory recipient, bytes memory message) {
451
+ IConduitTransceiver t = IConduitTransceiver(transceiver);
452
+ (recipient, message) = t.conduitBootstrap(route, deployer, conduitSalt, body);
453
+ if (recipient.length != 0) {
454
+ fee = _quoteMessage(recipient, message, attributes);
455
+ } else if (typed) {
456
+ fee = t.quoteBootstrap(chainKey, deployer, conduitSalt, Payload.decodeCallsFromMemory(body), attributes);
457
+ } else {
458
+ fee = t.quoteBootstrapElements(
459
+ chainKey, deployer, conduitSalt, Payload.decodeElementsFromMemory(body), attributes
460
+ );
461
+ }
462
+ }
463
+
464
+ /// @notice Send the bootstrap to `chainKey`, paying its quote from this balance.
465
+ /// @dev Runs after `_markBootstrapped`, so a re-entrant second bootstrap meets the record.
466
+ function _sendBootstrap(
467
+ bytes32 chainKey,
468
+ bytes memory route,
469
+ bool typed,
470
+ bytes memory body,
471
+ bytes[] calldata attributes
472
+ ) private {
473
+ (uint256 fee, bytes memory recipient, bytes memory message) =
474
+ _quoteBootstrap(chainKey, route, typed, body, attributes);
475
+ _requireFunded(fee);
476
+ if (recipient.length != 0) {
477
+ _sendMessage(recipient, message, attributes, fee);
478
+ return;
479
+ }
480
+
481
+ IConduitTransceiver t = IConduitTransceiver(transceiver);
482
+ // forge-lint: disable-start(arbitrary-send-eth) the write-once transceiver
483
+ if (typed) {
484
+ t.bootstrap{value: fee}(chainKey, deployer, conduitSalt, Payload.decodeCallsFromMemory(body), attributes);
485
+ } else {
486
+ t.bootstrapElements{value: fee}(
487
+ chainKey, deployer, conduitSalt, Payload.decodeElementsFromMemory(body), attributes
488
+ );
489
+ }
490
+ // forge-lint: disable-end(arbitrary-send-eth)
491
+ }
492
+
493
+ /// @dev Sends and bootstraps spend this balance, including any attached top-up.
494
+ function _requireFunded(uint256 fee) private view {
495
+ if (address(this).balance < fee) revert InsufficientBalance(fee, address(this).balance);
496
+ }
497
+
498
+ /// @dev Records the destination before the transport is called: that call reaches a
499
+ /// provider endpoint and arbitrary code, so a re-entrant second bootstrap meets the
500
+ /// record. A revert unwinds it with everything else.
501
+ ///
502
+ /// @dev Records the receiver only where its address is already known (the chain does not
503
+ /// report), as the transceiver predicts it there: this conduit's own address only when
504
+ /// both chains use Ethereum's CREATE2. On a reporting chain the destination stays
505
+ /// unreachable until the report arrives, rather than addressed at a guess.
506
+ function _markBootstrapped(bytes32 chainKey, bytes memory route) private {
507
+ _bootstrapDispatched[chainKey] = true;
508
+ _setRoute(chainKey, route);
509
+
510
+ IConduitTransceiver t = IConduitTransceiver(transceiver);
511
+ if (!t.reportsReceiver(chainKey)) {
512
+ _setCounterpart(chainKey, t.predictReceiver(chainKey, deployer, conduitSalt));
513
+ }
514
+
515
+ emit DestinationBootstrapped(chainKey);
516
+ }
517
+
518
+ /* ================================= execute ================================= */
519
+
520
+ /// @notice Run a payload on this chain, with no bridge and no commitment.
521
+ ///
522
+ /// @dev Runs the calls itself: the conduit homed here is its transmitter, so there is no
523
+ /// receiver at home. Both ends share `Executor`'s loop, policy check, and
524
+ /// all-or-nothing rule.
525
+ ///
526
+ /// @dev Payable; value passes to the calls, and anything unspent stays at this
527
+ /// owner-controlled address.
528
+ function execute(Call[] calldata calls) external payable onlyConduitOwner {
529
+ if (calls.length == 0) revert EmptyExecution();
530
+ emit Executed(msg.sender, calls.length);
531
+ _execute(calls);
532
+ }
533
+
534
+ /* ============================== payload helpers ============================ */
535
+
536
+ /// @notice The call that pins `commitment` on a receiver, for inclusion in a payload
537
+ /// bound for that receiver's chain.
538
+ /// @dev Committing is a call, not a message kind: a payload whose single element is this
539
+ /// stores the hash on arrival, and anyone later supplies the array to `finalize`. The
540
+ /// receiver accepts it as a self-call from `_execute`.
541
+ function commitmentCall(address receiver, bytes32 commitment) public pure returns (Call memory) {
542
+ return Call({target: receiver, value: 0, data: abi.encodeCall(ICommitFinalize.commit, (commitment))});
543
+ }
544
+
545
+ /// @notice The call that withdraws an approval on a receiver, for inclusion in a payload
546
+ /// bound for that receiver's chain.
547
+ /// @dev Names the approval by hash, so it cannot go stale in transit: the approval either
548
+ /// still exists or the call reverts.
549
+ function cancellationCall(address receiver, bytes32 commitment) public pure returns (Call memory) {
550
+ return Call({target: receiver, value: 0, data: abi.encodeCall(ICancel.cancel, (commitment))});
551
+ }
552
+
553
+ /* ================================= preview ================================= */
554
+
555
+ /// @notice The commitment a payload will need on one EVM destination.
556
+ ///
557
+ /// @dev EVM destinations only. Every `Call[]` chain hashes with keccak256, which
558
+ /// `ReceiverBase` enforces from bytecode frozen alongside this, so the answer cannot go
559
+ /// stale. Non-EVM destinations are previewed through `ChainRegistry.commitmentFor`,
560
+ /// whose per-chain plugins can grow after this conduit is frozen.
561
+ ///
562
+ /// @dev `pure`, so it runs off-chain against the exact array the signers reviewed.
563
+ function commitmentFor(uint256 destinationChainId, Call[] memory calls) public pure returns (bytes32) {
564
+ return Commitment.hashCalls(ChainKey.forEvm(destinationChainId), calls);
565
+ }
566
+
567
+ /// @notice `commitmentFor`, for an EVM destination named by its ERC-7930 identifier.
568
+ /// @dev Refuses a non-EVM identifier; `ChainRegistry.commitmentFor` answers those.
569
+ function commitmentForChain(bytes calldata destinationChainIdentifier, Call[] memory calls)
570
+ public
571
+ pure
572
+ returns (bytes32)
573
+ {
574
+ if (!Payload.isTypedDestination(destinationChainIdentifier)) {
575
+ revert TypedPayloadToNonEvmDestination();
576
+ }
577
+ return Commitment.hashCalls(ChainKey.fromIdentifier(destinationChainIdentifier), calls);
578
+ }
579
+
580
+ /// @notice Accept ETH: the balance every send and bootstrap is paid from, and where a
581
+ /// provider's refund lands. Without `receive` a refund would revert the send.
582
+ receive() external payable {}
583
+
584
+ /// @inheritdoc OutboundBase
585
+ /// @dev Path A: this conduit paid from its own balance. Path B refunds come here too, as
586
+ /// the transceiver's caller.
587
+ function _refundTo() internal view override returns (address) {
588
+ return address(this);
589
+ }
590
+
591
+ /// @inheritdoc OutboundBase
592
+ function _defaultGas(bytes memory) internal pure override returns (uint256) {
593
+ return DeliveryGas.PAYLOAD;
594
+ }
595
+
596
+ function __TransmitterBase_init(address owner_, address transceiver_, bytes32 salt_) internal onlyInitializing {
597
+ if (transceiver_ == address(0)) revert NoTransceiver();
598
+
599
+ transceiver = transceiver_;
600
+ conduitSalt = salt_;
601
+ deployer = owner_;
602
+ emit TransmitterConfigured(owner_, transceiver_);
603
+ }
604
+ }