@crossecute/protocol 0.0.0-stage → 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 +26 -2
  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 +36 -4
@@ -0,0 +1,307 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {OutboundBase} from "../outbound/OutboundBase.sol";
5
+ import {Roles} from "../Roles.sol";
6
+ import {Envelope} from "../Envelope.sol";
7
+ import {DeliveryGas} from "../DeliveryGas.sol";
8
+ import {ChainKey} from "../../addressing/ChainKey.sol";
9
+ import {Erc7930} from "../../addressing/Erc7930.sol";
10
+ import {IChainRegistryRefs} from "../../registry/IChainRegistryRefs.sol";
11
+ import {Provenance} from "../../registry/Provenance.sol";
12
+ import {IBootstrapAllowlist} from "./IBootstrapAllowlist.sol";
13
+ import {ProxyInitCode} from "../../conduit/ProxyInitCode.sol";
14
+ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
15
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
16
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
17
+
18
+ /// @notice What a transceiver needs from the transmitter logic it arms a conduit with.
19
+ interface ITransmitterInit {
20
+ function initialize(address owner, address transceiver, bytes32 salt) external;
21
+ }
22
+
23
+ /// @notice The one thing a transceiver tells a conduit after creating it.
24
+ /// @dev The single exception to a transceiver having no authority over a conduit: the
25
+ /// conduit cannot learn its receiver's address on a chain this one cannot derive, and
26
+ /// only its transceiver authenticates the message carrying it.
27
+ interface IConduitReceiverReport {
28
+ function onDestinationReceiverReported(bytes32 chainKey, bytes calldata receiver) external;
29
+ }
30
+
31
+ /// @notice What a transceiver is configured with, once, at initialization.
32
+ /// @dev A struct, not loose arguments: initializers sit near the stack limit at `paris`.
33
+ struct TransceiverConfig {
34
+ /// The only chance to name gateways (see `Roles.grantRole`).
35
+ address[] gateways;
36
+ address transmitterImplementation;
37
+ address receiverImplementation;
38
+ /// The governor's conduit, named by its owner, salt, and home: its transmitter
39
+ /// on that home, its receiver everywhere else. It owns this transceiver.
40
+ address governorOwner;
41
+ bytes32 governorSalt;
42
+ /// The governor's home as an ERC-7930 chain identifier, which becomes its route here.
43
+ bytes governorHome;
44
+ /// Where bootstrap fees go and the report float leaves to. Write-once.
45
+ address treasury;
46
+ /// The routing `setRouting` would set, so that on any chain but the governor's home this
47
+ /// transceiver can accept the bootstrap that creates its owner; the home must be
48
+ /// `Predetermined` and not suspended. Zero leaves it to the owner, which only the governor's
49
+ /// home can do.
50
+ IChainRegistryRefs chainRegistry;
51
+ bytes32 messageProvider;
52
+ Provenance minCounterpartProvenance;
53
+ }
54
+
55
+ /// @title TransceiverCore
56
+ /// @notice The state, events, errors, and internal logic a transceiver's two contracts share:
57
+ /// `TransceiverBase`, which holds the paths every message takes, and
58
+ /// `TransceiverExtension`, which holds initialization, owner configuration, and views and
59
+ /// runs by `DELEGATECALL` from `TransceiverBase`'s `fallback`.
60
+ ///
61
+ /// @dev Both inherit this contract and nothing else that declares sequential storage before it,
62
+ /// so the fields below sit at the same slots in each (R8.2). A provider's own fields come
63
+ /// after them and are never touched by the extension.
64
+ abstract contract TransceiverCore is Initializable, OutboundBase, Roles, OwnableUpgradeable {
65
+ /// This chain's chainKey: the home of every conduit this transceiver creates as a
66
+ /// transmitter.
67
+ /// @dev An immutable of each implementation, fixed when it is deployed on this chain, so a
68
+ /// chain split cannot move a conduit's derivation. `TransceiverBase` refuses an
69
+ /// extension whose value differs from its own.
70
+ bytes32 public immutable localChainKey;
71
+
72
+ /// @notice The initcode hash every crossecute conduit deploys from, for transmitters and
73
+ /// receivers alike. See `Proxy` for why it has no constructor arguments.
74
+ /// @dev An input to Ethereum's CREATE2 formula only. zkSync deploys from a zksolc artifact
75
+ /// hash instead; see `_predictConduit`.
76
+ /// @dev Immutable, not constant: a constant is evaluated at runtime, which would put the
77
+ /// initcode in every runtime that reads this (#70); an immutable puts it in initcode only.
78
+ /// Computed rather than written out so a build with other compiler settings, such as
79
+ /// `forge coverage`'s unoptimized one, derives the conduits it deploys.
80
+ bytes32 public immutable PROXY_INIT_CODE_HASH = ProxyInitCode.hash();
81
+
82
+ /// The transmitter logic every conduit homed here is armed with. Write-once: conduits
83
+ /// lock when armed, so a change would split them into two logic versions by creation time.
84
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
85
+ address internal _transmitterImplementation;
86
+ /// The receiver logic every conduit homed elsewhere is armed with. Write-once.
87
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
88
+ address internal _receiverImplementation;
89
+
90
+ /// Whether conduit addresses here differ from Ethereum's CREATE2. When true, every receiver
91
+ /// created here is reported to its home, which cannot derive it. Write-once, and set by the
92
+ /// contract rather than its caller, so it cannot disagree with `_predictConduit`.
93
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
94
+ bool internal _addressesDiverge;
95
+
96
+ /// The registry this transceiver reads provenance and derivations from.
97
+ IChainRegistryRefs internal _chainRegistry;
98
+ /// keccak256 of this transceiver's message provider name, e.g. "layerzero".
99
+ bytes32 internal _messageProvider;
100
+ /// The weakest counterpart provenance this transceiver will send to or accept from.
101
+ /// @dev A parity-chain counterpart is `Predetermined`. Solana, Sui, Aptos, Starknet, zkSync,
102
+ /// and Tron reach only `Unique`, so this decides whether this transceiver talks to them.
103
+ Provenance internal _minCounterpartProvenance;
104
+
105
+ /// chainKey => what standing a conduit up there costs, on top of the message fee.
106
+ /// @dev Pays for the return leg on a chain that has one: a diverging chain reports each
107
+ /// conduit home from its float. Zero by default, so parity destinations, which never
108
+ /// report, pay nothing. The fee lands in this chain's treasury in this chain's
109
+ /// currency; the float there is funded out of band.
110
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
111
+ mapping(bytes32 => uint256) internal _bootstrapFee;
112
+
113
+ /// Where a bootstrap fee goes, the moment it is charged, and the only address the report
114
+ /// float leaves to. Write-once.
115
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
116
+ address internal _treasury;
117
+
118
+ /// True only while a receiver report is being sent, which pays from this contract's float.
119
+ bool internal _reporting;
120
+
121
+ /// May set bootstrap fees besides the owner. The owner is the governor's conduit, which off
122
+ /// the governor's home acts only through a cross-chain message, so this lets a local key
123
+ /// price or halt bootstraps (#78). Set and replaced by the owner.
124
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverExtension`
125
+ address internal _operator;
126
+
127
+ /// Where set, the only conduits that may bootstrap or be quoted one. Set by the owner; zero
128
+ /// lets every conduit.
129
+ // forge-lint: disable-next-line(unused-state-variables) read only by `TransceiverBase` and `TransceiverExtension`
130
+ IBootstrapAllowlist internal _bootstrapAllowlist;
131
+
132
+ event ConduitCreated(address indexed owner, address indexed conduit, bytes32 salt, bytes32 homeChainKey);
133
+ /// @dev Path B's record, since a transceiver is not an ERC-7786 gateway source and emits
134
+ /// no `MessageSent`. `(chainKey, owner, salt)` identifies the conduit.
135
+ event BootstrapSent(bytes32 indexed destinationChainKey, address indexed owner, bytes32 salt);
136
+ event TransmitterImplementationSet(address implementation);
137
+ event ReceiverImplementationSet(address implementation);
138
+ event AddressesDivergeSet(bool addressesDiverge);
139
+ event RoutingSet(address chainRegistry, bytes32 messageProvider, Provenance minCounterpartProvenance);
140
+ event BootstrapFeeSet(bytes32 indexed chainKey, uint256 fee);
141
+ event OperatorSet(address operator);
142
+ event BootstrapAllowlistSet(address allowlist);
143
+ event BootstrapFeePaid(bytes32 indexed chainKey, address indexed to, uint256 amount);
144
+ event DestinationReceiverReported(bytes32 indexed chainKey, address indexed owner, bytes32 salt, address conduit);
145
+ event ReceiverReported(bytes32 indexed home, address indexed owner, bytes32 salt, address receiver);
146
+ event Withdrawn(address indexed to, uint256 amount);
147
+
148
+ error ZeroOwner();
149
+ /// @dev The caller is not the conduit `(owner, salt)` resolves to.
150
+ error NotTheConduit(address owner, bytes32 salt, address caller);
151
+ error NoConduitImplementation();
152
+ error NoTreasury();
153
+ /// @dev The counterpart on that chain is not a 20-byte EVM address, so no EVM CREATE2
154
+ /// prediction applies there.
155
+ error CounterpartNotEvm(bytes32 chainKey);
156
+ error NoChainRegistry();
157
+ /// @dev The registry holds no deployment record for this provider.
158
+ error NoProviderDeployment();
159
+ /// @dev The route resolved to a known chain, but the sender is not its counterpart.
160
+ error NotCounterpart(bytes32 chainKey);
161
+ /// @dev The registry has suspended the chain.
162
+ error ChainSuspended(bytes32 chainKey);
163
+ /// @dev The chain's grade is below this transceiver's bar.
164
+ error InsufficientCounterpartProvenance(bytes32 chainKey, Provenance grade);
165
+ /// @dev Re-pointing a counterpart would redirect the destination; moving one is a redeploy.
166
+ error CounterpartAlreadySet(bytes32 chainKey);
167
+ /// @dev A `Predetermined` chain's counterpart is `_parityAddress`, in use before any is
168
+ /// recorded, so a recorded one may only restate it.
169
+ error CounterpartNotParity(bytes32 chainKey);
170
+ /// @dev The derivation inputs are not the ones this transaction approved. See
171
+ /// `resolveCounterpart`.
172
+ error ParamsCommitmentMismatch(bytes32 chainKey);
173
+ /// @dev A chain reported an address that says it lives somewhere else.
174
+ error ReportedChainMismatch(bytes32 authenticated, bytes32 reported);
175
+ /// @dev A chain whose addresses this contract can recompute tried to report one; the
176
+ /// derivation outranks the claim.
177
+ error ChainDoesNotReport(bytes32 chainKey);
178
+ /// @dev The registry or provider id is already set to a different value. See `setRouting`.
179
+ error RoutingAlreadySet();
180
+ /// @dev This chain is never a destination or an origin: a conduit homed here is its
181
+ /// transmitter, and a receiver here would collide with it.
182
+ error IsLocalChain(bytes32 chainKey);
183
+ /// @dev The caller sent less than the destination's fee.
184
+ error InsufficientBootstrapFee(uint256 required, uint256 provided);
185
+ /// @dev The destination reports its receivers, and its bootstrap fee is unset.
186
+ error NoBootstrapFee(bytes32 chainKey);
187
+ /// @dev The registry has not marked the provider's transceiver on that chain live.
188
+ error DestinationNotLive(bytes32 chainKey);
189
+ error FeeTransferFailed(address to, uint256 amount);
190
+ /// @dev An EVM receiver can only answer to an EVM transmitter.
191
+ error SourceTransmitterNotEvm(bytes32 transmitter);
192
+ /// @dev The bootstrap carries a transmitter other than the one the home's transceiver derives
193
+ /// for this conduit, so the origin's provider id, route, or transceiver address disagree
194
+ /// with this chain's. Refused on the first bootstrap rather than leaving every such
195
+ /// conduit unreachable.
196
+ error SourceTransmitterMismatch(address expected, address carried);
197
+ error NotTreasury(address caller);
198
+ error NotOwnerOrOperator(address caller);
199
+ error NotAllowed(address conduit);
200
+ error WithdrawFailed(uint256 amount);
201
+
202
+ constructor() {
203
+ localChainKey = ChainKey.local();
204
+ }
205
+
206
+ /* ============================== conduit derivation ============================= */
207
+
208
+ /// @notice The salt an owner's conduit deploys at, on every chain.
209
+ /// @dev The owner is the identity every chain names; the salt lets one owner hold several
210
+ /// conduits; the home separates the same owner and salt homed on two chains, whose
211
+ /// transmitter on one would otherwise sit where the other's receiver lands. The home
212
+ /// is its own field: adding or XORing it into `salt` would let one owner make two
213
+ /// homes collide. `abi.encode` is fixed-width, so no two triples collide.
214
+ function _conduitSalt(address owner, bytes32 salt, bytes32 homeChainKey) internal pure returns (bytes32) {
215
+ return keccak256(abi.encode(owner, salt, homeChainKey));
216
+ }
217
+
218
+ /// @notice Where an owner's conduit lives on this chain, before it exists.
219
+ /// @dev Ethereum's CREATE2, identical wherever the formula holds since all three inputs
220
+ /// are: this address (one per provider on every chain), the salt, and a constant
221
+ /// initcode. zkSync and Tron derive differently, so a variant there overrides this,
222
+ /// in both its `TransceiverBase` and its extension, and `_deployConduit` with it;
223
+ /// `_createConduit` checks the two agree.
224
+ function _predictConduit(address owner, bytes32 salt, bytes32 homeChainKey)
225
+ internal
226
+ view
227
+ virtual
228
+ returns (address)
229
+ {
230
+ return Create2.computeAddress(_conduitSalt(owner, salt, homeChainKey), PROXY_INIT_CODE_HASH, address(this));
231
+ }
232
+
233
+ /// @notice `(owner, salt)`'s conduit homed on this chain: its transmitter here.
234
+ /// @dev One expression for the bootstrap caller check, the transmitter a bootstrap and its
235
+ /// quote carry, and every lookup of a conduit a report is for, so they cannot drift.
236
+ function _localConduit(address owner, bytes32 salt) internal view returns (address) {
237
+ return _predictConduit(owner, salt, localChainKey);
238
+ }
239
+
240
+ /// @notice The exact report bytes for `(owner, salt)` and `receiver`.
241
+ function _reportPayload(address owner, bytes32 salt, address receiver) internal view returns (bytes memory) {
242
+ return Envelope.encodeReceiverReport(owner, salt, Erc7930.encodeEvm(block.chainid, receiver));
243
+ }
244
+
245
+ /* ================================= counterparts ================================ */
246
+
247
+ /// @inheritdoc OutboundBase
248
+ ///
249
+ /// @dev Applies the registry's per-chain grade and suspension, which every provider's
250
+ /// transceiver reads alike. Every bootstrap and report sent, and every delivery
251
+ /// authenticated, goes through here, so a suspended chain is cut off both ways.
252
+ ///
253
+ /// @dev An unset counterpart on a `Predetermined` chain is `_parityAddress(chainKey)`: every
254
+ /// transceiver of a provider is a `Proxy` deployed through `ProxyDeployer` at
255
+ /// one salt by the deployer the registry records for that chain, which is what
256
+ /// `Predetermined` means (#33).
257
+ function _counterpartOn(bytes32 chainKey) internal view virtual override returns (bytes memory) {
258
+ if (chainKey == localChainKey) revert IsLocalChain(chainKey);
259
+ if (address(_chainRegistry) == address(0)) revert NoChainRegistry();
260
+ if (_chainRegistry.isSuspended(chainKey)) revert ChainSuspended(chainKey);
261
+
262
+ Provenance grade = _chainRegistry.provenanceFor(chainKey);
263
+ if (uint8(grade) < uint8(_minCounterpartProvenance)) {
264
+ revert InsufficientCounterpartProvenance(chainKey, grade);
265
+ }
266
+
267
+ if (!hasCounterpart(chainKey)) {
268
+ if (grade != Provenance.Predetermined) revert NoCounterpartFor(chainKey);
269
+ return abi.encodePacked(_parityAddress(chainKey));
270
+ }
271
+ return OutboundBase._counterpartOn(chainKey);
272
+ }
273
+
274
+ /// @notice The counterpart on `chainKey` as an EVM address, refusing any other width.
275
+ function _evmCounterpartOn(bytes32 chainKey) internal view returns (address) {
276
+ bytes memory there = _counterpartOn(chainKey);
277
+ if (there.length != 20) revert CounterpartNotEvm(chainKey);
278
+ // forge-lint: disable-next-line(unsafe-typecast) length checked above
279
+ return address(bytes20(there));
280
+ }
281
+
282
+ /// @notice Where this provider's transceiver sits on the `Predetermined` chain `chainKey`: the
283
+ /// registry's prediction from the deployer recorded for that chain.
284
+ /// @dev Not `address(this)`: a chain added after a deployer rotation has a different
285
+ /// deployer, hence a different address. Reverts while the provider's deployment, or its
286
+ /// entry on `chainKey`, is unrecorded, so nothing authenticates or is sent to a guessed
287
+ /// address.
288
+ function _parityAddress(bytes32 chainKey) internal view virtual returns (address) {
289
+ return _chainRegistry.predictTransceiver(chainKey, _messageProvider);
290
+ }
291
+
292
+ /* =================================== sending =================================== */
293
+
294
+ /// @notice Where a provider returns an overpaid fee: whoever paid it.
295
+ /// @dev A report's overpayment returns to the float it was paid from. Anything else, a
296
+ /// bootstrap above all, refunds the conduit that paid.
297
+ function _refundTo() internal view virtual override returns (address) {
298
+ return _reporting ? address(this) : msg.sender;
299
+ }
300
+
301
+ /// @inheritdoc OutboundBase
302
+ /// @dev By the envelope, not `_reporting`, so `quoteMessage` prices a report as it is sent.
303
+ /// A report's gas is fixed here because the float pays for it.
304
+ function _defaultGas(bytes memory payload) internal pure override returns (uint256) {
305
+ return Envelope.isReceiverReport(payload) ? DeliveryGas.REPORT : DeliveryGas.BOOTSTRAP;
306
+ }
307
+ }
@@ -0,0 +1,278 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {TransceiverCore, TransceiverConfig} from "./TransceiverCore.sol";
5
+ import {IBootstrapAllowlist} from "./IBootstrapAllowlist.sol";
6
+ import {Erc7930} from "../../addressing/Erc7930.sol";
7
+ import {IChainRegistryRefs} from "../../registry/IChainRegistryRefs.sol";
8
+ import {Provenance} from "../../registry/Provenance.sol";
9
+
10
+ /// @title TransceiverExtension
11
+ /// @notice A transceiver's initialization, owner configuration, float withdrawal, and the views
12
+ /// no contract calls. Runs only by `DELEGATECALL` from `TransceiverBase`'s `fallback`, so
13
+ /// its storage is the transceiver's and `msg.sender` is the transceiver's caller.
14
+ ///
15
+ /// @dev Provider-agnostic: every provider's transceiver on a chain can name the same one. A
16
+ /// chain whose conduit addresses diverge uses the matching variant in
17
+ /// `DivergentTransceiver.sol`, which derives as that chain's `TransceiverBase` does.
18
+ ///
19
+ /// @dev Holds no upgrade path and calls no address it reads from storage except the registry the
20
+ /// base already calls. Called directly, it runs against its own empty storage, where every
21
+ /// owner and treasury check fails and initialization is disabled.
22
+ contract TransceiverExtension is TransceiverCore {
23
+ /// @dev `initializeBase` was not called by the transceiver itself.
24
+ error NotSelf(address caller);
25
+ /// @dev Sending is `TransceiverBase`'s; the extension's copy of `quoteMessage` is unreachable
26
+ /// through a transceiver, whose own copy answers first.
27
+ error SendsNothing();
28
+
29
+ constructor() {
30
+ _disableInitializers();
31
+ }
32
+
33
+ /* ================================ initialization ============================== */
34
+
35
+ /// @notice Configure and grant the gateways. See `TransceiverBase.__TransceiverBase_init`,
36
+ /// the only caller.
37
+ ///
38
+ /// @dev Reachable through the transceiver's `fallback` by anyone while it is initializing, so
39
+ /// it accepts only the transceiver's self-call, as `Roles.grantRole` does (#68).
40
+ function initializeBase(TransceiverConfig memory c, bool addressesDiverge_) external onlyInitializing {
41
+ if (msg.sender != address(this)) revert NotSelf(msg.sender);
42
+ if (c.transmitterImplementation == address(0)) revert NoConduitImplementation();
43
+ if (c.receiverImplementation == address(0)) revert NoConduitImplementation();
44
+ if (c.treasury == address(0)) revert NoTreasury();
45
+ if (c.governorOwner == address(0)) revert ZeroOwner();
46
+
47
+ // The identifier names the owner's home, so it must be the canonical one for its key.
48
+ bytes32 governorHome = keccak256(c.governorHome);
49
+ _requireNames(c.governorHome, governorHome);
50
+
51
+ _transmitterImplementation = c.transmitterImplementation;
52
+ emit TransmitterImplementationSet(c.transmitterImplementation);
53
+ _receiverImplementation = c.receiverImplementation;
54
+ emit ReceiverImplementationSet(c.receiverImplementation);
55
+ _addressesDiverge = addressesDiverge_;
56
+ emit AddressesDivergeSet(addressesDiverge_);
57
+ _treasury = c.treasury;
58
+
59
+ __Ownable_init(_predictConduit(c.governorOwner, c.governorSalt, governorHome));
60
+
61
+ // Born accepting the governor's home as an origin: the owner is the governor's
62
+ // conduit here, which only a bootstrap from that home can create.
63
+ if (address(c.chainRegistry) != address(0)) {
64
+ _setRouting(c.chainRegistry, c.messageProvider, c.minCounterpartProvenance);
65
+ // Only the owner, which that bootstrap creates, could set a counterpart, so the
66
+ // home's must resolve now or the transceiver is never usable (#32).
67
+ if (governorHome != localChainKey) _counterpartOn(governorHome);
68
+ }
69
+ if (governorHome != localChainKey) _setRoute(governorHome, c.governorHome);
70
+
71
+ for (uint256 i; i < c.gateways.length; ++i) {
72
+ if (c.gateways[i] != address(0)) {
73
+ _grantInitialRole(GATEWAY_ROLE, c.gateways[i]);
74
+ }
75
+ }
76
+ }
77
+
78
+ /* ================================ configuration ================================ */
79
+
80
+ /// @notice Teach this transceiver how a chain is named. Write-once, and the owner's.
81
+ /// @dev A binding wraps this only where it keeps a provider-native id.
82
+ function setRoute(bytes32 chainKey, bytes memory route) public onlyOwner {
83
+ _setRoute(chainKey, route);
84
+ }
85
+
86
+ /// @notice Point this transceiver at the registry, name its provider id, and set its
87
+ /// provenance bar.
88
+ ///
89
+ /// @dev The registry and provider id are write-once, like routes and counterparts:
90
+ /// repointing either would redirect every future lookup. The same pair again is a
91
+ /// no-op; a different one reverts `RoutingAlreadySet`.
92
+ ///
93
+ /// @dev `minCounterpartProvenance` stays rebindable and is written on every call: it is
94
+ /// how the owner reacts to a bridge's standing changing without a redeploy.
95
+ function setRouting(
96
+ IChainRegistryRefs chainRegistry_,
97
+ bytes32 messageProvider_,
98
+ Provenance minCounterpartProvenance_
99
+ ) external onlyOwner {
100
+ _setRouting(chainRegistry_, messageProvider_, minCounterpartProvenance_);
101
+ }
102
+
103
+ function _setRouting(
104
+ IChainRegistryRefs chainRegistry_,
105
+ bytes32 messageProvider_,
106
+ Provenance minCounterpartProvenance_
107
+ ) private {
108
+ if (address(_chainRegistry) != address(0)) {
109
+ if (_chainRegistry != chainRegistry_ || _messageProvider != messageProvider_) {
110
+ revert RoutingAlreadySet();
111
+ }
112
+ } else {
113
+ _chainRegistry = chainRegistry_;
114
+ _messageProvider = messageProvider_;
115
+ }
116
+
117
+ _minCounterpartProvenance = minCounterpartProvenance_;
118
+ emit RoutingSet(address(chainRegistry_), messageProvider_, minCounterpartProvenance_);
119
+ }
120
+
121
+ /// @notice Record where this provider's transceiver sits on another chain.
122
+ ///
123
+ /// @dev Held here, not in the registry, because a location is per (chain, provider) and
124
+ /// this transceiver is that pair. The registry holds what is common to every provider:
125
+ /// `provenanceFor(chainKey)`. Write-once: re-pointing a counterpart redirects it.
126
+ /// @param interop Canonical ERC-7930 bytes, so the registry can check it is well-formed
127
+ /// and on `chainKey`. The address half is stored, since inbound senders are
128
+ /// compared to it.
129
+ function setCounterpart(bytes32 chainKey, bytes calldata interop) external onlyOwner {
130
+ if (address(_chainRegistry) == address(0)) revert NoChainRegistry();
131
+ if (hasCounterpart(chainKey)) revert CounterpartAlreadySet(chainKey);
132
+
133
+ _recordCounterpart(chainKey, interop);
134
+ }
135
+
136
+ /// @notice Record the counterpart by recomputing it from the registry's deriver and inputs
137
+ /// for that chain, rather than by being told.
138
+ /// @dev The inputs were written in an earlier transaction, so `paramsCommitment` puts
139
+ /// their hash in this one's signed calldata: the signers approve the inputs, not a
140
+ /// pointer to them.
141
+ /// @param paramsCommitment `keccak256(chainRegistry.deriveParams(chainKey))`.
142
+ function resolveCounterpart(bytes32 chainKey, bytes32 paramsCommitment) external onlyOwner {
143
+ if (address(_chainRegistry) == address(0)) revert NoChainRegistry();
144
+ if (hasCounterpart(chainKey)) revert CounterpartAlreadySet(chainKey);
145
+ if (keccak256(_chainRegistry.deriveParams(chainKey)) != paramsCommitment) {
146
+ revert ParamsCommitmentMismatch(chainKey);
147
+ }
148
+
149
+ _recordCounterpart(chainKey, _chainRegistry.expectedTransceiver(chainKey));
150
+ }
151
+
152
+ /// @dev The default `_counterpartOn` answers on a `Predetermined` chain is live from the
153
+ /// moment the chain is graded, so a counterpart recorded later must equal it: otherwise
154
+ /// `hasCounterpart` being false would let the owner repoint a lane in use.
155
+ function _recordCounterpart(bytes32 chainKey, bytes memory interop) private {
156
+ _chainRegistry.validateLocation(chainKey, interop);
157
+ bytes memory addr = Erc7930.parseStrict(interop).addr;
158
+ if (
159
+ _chainRegistry.provenanceFor(chainKey) == Provenance.Predetermined
160
+ && keccak256(addr) != keccak256(abi.encodePacked(_parityAddress(chainKey)))
161
+ ) revert CounterpartNotParity(chainKey);
162
+ _setCounterpart(chainKey, addr);
163
+ }
164
+
165
+ /// @notice Name the contract that decides which conduits may bootstrap. Zero lets every
166
+ /// conduit.
167
+ function setBootstrapAllowlist(IBootstrapAllowlist allowlist) external onlyOwner {
168
+ _bootstrapAllowlist = allowlist;
169
+ emit BootstrapAllowlistSet(address(allowlist));
170
+ }
171
+
172
+ /// @notice Name who may set bootstrap fees besides the owner. Zero names no one.
173
+ // forge-lint: disable-next-line(missing-zero-check) zero removes the operator
174
+ function setOperator(address operator_) external onlyOwner {
175
+ _operator = operator_;
176
+ emit OperatorSet(operator_);
177
+ }
178
+
179
+ /// @notice Set what standing a conduit up on `chainKey` costs. The owner's or the operator's.
180
+ /// @dev Rebindable: a price redirects nothing. Zero halts bootstraps to a reporting chain.
181
+ function setBootstrapFee(bytes32 chainKey, uint256 fee) external {
182
+ if (msg.sender != _operator && msg.sender != owner()) revert NotOwnerOrOperator(msg.sender);
183
+ _bootstrapFee[chainKey] = fee;
184
+ emit BootstrapFeeSet(chainKey, fee);
185
+ }
186
+
187
+ /* =================================== the float ================================= */
188
+
189
+ /// @notice Send `amount` of the float to the treasury, at the treasury's call.
190
+ /// @dev Gated, not a permissionless sweep: anyone able to empty the float could make every
191
+ /// bootstrap here that reports revert.
192
+ function withdraw(uint256 amount) external {
193
+ address to = _treasury;
194
+ if (msg.sender != to) revert NotTreasury(msg.sender);
195
+
196
+ emit Withdrawn(to, amount);
197
+ (bool ok,) = to.call{value: amount}("");
198
+ if (!ok) revert WithdrawFailed(amount);
199
+ }
200
+
201
+ /* ==================================== views ==================================== */
202
+
203
+ /// @notice The salt an owner's conduit deploys at, on every chain. See `_conduitSalt`.
204
+ function conduitSalt(address owner, bytes32 salt, bytes32 homeChainKey) external pure returns (bytes32) {
205
+ return _conduitSalt(owner, salt, homeChainKey);
206
+ }
207
+
208
+ /// @notice Where an owner's conduit lives on this chain, before it exists.
209
+ function predictConduit(address owner, bytes32 salt, bytes32 homeChainKey) external view returns (address) {
210
+ return _predictConduit(owner, salt, homeChainKey);
211
+ }
212
+
213
+ /// @notice Where `(owner, salt)`'s transmitter lives, before it exists.
214
+ function predictTransmitter(address owner, bytes32 salt) external view returns (address) {
215
+ return _localConduit(owner, salt);
216
+ }
217
+
218
+ /// @notice The exact report bytes for `(owner, salt)` and `receiver`, so its cost can be
219
+ /// quoted with `quoteMessage` before it is owed.
220
+ function reportPayload(address owner, bytes32 salt, address receiver) external view returns (bytes memory) {
221
+ return _reportPayload(owner, salt, receiver);
222
+ }
223
+
224
+ function transmitterImplementation() external view returns (address) {
225
+ return _transmitterImplementation;
226
+ }
227
+
228
+ function receiverImplementation() external view returns (address) {
229
+ return _receiverImplementation;
230
+ }
231
+
232
+ function addressesDiverge() external view returns (bool) {
233
+ return _addressesDiverge;
234
+ }
235
+
236
+ function chainRegistry() external view returns (IChainRegistryRefs) {
237
+ return _chainRegistry;
238
+ }
239
+
240
+ function messageProvider() external view returns (bytes32) {
241
+ return _messageProvider;
242
+ }
243
+
244
+ function minCounterpartProvenance() external view returns (Provenance) {
245
+ return _minCounterpartProvenance;
246
+ }
247
+
248
+ function bootstrapFee(bytes32 chainKey) external view returns (uint256) {
249
+ return _bootstrapFee[chainKey];
250
+ }
251
+
252
+ function operator() external view returns (address) {
253
+ return _operator;
254
+ }
255
+
256
+ function bootstrapAllowlist() external view returns (IBootstrapAllowlist) {
257
+ return _bootstrapAllowlist;
258
+ }
259
+
260
+ function treasury() external view returns (address) {
261
+ return _treasury;
262
+ }
263
+
264
+ /* =================================== sending =================================== */
265
+
266
+ function _sendMessage(bytes memory, bytes memory, bytes[] memory, uint256)
267
+ internal
268
+ pure
269
+ override
270
+ returns (bytes32)
271
+ {
272
+ revert SendsNothing();
273
+ }
274
+
275
+ function _quoteMessage(bytes memory, bytes memory, bytes[] memory) internal pure override returns (uint256) {
276
+ revert SendsNothing();
277
+ }
278
+ }
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {Erc7930} from "../addressing/Erc7930.sol";
5
+
6
+ /// @notice Narrowing a provider's address forms to an EVM address, refusing rather than
7
+ /// truncating or padding anything that is not one (R4.3).
8
+ library ProviderAddress {
9
+ error UnsupportedRecipient(bytes addr);
10
+ error UnsupportedSender(bytes32 sender);
11
+ /// @notice A provider endpoint passed to a constructor was zero; it is immutable.
12
+ error ZeroEndpoint();
13
+
14
+ /// @notice The receiving address, for a provider that delivers to it as an EVM address
15
+ /// (CCIP, Hyperlane, Wormhole, OP Stack).
16
+ /// @dev A recipient is only checked against the recorded counterpart, which may be a
17
+ /// non-EVM address. LayerZero is exempt: it delivers to its recorded peer instead.
18
+ function evmRecipient(bytes memory recipient) internal pure returns (address) {
19
+ bytes memory addr = Erc7930.parseStrict(recipient).addr;
20
+ if (addr.length != 20) revert UnsupportedRecipient(addr);
21
+ // forge-lint: disable-next-line(unsafe-typecast) length checked above
22
+ return address(bytes20(addr));
23
+ }
24
+
25
+ /// @notice A provider-reported 32-byte sender, left-padded when it is an EVM address.
26
+ /// @dev Nonzero high bytes are a non-EVM sender; truncating would let its low 20 bytes
27
+ /// pass as an unrelated EVM account.
28
+ function evmSender(bytes32 sender) internal pure returns (address) {
29
+ if (uint256(sender) > type(uint160).max) revert UnsupportedSender(sender);
30
+ return address(uint160(uint256(sender)));
31
+ }
32
+ }