-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Expand file tree
/
Copy pathdraft-InteroperableAddress.sol
More file actions
245 lines (217 loc) · 10.5 KB
/
draft-InteroperableAddress.sol
File metadata and controls
245 lines (217 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (utils/draft-InteroperableAddress.sol)
pragma solidity ^0.8.26;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {Bytes} from "./Bytes.sol";
import {Calldata} from "./Calldata.sol";
/**
* @dev Helper library to format and parse https://ethereum-magicians.org/t/erc-7930-interoperable-addresses/23365[ERC-7930] interoperable
* addresses.
*/
library InteroperableAddress {
using SafeCast for uint256;
using Bytes for bytes;
error InteroperableAddressParsingError(bytes);
error InteroperableAddressEmptyReferenceAndAddress();
/**
* @dev Format an ERC-7930 interoperable address (version 1) from its components `chainType`, `chainReference`
* and `addr`. This is a generic function that supports any chain type, chain reference and address supported by
* ERC-7930, including interoperable addresses with empty chain reference or empty address.
*/
function formatV1(
bytes2 chainType,
bytes memory chainReference,
bytes memory addr
) internal pure returns (bytes memory) {
require(chainReference.length > 0 || addr.length > 0, InteroperableAddressEmptyReferenceAndAddress());
return
abi.encodePacked(
bytes2(0x0001),
chainType,
chainReference.length.toUint8(),
chainReference,
addr.length.toUint8(),
addr
);
}
/**
* @dev Variant of {formatV1-bytes2-bytes-bytes-} specific to EVM chains. Returns the ERC-7930 interoperable
* address (version 1) for a given chainid and ethereum address.
*/
function formatEvmV1(uint256 chainid, address addr) internal pure returns (bytes memory) {
bytes memory chainReference = _toChainReference(chainid);
return abi.encodePacked(bytes4(0x00010000), uint8(chainReference.length), chainReference, uint8(20), addr);
}
/**
* @dev Variant of {formatV1-bytes2-bytes-bytes-} that specifies an EVM chain without an address.
*/
function formatEvmV1(uint256 chainid) internal pure returns (bytes memory) {
bytes memory chainReference = _toChainReference(chainid);
return abi.encodePacked(bytes4(0x00010000), uint8(chainReference.length), chainReference, uint8(0));
}
/**
* @dev Variant of {formatV1-bytes2-bytes-bytes-} that specifies an EVM address without a chain reference.
*/
function formatEvmV1(address addr) internal pure returns (bytes memory) {
return abi.encodePacked(bytes6(0x000100000014), addr);
}
/**
* @dev Parse an ERC-7930 interoperable address (version 1) into its different components. Reverts if the input is
* not following a version 1 of ERC-7930
*
* NOTE: Trailing bytes after a valid v1 encoding are ignored. The same decoded address may therefore correspond
* to multiple distinct input byte strings.
*/
function parseV1(
bytes memory self
) internal pure returns (bytes2 chainType, bytes memory chainReference, bytes memory addr) {
bool success;
(success, chainType, chainReference, addr) = tryParseV1(self);
require(success, InteroperableAddressParsingError(self));
}
/**
* @dev Variant of {parseV1} that handles calldata slices to reduce memory copy costs.
*/
function parseV1Calldata(
bytes calldata self
) internal pure returns (bytes2 chainType, bytes calldata chainReference, bytes calldata addr) {
bool success;
(success, chainType, chainReference, addr) = tryParseV1Calldata(self);
require(success, InteroperableAddressParsingError(self));
}
/**
* @dev Variant of {parseV1} that does not revert on invalid input. Instead, it returns `false` as the first
* return value to indicate parsing failure when the input does not follow version 1 of ERC-7930.
*/
function tryParseV1(
bytes memory self
) internal pure returns (bool success, bytes2 chainType, bytes memory chainReference, bytes memory addr) {
unchecked {
if (self.length < 0x06) return (false, 0x0000, _emptyBytesMemory(), _emptyBytesMemory());
bytes2 version = _readBytes2(self, 0x00);
if (version != bytes2(0x0001)) return (false, 0x0000, _emptyBytesMemory(), _emptyBytesMemory());
uint8 chainReferenceLength = uint8(self[0x04]);
if (self.length < 0x06 + chainReferenceLength)
return (false, 0x0000, _emptyBytesMemory(), _emptyBytesMemory());
chainReference = self.slice(0x05, 0x05 + chainReferenceLength);
uint8 addrLength = uint8(self[0x05 + chainReferenceLength]);
if (self.length < 0x06 + chainReferenceLength + addrLength)
return (false, 0x0000, _emptyBytesMemory(), _emptyBytesMemory());
addr = self.slice(0x06 + chainReferenceLength, 0x06 + chainReferenceLength + addrLength);
// At least one of chainReference or addr must be non-empty
success = (chainReferenceLength > 0) || (addrLength > 0);
chainType = success ? _readBytes2(self, 0x02) : bytes2(0);
}
}
/**
* @dev Variant of {tryParseV1} that handles calldata slices to reduce memory copy costs.
*/
function tryParseV1Calldata(
bytes calldata self
) internal pure returns (bool success, bytes2 chainType, bytes calldata chainReference, bytes calldata addr) {
unchecked {
if (self.length < 0x06) return (false, 0x0000, Calldata.emptyBytes(), Calldata.emptyBytes());
bytes2 version = _readBytes2Calldata(self, 0x00);
if (version != bytes2(0x0001)) return (false, 0x0000, Calldata.emptyBytes(), Calldata.emptyBytes());
uint8 chainReferenceLength = uint8(self[0x04]);
if (self.length < 0x06 + chainReferenceLength)
return (false, 0x0000, Calldata.emptyBytes(), Calldata.emptyBytes());
chainReference = self[0x05:0x05 + chainReferenceLength];
uint8 addrLength = uint8(self[0x05 + chainReferenceLength]);
if (self.length < 0x06 + chainReferenceLength + addrLength)
return (false, 0x0000, Calldata.emptyBytes(), Calldata.emptyBytes());
addr = self[0x06 + chainReferenceLength:0x06 + chainReferenceLength + addrLength];
// At least one of chainReference or addr must be non-empty
success = (chainReferenceLength > 0) || (addrLength > 0);
chainType = success ? _readBytes2Calldata(self, 0x02) : bytes2(0);
}
}
/**
* @dev Parse a ERC-7930 interoperable address (version 1) corresponding to an EIP-155 chain. The `chainId` and
* `addr` return values will be zero if the input doesn't include a chainReference or an address, respectively.
*
* NOTE: Trailing bytes after a valid v1 encoding are ignored. The same decoded (chainId, addr) may therefore
* correspond to multiple distinct input byte strings.
*
* Requirements:
*
* * The input must be a valid ERC-7930 interoperable address (version 1)
* * The underlying chainType must be "eip-155"
*/
function parseEvmV1(bytes memory self) internal pure returns (uint256 chainId, address addr) {
bool success;
(success, chainId, addr) = tryParseEvmV1(self);
require(success, InteroperableAddressParsingError(self));
}
/**
* @dev Variant of {parseEvmV1} that handles calldata slices to reduce memory copy costs.
*/
function parseEvmV1Calldata(bytes calldata self) internal pure returns (uint256 chainId, address addr) {
bool success;
(success, chainId, addr) = tryParseEvmV1Calldata(self);
require(success, InteroperableAddressParsingError(self));
}
/**
* @dev Variant of {parseEvmV1} that does not revert on invalid input. Instead, it returns `false` as the first
* return value to indicate parsing failure when the input does not follow version 1 of ERC-7930.
*/
function tryParseEvmV1(bytes memory self) internal pure returns (bool success, uint256 chainId, address addr) {
(bool success_, bytes2 chainType_, bytes memory chainReference_, bytes memory addr_) = tryParseV1(self);
return
(success_ &&
chainType_ == 0x0000 &&
chainReference_.length < 33 &&
(addr_.length == 0 || addr_.length == 20))
? (
true,
uint256(bytes32(chainReference_)) >> (256 - 8 * chainReference_.length),
address(bytes20(addr_))
)
: (false, 0, address(0));
}
/**
* @dev Variant of {tryParseEvmV1} that handles calldata slices to reduce memory copy costs.
*/
function tryParseEvmV1Calldata(
bytes calldata self
) internal pure returns (bool success, uint256 chainId, address addr) {
(bool success_, bytes2 chainType_, bytes calldata chainReference_, bytes calldata addr_) = tryParseV1Calldata(
self
);
return
(success_ &&
chainType_ == 0x0000 &&
chainReference_.length < 33 &&
(addr_.length == 0 || addr_.length == 20))
? (
true,
uint256(bytes32(chainReference_)) >> (256 - 8 * chainReference_.length),
address(bytes20(addr_))
)
: (false, 0, address(0));
}
function _toChainReference(uint256 chainid) private pure returns (bytes memory) {
unchecked {
// length fits in a uint8: log256(type(uint256).max) is 31
uint256 length = Math.log256(chainid) + 1;
return abi.encodePacked(chainid).slice(32 - length);
}
}
function _readBytes2(bytes memory buffer, uint256 offset) private pure returns (bytes2 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := shl(240, shr(240, mload(add(add(buffer, 0x20), offset))))
}
}
function _readBytes2Calldata(bytes calldata buffer, uint256 offset) private pure returns (bytes2 value) {
assembly ("memory-safe") {
value := shl(240, shr(240, calldataload(add(buffer.offset, offset))))
}
}
function _emptyBytesMemory() private pure returns (bytes memory result) {
assembly ("memory-safe") {
result := 0x60 // mload(0x60) is always 0
}
}
}