@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.
- package/README.md +25 -1
- 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 +34 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {Erc7930} from "./Erc7930.sol";
|
|
5
|
+
|
|
6
|
+
/// @title ChainKey
|
|
7
|
+
/// @notice The protocol's single name for a destination: keccak256 of the canonical
|
|
8
|
+
/// ERC-7930 chain identifier.
|
|
9
|
+
///
|
|
10
|
+
/// @dev For eip155 nobody stores one: the source derives the destination's key from its
|
|
11
|
+
/// `uint256` chain id and the destination derives its own from `block.chainid`.
|
|
12
|
+
///
|
|
13
|
+
/// @dev It is both the commitment domain, folded into `Commitment.hashCalls` and recomputed
|
|
14
|
+
/// by the receiver with no configuration, and the key every routing table is indexed by
|
|
15
|
+
/// (routes and counterparts on a transceiver, chain data in the registry). A raw chain
|
|
16
|
+
/// id would serve the first only on EVM destinations.
|
|
17
|
+
library ChainKey {
|
|
18
|
+
/// @notice The key for an eip155 chain. Pure: no storage, no registry, no round trip.
|
|
19
|
+
function forEvm(uint256 chainId) internal pure returns (bytes32) {
|
|
20
|
+
return keccak256(Erc7930.encodeEvmChain(chainId));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice This chain's own key, as the destination-side receiver computes it.
|
|
24
|
+
/// @dev The counterpart to `forEvm`, and the reason the commitment domain needs no
|
|
25
|
+
/// configuration on the destination. A non-EVM receiver overrides its equivalent
|
|
26
|
+
/// with a literal for its own chain identifier: a property of the deployment
|
|
27
|
+
/// target, not something an operator sets.
|
|
28
|
+
function local() internal view returns (bytes32) {
|
|
29
|
+
return forEvm(block.chainid);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// @notice The key for any chain, from an ERC-7930 envelope.
|
|
33
|
+
/// @dev Accepts a conduit envelope as well as a bare chain identifier;
|
|
34
|
+
/// `toChainIdentifier` reduces it, so every address on a chain yields one key.
|
|
35
|
+
/// `parseStrict` runs inside, so a non-canonical framing reverts here rather
|
|
36
|
+
/// than becoming a key nothing else can reproduce.
|
|
37
|
+
function fromIdentifier(bytes memory identifier) internal pure returns (bytes32) {
|
|
38
|
+
return fromParsed(Erc7930.parseStrict(identifier));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// @notice `fromIdentifier`, for an envelope `parseStrict` has already read.
|
|
42
|
+
function fromParsed(Erc7930.Interop memory io) internal pure returns (bytes32) {
|
|
43
|
+
return keccak256(Erc7930.encodeChainId(io.chainType, io.chainRef));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
/// @title ChainType
|
|
5
|
+
/// @notice The single allocation table for ERC-7930 ChainType values.
|
|
6
|
+
///
|
|
7
|
+
/// @dev Why this is one file. A ChainType is baked into every envelope and registry keys are
|
|
8
|
+
/// `keccak256(envelope)`, so two files each picking a provisional value is how you get a
|
|
9
|
+
/// silent collision that only surfaces as two chains sharing a key.
|
|
10
|
+
///
|
|
11
|
+
/// @dev Onboarding a chain type is two steps, and allocating here is only the first.
|
|
12
|
+
/// `Erc7930.parseStrict` reads the field as an opaque `uint16` and validates nothing
|
|
13
|
+
/// against this table, so a value's presence here buys collision avoidance and nothing
|
|
14
|
+
/// else. The profile's canonicity rule must be written into `parseStrict` alongside it,
|
|
15
|
+
/// or two encodings of one address hash to two registry keys with no error anywhere.
|
|
16
|
+
/// See "Adding a chain type" in the README.
|
|
17
|
+
///
|
|
18
|
+
/// @dev Assigned values come from the casa namespace registry's CAIP-350 profiles.
|
|
19
|
+
/// Provisional values are local, live at or above `PROVISIONAL_FLOOR`, and face a
|
|
20
|
+
/// re-keying migration if casa later publishes a different value for them. Casa
|
|
21
|
+
/// allocates upward from 0x0000, so 0xFF00..0xFFFF makes a collision implausible.
|
|
22
|
+
library ChainType {
|
|
23
|
+
/* ================================ assigned ================================= */
|
|
24
|
+
|
|
25
|
+
uint16 internal constant EIP155 = 0x0000; // eip155/caip350.md
|
|
26
|
+
uint16 internal constant BIP122 = 0x0001; // bip122/caip350.md
|
|
27
|
+
uint16 internal constant SOLANA = 0x0002; // solana/caip350.md
|
|
28
|
+
uint16 internal constant STARKNET = 0x0003; // starknet/caip350.md
|
|
29
|
+
|
|
30
|
+
/* =============================== provisional =============================== */
|
|
31
|
+
|
|
32
|
+
/// @dev Anything at or above this is locally assigned and subject to re-keying.
|
|
33
|
+
uint16 internal constant PROVISIONAL_FLOOR = 0xFF00;
|
|
34
|
+
|
|
35
|
+
/// @dev Aptos-Move: 32-byte AccountAddress, SHA3-256 derivation. Movement shares
|
|
36
|
+
/// this profile and is distinguished by ChainReference, not ChainType.
|
|
37
|
+
uint16 internal constant APTOS = 0xFF01;
|
|
38
|
+
/// @dev Sui-Move: 32-byte address/ObjectID, BLAKE2b-256 derivation.
|
|
39
|
+
uint16 internal constant SUI = 0xFF02;
|
|
40
|
+
/// @dev Cosmos/CosmWasm: 32-byte canonical address (20 on forked wasmd chains).
|
|
41
|
+
/// `cosmos/` has a CAIP-2 profile only.
|
|
42
|
+
uint16 internal constant COSMOS = 0xFF03;
|
|
43
|
+
/// @dev NEAR: named accounts are variable-length UTF-8, implicit accounts are 32
|
|
44
|
+
/// bytes, eth-implicit are 20. `near/` has a CAIP-2 profile only.
|
|
45
|
+
uint16 internal constant NEAR = 0xFF04;
|
|
46
|
+
|
|
47
|
+
function isProvisional(uint16 chainType) internal pure returns (bool) {
|
|
48
|
+
return chainType >= PROVISIONAL_FLOOR;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {ChainType} from "./ChainType.sol";
|
|
5
|
+
|
|
6
|
+
/// @title Erc7930
|
|
7
|
+
/// @notice Encoding, strict parsing, and key derivation for ERC-7930 Interoperable
|
|
8
|
+
/// Addresses: the binary envelope that binds a chain identity to an address.
|
|
9
|
+
///
|
|
10
|
+
/// @dev Layout (all lengths in bytes):
|
|
11
|
+
/// ┌─────────┬───────────┬──────────────────────┬────────────────┬───────────────┬─────────┐
|
|
12
|
+
/// │ Version │ ChainType │ ChainReferenceLength │ ChainReference │ AddressLength │ Address │
|
|
13
|
+
/// │ 2 │ 2 │ 1 │ variable │ 1 │variable │
|
|
14
|
+
/// └─────────┴───────────┴──────────────────────┴────────────────┴───────────────┴─────────┘
|
|
15
|
+
///
|
|
16
|
+
/// This solves the arbitrary-length problem structurally: a NEAR named account,
|
|
17
|
+
/// a 57-byte Cardano address, a TON workchain+account_id, and a 20-byte EVM
|
|
18
|
+
/// address are all the same shape. The envelope carries the length, so no bespoke
|
|
19
|
+
/// Form/kind discriminant is needed.
|
|
20
|
+
///
|
|
21
|
+
/// @dev Canonicity is the whole game here. ERC-7930's own Security Considerations warn
|
|
22
|
+
/// implementers using these as mapping keys to check each CAIP-350 profile for
|
|
23
|
+
/// non-canonical encodings. Two byte strings that mean the same address but differ
|
|
24
|
+
/// by one byte hash to different keys: a silent split-brain in any registry.
|
|
25
|
+
/// This library therefore rejects non-minimal chain references (`0x0001` for
|
|
26
|
+
/// chain 1 is invalid; `0x01` is the only valid form) and rejects trailing bytes.
|
|
27
|
+
/// Never accept a raw interop blob from a remote caller without `parseStrict`.
|
|
28
|
+
///
|
|
29
|
+
/// @dev The rules are per profile, and a chain type with no rule is accepted. `chainType`
|
|
30
|
+
/// is read as an opaque `uint16` and never checked against `ChainType.sol`, so an
|
|
31
|
+
/// unallocated value parses and registers. It simply arrives with no canonicity
|
|
32
|
+
/// condition attached: both `0x00cafe` and `0xcafe` would pass as chain references
|
|
33
|
+
/// for the same chain and hash to two different keys.
|
|
34
|
+
///
|
|
35
|
+
/// **Adding a `ChainType` constant does not close that.** The rule has to be written
|
|
36
|
+
/// into `parseStrict` below, beside the eip155 and starknet cases. It is the one step of
|
|
37
|
+
/// onboarding a chain that nothing here will remind you about: see "Adding a chain type"
|
|
38
|
+
/// in the README. `test/UnknownChainType.t.sol` pins the current behaviour.
|
|
39
|
+
library Erc7930 {
|
|
40
|
+
uint16 internal constant VERSION_1 = 0x0001;
|
|
41
|
+
|
|
42
|
+
/* --------------------------- CAIP-350 chain types --------------------------- */
|
|
43
|
+
// Aliases into `ChainType`, which is the one allocation table for the repo.
|
|
44
|
+
uint16 internal constant CT_EIP155 = ChainType.EIP155;
|
|
45
|
+
uint16 internal constant CT_STARKNET = ChainType.STARKNET;
|
|
46
|
+
|
|
47
|
+
/// @dev Starknet addresses are 32-byte field elements, zero-padded. Unlike eip155,
|
|
48
|
+
/// leading zeros are required, so minimality must not be enforced on them.
|
|
49
|
+
uint256 internal constant STARKNET_ADDRESS_BYTES = 32;
|
|
50
|
+
|
|
51
|
+
error BadVersion();
|
|
52
|
+
error BadLength();
|
|
53
|
+
error NonMinimalChainRef();
|
|
54
|
+
error TrailingBytes();
|
|
55
|
+
error EmptyEnvelope();
|
|
56
|
+
error NotEvm();
|
|
57
|
+
error BadStarknetAddressLength();
|
|
58
|
+
|
|
59
|
+
struct Interop {
|
|
60
|
+
uint16 version;
|
|
61
|
+
uint16 chainType;
|
|
62
|
+
bytes chainRef;
|
|
63
|
+
bytes addr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ================================ encoding ================================ */
|
|
67
|
+
|
|
68
|
+
/// @notice Build an interoperable address from parts.
|
|
69
|
+
/// @dev `chainRef` must already be minimally encoded per its CAIP-350 profile.
|
|
70
|
+
/// Use `encodeEvm` for eip155 rather than encoding the chain id by hand.
|
|
71
|
+
function encode(uint16 chainType, bytes memory chainRef, bytes memory addr) internal pure returns (bytes memory) {
|
|
72
|
+
if (chainRef.length > 255 || addr.length > 255) revert BadLength();
|
|
73
|
+
if (chainRef.length == 0 && addr.length == 0) revert EmptyEnvelope();
|
|
74
|
+
// forge-lint: disable-next-line(encode-packed-collision,unsafe-typecast) lengths checked <= 255 above, and each is prefixed
|
|
75
|
+
return abi.encodePacked(VERSION_1, chainType, uint8(chainRef.length), chainRef, uint8(addr.length), addr);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// @notice eip155 interoperable address. ChainReference is the chain id as a
|
|
79
|
+
/// Minimal big-endian integer: chain 1 -> 0x01, Base (8453) -> 0x2105.
|
|
80
|
+
function encodeEvm(uint256 chainId, address a) internal pure returns (bytes memory) {
|
|
81
|
+
return encode(CT_EIP155, minimalBigEndian(chainId), abi.encodePacked(a));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// @notice A Chain Identifier: an interop address with a zero-length Address.
|
|
85
|
+
/// Identifies a chain rather than an account on it.
|
|
86
|
+
function encodeChainId(uint16 chainType, bytes memory chainRef) internal pure returns (bytes memory) {
|
|
87
|
+
return encode(chainType, chainRef, "");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function encodeEvmChain(uint256 chainId) internal pure returns (bytes memory) {
|
|
91
|
+
return encodeChainId(CT_EIP155, minimalBigEndian(chainId));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ================================ parsing ================================= */
|
|
95
|
+
|
|
96
|
+
/// @notice Parse and fully validate. Reverts on anything non-canonical.
|
|
97
|
+
/// @dev Checks: version, declared lengths against actual, no trailing bytes,
|
|
98
|
+
/// not both-empty, and minimal chain-reference encoding for eip155.
|
|
99
|
+
function parseStrict(bytes memory raw) internal pure returns (Interop memory io) {
|
|
100
|
+
if (raw.length < 6) revert BadLength();
|
|
101
|
+
|
|
102
|
+
io.version = (uint16(uint8(raw[0])) << 8) | uint16(uint8(raw[1]));
|
|
103
|
+
if (io.version != VERSION_1) revert BadVersion();
|
|
104
|
+
io.chainType = (uint16(uint8(raw[2])) << 8) | uint16(uint8(raw[3]));
|
|
105
|
+
|
|
106
|
+
uint256 crLen = uint8(raw[4]);
|
|
107
|
+
if (raw.length < 5 + crLen + 1) revert BadLength();
|
|
108
|
+
io.chainRef = _slice(raw, 5, crLen);
|
|
109
|
+
|
|
110
|
+
uint256 alPos = 5 + crLen;
|
|
111
|
+
uint256 aLen = uint8(raw[alPos]);
|
|
112
|
+
if (raw.length != alPos + 1 + aLen) revert TrailingBytes();
|
|
113
|
+
io.addr = _slice(raw, alPos + 1, aLen);
|
|
114
|
+
|
|
115
|
+
if (crLen == 0 && aLen == 0) revert EmptyEnvelope();
|
|
116
|
+
|
|
117
|
+
// eip155: chain reference is a minimal big-endian integer. A leading zero byte
|
|
118
|
+
// means two distinct encodings of the same chain, and therefore two keys.
|
|
119
|
+
if (io.chainType == CT_EIP155 && crLen > 0) {
|
|
120
|
+
if (io.chainRef[0] == 0x00) revert NonMinimalChainRef();
|
|
121
|
+
if (crLen > 32) revert BadLength();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// starknet: the opposite rule. Addresses are fixed-width 32-byte field
|
|
125
|
+
// elements with leading zeros, so width is the canonicity condition here. Chain
|
|
126
|
+
// references are the UTF-8 chain ID string ("SN_MAIN"), not an integer, so the
|
|
127
|
+
// eip155 minimality rule would reject every valid one.
|
|
128
|
+
// The value-range check (< ADDR_BOUND) is not a structural property and lives
|
|
129
|
+
// in StarknetDerive, wired in as a per-chain validator.
|
|
130
|
+
if (io.chainType == CT_STARKNET && aLen != 0) {
|
|
131
|
+
if (aLen != STARKNET_ADDRESS_BYTES) revert BadStarknetAddressLength();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// @notice The EVM chain id an `eip155` address names; `encodeEvmChain`'s inverse.
|
|
136
|
+
/// @dev `parseStrict` has already bounded the reference to a minimal 1 to 32 bytes.
|
|
137
|
+
function evmChainId(Interop memory io) internal pure returns (uint256 id) {
|
|
138
|
+
if (io.chainType != CT_EIP155 || io.chainRef.length == 0) revert NotEvm();
|
|
139
|
+
for (uint256 i; i < io.chainRef.length; ++i) {
|
|
140
|
+
id = (id << 8) | uint8(io.chainRef[i]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// @notice The Chain Identifier form of an interop address: same chain, no account.
|
|
145
|
+
/// @dev This is what makes `chainKey` stable across every address on a chain.
|
|
146
|
+
function toChainIdentifier(bytes memory raw) internal pure returns (bytes memory) {
|
|
147
|
+
Interop memory io = parseStrict(raw);
|
|
148
|
+
return encodeChainId(io.chainType, io.chainRef);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ================================== keys =================================== */
|
|
152
|
+
|
|
153
|
+
/// @notice Grouping key for "which chain is this on", independent of the account.
|
|
154
|
+
function chainKey(bytes memory raw) internal pure returns (bytes32) {
|
|
155
|
+
return keccak256(toChainIdentifier(raw));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ================================ helpers ================================== */
|
|
159
|
+
|
|
160
|
+
/// @notice True if this envelope names an EVM chain with a 20-byte address, i.e.
|
|
161
|
+
/// the registry can resolve it locally with CREATE2 instead of a round trip.
|
|
162
|
+
function isEvmAccount(Interop memory io) internal pure returns (bool) {
|
|
163
|
+
return io.chainType == CT_EIP155 && io.addr.length == 20 && io.chainRef.length > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function toAddress(Interop memory io) internal pure returns (address a) {
|
|
167
|
+
if (!isEvmAccount(io)) revert NotEvm();
|
|
168
|
+
bytes memory b = io.addr;
|
|
169
|
+
assembly {
|
|
170
|
+
a := shr(96, mload(add(b, 32)))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// @notice Minimal big-endian encoding of an integer: the eip155 chain-reference
|
|
175
|
+
/// rule: chain 1 is `0x01`, Base (8453) is `0x2105`, and a leading zero byte
|
|
176
|
+
/// is invalid.
|
|
177
|
+
///
|
|
178
|
+
/// @dev It lives here because it is an encoding rule, not a derivation. Putting it in
|
|
179
|
+
/// the address-derivation library would make `addressing` import `derivation`
|
|
180
|
+
/// while every deriver imports `addressing` back: a cycle between the two
|
|
181
|
+
/// folders over one integer helper. Canonicity is this library's
|
|
182
|
+
/// responsibility, so the rule that decides it belongs next to `parseStrict`,
|
|
183
|
+
/// which is what rejects violations of it.
|
|
184
|
+
///
|
|
185
|
+
/// @dev `AddressDerive` uses it too, for the RLP nonce in a CREATE derivation. That
|
|
186
|
+
/// is a different rule reached by the same encoding, and it had its own private
|
|
187
|
+
/// copy of this body until the two were merged here. `derivation` imports
|
|
188
|
+
/// `addressing`, which is the direction this comment always said was open.
|
|
189
|
+
function minimalBigEndian(uint256 x) internal pure returns (bytes memory out) {
|
|
190
|
+
uint256 len;
|
|
191
|
+
uint256 t = x;
|
|
192
|
+
while (t != 0) {
|
|
193
|
+
++len;
|
|
194
|
+
t >>= 8;
|
|
195
|
+
}
|
|
196
|
+
out = new bytes(len);
|
|
197
|
+
for (uint256 i; i < len; ++i) {
|
|
198
|
+
// forge-lint: disable-next-line(unsafe-typecast) one byte of x
|
|
199
|
+
out[len - 1 - i] = bytes1(uint8(x >> (8 * i)));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function _slice(bytes memory src, uint256 start, uint256 len) private pure returns (bytes memory out) {
|
|
204
|
+
out = new bytes(len);
|
|
205
|
+
for (uint256 i; i < len; ++i) {
|
|
206
|
+
out[i] = src[start + i];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {ChainType} from "./ChainType.sol";
|
|
5
|
+
import {Erc7930} from "./Erc7930.sol";
|
|
6
|
+
|
|
7
|
+
/// @title Move
|
|
8
|
+
/// @notice Reference extension for Move chains (Aptos, Movement, Sui).
|
|
9
|
+
///
|
|
10
|
+
/// @dev Why this exists. Two gaps the ERC-7930 envelope cannot close on its own:
|
|
11
|
+
///
|
|
12
|
+
/// 1. No assigned chain type. As of writing, the casa namespace registry defines
|
|
13
|
+
/// CAIP-350 profiles for exactly four namespaces: eip155 (0x0000), bip122
|
|
14
|
+
/// (0x0001), solana (0x0002), starknet (0x0003). `aptos/` and `sui/` have
|
|
15
|
+
/// CAIP-2 profiles only: no CAIP-10, no CAIP-350. There is therefore no
|
|
16
|
+
/// standard ChainType and no standard address serialization for Move chains.
|
|
17
|
+
/// The values below are provisional. See the migration note.
|
|
18
|
+
///
|
|
19
|
+
/// 2. A Move module has no address. A transceiver on Aptos is
|
|
20
|
+
/// `account_address::module_name`; on Sui it is a function in a package.
|
|
21
|
+
/// The 32-byte account or package ID fits the envelope's Address field, but
|
|
22
|
+
/// the qualified name does not, and no amount of length-prefixing changes
|
|
23
|
+
/// that: it is a different kind of thing, not a longer address.
|
|
24
|
+
///
|
|
25
|
+
/// @dev Design: separation of address and qualification.
|
|
26
|
+
/// The 7930 Address field carries only the 32-byte account/package ID, which is
|
|
27
|
+
/// almost certainly what a future CAIP-350 profile will specify. Everything else
|
|
28
|
+
/// (module name, function name, type arguments, Sui package lineage) lives in
|
|
29
|
+
/// a MoveQualifier attached alongside. When casa assigns real chain types, only
|
|
30
|
+
/// the ChainType constants change; the qualifier structure is untouched.
|
|
31
|
+
///
|
|
32
|
+
/// @dev Migration. Registry keys are `keccak256(envelope)`, and the envelope contains
|
|
33
|
+
/// the ChainType. If casa assigns Aptos or Sui a value different from the
|
|
34
|
+
/// provisional ones here, every id for those chains changes. That is a
|
|
35
|
+
/// re-keying migration across the registry, not a config edit. Provisional types
|
|
36
|
+
/// are deliberately confined to the 0xFF00..0xFFFF range so `isProvisional` can
|
|
37
|
+
/// flag affected refs, and so a collision with a casa assignment (which allocates
|
|
38
|
+
/// upward from 0x0000) is implausible rather than merely unlikely.
|
|
39
|
+
library Move {
|
|
40
|
+
/* ========================= provisional chain types ======================== */
|
|
41
|
+
|
|
42
|
+
/// @dev All values below are allocated in `ChainType`, which is the one table for
|
|
43
|
+
/// the whole repo. These are aliases kept for readability at the call sites.
|
|
44
|
+
uint16 internal constant PROVISIONAL_FLOOR = ChainType.PROVISIONAL_FLOOR;
|
|
45
|
+
|
|
46
|
+
/// @dev Aptos-Move address semantics: 32-byte AccountAddress, SHA3-256 derivation.
|
|
47
|
+
/// Movement shares this profile and is distinguished by ChainReference, not
|
|
48
|
+
/// by ChainType: the address format is identical, which is the same basis
|
|
49
|
+
/// Casa uses to group networks under one namespace.
|
|
50
|
+
uint16 internal constant CT_PROV_APTOS = ChainType.APTOS;
|
|
51
|
+
|
|
52
|
+
/// @dev Sui-Move: 32-byte address/ObjectID, BLAKE2b-256 derivation.
|
|
53
|
+
uint16 internal constant CT_PROV_SUI = ChainType.SUI;
|
|
54
|
+
|
|
55
|
+
/// @dev Chain references (UTF-8, following the Starknet profile's convention of
|
|
56
|
+
/// using the CAIP-2 reference string directly rather than an integer).
|
|
57
|
+
bytes internal constant REF_APTOS_MAINNET = "mainnet";
|
|
58
|
+
bytes internal constant REF_MOVEMENT_MAINNET = "movement-mainnet";
|
|
59
|
+
bytes internal constant REF_SUI_MAINNET = "mainnet";
|
|
60
|
+
|
|
61
|
+
uint256 internal constant MOVE_ADDRESS_BYTES = 32;
|
|
62
|
+
|
|
63
|
+
function isProvisional(uint16 chainType) internal pure returns (bool) {
|
|
64
|
+
return chainType >= PROVISIONAL_FLOOR;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isMoveChain(uint16 chainType) internal pure returns (bool) {
|
|
68
|
+
return chainType == CT_PROV_APTOS || chainType == CT_PROV_SUI;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ============================== qualification ============================= */
|
|
72
|
+
|
|
73
|
+
enum MoveKind {
|
|
74
|
+
Unset,
|
|
75
|
+
/// A plain account address. No module, no function.
|
|
76
|
+
Account,
|
|
77
|
+
/// An object (Aptos Object<T>, Sui ObjectID). No module, no function.
|
|
78
|
+
Object,
|
|
79
|
+
/// A published package (Sui) or the publishing account (Aptos).
|
|
80
|
+
Package,
|
|
81
|
+
/// `address::module_name`.
|
|
82
|
+
Module,
|
|
83
|
+
/// `address::module_name::function_name`, optionally with type arguments.
|
|
84
|
+
/// This is what a transceiver call target actually is.
|
|
85
|
+
Entry
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// @notice Everything about a Move reference that is not the 32-byte address.
|
|
89
|
+
struct MoveQualifier {
|
|
90
|
+
MoveKind kind;
|
|
91
|
+
/// Move Identifier. Empty unless kind is Module or Entry.
|
|
92
|
+
string moduleName;
|
|
93
|
+
/// Move Identifier. Empty unless kind is Entry.
|
|
94
|
+
string functionName;
|
|
95
|
+
/// BCS-serialized `Vec<TypeTag>`. Empty if the entry takes no type arguments.
|
|
96
|
+
/// Opaque here: serialize off-chain; do not build BCS in Solidity.
|
|
97
|
+
bytes typeArgs;
|
|
98
|
+
/// Sui only. A package upgrade mints a new package ID, but types retain the
|
|
99
|
+
/// Original package ID in their StructTag while calls target the latest. A
|
|
100
|
+
/// single id would silently mean the wrong thing after the first upgrade, so
|
|
101
|
+
/// the envelope address holds the call target (latest) and this holds type
|
|
102
|
+
/// identity (original). Zero when not applicable.
|
|
103
|
+
bytes32 originalPackageId;
|
|
104
|
+
/// Sui only. A shared-object reference is `(ObjectID, initial_shared_version)`;
|
|
105
|
+
/// the ID alone is not enough to construct a call. Zero if not shared.
|
|
106
|
+
uint64 initialSharedVersion;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
error BadKind();
|
|
110
|
+
error ModuleNameRequired();
|
|
111
|
+
error ModuleNameForbidden();
|
|
112
|
+
error FunctionNameRequired();
|
|
113
|
+
error FunctionNameForbidden();
|
|
114
|
+
error TypeArgsForbidden();
|
|
115
|
+
error BadIdentifier();
|
|
116
|
+
error SuiOnlyField();
|
|
117
|
+
error BadMoveAddressLength();
|
|
118
|
+
error NotMoveChain();
|
|
119
|
+
|
|
120
|
+
/// @notice Commitment over a qualifier, for inclusion in a signed payload.
|
|
121
|
+
/// @dev abi.encode (not encodePacked): fixed field framing, so no two distinct
|
|
122
|
+
/// qualifiers can produce the same preimage by shifting bytes between the
|
|
123
|
+
/// variable-length fields.
|
|
124
|
+
function hash(MoveQualifier memory q) internal pure returns (bytes32) {
|
|
125
|
+
return keccak256(
|
|
126
|
+
abi.encode(
|
|
127
|
+
uint8(q.kind), q.moduleName, q.functionName, q.typeArgs, q.originalPackageId, q.initialSharedVersion
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// @notice Structural validation of a qualifier against its chain.
|
|
133
|
+
function validate(MoveQualifier memory q, uint16 chainType) internal pure {
|
|
134
|
+
if (!isMoveChain(chainType)) revert NotMoveChain();
|
|
135
|
+
if (q.kind == MoveKind.Unset) revert BadKind();
|
|
136
|
+
|
|
137
|
+
bool wantsModule = (q.kind == MoveKind.Module || q.kind == MoveKind.Entry);
|
|
138
|
+
bool wantsFunction = (q.kind == MoveKind.Entry);
|
|
139
|
+
|
|
140
|
+
if (wantsModule) {
|
|
141
|
+
if (bytes(q.moduleName).length == 0) revert ModuleNameRequired();
|
|
142
|
+
requireIdentifier(q.moduleName);
|
|
143
|
+
} else if (bytes(q.moduleName).length != 0) {
|
|
144
|
+
revert ModuleNameForbidden();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (wantsFunction) {
|
|
148
|
+
if (bytes(q.functionName).length == 0) revert FunctionNameRequired();
|
|
149
|
+
requireIdentifier(q.functionName);
|
|
150
|
+
} else if (bytes(q.functionName).length != 0) {
|
|
151
|
+
revert FunctionNameForbidden();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Type arguments attach to a call, not to a bare address or module.
|
|
155
|
+
if (!wantsFunction && q.typeArgs.length != 0) revert TypeArgsForbidden();
|
|
156
|
+
|
|
157
|
+
// Sui-only fields must be zero on Aptos, or the same logical reference could
|
|
158
|
+
// be expressed two ways and hash to two different commitments.
|
|
159
|
+
if (chainType != CT_PROV_SUI) {
|
|
160
|
+
if (q.originalPackageId != bytes32(0)) revert SuiOnlyField();
|
|
161
|
+
if (q.initialSharedVersion != 0) revert SuiOnlyField();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// @notice Move Identifier charset: ASCII, first character a letter or underscore,
|
|
166
|
+
/// remainder alphanumeric or underscore.
|
|
167
|
+
/// @dev This is a canonicity guard, not decoration. Without it a module name could
|
|
168
|
+
/// carry whitespace, a homoglyph, or a trailing NUL and still produce a
|
|
169
|
+
/// well-formed commitment that no Move VM will ever resolve.
|
|
170
|
+
function requireIdentifier(string memory s) internal pure {
|
|
171
|
+
bytes memory b = bytes(s);
|
|
172
|
+
uint256 n = b.length;
|
|
173
|
+
if (n == 0 || n > 255) revert BadIdentifier();
|
|
174
|
+
|
|
175
|
+
bytes1 c0 = b[0];
|
|
176
|
+
bool okFirst = (c0 >= 0x41 && c0 <= 0x5a) // A-Z
|
|
177
|
+
|| (c0 >= 0x61 && c0 <= 0x7a) // a-z
|
|
178
|
+
|| c0 == 0x5f; // _
|
|
179
|
+
if (!okFirst) revert BadIdentifier();
|
|
180
|
+
|
|
181
|
+
for (uint256 i = 1; i < n; ++i) {
|
|
182
|
+
bytes1 c = b[i];
|
|
183
|
+
bool okRest = (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39) // 0-9
|
|
184
|
+
|| c == 0x5f;
|
|
185
|
+
if (!okRest) revert BadIdentifier();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ================================ encoding ================================ */
|
|
190
|
+
|
|
191
|
+
/// @notice Build the 7930 envelope for a Move address. The address field holds the
|
|
192
|
+
/// 32-byte account/package ID and nothing else.
|
|
193
|
+
function encodeMove(uint16 chainType, bytes memory chainRef, bytes32 addr) internal pure returns (bytes memory) {
|
|
194
|
+
if (!isMoveChain(chainType)) revert NotMoveChain();
|
|
195
|
+
return Erc7930.encode(chainType, chainRef, abi.encodePacked(addr));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function encodeAptos(bytes memory chainRef, bytes32 addr) internal pure returns (bytes memory) {
|
|
199
|
+
return encodeMove(CT_PROV_APTOS, chainRef, addr);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function encodeSui(bytes memory chainRef, bytes32 addr) internal pure returns (bytes memory) {
|
|
203
|
+
return encodeMove(CT_PROV_SUI, chainRef, addr);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function toAddress(bytes memory addrField) internal pure returns (bytes32 v) {
|
|
207
|
+
if (addrField.length != MOVE_ADDRESS_BYTES) revert BadMoveAddressLength();
|
|
208
|
+
assembly {
|
|
209
|
+
v := mload(add(addrField, 32))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
|
|
5
|
+
import {IProxy} from "./Proxy.sol";
|
|
6
|
+
import {ProxyInitCode} from "./ProxyInitCode.sol";
|
|
7
|
+
|
|
8
|
+
/// @title Conduits
|
|
9
|
+
/// @notice The steps every contract that creates conduits takes, in this order: refuse an
|
|
10
|
+
/// occupied address, deploy `Proxy` by CREATE2, check it landed at the predicted
|
|
11
|
+
/// address, then arm and lock it.
|
|
12
|
+
///
|
|
13
|
+
/// @dev Separate functions rather than one, so a creator on a chain that deploys differently
|
|
14
|
+
/// (zkSync) replaces only `deploy`, and so a creator builds its initializer only after the
|
|
15
|
+
/// address check, which then reports a mismatch before anything the initializer reads.
|
|
16
|
+
library Conduits {
|
|
17
|
+
error ConduitExists(address owner, bytes32 salt, address conduit);
|
|
18
|
+
/// @dev The prediction and the deployment disagree: a variant on a diverging chain changed
|
|
19
|
+
/// one and not the other.
|
|
20
|
+
error ConduitAddressMismatch(address predicted, address deployed);
|
|
21
|
+
|
|
22
|
+
function requireVacant(address conduit, address owner, bytes32 salt) internal view {
|
|
23
|
+
if (conduit.code.length != 0) revert ConduitExists(owner, salt, conduit);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// @notice CREATE2 of `Proxy`'s initcode, read from the contract `ProxyInitCode`
|
|
27
|
+
/// stored it in.
|
|
28
|
+
function deploy(bytes32 salt, address initCodeStore) internal returns (address) {
|
|
29
|
+
return Create2.deploy(0, salt, ProxyInitCode.load(initCodeStore));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// @dev Every address the protocol publishes comes from the prediction, so a deployment
|
|
33
|
+
/// elsewhere reverts here with a reason rather than failing later without one.
|
|
34
|
+
function requireLanded(address predicted, address deployed) internal pure {
|
|
35
|
+
if (deployed != predicted) revert ConduitAddressMismatch(predicted, deployed);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// @notice Arm and lock the proxy in one call, so it never has logic and a live upgrade key
|
|
39
|
+
/// at once.
|
|
40
|
+
function arm(address conduit, address implementation, bytes memory initializer) internal {
|
|
41
|
+
IProxy(conduit).upgradeInitializeAndLock(implementation, initializer);
|
|
42
|
+
}
|
|
43
|
+
}
|