@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,43 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ /// @notice One call in a payload.
5
+ /// @dev The ERC-7579 `Execution` tuple, also used by ERC-7821, so existing payload builders
6
+ /// work unchanged. Target and value are in the approved array, so a payload cannot be
7
+ /// redirected or re-priced at execution.
8
+ struct Call {
9
+ address target;
10
+ uint256 value;
11
+ bytes data;
12
+ }
13
+
14
+ /// @title Calls
15
+ /// @notice Conversion between the typed `Call` and the canonical opaque element.
16
+ /// @dev The two forms must hash identically, so an approval made in one form can be
17
+ /// discharged in the other.
18
+ library Calls {
19
+ /// @notice The canonical opaque element for a typed call.
20
+ /// @dev Encodes the fields, not the struct: `abi.encode(c)` prepends an offset word for the
21
+ /// dynamic tuple and would hash differently.
22
+ function encode(Call memory c) internal pure returns (bytes memory) {
23
+ return abi.encode(c.target, c.value, c.data);
24
+ }
25
+
26
+ /// @notice The element hash. Equals `keccak256(encode(c))`.
27
+ function hash(Call memory c) internal pure returns (bytes32) {
28
+ return keccak256(abi.encode(c.target, c.value, c.data));
29
+ }
30
+
31
+ /// @notice Split a canonical opaque element back into a typed call. Reverts if it is not
32
+ /// `abi.encode(address, uint256, bytes)`.
33
+ function decode(bytes memory element) internal pure returns (Call memory c) {
34
+ (c.target, c.value, c.data) = abi.decode(element, (address, uint256, bytes));
35
+ }
36
+
37
+ function encodeAll(Call[] memory calls) internal pure returns (bytes[] memory out) {
38
+ out = new bytes[](calls.length);
39
+ for (uint256 i; i < calls.length; ++i) {
40
+ out[i] = encode(calls[i]);
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {ChainKey} from "../addressing/ChainKey.sol";
5
+ import {Call, Calls} from "./Call.sol";
6
+ import {Blake2b256} from "../derivation/Blake2b256.sol";
7
+
8
+ /// @notice The hash a destination computes its commitments with.
9
+ ///
10
+ /// @dev The fold is fixed and only the primitive varies, so the home can reproduce any
11
+ /// destination's commitment for a signer. The cost is that a non-EVM receiver implements
12
+ /// a byte fold rather than its native digest (TON's cell hash, Starknet's
13
+ /// `poseidon_hash_span`).
14
+ ///
15
+ /// @dev An EVM receiver only ever uses keccak256; the others are for the home building a
16
+ /// commitment another VM will recompute.
17
+ enum Scheme {
18
+ /// EVM. The opcode.
19
+ Keccak256,
20
+ /// Ton. `sha256` is an EVM builtin, so this stays computable on both sides.
21
+ Sha256,
22
+ /// Cardano. EIP-152 precompile 0x09: see `Blake2b256`. The reason dispatching
23
+ /// functions are `view` rather than `pure`.
24
+ Blake2b256Scheme,
25
+ /// Starknet. Declared, not implemented: Poseidon needs the exact round constants and MDS
26
+ /// matrix, checked against vectors (`docs/todo.md` §4). Until then a Starknet commitment
27
+ /// is computed off-chain and carried in an element calling that receiver's `commit`.
28
+ Poseidon
29
+ }
30
+
31
+ /// @title Commitment
32
+ /// @notice The commitment over a call array, bound to exactly one destination.
33
+ ///
34
+ /// @dev Seeded with the destination's chainKey, since conduits share addresses across chains
35
+ /// and a commitment must not replay between them. A chainKey rather than a chain id
36
+ /// because non-EVM chains have none; on the EVM it still derives from `block.chainid`.
37
+ ///
38
+ /// @dev Defined over opaque elements, which it never looks inside, so no VM's call format is
39
+ /// parsed in Solidity. The typed overloads produce the same hash, since `Calls.encode`
40
+ /// yields exactly the opaque element.
41
+ library Commitment {
42
+ /// @dev The scheme has no implementation on this chain, so a commitment for it must be
43
+ /// computed off-chain and approved as a digest.
44
+ error SchemeNotComputable(Scheme scheme);
45
+ /// @dev An empty array would hash to the seed alone, an approval that runs nothing.
46
+ error EmptyCommitment();
47
+
48
+ /// @notice The hash a receiver on this chain will require, for typed calls.
49
+ function hashCalls(Call[] memory calls) internal view returns (bytes32) {
50
+ return hashCalls(ChainKey.local(), calls);
51
+ }
52
+
53
+ /// @notice The same value, from typed calls.
54
+ /// @dev Equal to the opaque overload over `Calls.encodeAll(calls)`, asserted in
55
+ /// `test/PayloadEncoding.t.sol`. Array parameters are `memory` throughout because
56
+ /// Solidity will not overload on data location.
57
+ function hashCalls(bytes32 destinationChainKey, Call[] memory calls) internal pure returns (bytes32 hashed) {
58
+ uint256 len = calls.length;
59
+ if (len == 0) revert EmptyCommitment();
60
+ hashed = _seed(destinationChainKey);
61
+ for (uint256 i = 0; i < len; i++) {
62
+ hashed = _fold(hashed, Calls.hash(calls[i]));
63
+ }
64
+ }
65
+
66
+ /// @notice Canonical: the hash a receiver on `destinationChainKey` requires, over the
67
+ /// portable opaque elements.
68
+ /// @dev The source passes the destination's key; the local one yields a commitment the
69
+ /// far side never matches.
70
+ function hashCalls(bytes32 destinationChainKey, bytes[] memory elements) internal pure returns (bytes32 hashed) {
71
+ uint256 len = elements.length;
72
+ if (len == 0) revert EmptyCommitment();
73
+ hashed = _seed(destinationChainKey);
74
+ for (uint256 i = 0; i < len; i++) {
75
+ hashed = _fold(hashed, keccak256(elements[i]));
76
+ }
77
+ }
78
+
79
+ /* ============================ non-EVM destinations =========================== */
80
+
81
+ /// @notice The commitment a destination using `scheme` will require.
82
+ /// @dev Source side only; EVM receivers use the keccak overloads above. `view` for
83
+ /// `Blake2b256`'s precompile `staticcall`, read through `eth_call` by a signer.
84
+ function hashCalls(Scheme scheme, bytes32 destinationChainKey, bytes[] memory elements)
85
+ internal
86
+ view
87
+ returns (bytes32 hashed)
88
+ {
89
+ uint256 len = elements.length;
90
+ if (len == 0) revert EmptyCommitment();
91
+ hashed = _hash(scheme, abi.encode(destinationChainKey));
92
+ for (uint256 i = 0; i < len; i++) {
93
+ hashed = _hash(scheme, abi.encodePacked(hashed, _hash(scheme, elements[i])));
94
+ }
95
+ }
96
+
97
+ /// @notice The same value, from typed calls.
98
+ /// @dev Delegates to the opaque overload over `Calls.encodeAll`, so both spellings share
99
+ /// one fold.
100
+ function hashCalls(Scheme scheme, bytes32 destinationChainKey, Call[] memory calls)
101
+ internal
102
+ view
103
+ returns (bytes32)
104
+ {
105
+ return hashCalls(scheme, destinationChainKey, Calls.encodeAll(calls));
106
+ }
107
+
108
+ /// @notice Whether this chain can compute `scheme` at all. False only for `Poseidon`.
109
+ function isComputable(Scheme scheme) internal pure returns (bool) {
110
+ return scheme != Scheme.Poseidon;
111
+ }
112
+
113
+ /* ================================== internals =============================== */
114
+
115
+ function _seed(bytes32 destinationChainKey) private pure returns (bytes32) {
116
+ return keccak256(abi.encode(destinationChainKey));
117
+ }
118
+
119
+ function _fold(bytes32 acc, bytes32 elementHash) private pure returns (bytes32) {
120
+ return keccak256(abi.encodePacked(acc, elementHash));
121
+ }
122
+
123
+ /// @dev Reverts for a scheme with no implementation here rather than falling back to a
124
+ /// keccak256 digest the destination could never match.
125
+ function _hash(Scheme scheme, bytes memory data) private view returns (bytes32) {
126
+ if (scheme == Scheme.Keccak256) return keccak256(data);
127
+ if (scheme == Scheme.Sha256) return sha256(data);
128
+ if (scheme == Scheme.Blake2b256Scheme) return Blake2b256.hash(data);
129
+ revert SchemeNotComputable(scheme);
130
+ }
131
+ }
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ /// @notice The destination gas a message is sent with when its attributes name none, by what
5
+ /// it does on arrival.
6
+ /// @dev Measured cold on Forge's EVM with every binding's fixture (deploy/CHECKS.md §4), and
7
+ /// held at least 10% above that by `ProviderDeliveryGasSpec`: a bootstrap that creates a
8
+ /// conduit with no calls 442k to 497k, a report arriving home up to 123k on the bindings
9
+ /// that carry one (2026-11-08).
10
+ /// A bootstrap on a reporting chain also pays for the report's send. On EraVM a
11
+ /// bootstrap's execution is about 385k to 389k; its pubdata is not measured. A gas
12
+ /// attribute replaces these with no floor: a delivery short of gas reverts and is retried
13
+ /// with more (provider-spec R1.5).
14
+ library DeliveryGas {
15
+ uint256 internal constant BOOTSTRAP = 1_000_000;
16
+ uint256 internal constant REPORT = 250_000;
17
+ uint256 internal constant PAYLOAD = 200_000;
18
+ }
@@ -0,0 +1,123 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {Call} from "./Call.sol";
5
+
6
+ /// @title Envelope
7
+ /// @notice The message bodies that cross between transceivers, each led by its kind.
8
+ ///
9
+ /// @dev Tagged: once every transceiver both sends and receives bootstraps and reports, the
10
+ /// direction no longer decides the shape, and decoding the wrong shape can misread
11
+ /// silently. A decoder checks the tag before it reads anything else.
12
+ ///
13
+ /// @dev Kinds start at 1, so a zero word, or the leading owner word of an untagged v1 body,
14
+ /// is refused rather than read as a kind.
15
+ ///
16
+ /// @dev `abi.encode`, not `encodePacked`, so no two messages share a byte string.
17
+ library Envelope {
18
+ uint8 internal constant BOOTSTRAP = 1;
19
+ uint8 internal constant BOOTSTRAP_ELEMENTS = 2;
20
+ uint8 internal constant RECEIVER_REPORT = 3;
21
+
22
+ /// @dev Shorter than the one word that holds the kind.
23
+ error EnvelopeTooShort();
24
+ /// @dev The leading word is not a kind this protocol defines.
25
+ error UnknownEnvelopeKind(uint256 kind);
26
+ /// @dev A known kind, but not the one this decoder reads.
27
+ error UnexpectedEnvelopeKind(uint8 expected, uint8 got);
28
+
29
+ /// @notice The kind of `message`, reverting unless it is one this protocol defines.
30
+ function kindOf(bytes calldata message) internal pure returns (uint8) {
31
+ if (message.length < 32) revert EnvelopeTooShort();
32
+ uint256 kind = abi.decode(message[:32], (uint256));
33
+ if (kind == 0 || kind > RECEIVER_REPORT) revert UnknownEnvelopeKind(kind);
34
+ // forge-lint: disable-next-line(unsafe-typecast) bounded by the check above
35
+ return uint8(kind);
36
+ }
37
+
38
+ function _expect(bytes calldata message, uint8 kind) private pure {
39
+ uint8 got = kindOf(message);
40
+ if (got != kind) revert UnexpectedEnvelopeKind(kind, got);
41
+ }
42
+
43
+ /* ================================= bootstrap =============================== */
44
+
45
+ /// @notice The payload that stands a receiver up, credited to the transmitter it
46
+ /// will answer to.
47
+ ///
48
+ /// @dev Calls, not a commitment: the transceiver creates and initializes the receiver and
49
+ /// is never in the path again. A payload that should wait carries a self-call to
50
+ /// `commit`.
51
+ ///
52
+ /// @dev Names the owner and salt, from which the destination derives the conduit address.
53
+ /// A transceiver is shared, so nothing the bridge reports says who authorized the
54
+ /// message.
55
+ ///
56
+ /// @dev Carries the transmitter the receiver will answer to. The origin transceiver sends
57
+ /// only for the conduit `(owner, salt)` resolves to there, so the value is vouched for
58
+ /// by the same authenticated message, and no destination needs another chain's
59
+ /// address formula to derive it. A fixed word rather than `bytes`, which would add an
60
+ /// offset and a length word to every bootstrap: an EVM address left-padded, or a
61
+ /// 32-byte key on a VM that has one.
62
+ function encodeBootstrap(address owner, bytes32 salt, bytes32 transmitter, Call[] memory calls)
63
+ internal
64
+ pure
65
+ returns (bytes memory)
66
+ {
67
+ return abi.encode(BOOTSTRAP, owner, salt, transmitter, calls);
68
+ }
69
+
70
+ function decodeBootstrap(bytes calldata message)
71
+ internal
72
+ pure
73
+ returns (address owner, bytes32 salt, bytes32 transmitter, Call[] memory calls)
74
+ {
75
+ _expect(message, BOOTSTRAP);
76
+ (, owner, salt, transmitter, calls) = abi.decode(message, (uint8, address, bytes32, bytes32, Call[]));
77
+ }
78
+
79
+ /// @notice The same message, for a destination whose calls this chain cannot express.
80
+ /// @dev No Solidity decoder: the EVM transceiver refuses this kind, and a non-EVM
81
+ /// transceiver decodes it in its own language.
82
+ function encodeBootstrapElements(address owner, bytes32 salt, bytes32 transmitter, bytes[] memory elements)
83
+ internal
84
+ pure
85
+ returns (bytes memory)
86
+ {
87
+ return abi.encode(BOOTSTRAP_ELEMENTS, owner, salt, transmitter, elements);
88
+ }
89
+
90
+ /* ============================== receiver report ============================= */
91
+
92
+ /// @notice Where the destination created a transmitter's receiver.
93
+ ///
94
+ /// @dev Only chains whose addresses the home cannot recompute send this: zkSync and Tron,
95
+ /// whose CREATE2 formulas differ, and non-EVM chains such as Starknet.
96
+ ///
97
+ /// @dev No request id: the chain comes from the authenticated origin and `(owner, salt)`
98
+ /// is stated here, which identifies the conduit the home's transceiver forwards the
99
+ /// report to. The conduit refuses a second report.
100
+ /// @param interop Canonical ERC-7930 bytes for the receiver on the reporting chain.
101
+ function encodeReceiverReport(address owner, bytes32 salt, bytes memory interop)
102
+ internal
103
+ pure
104
+ returns (bytes memory)
105
+ {
106
+ return abi.encode(RECEIVER_REPORT, owner, salt, interop);
107
+ }
108
+
109
+ /// @notice Whether `message` is led by the report kind. Reads nothing past the kind.
110
+ function isReceiverReport(bytes memory message) internal pure returns (bool) {
111
+ // forge-lint: disable-next-line(unsafe-typecast) the leading word, after the length check
112
+ return message.length >= 32 && uint256(bytes32(message)) == RECEIVER_REPORT;
113
+ }
114
+
115
+ function decodeReceiverReport(bytes calldata message)
116
+ internal
117
+ pure
118
+ returns (address owner, bytes32 salt, bytes memory interop)
119
+ {
120
+ _expect(message, RECEIVER_REPORT);
121
+ (, owner, salt, interop) = abi.decode(message, (uint8, address, bytes32, bytes));
122
+ }
123
+ }
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {Call} from "./Call.sol";
5
+
6
+ /// @title Executor
7
+ /// @notice Running a verified call array, and the policy that gates it.
8
+ /// @dev Shared by every contract that runs payloads: a receiver runs what arrives, and a
9
+ /// transmitter what its owner hands it. A transceiver runs none. No storage.
10
+ abstract contract Executor {
11
+ error SelectorNotAllowed(address target, bytes4 selector);
12
+ /// @dev Carries the index of the element that failed.
13
+ error CallFailed(uint256 index, bytes reason);
14
+ /// @dev An empty array proves no intent, so it is refused rather than a successful no-op.
15
+ error EmptyExecution();
16
+
17
+ /// @notice Call policy. The default allows everything.
18
+ ///
19
+ /// @dev Open by default: conduits are full-power and answer to one owner, as its own account
20
+ /// would, and anything that can deliver an authorized payload could forge a commitment
21
+ /// as easily as a call. Failing closed would revert every payload, the bootstrap payload
22
+ /// included, until overridden.
23
+ ///
24
+ /// @dev Merkle-verified calls are not in v1. If added, the model is Veda's
25
+ /// `ManagerWithMerkleVerification`:
26
+ /// https://github.com/Veda-Labs/boring-vault/blob/main/src/base/Roles/ManagerWithMerkleVerification.sol
27
+ function isAllowed(address, bytes4) public view virtual returns (bool) {
28
+ return true;
29
+ }
30
+
31
+ /// @notice Run the verified calls, in order, all or nothing.
32
+ /// @dev An approval covers the array as a unit, so a prefix must not stand. The failing
33
+ /// call's revert reason is carried in `CallFailed`.
34
+ function _execute(Call[] memory calls) internal virtual {
35
+ uint256 len = calls.length;
36
+ for (uint256 i; i < len; ++i) {
37
+ Call memory c = calls[i];
38
+
39
+ // Under four bytes there is no selector (a value transfer or a fallback hit).
40
+ // `bytes4` of short data right-pads with zeros and would invent one.
41
+ // forge-lint: disable-next-line(unsafe-typecast) the first 4 bytes; length checked
42
+ bytes4 selector = c.data.length >= 4 ? bytes4(c.data) : bytes4(0);
43
+ if (!isAllowed(c.target, selector)) {
44
+ revert SelectorNotAllowed(c.target, selector);
45
+ }
46
+
47
+ // forge-lint: disable-next-line(arbitrary-send-eth,calls-loop) owner-approved calls, all or nothing
48
+ (bool ok, bytes memory reason) = c.target.call{value: c.value}(c.data);
49
+ if (!ok) revert CallFailed(i, reason);
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,68 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.4;
3
+
4
+ /// @title IErc7786
5
+ /// @notice ERC-7786's two interfaces, vendored: the send surface `TransmitterBase` implements
6
+ /// and the receive surface `ReceiverBase` implements.
7
+ ///
8
+ /// @dev Copied from `@openzeppelin/contracts/interfaces/draft-IERC7786.sol` at v5.5.0;
9
+ /// only this header and the file name differ. Vendored because the ERC is a draft and
10
+ /// these selectors and the event topic are this protocol's ABI: an upstream change becomes
11
+ /// a reviewed edit rather than a side effect of a dependency bump.
12
+ ///
13
+ /// @dev OpenZeppelin's types and these are distinct to Solidity. Integrators import from
14
+ /// here; a binding whose SDK returns OpenZeppelin's variant casts through the address.
15
+
16
+ /// @dev Interface for ERC-7786 source gateways.
17
+ interface IERC7786GatewaySource {
18
+ /// @dev Emitted when a message is created. If `sendId` is zero, no further processing is
19
+ /// necessary. If `sendId` is not zero, then further (gateway specific, and
20
+ /// non-standardized) action is required.
21
+ event MessageSent(
22
+ bytes32 indexed sendId,
23
+ bytes sender, // Binary Interoperable Address
24
+ bytes recipient, // Binary Interoperable Address
25
+ bytes payload,
26
+ uint256 value,
27
+ bytes[] attributes
28
+ );
29
+
30
+ /// @dev Thrown when a message creation fails because of an unsupported attribute.
31
+ error UnsupportedAttribute(bytes4 selector);
32
+
33
+ /// @dev Whether an attribute is supported or not.
34
+ function supportsAttribute(bytes4 selector) external view returns (bool);
35
+
36
+ /// @dev Endpoint for creating a new message. If the message requires further (gateway
37
+ /// specific) processing before it can be sent to the destination chain, then a
38
+ /// non-zero `sendId` must be returned. Otherwise, the message MUST be sent and this
39
+ /// function must return 0.
40
+ ///
41
+ /// MUST emit a {MessageSent} event.
42
+ ///
43
+ /// If any of the `attributes` is not supported, this function SHOULD revert with an
44
+ /// {UnsupportedAttribute} error. Other errors SHOULD revert with errors not
45
+ /// specified in ERC-7786.
46
+ function sendMessage(
47
+ bytes calldata recipient, // Binary Interoperable Address
48
+ bytes calldata payload,
49
+ bytes[] calldata attributes
50
+ )
51
+ external
52
+ payable
53
+ returns (bytes32 sendId);
54
+ }
55
+
56
+ /// @dev Interface for the ERC-7786 client contract (receiver).
57
+ interface IERC7786Recipient {
58
+ /// @dev Endpoint for receiving a cross-chain message. May be called directly by the
59
+ /// gateway.
60
+ function receiveMessage(
61
+ bytes32 receiveId,
62
+ bytes calldata sender, // Binary Interoperable Address
63
+ bytes calldata payload
64
+ )
65
+ external
66
+ payable
67
+ returns (bytes4);
68
+ }
@@ -0,0 +1,90 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {Call} from "./Call.sol";
5
+ import {Erc7930} from "../addressing/Erc7930.sol";
6
+
7
+ /// @title Payload
8
+ /// @notice The wire encoding of a call array, in the one shape its destination speaks.
9
+ ///
10
+ /// @dev An EVM destination receives `Call[]`; every other VM receives opaque `bytes[]` in its
11
+ /// own call encoding. The sender picks by destination and the receiver decodes the one
12
+ /// shape its VM implies, so there is no form tag.
13
+ ///
14
+ /// @dev That relies on no path sending opaque elements to an EVM receiver, not on
15
+ /// `abi.decode` rejecting the wrong shape. A destination that could accept both needs the
16
+ /// tag back.
17
+ ///
18
+ /// @dev Both forms of one payload commit to one hash: `Commitment` folds per-element hashes
19
+ /// and `Calls.encode` produces exactly the opaque element.
20
+ library Payload {
21
+ /* ============================== EVM destinations ============================ */
22
+
23
+ /// @notice Wire bytes for an EVM destination.
24
+ function encodeCalls(Call[] memory calls) internal pure returns (bytes memory) {
25
+ return abi.encode(calls);
26
+ }
27
+
28
+ /// @notice Decode a payload that arrived on an EVM chain. Reverts on anything that is not
29
+ /// `Call[]`.
30
+ function decodeCalls(bytes calldata wire) internal pure returns (Call[] memory) {
31
+ return abi.decode(wire, (Call[]));
32
+ }
33
+
34
+ /// @notice `decodeCalls`, for a wire already in memory.
35
+ /// @dev Separate because Solidity will not overload on data location, and a receiver's
36
+ /// payload stays in calldata rather than paying a copy on every delivery.
37
+ function decodeCallsFromMemory(bytes memory wire) internal pure returns (Call[] memory) {
38
+ return abi.decode(wire, (Call[]));
39
+ }
40
+
41
+ /* ============================ non-EVM destinations ========================== */
42
+
43
+ /// @notice Wire bytes for a destination whose calls this chain cannot express.
44
+ /// @dev Each element is that VM's own call encoding (a Solana instruction with its
45
+ /// accounts, a Starknet `(to, selector, calldata)`, an Aptos entry function), unparsed
46
+ /// here.
47
+ function encodeElements(bytes[] memory elements) internal pure returns (bytes memory) {
48
+ return abi.encode(elements);
49
+ }
50
+
51
+ function decodeElements(bytes calldata wire) internal pure returns (bytes[] memory) {
52
+ return abi.decode(wire, (bytes[]));
53
+ }
54
+
55
+ /// @notice `decodeElements`, for a wire already in memory.
56
+ function decodeElementsFromMemory(bytes memory wire) internal pure returns (bytes[] memory) {
57
+ return abi.decode(wire, (bytes[]));
58
+ }
59
+
60
+ /* ================================ bootstrap ================================= */
61
+
62
+ /// @notice The plain bootstrap payload: the destination's ERC-7930 chain identifier, and
63
+ /// the body the receiver runs on creation, in that destination's one form.
64
+ /// @dev The destination travels inside the payload because `bootstrap` takes no other
65
+ /// argument naming it.
66
+ function encodeBootstrap(bytes memory destinationChainIdentifier, bytes memory body)
67
+ internal
68
+ pure
69
+ returns (bytes memory)
70
+ {
71
+ return abi.encode(destinationChainIdentifier, body);
72
+ }
73
+
74
+ function decodeBootstrap(bytes calldata payload)
75
+ internal
76
+ pure
77
+ returns (bytes memory destinationChainIdentifier, bytes memory body)
78
+ {
79
+ return abi.decode(payload, (bytes, bytes));
80
+ }
81
+
82
+ /* ================================ selection ================================= */
83
+
84
+ /// @notice Whether a destination takes the typed form.
85
+ /// @dev Takes the identifier, not the chainKey, which is a hash and cannot be asked its
86
+ /// chain type.
87
+ function isTypedDestination(bytes memory chainIdentifier) internal pure returns (bool) {
88
+ return Erc7930.parseStrict(chainIdentifier).chainType == Erc7930.CT_EIP155;
89
+ }
90
+ }
@@ -0,0 +1,113 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {
5
+ AccessControlEnumerableUpgradeable
6
+ } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
7
+ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
8
+ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
9
+
10
+ /// @title Roles
11
+ /// @notice Which transports may carry this contract's messages, in either direction.
12
+ ///
13
+ /// @dev `GATEWAY_ROLE` names addresses, not powers: a gateway can neither configure a contract
14
+ /// nor grant anything. Configuration is `Ownable` on the transceiver, except bootstrap fees,
15
+ /// which an operator the owner names may also set. Sending and receiving check
16
+ /// the same role, so a contract cannot accept deliveries from one transport while sending
17
+ /// through another. `ReceiverBase` inherits this directly: it never sends, but the role
18
+ /// guards its external `receiveMessage`.
19
+ ///
20
+ /// @dev Not enumerable here: a transceiver's members are fixed at initialization, each grant
21
+ /// emits `RoleGranted`, and enumeration would cost every transceiver a kilobyte it does
22
+ /// not have under EIP-170 (#29). Conduits, whose receivers can revoke, add it through
23
+ /// `RolesEnumerable`.
24
+ abstract contract Roles is AccessControlUpgradeable {
25
+ /// @notice May deliver a message to this contract and carry one out of it. The only role.
26
+ /// @dev Namespaced so a provider SDK's own role of the same name cannot share its members.
27
+ bytes32 public constant GATEWAY_ROLE = keccak256("crossecute.role.GATEWAY");
28
+
29
+ /// @dev The caller of `grantRole` was not this contract.
30
+ error GrantNotFromSelf(address caller);
31
+
32
+ /// @notice Grant a role from this contract's own bootstrap payload, so an owner's first
33
+ /// payload can name its conduit's gateway. Closes when initialization does.
34
+ ///
35
+ /// @dev `onlyInitializing` replaces OZ's admin gate. No role has an admin, so after arming
36
+ /// no caller can add a member: not the owner, the governor, the creating transceiver, or a
37
+ /// member. The flag stays set while `__ReceiverBase_init` runs the payload, so every
38
+ /// contract the payload reaches would be inside the window too; only a self-call from
39
+ /// `_execute` is accepted (#68).
40
+ ///
41
+ /// @dev `public` because it overrides OZ's `grantRole`; a separate entry point would leave
42
+ /// the inherited one reachable.
43
+ function grantRole(bytes32 role, address conduit) public virtual override onlyInitializing {
44
+ if (msg.sender != address(this)) revert GrantNotFromSelf(msg.sender);
45
+ _grantRole(role, conduit);
46
+ }
47
+
48
+ /// @notice An initializer's own grant, such as the provider endpoint it names as a gateway.
49
+ function _grantInitialRole(bytes32 role, address conduit) internal onlyInitializing {
50
+ _grantRole(role, conduit);
51
+ }
52
+
53
+ /// @notice Whether `conduit` holds `role`.
54
+ /// @dev Re-declared so an override below names one base.
55
+ function hasRole(bytes32 role, address conduit) public view virtual override returns (bool) {
56
+ return super.hasRole(role, conduit);
57
+ }
58
+ }
59
+
60
+ /// @title RolesEnumerable
61
+ /// @notice `Roles`, with the member set readable on-chain, for conduits: a receiver can drop a
62
+ /// gateway through `revokeGateway`, so what remains is worth reading directly.
63
+ /// @dev `AccessControlEnumerableUpgradeable` compiles at `paris` only through OZ 5.4, since
64
+ /// 5.5's `Arrays` uses `mcopy`; a bump past 5.4 breaks this inheritance first (README,
65
+ /// Assumptions).
66
+ abstract contract RolesEnumerable is Roles, AccessControlEnumerableUpgradeable {
67
+ function grantRole(bytes32 role, address conduit)
68
+ public
69
+ virtual
70
+ override(Roles, AccessControlUpgradeable, IAccessControl)
71
+ onlyInitializing
72
+ {
73
+ super.grantRole(role, conduit);
74
+ }
75
+
76
+ function hasRole(bytes32 role, address conduit)
77
+ public
78
+ view
79
+ virtual
80
+ override(Roles, AccessControlUpgradeable, IAccessControl)
81
+ returns (bool)
82
+ {
83
+ return super.hasRole(role, conduit);
84
+ }
85
+
86
+ function _grantRole(bytes32 role, address conduit)
87
+ internal
88
+ virtual
89
+ override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable)
90
+ returns (bool)
91
+ {
92
+ return super._grantRole(role, conduit);
93
+ }
94
+
95
+ function _revokeRole(bytes32 role, address conduit)
96
+ internal
97
+ virtual
98
+ override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable)
99
+ returns (bool)
100
+ {
101
+ return super._revokeRole(role, conduit);
102
+ }
103
+
104
+ function supportsInterface(bytes4 interfaceId)
105
+ public
106
+ view
107
+ virtual
108
+ override(AccessControlUpgradeable, AccessControlEnumerableUpgradeable)
109
+ returns (bool)
110
+ {
111
+ return super.supportsInterface(interfaceId);
112
+ }
113
+ }