@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.
- package/README.md +26 -2
- package/abi/Blake2b256.json +1 -0
- package/abi/Blake2bScheme.json +1 -0
- package/abi/ChainRegistry.json +1 -0
- package/abi/Commitment.json +1 -0
- package/abi/Conduits.json +1 -0
- package/abi/DivergentConduits.json +1 -0
- package/abi/DivergentTransceiver.json +1 -0
- package/abi/DivergentTransceiverExtension.json +1 -0
- package/abi/Envelope.json +1 -0
- package/abi/Erc7930.json +1 -0
- package/abi/Executor.json +1 -0
- package/abi/IBootstrapAllowlist.json +1 -0
- package/abi/ICancel.json +1 -0
- package/abi/IChainRegistryRefs.json +1 -0
- package/abi/ICommitFinalize.json +1 -0
- package/abi/ICommitmentScheme.json +1 -0
- package/abi/IConduitReceiverReport.json +1 -0
- package/abi/IConduitTransceiver.json +1 -0
- package/abi/IERC7786GatewaySource.json +1 -0
- package/abi/IERC7786Recipient.json +1 -0
- package/abi/IExecute.json +1 -0
- package/abi/ILzOftConduitFactory.json +1 -0
- package/abi/IProviderIdTable.json +1 -0
- package/abi/IProxy.json +1 -0
- package/abi/IReceiverInit.json +1 -0
- package/abi/IRefValidator.json +1 -0
- package/abi/IReportFloat.json +1 -0
- package/abi/ITransmitterInit.json +1 -0
- package/abi/IVmDeriver.json +1 -0
- package/abi/KeccakScheme.json +1 -0
- package/abi/Move.json +1 -0
- package/abi/MoveValidator.json +1 -0
- package/abi/OpStackTransmitterBase.json +1 -0
- package/abi/OutboundBase.json +1 -0
- package/abi/OwnableTransmitter.json +1 -0
- package/abi/ProviderAddress.json +1 -0
- package/abi/ProviderAttribute.json +1 -0
- package/abi/ProviderChainId.json +1 -0
- package/abi/ProviderReceiver.json +1 -0
- package/abi/ProviderTransceiver.json +1 -0
- package/abi/Proxy.json +1 -0
- package/abi/ProxyDeployer.json +1 -0
- package/abi/ProxyInitCode.json +1 -0
- package/abi/ReceiverBase.json +1 -0
- package/abi/Roles.json +1 -0
- package/abi/RolesEnumerable.json +1 -0
- package/abi/SchemeFold.json +1 -0
- package/abi/Sha256Scheme.json +1 -0
- package/abi/StarknetDerive.json +1 -0
- package/abi/StarknetValidator.json +1 -0
- package/abi/SuiDerive.json +1 -0
- package/abi/TransceiverBase.json +1 -0
- package/abi/TransceiverCore.json +1 -0
- package/abi/TransceiverExtension.json +1 -0
- package/abi/TransmitterBase.json +1 -0
- package/abi/Treasury.json +1 -0
- package/abi/TronConduits.json +1 -0
- package/abi/TronTransceiver.json +1 -0
- package/abi/TronTransceiverExtension.json +1 -0
- package/abi/VmDeriver.json +1 -0
- package/abi/ZkSyncConduits.json +1 -0
- package/abi/ZkSyncTransceiver.json +1 -0
- package/abi/ZkSyncTransceiverExtension.json +1 -0
- package/abi/index.d.ts +63 -0
- package/abi/index.js +63 -0
- package/contracts/addressing/ChainKey.sol +45 -0
- package/contracts/addressing/ChainType.sol +50 -0
- package/contracts/addressing/Erc7930.sol +209 -0
- package/contracts/addressing/Move.sol +212 -0
- package/contracts/conduit/Conduits.sol +43 -0
- package/contracts/conduit/Proxy.sol +144 -0
- package/contracts/conduit/ProxyDeployer.sol +42 -0
- package/contracts/conduit/ProxyInitCode.sol +47 -0
- package/contracts/derivation/AddressDerive.sol +326 -0
- package/contracts/derivation/BitcoinDerive.sol +33 -0
- package/contracts/derivation/Blake2b256.sol +122 -0
- package/contracts/derivation/IVmDeriver.sol +35 -0
- package/contracts/derivation/StarknetDerive.sol +93 -0
- package/contracts/derivation/SuiDerive.sol +102 -0
- package/contracts/derivation/VmDeriver.sol +291 -0
- package/contracts/messaging/Call.sol +43 -0
- package/contracts/messaging/Commitment.sol +131 -0
- package/contracts/messaging/DeliveryGas.sol +18 -0
- package/contracts/messaging/Envelope.sol +123 -0
- package/contracts/messaging/Executor.sol +52 -0
- package/contracts/messaging/IErc7786.sol +68 -0
- package/contracts/messaging/Payload.sol +90 -0
- package/contracts/messaging/Roles.sol +113 -0
- package/contracts/messaging/inbound/ReceiverBase.sol +359 -0
- package/contracts/messaging/outbound/OutboundBase.sol +221 -0
- package/contracts/messaging/outbound/OwnableTransmitter.sol +23 -0
- package/contracts/messaging/outbound/TransmitterBase.sol +604 -0
- package/contracts/messaging/transceiver/DivergentConduits.sol +92 -0
- package/contracts/messaging/transceiver/DivergentTransceiver.sol +97 -0
- package/contracts/messaging/transceiver/IBootstrapAllowlist.sol +8 -0
- package/contracts/messaging/transceiver/TransceiverBase.sol +541 -0
- package/contracts/messaging/transceiver/TransceiverCore.sol +307 -0
- package/contracts/messaging/transceiver/TransceiverExtension.sol +278 -0
- package/contracts/protocols/ProviderAddress.sol +32 -0
- package/contracts/protocols/ProviderAttribute.sol +65 -0
- package/contracts/protocols/ProviderChainId.sol +115 -0
- package/contracts/protocols/ProviderReceiver.sol +24 -0
- package/contracts/protocols/ProviderTransceiver.sol +48 -0
- package/contracts/protocols/layerzero/ILzOftConduitFactory.sol +10 -0
- package/contracts/protocols/op-stack/OpStackTransmitterBase.sol +11 -0
- package/contracts/registry/ChainRegistry.sol +667 -0
- package/contracts/registry/IChainRegistryRefs.sol +81 -0
- package/contracts/registry/ICommitmentScheme.sol +46 -0
- package/contracts/registry/IRefValidator.sol +13 -0
- package/contracts/registry/Provenance.sol +25 -0
- package/contracts/schemes/Blake2bScheme.sol +18 -0
- package/contracts/schemes/KeccakScheme.sol +19 -0
- package/contracts/schemes/Sha256Scheme.sol +15 -0
- package/contracts/treasury/Treasury.sol +76 -0
- package/contracts/validators/MoveValidator.sol +24 -0
- package/contracts/validators/StarknetValidator.sol +20 -0
- package/package.json +36 -4
|
@@ -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
|
+
}
|