solidity-scale-codec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +48 -0
- package/package.json +41 -0
- package/src/Scale/Array/BoolArr.sol +44 -0
- package/src/Scale/Array/I128Arr.sol +44 -0
- package/src/Scale/Array/I16Arr.sol +44 -0
- package/src/Scale/Array/I256Arr.sol +44 -0
- package/src/Scale/Array/I32Arr.sol +44 -0
- package/src/Scale/Array/I64Arr.sol +44 -0
- package/src/Scale/Array/I8Arr.sol +44 -0
- package/src/Scale/Array/U128Arr.sol +44 -0
- package/src/Scale/Array/U16Arr.sol +44 -0
- package/src/Scale/Array/U256Arr.sol +44 -0
- package/src/Scale/Array/U32Arr.sol +44 -0
- package/src/Scale/Array/U64Arr.sol +44 -0
- package/src/Scale/Array/U8Arr.sol +44 -0
- package/src/Scale/Array.sol +17 -0
- package/src/Scale/Bool/Bool.sol +40 -0
- package/src/Scale/Bool.sol +4 -0
- package/src/Scale/Compact/Compact.sol +543 -0
- package/src/Scale/Compact/Compact.t.sol +326 -0
- package/src/Scale/Compact.sol +4 -0
- package/src/Scale/Signed/I128.sol +39 -0
- package/src/Scale/Signed/I16.sol +39 -0
- package/src/Scale/Signed/I256.sol +39 -0
- package/src/Scale/Signed/I32.sol +39 -0
- package/src/Scale/Signed/I64.sol +39 -0
- package/src/Scale/Signed/I8.sol +39 -0
- package/src/Scale/Signed.sol +10 -0
- package/src/Scale/Unsigned/U128.sol +40 -0
- package/src/Scale/Unsigned/U16.sol +40 -0
- package/src/Scale/Unsigned/U256.sol +40 -0
- package/src/Scale/Unsigned/U32.sol +40 -0
- package/src/Scale/Unsigned/U64.sol +40 -0
- package/src/Scale/Unsigned/U8.sol +40 -0
- package/src/Scale/Unsigned.sol +10 -0
- package/src/Utils/LittleEndian/LittleEndian.sol +354 -0
- package/src/Utils/LittleEndian/LittleEndian.t.sol +542 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
/// @title Compact
|
|
5
|
+
/// @notice SCALE Compact encoding for space-efficient unsigned integer representation
|
|
6
|
+
/// @dev Encoding modes based on value range:
|
|
7
|
+
/// - 0b00: single-byte mode (0-63), 6 bits of data
|
|
8
|
+
/// - 0b01: two-byte mode (64-16383), 14 bits of data
|
|
9
|
+
/// - 0b10: four-byte mode (16384-1073741823), 30 bits of data
|
|
10
|
+
/// - 0b11: big-integer mode (>1073741823), variable length up to 67 bytes
|
|
11
|
+
/// @dev Reference: https://docs.polkadot.com/polkadot-protocol/basics/data-encoding/#compact-general-integers
|
|
12
|
+
library Compact {
|
|
13
|
+
error InvalidCompactEncoding();
|
|
14
|
+
error ValueOutOfRange();
|
|
15
|
+
error NonCanonicalEncoding();
|
|
16
|
+
|
|
17
|
+
// ============ Constants ============
|
|
18
|
+
|
|
19
|
+
uint256 private constant SINGLE_BYTE_MAX = 0x3F; // 63
|
|
20
|
+
uint256 private constant TWO_BYTE_MAX = 0x3FFF; // 16383
|
|
21
|
+
uint256 private constant FOUR_BYTE_MAX = 0x3FFFFFFF; // 1073741823
|
|
22
|
+
|
|
23
|
+
uint8 private constant MODE_SINGLE = 0x00;
|
|
24
|
+
uint8 private constant MODE_TWO = 0x01;
|
|
25
|
+
uint8 private constant MODE_FOUR = 0x02;
|
|
26
|
+
uint8 private constant MODE_BIG = 0x03;
|
|
27
|
+
|
|
28
|
+
// ============ Encoding (generic) ============
|
|
29
|
+
|
|
30
|
+
/// @notice Encodes a uint256 value to SCALE Compact format
|
|
31
|
+
/// @param value The value to encode
|
|
32
|
+
/// @return Compact-encoded bytes in little-endian
|
|
33
|
+
function encode(uint256 value) internal pure returns (bytes memory) {
|
|
34
|
+
if (value <= SINGLE_BYTE_MAX) {
|
|
35
|
+
return _encodeSingleByte(value);
|
|
36
|
+
} else if (value <= TWO_BYTE_MAX) {
|
|
37
|
+
return _encodeTwoByte(value);
|
|
38
|
+
} else if (value <= FOUR_BYTE_MAX) {
|
|
39
|
+
return _encodeFourByte(value);
|
|
40
|
+
} else {
|
|
41
|
+
return _encodeBigInt(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============ Encoding (typed) ============
|
|
46
|
+
|
|
47
|
+
/// @notice Encodes uint8 to Compact
|
|
48
|
+
function encodeU8(uint8 value) internal pure returns (bytes memory) {
|
|
49
|
+
if (value <= SINGLE_BYTE_MAX) {
|
|
50
|
+
return _encodeSingleByte(value);
|
|
51
|
+
} else {
|
|
52
|
+
return _encodeTwoByte(value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// @notice Encodes uint16 to Compact
|
|
57
|
+
function encodeU16(uint16 value) internal pure returns (bytes memory) {
|
|
58
|
+
if (value <= SINGLE_BYTE_MAX) {
|
|
59
|
+
return _encodeSingleByte(value);
|
|
60
|
+
} else if (value <= TWO_BYTE_MAX) {
|
|
61
|
+
return _encodeTwoByte(value);
|
|
62
|
+
} else {
|
|
63
|
+
return _encodeFourByte(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// @notice Encodes uint32 to Compact
|
|
68
|
+
function encodeU32(uint32 value) internal pure returns (bytes memory) {
|
|
69
|
+
if (value <= SINGLE_BYTE_MAX) {
|
|
70
|
+
return _encodeSingleByte(value);
|
|
71
|
+
} else if (value <= TWO_BYTE_MAX) {
|
|
72
|
+
return _encodeTwoByte(value);
|
|
73
|
+
} else if (value <= FOUR_BYTE_MAX) {
|
|
74
|
+
return _encodeFourByte(value);
|
|
75
|
+
} else {
|
|
76
|
+
return _encodeBigInt(uint256(value));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// @notice Encodes uint64 to Compact
|
|
81
|
+
function encodeU64(uint64 value) internal pure returns (bytes memory) {
|
|
82
|
+
return encode(uint256(value));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// @notice Encodes uint128 to Compact
|
|
86
|
+
function encodeU128(uint128 value) internal pure returns (bytes memory) {
|
|
87
|
+
return encode(uint256(value));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============ Internal encoding helpers ============
|
|
91
|
+
|
|
92
|
+
/// @dev Single-byte mode: value << 2 | 0b00
|
|
93
|
+
function _encodeSingleByte(
|
|
94
|
+
uint256 value
|
|
95
|
+
) private pure returns (bytes memory) {
|
|
96
|
+
return abi.encodePacked(uint8(value << 2));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// @dev Two-byte mode: value << 2 | 0b01, little-endian
|
|
100
|
+
function _encodeTwoByte(uint256 value) private pure returns (bytes memory) {
|
|
101
|
+
uint16 encoded = uint16((value << 2) | MODE_TWO);
|
|
102
|
+
return abi.encodePacked(uint8(encoded & 0xFF), uint8(encoded >> 8));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// @dev Four-byte mode: value << 2 | 0b10, little-endian
|
|
106
|
+
function _encodeFourByte(
|
|
107
|
+
uint256 value
|
|
108
|
+
) private pure returns (bytes memory) {
|
|
109
|
+
uint32 encoded = uint32((value << 2) | MODE_FOUR);
|
|
110
|
+
return
|
|
111
|
+
abi.encodePacked(
|
|
112
|
+
uint8(encoded & 0xFF),
|
|
113
|
+
uint8((encoded >> 8) & 0xFF),
|
|
114
|
+
uint8((encoded >> 16) & 0xFF),
|
|
115
|
+
uint8(encoded >> 24)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// @dev Big-integer mode: header byte + raw value bytes
|
|
120
|
+
/// Header: ((bytesNeeded - 4) << 2) | 0b11
|
|
121
|
+
function _encodeBigInt(uint256 value) private pure returns (bytes memory) {
|
|
122
|
+
uint8 bytesNeeded = _bytesNeeded(value);
|
|
123
|
+
uint8 header = ((bytesNeeded - 4) << 2) | MODE_BIG;
|
|
124
|
+
|
|
125
|
+
bytes memory result = new bytes(1 + bytesNeeded);
|
|
126
|
+
result[0] = bytes1(header);
|
|
127
|
+
|
|
128
|
+
uint256 v = value;
|
|
129
|
+
for (uint8 i = 0; i < bytesNeeded; i++) {
|
|
130
|
+
result[1 + i] = bytes1(uint8(v & 0xFF));
|
|
131
|
+
v >>= 8;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// @dev Calculate minimum bytes needed to represent value
|
|
138
|
+
function _bytesNeeded(uint256 value) private pure returns (uint8) {
|
|
139
|
+
if (value == 0) return 1;
|
|
140
|
+
|
|
141
|
+
uint8 count = 0;
|
|
142
|
+
while (value > 0) {
|
|
143
|
+
value >>= 8;
|
|
144
|
+
count++;
|
|
145
|
+
}
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============ Decoding ============
|
|
150
|
+
|
|
151
|
+
/// @notice Decodes Compact bytes to uint256
|
|
152
|
+
/// @param data The compact-encoded bytes
|
|
153
|
+
/// @return value The decoded value
|
|
154
|
+
/// @return bytesRead Number of bytes consumed
|
|
155
|
+
function decode(
|
|
156
|
+
bytes memory data
|
|
157
|
+
) internal pure returns (uint256 value, uint256 bytesRead) {
|
|
158
|
+
return decodeAt(data, 0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// @notice Decodes Compact bytes starting at offset
|
|
162
|
+
/// @dev Validates canonical encoding (rejects values that could use smaller mode)
|
|
163
|
+
/// @param data The compact-encoded bytes
|
|
164
|
+
/// @param offset Starting position in data
|
|
165
|
+
/// @return value The decoded value
|
|
166
|
+
/// @return bytesRead Number of bytes consumed
|
|
167
|
+
function decodeAt(
|
|
168
|
+
bytes memory data,
|
|
169
|
+
uint256 offset
|
|
170
|
+
) internal pure returns (uint256 value, uint256 bytesRead) {
|
|
171
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
172
|
+
|
|
173
|
+
uint8 firstByte = uint8(data[offset]);
|
|
174
|
+
uint8 mode = firstByte & 0x03;
|
|
175
|
+
|
|
176
|
+
if (mode == MODE_SINGLE) {
|
|
177
|
+
// Single-byte mode: valid range 0-63
|
|
178
|
+
value = firstByte >> 2;
|
|
179
|
+
bytesRead = 1;
|
|
180
|
+
} else if (mode == MODE_TWO) {
|
|
181
|
+
// Two-byte mode: valid range 64-16383
|
|
182
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
183
|
+
value =
|
|
184
|
+
(uint256(firstByte) |
|
|
185
|
+
(uint256(uint8(data[offset + 1])) << 8)) >>
|
|
186
|
+
2;
|
|
187
|
+
|
|
188
|
+
// Canonical check: must be > SINGLE_BYTE_MAX
|
|
189
|
+
if (value <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
190
|
+
|
|
191
|
+
bytesRead = 2;
|
|
192
|
+
} else if (mode == MODE_FOUR) {
|
|
193
|
+
// Four-byte mode: valid range 16384-1073741823
|
|
194
|
+
if (data.length < offset + 4) revert InvalidCompactEncoding();
|
|
195
|
+
value =
|
|
196
|
+
(uint256(firstByte) |
|
|
197
|
+
(uint256(uint8(data[offset + 1])) << 8) |
|
|
198
|
+
(uint256(uint8(data[offset + 2])) << 16) |
|
|
199
|
+
(uint256(uint8(data[offset + 3])) << 24)) >>
|
|
200
|
+
2;
|
|
201
|
+
|
|
202
|
+
// Canonical check: must be > TWO_BYTE_MAX
|
|
203
|
+
if (value <= TWO_BYTE_MAX) revert NonCanonicalEncoding();
|
|
204
|
+
|
|
205
|
+
bytesRead = 4;
|
|
206
|
+
} else {
|
|
207
|
+
// Big-integer mode: valid range > 1073741823
|
|
208
|
+
uint8 byteLen = (firstByte >> 2) + 4;
|
|
209
|
+
if (data.length < offset + 1 + byteLen)
|
|
210
|
+
revert InvalidCompactEncoding();
|
|
211
|
+
|
|
212
|
+
value = 0;
|
|
213
|
+
for (uint8 i = 0; i < byteLen; i++) {
|
|
214
|
+
value |= uint256(uint8(data[offset + 1 + i])) << (i * 8);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Canonical check: must be > FOUR_BYTE_MAX
|
|
218
|
+
if (value <= FOUR_BYTE_MAX) revert NonCanonicalEncoding();
|
|
219
|
+
|
|
220
|
+
// Additional canonical check: value must require exactly byteLen bytes
|
|
221
|
+
// (no leading zero bytes allowed)
|
|
222
|
+
uint8 minBytesNeeded = _bytesNeeded(value);
|
|
223
|
+
if (byteLen != minBytesNeeded) revert NonCanonicalEncoding();
|
|
224
|
+
|
|
225
|
+
bytesRead = 1 + byteLen;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ============ Typed decoding with range validation ============
|
|
230
|
+
|
|
231
|
+
/// @notice Decodes to uint8 with range validation
|
|
232
|
+
function decodeU8(
|
|
233
|
+
bytes memory data
|
|
234
|
+
) internal pure returns (uint8 value, uint256 bytesRead) {
|
|
235
|
+
return decodeU8At(data, 0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// @notice Decodes to uint8 at offset with range validation
|
|
239
|
+
function decodeU8At(
|
|
240
|
+
bytes memory data,
|
|
241
|
+
uint256 offset
|
|
242
|
+
) internal pure returns (uint8 value, uint256 bytesRead) {
|
|
243
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
244
|
+
|
|
245
|
+
uint8 firstByte = uint8(data[offset]);
|
|
246
|
+
uint8 mode = firstByte & 0x03;
|
|
247
|
+
|
|
248
|
+
// u8 only supports single-byte and two-byte modes
|
|
249
|
+
if (mode == MODE_SINGLE) {
|
|
250
|
+
value = uint8(firstByte >> 2);
|
|
251
|
+
bytesRead = 1;
|
|
252
|
+
} else if (mode == MODE_TWO) {
|
|
253
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
254
|
+
uint256 v = (uint256(firstByte) |
|
|
255
|
+
(uint256(uint8(data[offset + 1])) << 8)) >> 2;
|
|
256
|
+
|
|
257
|
+
// Canonical: must be > 63 and <= 255 for u8
|
|
258
|
+
if (v <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
259
|
+
if (v > type(uint8).max) revert ValueOutOfRange();
|
|
260
|
+
|
|
261
|
+
value = uint8(v);
|
|
262
|
+
bytesRead = 2;
|
|
263
|
+
} else {
|
|
264
|
+
revert ValueOutOfRange();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// @notice Decodes to uint16 with range validation
|
|
269
|
+
function decodeU16(
|
|
270
|
+
bytes memory data
|
|
271
|
+
) internal pure returns (uint16 value, uint256 bytesRead) {
|
|
272
|
+
return decodeU16At(data, 0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// @notice Decodes to uint16 at offset with range validation
|
|
276
|
+
function decodeU16At(
|
|
277
|
+
bytes memory data,
|
|
278
|
+
uint256 offset
|
|
279
|
+
) internal pure returns (uint16 value, uint256 bytesRead) {
|
|
280
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
281
|
+
|
|
282
|
+
uint8 firstByte = uint8(data[offset]);
|
|
283
|
+
uint8 mode = firstByte & 0x03;
|
|
284
|
+
|
|
285
|
+
if (mode == MODE_SINGLE) {
|
|
286
|
+
value = uint16(firstByte >> 2);
|
|
287
|
+
bytesRead = 1;
|
|
288
|
+
} else if (mode == MODE_TWO) {
|
|
289
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
290
|
+
uint256 v = (uint256(firstByte) |
|
|
291
|
+
(uint256(uint8(data[offset + 1])) << 8)) >> 2;
|
|
292
|
+
|
|
293
|
+
if (v <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
294
|
+
|
|
295
|
+
value = uint16(v);
|
|
296
|
+
bytesRead = 2;
|
|
297
|
+
} else if (mode == MODE_FOUR) {
|
|
298
|
+
if (data.length < offset + 4) revert InvalidCompactEncoding();
|
|
299
|
+
uint256 v = (uint256(firstByte) |
|
|
300
|
+
(uint256(uint8(data[offset + 1])) << 8) |
|
|
301
|
+
(uint256(uint8(data[offset + 2])) << 16) |
|
|
302
|
+
(uint256(uint8(data[offset + 3])) << 24)) >> 2;
|
|
303
|
+
|
|
304
|
+
if (v <= TWO_BYTE_MAX) revert NonCanonicalEncoding();
|
|
305
|
+
if (v > type(uint16).max) revert ValueOutOfRange();
|
|
306
|
+
|
|
307
|
+
value = uint16(v);
|
|
308
|
+
bytesRead = 4;
|
|
309
|
+
} else {
|
|
310
|
+
revert ValueOutOfRange();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// @notice Decodes to uint32 with range validation
|
|
315
|
+
function decodeU32(
|
|
316
|
+
bytes memory data
|
|
317
|
+
) internal pure returns (uint32 value, uint256 bytesRead) {
|
|
318
|
+
return decodeU32At(data, 0);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// @notice Decodes to uint32 at offset with range validation
|
|
322
|
+
function decodeU32At(
|
|
323
|
+
bytes memory data,
|
|
324
|
+
uint256 offset
|
|
325
|
+
) internal pure returns (uint32 value, uint256 bytesRead) {
|
|
326
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
327
|
+
|
|
328
|
+
uint8 firstByte = uint8(data[offset]);
|
|
329
|
+
uint8 mode = firstByte & 0x03;
|
|
330
|
+
|
|
331
|
+
if (mode == MODE_SINGLE) {
|
|
332
|
+
value = uint32(firstByte >> 2);
|
|
333
|
+
bytesRead = 1;
|
|
334
|
+
} else if (mode == MODE_TWO) {
|
|
335
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
336
|
+
uint256 v = (uint256(firstByte) |
|
|
337
|
+
(uint256(uint8(data[offset + 1])) << 8)) >> 2;
|
|
338
|
+
|
|
339
|
+
if (v <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
340
|
+
|
|
341
|
+
value = uint32(v);
|
|
342
|
+
bytesRead = 2;
|
|
343
|
+
} else if (mode == MODE_FOUR) {
|
|
344
|
+
if (data.length < offset + 4) revert InvalidCompactEncoding();
|
|
345
|
+
uint256 v = (uint256(firstByte) |
|
|
346
|
+
(uint256(uint8(data[offset + 1])) << 8) |
|
|
347
|
+
(uint256(uint8(data[offset + 2])) << 16) |
|
|
348
|
+
(uint256(uint8(data[offset + 3])) << 24)) >> 2;
|
|
349
|
+
|
|
350
|
+
if (v <= TWO_BYTE_MAX) revert NonCanonicalEncoding();
|
|
351
|
+
|
|
352
|
+
value = uint32(v);
|
|
353
|
+
bytesRead = 4;
|
|
354
|
+
} else {
|
|
355
|
+
// Big-integer mode
|
|
356
|
+
uint8 byteLen = (firstByte >> 2) + 4;
|
|
357
|
+
|
|
358
|
+
// For u32, only byteLen == 4 is valid (header 0b11 means 4 extra bytes)
|
|
359
|
+
if (byteLen != 4) revert ValueOutOfRange();
|
|
360
|
+
|
|
361
|
+
if (data.length < offset + 5) revert InvalidCompactEncoding();
|
|
362
|
+
|
|
363
|
+
uint256 v = 0;
|
|
364
|
+
for (uint8 i = 0; i < 4; i++) {
|
|
365
|
+
v |= uint256(uint8(data[offset + 1 + i])) << (i * 8);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (v <= FOUR_BYTE_MAX) revert NonCanonicalEncoding();
|
|
369
|
+
if (v > type(uint32).max) revert ValueOutOfRange();
|
|
370
|
+
|
|
371
|
+
value = uint32(v);
|
|
372
|
+
bytesRead = 5;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// @notice Decodes to uint64 with range validation
|
|
377
|
+
function decodeU64(
|
|
378
|
+
bytes memory data
|
|
379
|
+
) internal pure returns (uint64 value, uint256 bytesRead) {
|
|
380
|
+
return decodeU64At(data, 0);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/// @notice Decodes to uint64 at offset with range validation
|
|
384
|
+
function decodeU64At(
|
|
385
|
+
bytes memory data,
|
|
386
|
+
uint256 offset
|
|
387
|
+
) internal pure returns (uint64 value, uint256 bytesRead) {
|
|
388
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
389
|
+
|
|
390
|
+
uint8 firstByte = uint8(data[offset]);
|
|
391
|
+
uint8 mode = firstByte & 0x03;
|
|
392
|
+
|
|
393
|
+
if (mode == MODE_SINGLE) {
|
|
394
|
+
value = uint64(firstByte >> 2);
|
|
395
|
+
bytesRead = 1;
|
|
396
|
+
} else if (mode == MODE_TWO) {
|
|
397
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
398
|
+
uint256 v = (uint256(firstByte) |
|
|
399
|
+
(uint256(uint8(data[offset + 1])) << 8)) >> 2;
|
|
400
|
+
|
|
401
|
+
if (v <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
402
|
+
|
|
403
|
+
value = uint64(v);
|
|
404
|
+
bytesRead = 2;
|
|
405
|
+
} else if (mode == MODE_FOUR) {
|
|
406
|
+
if (data.length < offset + 4) revert InvalidCompactEncoding();
|
|
407
|
+
uint256 v = (uint256(firstByte) |
|
|
408
|
+
(uint256(uint8(data[offset + 1])) << 8) |
|
|
409
|
+
(uint256(uint8(data[offset + 2])) << 16) |
|
|
410
|
+
(uint256(uint8(data[offset + 3])) << 24)) >> 2;
|
|
411
|
+
|
|
412
|
+
if (v <= TWO_BYTE_MAX) revert NonCanonicalEncoding();
|
|
413
|
+
|
|
414
|
+
value = uint64(v);
|
|
415
|
+
bytesRead = 4;
|
|
416
|
+
} else {
|
|
417
|
+
// Big-integer mode
|
|
418
|
+
uint8 byteLen = (firstByte >> 2) + 4;
|
|
419
|
+
|
|
420
|
+
// For u64, max byteLen is 8
|
|
421
|
+
if (byteLen > 8) revert ValueOutOfRange();
|
|
422
|
+
|
|
423
|
+
if (data.length < offset + 1 + byteLen)
|
|
424
|
+
revert InvalidCompactEncoding();
|
|
425
|
+
|
|
426
|
+
uint256 v = 0;
|
|
427
|
+
for (uint8 i = 0; i < byteLen; i++) {
|
|
428
|
+
v |= uint256(uint8(data[offset + 1 + i])) << (i * 8);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (v <= FOUR_BYTE_MAX) revert NonCanonicalEncoding();
|
|
432
|
+
|
|
433
|
+
// Canonical check: no leading zero bytes
|
|
434
|
+
uint8 minBytes = _bytesNeeded(v);
|
|
435
|
+
if (byteLen != minBytes) revert NonCanonicalEncoding();
|
|
436
|
+
|
|
437
|
+
if (v > type(uint64).max) revert ValueOutOfRange();
|
|
438
|
+
|
|
439
|
+
value = uint64(v);
|
|
440
|
+
bytesRead = 1 + byteLen;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/// @notice Decodes to uint128 with range validation
|
|
445
|
+
function decodeU128(
|
|
446
|
+
bytes memory data
|
|
447
|
+
) internal pure returns (uint128 value, uint256 bytesRead) {
|
|
448
|
+
return decodeU128At(data, 0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/// @notice Decodes to uint128 at offset with range validation
|
|
452
|
+
function decodeU128At(
|
|
453
|
+
bytes memory data,
|
|
454
|
+
uint256 offset
|
|
455
|
+
) internal pure returns (uint128 value, uint256 bytesRead) {
|
|
456
|
+
if (data.length <= offset) revert InvalidCompactEncoding();
|
|
457
|
+
|
|
458
|
+
uint8 firstByte = uint8(data[offset]);
|
|
459
|
+
uint8 mode = firstByte & 0x03;
|
|
460
|
+
|
|
461
|
+
if (mode == MODE_SINGLE) {
|
|
462
|
+
value = uint128(firstByte >> 2);
|
|
463
|
+
bytesRead = 1;
|
|
464
|
+
} else if (mode == MODE_TWO) {
|
|
465
|
+
if (data.length < offset + 2) revert InvalidCompactEncoding();
|
|
466
|
+
uint256 v = (uint256(firstByte) |
|
|
467
|
+
(uint256(uint8(data[offset + 1])) << 8)) >> 2;
|
|
468
|
+
|
|
469
|
+
if (v <= SINGLE_BYTE_MAX) revert NonCanonicalEncoding();
|
|
470
|
+
|
|
471
|
+
value = uint128(v);
|
|
472
|
+
bytesRead = 2;
|
|
473
|
+
} else if (mode == MODE_FOUR) {
|
|
474
|
+
if (data.length < offset + 4) revert InvalidCompactEncoding();
|
|
475
|
+
uint256 v = (uint256(firstByte) |
|
|
476
|
+
(uint256(uint8(data[offset + 1])) << 8) |
|
|
477
|
+
(uint256(uint8(data[offset + 2])) << 16) |
|
|
478
|
+
(uint256(uint8(data[offset + 3])) << 24)) >> 2;
|
|
479
|
+
|
|
480
|
+
if (v <= TWO_BYTE_MAX) revert NonCanonicalEncoding();
|
|
481
|
+
|
|
482
|
+
value = uint128(v);
|
|
483
|
+
bytesRead = 4;
|
|
484
|
+
} else {
|
|
485
|
+
// Big-integer mode
|
|
486
|
+
uint8 byteLen = (firstByte >> 2) + 4;
|
|
487
|
+
|
|
488
|
+
// For u128, max byteLen is 16
|
|
489
|
+
if (byteLen > 16) revert ValueOutOfRange();
|
|
490
|
+
|
|
491
|
+
if (data.length < offset + 1 + byteLen)
|
|
492
|
+
revert InvalidCompactEncoding();
|
|
493
|
+
|
|
494
|
+
uint256 v = 0;
|
|
495
|
+
for (uint8 i = 0; i < byteLen; i++) {
|
|
496
|
+
v |= uint256(uint8(data[offset + 1 + i])) << (i * 8);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (v <= FOUR_BYTE_MAX) revert NonCanonicalEncoding();
|
|
500
|
+
|
|
501
|
+
// Canonical check: no leading zero bytes
|
|
502
|
+
uint8 minBytes = _bytesNeeded(v);
|
|
503
|
+
if (byteLen != minBytes) revert NonCanonicalEncoding();
|
|
504
|
+
|
|
505
|
+
if (v > type(uint128).max) revert ValueOutOfRange();
|
|
506
|
+
|
|
507
|
+
value = uint128(v);
|
|
508
|
+
bytesRead = 1 + byteLen;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ============ Utilities ============
|
|
513
|
+
|
|
514
|
+
/// @notice Returns the encoded length without allocating
|
|
515
|
+
/// @param value The value to check
|
|
516
|
+
/// @return The number of bytes needed for compact encoding
|
|
517
|
+
function encodedLength(uint256 value) internal pure returns (uint256) {
|
|
518
|
+
if (value <= SINGLE_BYTE_MAX) return 1;
|
|
519
|
+
if (value <= TWO_BYTE_MAX) return 2;
|
|
520
|
+
if (value <= FOUR_BYTE_MAX) return 4;
|
|
521
|
+
return 1 + _bytesNeeded(value);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// @notice Checks if a value fits in single-byte mode
|
|
525
|
+
function isSingleByte(uint256 value) internal pure returns (bool) {
|
|
526
|
+
return value <= SINGLE_BYTE_MAX;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/// @notice Checks if a value fits in two-byte mode
|
|
530
|
+
function isTwoByte(uint256 value) internal pure returns (bool) {
|
|
531
|
+
return value > SINGLE_BYTE_MAX && value <= TWO_BYTE_MAX;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/// @notice Checks if a value fits in four-byte mode
|
|
535
|
+
function isFourByte(uint256 value) internal pure returns (bool) {
|
|
536
|
+
return value > TWO_BYTE_MAX && value <= FOUR_BYTE_MAX;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/// @notice Checks if a value requires big-integer mode
|
|
540
|
+
function isBigInt(uint256 value) internal pure returns (bool) {
|
|
541
|
+
return value > FOUR_BYTE_MAX;
|
|
542
|
+
}
|
|
543
|
+
}
|