@crossecute/protocol 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +25 -1
  2. package/abi/Blake2b256.json +1 -0
  3. package/abi/Blake2bScheme.json +1 -0
  4. package/abi/ChainRegistry.json +1 -0
  5. package/abi/Commitment.json +1 -0
  6. package/abi/Conduits.json +1 -0
  7. package/abi/DivergentConduits.json +1 -0
  8. package/abi/DivergentTransceiver.json +1 -0
  9. package/abi/DivergentTransceiverExtension.json +1 -0
  10. package/abi/Envelope.json +1 -0
  11. package/abi/Erc7930.json +1 -0
  12. package/abi/Executor.json +1 -0
  13. package/abi/IBootstrapAllowlist.json +1 -0
  14. package/abi/ICancel.json +1 -0
  15. package/abi/IChainRegistryRefs.json +1 -0
  16. package/abi/ICommitFinalize.json +1 -0
  17. package/abi/ICommitmentScheme.json +1 -0
  18. package/abi/IConduitReceiverReport.json +1 -0
  19. package/abi/IConduitTransceiver.json +1 -0
  20. package/abi/IERC7786GatewaySource.json +1 -0
  21. package/abi/IERC7786Recipient.json +1 -0
  22. package/abi/IExecute.json +1 -0
  23. package/abi/ILzOftConduitFactory.json +1 -0
  24. package/abi/IProviderIdTable.json +1 -0
  25. package/abi/IProxy.json +1 -0
  26. package/abi/IReceiverInit.json +1 -0
  27. package/abi/IRefValidator.json +1 -0
  28. package/abi/IReportFloat.json +1 -0
  29. package/abi/ITransmitterInit.json +1 -0
  30. package/abi/IVmDeriver.json +1 -0
  31. package/abi/KeccakScheme.json +1 -0
  32. package/abi/Move.json +1 -0
  33. package/abi/MoveValidator.json +1 -0
  34. package/abi/OpStackTransmitterBase.json +1 -0
  35. package/abi/OutboundBase.json +1 -0
  36. package/abi/OwnableTransmitter.json +1 -0
  37. package/abi/ProviderAddress.json +1 -0
  38. package/abi/ProviderAttribute.json +1 -0
  39. package/abi/ProviderChainId.json +1 -0
  40. package/abi/ProviderReceiver.json +1 -0
  41. package/abi/ProviderTransceiver.json +1 -0
  42. package/abi/Proxy.json +1 -0
  43. package/abi/ProxyDeployer.json +1 -0
  44. package/abi/ProxyInitCode.json +1 -0
  45. package/abi/ReceiverBase.json +1 -0
  46. package/abi/Roles.json +1 -0
  47. package/abi/RolesEnumerable.json +1 -0
  48. package/abi/SchemeFold.json +1 -0
  49. package/abi/Sha256Scheme.json +1 -0
  50. package/abi/StarknetDerive.json +1 -0
  51. package/abi/StarknetValidator.json +1 -0
  52. package/abi/SuiDerive.json +1 -0
  53. package/abi/TransceiverBase.json +1 -0
  54. package/abi/TransceiverCore.json +1 -0
  55. package/abi/TransceiverExtension.json +1 -0
  56. package/abi/TransmitterBase.json +1 -0
  57. package/abi/Treasury.json +1 -0
  58. package/abi/TronConduits.json +1 -0
  59. package/abi/TronTransceiver.json +1 -0
  60. package/abi/TronTransceiverExtension.json +1 -0
  61. package/abi/VmDeriver.json +1 -0
  62. package/abi/ZkSyncConduits.json +1 -0
  63. package/abi/ZkSyncTransceiver.json +1 -0
  64. package/abi/ZkSyncTransceiverExtension.json +1 -0
  65. package/abi/index.d.ts +63 -0
  66. package/abi/index.js +63 -0
  67. package/contracts/addressing/ChainKey.sol +45 -0
  68. package/contracts/addressing/ChainType.sol +50 -0
  69. package/contracts/addressing/Erc7930.sol +209 -0
  70. package/contracts/addressing/Move.sol +212 -0
  71. package/contracts/conduit/Conduits.sol +43 -0
  72. package/contracts/conduit/Proxy.sol +144 -0
  73. package/contracts/conduit/ProxyDeployer.sol +42 -0
  74. package/contracts/conduit/ProxyInitCode.sol +47 -0
  75. package/contracts/derivation/AddressDerive.sol +326 -0
  76. package/contracts/derivation/BitcoinDerive.sol +33 -0
  77. package/contracts/derivation/Blake2b256.sol +122 -0
  78. package/contracts/derivation/IVmDeriver.sol +35 -0
  79. package/contracts/derivation/StarknetDerive.sol +93 -0
  80. package/contracts/derivation/SuiDerive.sol +102 -0
  81. package/contracts/derivation/VmDeriver.sol +291 -0
  82. package/contracts/messaging/Call.sol +43 -0
  83. package/contracts/messaging/Commitment.sol +131 -0
  84. package/contracts/messaging/DeliveryGas.sol +18 -0
  85. package/contracts/messaging/Envelope.sol +123 -0
  86. package/contracts/messaging/Executor.sol +52 -0
  87. package/contracts/messaging/IErc7786.sol +68 -0
  88. package/contracts/messaging/Payload.sol +90 -0
  89. package/contracts/messaging/Roles.sol +113 -0
  90. package/contracts/messaging/inbound/ReceiverBase.sol +359 -0
  91. package/contracts/messaging/outbound/OutboundBase.sol +221 -0
  92. package/contracts/messaging/outbound/OwnableTransmitter.sol +23 -0
  93. package/contracts/messaging/outbound/TransmitterBase.sol +604 -0
  94. package/contracts/messaging/transceiver/DivergentConduits.sol +92 -0
  95. package/contracts/messaging/transceiver/DivergentTransceiver.sol +97 -0
  96. package/contracts/messaging/transceiver/IBootstrapAllowlist.sol +8 -0
  97. package/contracts/messaging/transceiver/TransceiverBase.sol +541 -0
  98. package/contracts/messaging/transceiver/TransceiverCore.sol +307 -0
  99. package/contracts/messaging/transceiver/TransceiverExtension.sol +278 -0
  100. package/contracts/protocols/ProviderAddress.sol +32 -0
  101. package/contracts/protocols/ProviderAttribute.sol +65 -0
  102. package/contracts/protocols/ProviderChainId.sol +115 -0
  103. package/contracts/protocols/ProviderReceiver.sol +24 -0
  104. package/contracts/protocols/ProviderTransceiver.sol +48 -0
  105. package/contracts/protocols/layerzero/ILzOftConduitFactory.sol +10 -0
  106. package/contracts/protocols/op-stack/OpStackTransmitterBase.sol +11 -0
  107. package/contracts/registry/ChainRegistry.sol +667 -0
  108. package/contracts/registry/IChainRegistryRefs.sol +81 -0
  109. package/contracts/registry/ICommitmentScheme.sol +46 -0
  110. package/contracts/registry/IRefValidator.sol +13 -0
  111. package/contracts/registry/Provenance.sol +25 -0
  112. package/contracts/schemes/Blake2bScheme.sol +18 -0
  113. package/contracts/schemes/KeccakScheme.sol +19 -0
  114. package/contracts/schemes/Sha256Scheme.sol +15 -0
  115. package/contracts/treasury/Treasury.sol +76 -0
  116. package/contracts/validators/MoveValidator.sol +24 -0
  117. package/contracts/validators/StarknetValidator.sol +20 -0
  118. package/package.json +34 -3
@@ -0,0 +1,144 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ /// @notice The one call a `Proxy` accepts from its deployer. Declared separately so
5
+ /// callers have a selector without the proxy shadowing the implementation's ABI.
6
+ interface IProxy {
7
+ function upgradeInitializeAndLock(address implementation, bytes calldata data) external;
8
+ }
9
+
10
+ /// @title Proxy
11
+ /// @notice The proxy every crossecute conduit is deployed as, transmitter and receiver alike,
12
+ /// and every transceiver, through `ProxyDeployer`. Its implementation is locked
13
+ /// once armed; its owner's authority is unchanged.
14
+ ///
15
+ /// @dev No constructor arguments, so its initcode is one constant and a transmitter (from its
16
+ /// home's transceiver) and a receiver (from another chain's) at the same deployer address
17
+ /// and salt land on one address. An EIP-1167 clone embeds its implementation in the initcode and could not.
18
+ ///
19
+ /// @dev The deployer can arm the proxy only while no implementation is set. The single admin
20
+ /// operation installs the implementation, runs its initializer, and by installing it ends
21
+ /// the deployer's ability to arm, so no conduit or transceiver ever has a live upgrade key
22
+ /// and real logic at once. An implementation must not write the ERC-1967 implementation
23
+ /// slot: clearing it would let the deployer arm again.
24
+ ///
25
+ /// @dev The deployer is an immutable, so it sits in the runtime code and not the initcode: the
26
+ /// address is unchanged, but the code hash differs per deployer, and a proxy is verified by
27
+ /// its CREATE2 address. A call on an armed proxy reads the implementation slot and nothing else.
28
+ ///
29
+ /// @dev The admin operation is routed in `fallback`, not declared, so it shadows no selector.
30
+ /// Once armed every call delegates, as in a plain ERC-1967 proxy.
31
+ contract Proxy {
32
+ // forge-lint: disable-next-line(unused-error) reverted in assembly; declared for the ABI
33
+ error UnknownAdminCall(bytes4 selector);
34
+
35
+ event Locked(address implementation);
36
+
37
+ // ERC1967Utils.IMPLEMENTATION_SLOT and the errors and events the arming call reports, as
38
+ // literals so assembly can read them; `ProxyDifferential` compares every one against
39
+ // the library-built reference.
40
+ //
41
+ // bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)
42
+ bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
43
+ // keccak256("Upgraded(address)"), IERC1967
44
+ bytes32 private constant UPGRADED = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
45
+ // keccak256("Locked(address)")
46
+ bytes32 private constant LOCKED = 0x44427e3003a08f22cf803894075ac0297524e09e521fc1c15bc91741ce3dc159;
47
+ // bytes4(keccak256("upgradeInitializeAndLock(address,bytes)"))
48
+ uint256 private constant ARM = 0x885088b2;
49
+ // bytes4(keccak256("UnknownAdminCall(bytes4)")), declared above
50
+ uint256 private constant UNKNOWN_ADMIN_CALL = 0xf3e81dfa;
51
+ // bytes4(keccak256("ERC1967InvalidImplementation(address)")), as in ERC1967Utils
52
+ uint256 private constant INVALID_IMPLEMENTATION = 0x4c9c8ce3;
53
+ // bytes4(keccak256("ERC1967NonPayable()")), as in ERC1967Utils
54
+ uint256 private constant NON_PAYABLE = 0xb398979f;
55
+ // bytes4(keccak256("FailedCall()")), as in OpenZeppelin's Errors
56
+ uint256 private constant FAILED_CALL = 0xd6bda275;
57
+
58
+ address private immutable ADMIN;
59
+
60
+ constructor() {
61
+ ADMIN = msg.sender;
62
+ }
63
+
64
+ fallback() external payable {
65
+ address current;
66
+ assembly {
67
+ current := sload(IMPLEMENTATION_SLOT) // implementation; zero while the proxy is blank
68
+ }
69
+
70
+ // Armed, or not the deployer: an ordinary call. A blank proxy delegates to no code and
71
+ // succeeds. `ADMIN` is read only while blank.
72
+ if (current != address(0) || msg.sender != ADMIN) {
73
+ assembly {
74
+ calldatacopy(0, 0, calldatasize()) // copy the whole call to memory at 0
75
+ let ok := delegatecall(gas(), current, 0, calldatasize(), 0, 0) // run it as this proxy
76
+ returndatacopy(0, 0, returndatasize()) // copy its output, or its revert data, to memory at 0
77
+ if iszero(ok) { revert(0, returndatasize()) } // pass a revert through unchanged
78
+ return(0, returndatasize()) // return its output
79
+ }
80
+ }
81
+
82
+ assembly {
83
+ let size := calldatasize() // total bytes of calldata
84
+ let sig := and(calldataload(0), shl(224, 0xffffffff)) // selector, left-aligned; zero-padded if short
85
+ if iszero(eq(sig, shl(224, ARM))) {
86
+ // anything but the arming selector is refused
87
+ mstore(0, shl(224, UNKNOWN_ADMIN_CALL)) // UnknownAdminCall(bytes4), selector left-aligned at 0
88
+ mstore(4, sig) // its argument, the selector received, at 4
89
+ revert(0, 36) // selector and one 32-byte argument
90
+ }
91
+
92
+ // abi.decode(msg.data[4:], (address, bytes)), refusing what it refuses. A length over
93
+ // 2^64 reverts with no data here and with Panic(0x41) there.
94
+ if lt(size, 68) { revert(0, 0) } // needs the selector and both head words: 4 + 64
95
+ let implementation := calldataload(4) // first argument
96
+ if shr(160, implementation) { revert(0, 0) } // an address with high bits set is malformed
97
+ let offset := calldataload(36) // second argument: where the bytes sit, counted after the selector
98
+ if gt(offset, 0xffffffffffffffff) { revert(0, 0) } // keeps the additions below from wrapping
99
+ let start := add(offset, 4) // position of the length word in calldata
100
+ let length := calldataload(start) // number of initializer bytes; zero if read past the end
101
+ if gt(length, 0xffffffffffffffff) { revert(0, 0) } // keeps `data + length` from wrapping
102
+ let data := add(start, 32) // where the initializer bytes begin
103
+ if gt(add(data, length), size) { revert(0, 0) } // must end in the calldata; refuses a far start
104
+
105
+ if iszero(extcodesize(implementation)) {
106
+ // an implementation with no code, or the zero address, is refused
107
+ mstore(0, shl(224, INVALID_IMPLEMENTATION)) // ERC1967InvalidImplementation(address)
108
+ mstore(4, implementation) // its argument at 4
109
+ revert(0, 36) // selector and one 32-byte argument
110
+ }
111
+ sstore(IMPLEMENTATION_SLOT, implementation) // install it; the slot is now non-zero, which closes arming
112
+ log2(0, 0, UPGRADED, implementation) // Upgraded(address indexed implementation), as ERC-1967
113
+
114
+ switch length // with or without an initializer
115
+ case 0 {
116
+ // no initializer
117
+ if callvalue() {
118
+ // ether would have nowhere to go: refused, as ERC1967Utils does
119
+ mstore(0, shl(224, NON_PAYABLE)) // ERC1967NonPayable()
120
+ revert(0, 4) // selector only
121
+ }
122
+ }
123
+ default {
124
+ // run the initializer
125
+ let ptr := mload(0x40) // free memory, so the copy touches nothing in use
126
+ calldatacopy(ptr, data, length) // copy the initializer bytes
127
+ if iszero(delegatecall(gas(), implementation, ptr, length, 0, 0)) {
128
+ // run it as this proxy; a failure reverts the whole arming
129
+ if iszero(returndatasize()) {
130
+ // it reverted with no data
131
+ mstore(0, shl(224, FAILED_CALL)) // FailedCall(), so the revert is not silent
132
+ revert(0, 4) // selector only
133
+ }
134
+ returndatacopy(ptr, 0, returndatasize()) // copy its revert data
135
+ revert(ptr, returndatasize()) // pass it through unchanged
136
+ }
137
+ }
138
+
139
+ mstore(0, implementation) // the one data word of Locked
140
+ log1(0, 32, LOCKED) // Locked(address implementation)
141
+ return(0, 0) // the arming call returns nothing
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
5
+ import {Proxy, IProxy} from "./Proxy.sol";
6
+
7
+ // Where `ProxyDeployer` sits: Arachnid's factory at salt zero, so the same address on every
8
+ // standard EVM chain. A `Predetermined` chain is one whose transceivers were deployed through it
9
+ // (#33). A literal, since a zkSync or Tron registry's compiler would derive another;
10
+ // `ProxyDeployer.t.sol` pins it to solc's build.
11
+ address constant PROXY_DEPLOYER = 0x415DaAF39EB13593a0Fe5E140bc97C4C9a63e116;
12
+
13
+ /// @notice The CREATE2 salt `ProxyDeployer` deploys under for `deployedBy`'s `salt`, where
14
+ /// `deployedBy` is the account that calls `deploy`.
15
+ /// @dev The caller is hashed in so that nobody else can deploy at a caller's address first
16
+ /// with their own implementation. `ChainRegistry.predictTransceiver` derives with it.
17
+ function proxySalt(address deployedBy, bytes32 salt) pure returns (bytes32) {
18
+ return keccak256(abi.encode(deployedBy, salt));
19
+ }
20
+
21
+ /// @title ProxyDeployer
22
+ /// @notice Deploys a `Proxy` and arms it in one call, so a transceiver has no window in
23
+ /// which another caller could install its logic.
24
+ ///
25
+ /// @dev Deployed through Arachnid's factory with no constructor arguments, so it has one address
26
+ /// on every standard EVM chain, and every transceiver it deploys for one caller and salt
27
+ /// shares one address too. A `Proxy` makes its deployer its admin, which is why the
28
+ /// proxy cannot be deployed through Arachnid's factory directly: that factory makes no
29
+ /// calls and could never arm it.
30
+ contract ProxyDeployer {
31
+ event Deployed(address indexed deployedBy, bytes32 salt, address proxy, address implementation);
32
+
33
+ function deploy(bytes32 salt, address implementation, bytes calldata data) external returns (address proxy) {
34
+ proxy = Create2.deploy(0, proxySalt(msg.sender, salt), type(Proxy).creationCode);
35
+ IProxy(proxy).upgradeInitializeAndLock(implementation, data);
36
+ emit Deployed(msg.sender, salt, proxy, implementation);
37
+ }
38
+
39
+ function predict(address deployedBy, bytes32 salt) external view returns (address) {
40
+ return Create2.computeAddress(proxySalt(deployedBy, salt), keccak256(type(Proxy).creationCode));
41
+ }
42
+ }
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.0;
3
+
4
+ import {Proxy} from "./Proxy.sol";
5
+
6
+ /// @notice `Proxy`'s initcode held by another contract, which returns it to any call, so a
7
+ /// contract that deploys conduits reads it rather than carrying 0.6 KB of it in its own
8
+ /// runtime (#70).
9
+ ///
10
+ /// @dev CREATE2 hashes the initcode bytes, not where they were read from, so a conduit deployed
11
+ /// from the loaded copy lands where `PROXY_INIT_CODE_HASH` predicts.
12
+ ///
13
+ /// @dev Read by `STATICCALL` rather than `EXTCODECOPY`, which zksolc refuses to compile: every
14
+ /// contract a zkSync variant's imports reach is compiled for EraVM, the plain transceivers
15
+ /// among them.
16
+ library ProxyInitCode {
17
+ error StoreFailed();
18
+
19
+ /// @notice What every CREATE2 prediction of a conduit hashes.
20
+ function hash() internal pure returns (bytes32) {
21
+ return keccak256(type(Proxy).creationCode);
22
+ }
23
+
24
+ /// @notice Deploy the code-only contract. `Proxy`'s initcode appears only in the caller's
25
+ /// initcode when this runs from a constructor.
26
+ function store() internal returns (address initCodeStore) {
27
+ bytes memory initCode = type(Proxy).creationCode;
28
+ // PUSH2 len, DUP1, PUSH1 12, PUSH1 0, CODECOPY, PUSH1 0, RETURN: on any call, returns what
29
+ // follows this 12-byte prefix, the initcode.
30
+ // forge-lint: disable-next-line(unsafe-typecast) a constant 0.6 KB
31
+ bytes memory runtime = abi.encodePacked(hex"61", uint16(initCode.length), hex"80600c6000396000f3", initCode);
32
+ // The same prefix shape, 14 bytes with PUSH4: returns `runtime` as the new contract's code.
33
+ // forge-lint: disable-next-line(unsafe-typecast) a constant 0.6 KB
34
+ bytes memory creation = abi.encodePacked(hex"63", uint32(runtime.length), hex"80600e6000396000f3", runtime);
35
+ assembly {
36
+ initCodeStore := create(0, add(creation, 0x20), mload(creation))
37
+ }
38
+ if (initCodeStore == address(0)) revert StoreFailed();
39
+ }
40
+
41
+ /// @notice The initcode `store` kept at `initCodeStore`.
42
+ function load(address initCodeStore) internal view returns (bytes memory code) {
43
+ bool ok;
44
+ (ok, code) = initCodeStore.staticcall("");
45
+ if (!ok || code.length == 0) revert StoreFailed();
46
+ }
47
+ }
@@ -0,0 +1,326 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Erc7930} from "../addressing/Erc7930.sol";
5
+
6
+ /// @title AddressDerive
7
+ /// @notice Deterministic address derivation across EVM and non-EVM chains, computed
8
+ /// on-chain inside an Ethereum transaction. Pure where possible, no chain state.
9
+ ///
10
+ /// @dev Formerly ForeignCreate2. EVM derivations are now a real path rather than a
11
+ /// test-only comparison, because on an EVM destination the registry can resolve
12
+ /// locally and skip the round trip entirely.
13
+ ///
14
+ /// @dev Scope. Everything here bottoms out in `keccak256` (native) or `sha256` (0x02).
15
+ /// Bitcoin's `ripemd160` lives in BitcoinDerive.sol, because
16
+ /// EraVM does not support that precompile and rejects any unit importing it, which a
17
+ /// zkSync transceiver needs this file for. Sui lives in SuiDerive.sol because BLAKE2b needs the
18
+ /// 0x09 precompile, which forces `view` rather than `pure`.
19
+ /// Not derivable on the EVM at any price: Aptos/Movement (SHA3-256, ~1e5 gas for
20
+ /// a hand-rolled Keccak-f), Starknet (Pedersen/Poseidon over the STARK curve),
21
+ /// Bitcoin P2TR (secp256k1 point addition). Route those through the `Unique` path.
22
+ library AddressDerive {
23
+ /* ====================================================================== */
24
+ /* EVM */
25
+ /* ====================================================================== */
26
+
27
+ /// @notice EIP-1014 CREATE2. The default resolution path.
28
+ function create2(address deployer, bytes32 salt, bytes32 initCodeHash) internal pure returns (address) {
29
+ return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash)))));
30
+ }
31
+
32
+ /// @notice Nonce-based CREATE, address = keccak256(rlp([deployer, nonce]))[12:].
33
+ /// @dev RLP has three cases for the nonce and getting them wrong is a classic bug:
34
+ /// 0 encodes as 0x80 (empty string, not 0x00), 1..127 encode as themselves,
35
+ /// and anything larger takes a 0x80+len prefix over minimal big-endian bytes.
36
+ ///
37
+ /// @dev The list prefix is `0xc0 + payload`, and the payload grows with the nonce.
38
+ /// The address item is always 21 bytes (0x94 plus twenty), and the nonce item is
39
+ /// 1 byte in the first two arms but `1 + len` in the third, so the prefix is
40
+ /// 0xd6 in the first two and `0xd6 + len` in the third. It read `0xd5 + len`
41
+ /// until `test/CreateDerivation.t.sol` was written. Every nonce at or above 128
42
+ /// produced a well-formed address that was not the one Ethereum would use. Only
43
+ /// `create3` called this, always with a hardcoded nonce of 1, so nothing
44
+ /// exercised the arm. `VmDeriver.Scheme.EvmCreate` takes the nonce from
45
+ /// registry params and could have reached it with any value.
46
+ function create(address deployer, uint256 nonce) internal pure returns (address) {
47
+ bytes memory rlp;
48
+ if (nonce == 0) {
49
+ rlp = abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80));
50
+ } else if (nonce <= 0x7f) {
51
+ // forge-lint: disable-next-line(unsafe-typecast) nonce <= 0x7f in this branch
52
+ rlp = abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce));
53
+ } else {
54
+ // Minimal big-endian, the same rule the eip155 chain reference uses.
55
+ // Borrowed from `Erc7930` rather than restated: `derivation` imports
56
+ // `addressing` and never the reverse, so this is the direction the
57
+ // dependency graph already runs, and one encoder cannot drift from itself.
58
+ bytes memory n = Erc7930.minimalBigEndian(nonce);
59
+ // forge-lint: disable-next-line(unsafe-typecast) n is at most 32 bytes
60
+ uint8 nLen = uint8(n.length);
61
+ rlp = abi.encodePacked(bytes1(0xd6 + nLen), bytes1(0x94), deployer, bytes1(0x80 + nLen), n);
62
+ }
63
+ return address(uint160(uint256(keccak256(rlp))));
64
+ }
65
+
66
+ /// @notice Creation-code hash of an EIP-1167 minimal proxy for `implementation`.
67
+ /// @dev For a destination whose contracts are EIP-1167 clones, reached through
68
+ /// `VmDeriver`'s clone schemes: the address is CREATE2 over the deployer, the salt, and
69
+ /// this hash. This protocol's own conduits are `Proxy`, not clones.
70
+ ///
71
+ /// The 55-byte layout is EIP-1167 verbatim: 10 bytes of creation code, then the
72
+ /// 45-byte runtime with the implementation address spliced in at byte 20:
73
+ /// 3d602d80600a3d3981f3 363d3d373d3d3d363d73 <impl> 5af43d82803e903d91602b57fd5bf3
74
+ /// Verified against OpenZeppelin's `Clones.predictDeterministicAddress` in tests
75
+ /// rather than trusted from memory. If OZ ever changes its proxy bytecode, this
76
+ /// constant is wrong and the test fails.
77
+ function cloneInitCodeHash(address implementation) internal pure returns (bytes32) {
78
+ return keccak256(
79
+ abi.encodePacked(
80
+ hex"3d602d80600a3d3981f3363d3d373d3d3d363d73", implementation, hex"5af43d82803e903d91602b57fd5bf3"
81
+ )
82
+ );
83
+ }
84
+
85
+ /// @notice Address of an EIP-1167 clone deployed with CREATE2.
86
+ function clone2(address deployer, address implementation, bytes32 salt) internal pure returns (address) {
87
+ return create2(deployer, salt, cloneInitCodeHash(implementation));
88
+ }
89
+
90
+ /// @dev Canonical CREATE3 proxy initcode: PUSH8 363d3d37363d34f0; 3d5260086018f3.
91
+ /// Constant across the 0xsequence, Solady, and LI.FI variants. Verified by
92
+ /// hashing the literal rather than copied from a header file.
93
+ bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
94
+ 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
95
+
96
+ /// @notice CREATE3: address depends only on (factory, salt), never on bytecode.
97
+ /// @dev Two stages. A fixed-initcode proxy is CREATE2-deployed, then that proxy
98
+ /// does a plain CREATE at nonce 1. The nonce is a hardcoded 1 from a fresh
99
+ /// contract, not the deployer's transaction history.
100
+ ///
101
+ /// Does not port to zkSync (both stages use different formulas) or to Tron
102
+ /// (CREATE derives from root tx id, so stage 2 is not a function of the proxy
103
+ /// address). Use only for chains sharing Ethereum's derivation.
104
+ function create3(address factory, bytes32 salt) internal pure returns (address) {
105
+ address proxy = create2(factory, salt, CREATE3_PROXY_INITCODE_HASH);
106
+ return create(proxy, 1);
107
+ }
108
+
109
+ /* ====================================================================== */
110
+ /* L1 -> L2 sender aliasing */
111
+ /* ====================================================================== */
112
+
113
+ /// @dev Shared by three stacks, which is why it is not under any of them. Arbitrum,
114
+ /// zkSync Era, and the OP Stack's `OptimismPortal` all rewrite the sender of an
115
+ /// L1-originated message by the same constant, and Arbitrum's own
116
+ /// `AddressAliasHelper.OFFSET` is this value. Filing it under one of them invites
117
+ /// the next binding to write a second copy.
118
+ uint160 internal constant L1_TO_L2_ALIAS_OFFSET = uint160(0x1111000000000000000000000000000000001111);
119
+
120
+ /// @notice The address an L1 contract appears as when its message arrives on L2.
121
+ ///
122
+ /// @dev Contracts only. An EOA is not aliased, because the collision this prevents is a
123
+ /// contract at some address on L1 impersonating a different contract that happens
124
+ /// to sit at the same address on L2.
125
+ function applyL1ToL2Alias(address l1) internal pure returns (address) {
126
+ unchecked {
127
+ return address(uint160(l1) + L1_TO_L2_ALIAS_OFFSET);
128
+ }
129
+ }
130
+
131
+ /// @notice Recover the L1 address from the sender an L2 actually observed.
132
+ ///
133
+ /// @dev This is the direction a binding needs, and the one whose absence is a silent bug
134
+ /// rather than a revert. `ReceiverBase` compares an inbound sender against
135
+ /// `sourceTransmitter`, which holds the transmitter's address on the home chain; an
136
+ /// L2 receiver sees that address plus the offset, so every inbound message is
137
+ /// refused until the binding subtracts it back out.
138
+ ///
139
+ /// @dev It is L1 -> L2 only, and applying it symmetrically corrupts the other direction.
140
+ /// A withdrawal carries the raw L2 sender: Arbitrum's `Outbox` hashes `l2Sender`
141
+ /// unaliased into the leaf it proves against. So a binding un-aliases on the
142
+ /// inbound-to-L2 path and nowhere else.
143
+ ///
144
+ /// @dev It cannot tell whether it was needed. The arithmetic wraps, so undoing an alias
145
+ /// that was never applied returns a perfectly well-formed address belonging to
146
+ /// nobody rather than failing. There is no "was this aliased" predicate and there
147
+ /// cannot be one: the caller decides from the direction of the message, never from
148
+ /// the value. That is why this is a binding's job and not `ReceiverBase`'s, which
149
+ /// cannot know which transport delivered.
150
+ ///
151
+ /// @dev Not every binding needs it. The OP Stack's `CrossDomainMessenger` un-aliases
152
+ /// internally and reports the original through `xDomainMessageSender()`, so only a
153
+ /// binding built directly on `OptimismPortal` has to. Arbitrum and zkSync have no
154
+ /// equivalent.
155
+ function undoL1ToL2Alias(address l2) internal pure returns (address) {
156
+ unchecked {
157
+ return address(uint160(l2) - L1_TO_L2_ALIAS_OFFSET);
158
+ }
159
+ }
160
+
161
+ /* ====================================================================== */
162
+ /* zkSync Era */
163
+ /* ====================================================================== */
164
+
165
+ bytes32 internal constant ZKSYNC_CREATE2_PREFIX = keccak256("zksyncCreate2");
166
+ bytes32 internal constant ZKSYNC_CREATE_PREFIX = keccak256("zksyncCreate");
167
+
168
+ /// @param sender If an L1 *contract* triggers the deploy via Bridgehub/Mailbox, pass
169
+ /// the aliased address. EOAs are not aliased.
170
+ /// @param bytecodeHash EraVM versioned hash: see `hashL2Bytecode`, not keccak(initcode).
171
+ function zksyncCreate2(address sender, bytes32 salt, bytes32 bytecodeHash, bytes32 constructorInputHash)
172
+ internal
173
+ pure
174
+ returns (address)
175
+ {
176
+ return address(
177
+ uint160(
178
+ uint256(
179
+ keccak256(
180
+ abi.encodePacked(
181
+ ZKSYNC_CREATE2_PREFIX,
182
+ bytes32(uint256(uint160(sender))),
183
+ salt,
184
+ bytecodeHash,
185
+ constructorInputHash
186
+ )
187
+ )
188
+ )
189
+ )
190
+ );
191
+ }
192
+
193
+ function zksyncCreate(address sender, uint256 nonce) internal pure returns (address) {
194
+ return address(
195
+ uint160(
196
+ uint256(
197
+ keccak256(abi.encodePacked(ZKSYNC_CREATE_PREFIX, bytes32(uint256(uint160(sender))), bytes32(nonce)))
198
+ )
199
+ )
200
+ );
201
+ }
202
+
203
+ /// @dev [0]=0x01 version, [1]=0x00, [2:4]=length in words, [4:32]=trailing 28 bytes
204
+ /// of sha256(bytecode). Invariants mirror EraVM. Requires zksolc output.
205
+ function hashL2Bytecode(bytes memory bytecode) internal pure returns (bytes32 h) {
206
+ require(bytecode.length % 32 == 0, "C2L: not word-aligned");
207
+ uint256 words = bytecode.length / 32;
208
+ require(words < 2 ** 16, "C2L: bytecode too long");
209
+ require(words % 2 == 1, "C2L: even word count");
210
+ h = sha256(bytecode);
211
+ h = h & bytes32(uint256(type(uint224).max));
212
+ h = h | bytes32(uint256(1) << 248);
213
+ h = h | bytes32(words << 224);
214
+ }
215
+
216
+ /* ====================================================================== */
217
+ /* Tron */
218
+ /* ====================================================================== */
219
+
220
+ /// @notice Same 85-byte preimage as EIP-1014; only the domain byte differs (0x41).
221
+ /// @dev Returns the bare 20-byte in-VM form. Prepend 0x41 + Base58Check off-chain.
222
+ /// initCodeHash must come from Tron-solc, not solc.
223
+ /// java-tron's CREATE2 hashes the sender in its 21-byte form, `0x41 ‖ deployer`
224
+ /// (`WalletUtil.generateContractAddress2`), which is where the 0x41 comes from.
225
+ function tronCreate2(address deployer, bytes32 salt, bytes32 initCodeHash) internal pure returns (address) {
226
+ return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0x41), deployer, salt, initCodeHash)))));
227
+ }
228
+
229
+ /* ====================================================================== */
230
+ /* Cosmos / CosmWasm */
231
+ /* ====================================================================== */
232
+
233
+ /// @dev sha256("module"), the ADR-028 domain tag.
234
+ bytes32 internal constant COSMOS_MODULE_TAG = 0x120970d812836f19888625587a4606a5ad23cef31c8684e601771552548fc6b9;
235
+
236
+ /// @notice CosmWasm Instantiate2: the CREATE2 analogue on Cosmos.
237
+ /// @dev sha256( sha256("module") ++ key ), key = "wasm\0" ++ be64/value pairs.
238
+ /// The "wasm\0" prefix exists in cosmwasm-std but is absent from the published
239
+ /// spec page. Verified against cosmwasm-std's own test vectors.
240
+ /// @param creator Canonical decoded address bytes, not the bech32 string.
241
+ function cosmosInstantiate2(bytes32 checksum, bytes memory creator, bytes memory salt, bytes memory initMsg)
242
+ internal
243
+ pure
244
+ returns (bytes32)
245
+ {
246
+ require(salt.length >= 1 && salt.length <= 64, "C2L: bad salt len");
247
+ // Each variable field is length-prefixed, and a memory length fits in 64 bits.
248
+ // forge-lint: disable-start(encode-packed-collision,unsafe-typecast)
249
+ bytes memory key = abi.encodePacked(
250
+ hex"7761736d00", // "wasm\0"
251
+ uint64(32),
252
+ checksum,
253
+ uint64(creator.length),
254
+ creator,
255
+ uint64(salt.length),
256
+ salt,
257
+ uint64(initMsg.length),
258
+ initMsg
259
+ );
260
+ // forge-lint: disable-end(encode-packed-collision,unsafe-typecast)
261
+ return sha256(abi.encodePacked(COSMOS_MODULE_TAG, key));
262
+ }
263
+
264
+ /* ====================================================================== */
265
+ /* Solana */
266
+ /* ====================================================================== */
267
+
268
+ /// @notice PDA for a known bump.
269
+ /// @dev This is not `find_program_address`. The canonical PDA is the first bump
270
+ /// counting down from 255 whose output is off the ed25519 curve; that check
271
+ /// needs Edwards decompression over GF(2^255-19) and is not implemented.
272
+ /// A too-high bump yields an on-curve point this function will happily return.
273
+ /// Bind the bump into committed inputs so signers approve it explicitly.
274
+ function solanaCreateProgramAddress(bytes[] memory seeds, uint8 bump, bytes32 programId)
275
+ internal
276
+ pure
277
+ returns (bytes32)
278
+ {
279
+ require(seeds.length <= 16, "C2L: too many seeds");
280
+ bytes memory pre;
281
+ for (uint256 i; i < seeds.length; ++i) {
282
+ require(seeds[i].length <= 32, "C2L: seed too long");
283
+ // forge-lint: disable-next-line(encode-packed-collision) Solana's PDA preimage is the bare concatenation
284
+ pre = abi.encodePacked(pre, seeds[i]);
285
+ }
286
+ return sha256(abi.encodePacked(pre, bump, programId, "ProgramDerivedAddress"));
287
+ }
288
+
289
+ /// @notice SPL Associated Token Account.
290
+ /// @dev Seed order is [owner, tokenProgram, mint]: the token program sits in the
291
+ /// middle, which is easy to get backwards. Verified against solders.
292
+ function solanaAssociatedTokenAccount(
293
+ bytes32 owner,
294
+ bytes32 mint,
295
+ bytes32 tokenProgram,
296
+ bytes32 ataProgram,
297
+ uint8 bump
298
+ ) internal pure returns (bytes32) {
299
+ return sha256(abi.encodePacked(owner, tokenProgram, mint, bump, ataProgram, "ProgramDerivedAddress"));
300
+ }
301
+
302
+ /// @notice SystemProgram::createWithSeed. No bump, no off-curve requirement.
303
+ function solanaCreateWithSeed(bytes32 base, bytes memory seed, bytes32 programId) internal pure returns (bytes32) {
304
+ return sha256(abi.encodePacked(base, seed, programId));
305
+ }
306
+
307
+ /* ====================================================================== */
308
+ /* NEAR */
309
+ /* ====================================================================== */
310
+
311
+ /// @notice NEAR deterministic account: NEAR's CREATE2, and free here since it
312
+ /// uses keccak256. Render as "0s" + lowercase hex, not "0x".
313
+ /// @param borshStateInit Borsh-serialized DeterministicAccountStateInit. Serialize
314
+ /// off-chain; do not build Borsh in Solidity.
315
+ function nearDeterministicAccount(bytes memory borshStateInit) internal pure returns (bytes20) {
316
+ // forge-lint: disable-next-line(unsafe-typecast) the address is the hash's low 20 bytes
317
+ return bytes20(uint160(uint256(keccak256(borshStateInit))));
318
+ }
319
+
320
+ /// @param uncompressedPubkey64 64 bytes, without the 0x04 SEC1 prefix.
321
+ function nearEthImplicitAccount(bytes memory uncompressedPubkey64) internal pure returns (bytes20) {
322
+ require(uncompressedPubkey64.length == 64, "C2L: bad pubkey len");
323
+ // forge-lint: disable-next-line(unsafe-typecast) the address is the hash's low 20 bytes
324
+ return bytes20(uint160(uint256(keccak256(uncompressedPubkey64))));
325
+ }
326
+ }
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ /// @title BitcoinDerive
5
+ /// @notice Bitcoin script and witness-program hashing, for `bip122` references.
6
+ ///
7
+ /// @dev Split out of `AddressDerive` to keep `ripemd160` off EraVM, and that is a compile
8
+ /// constraint rather than a taste one. zkSync's compiler rejects the `ripemd160`
9
+ /// precompile outright, and it rejects the whole compilation unit rather than the
10
+ /// unreachable function: a zkSync transceiver needs `AddressDerive.zksyncCreate2` and would
11
+ /// otherwise drag `hash160` in with it and fail to build. Nothing else separated these,
12
+ /// since both halves are "hash bytes with a precompile".
13
+ ///
14
+ /// @dev Nothing on a destination calls this. Bitcoin has no executor and no receiver: `bip122`
15
+ /// appears in the registry as a reference that can be validated and addressed, never
16
+ /// as a destination a payload runs on. Its only caller is `VmDeriver`, which nothing
17
+ /// deployed on EraVM imports, so `VmDeriver` cannot be built for EraVM either.
18
+ library BitcoinDerive {
19
+ /// @notice HASH160 = ripemd160(sha256(x)). P2PKH and P2WPKH witness programs.
20
+ function hash160(bytes memory data) internal pure returns (bytes20) {
21
+ return ripemd160(abi.encodePacked(sha256(data)));
22
+ }
23
+
24
+ /// @notice P2WSH witness program = sha256(witnessScript).
25
+ function p2wshProgram(bytes memory witnessScript) internal pure returns (bytes32) {
26
+ return sha256(witnessScript);
27
+ }
28
+
29
+ /// @notice P2SH script hash = hash160(redeemScript).
30
+ function p2shScriptHash(bytes memory redeemScript) internal pure returns (bytes20) {
31
+ return hash160(redeemScript);
32
+ }
33
+ }