solidity-scale-codec 0.1.1 → 0.1.2
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 +2 -0
- package/package.json +3 -1
- package/src/LittleEndian/LittleEndianU128.sol +49 -0
- package/src/LittleEndian/LittleEndianU16.sol +29 -0
- package/src/LittleEndian/LittleEndianU256.sol +101 -0
- package/src/LittleEndian/LittleEndianU32.sol +42 -0
- package/src/LittleEndian/LittleEndianU64.sol +45 -0
- package/src/LittleEndian/LittleEndianU8.sol +26 -0
- package/src/Scale/Array/BoolArr.sol +49 -35
- package/src/Scale/Array/I128Arr.sol +49 -35
- package/src/Scale/Array/I16Arr.sol +49 -35
- package/src/Scale/Array/I256Arr.sol +49 -35
- package/src/Scale/Array/I32Arr.sol +49 -35
- package/src/Scale/Array/I64Arr.sol +49 -35
- package/src/Scale/Array/I8Arr.sol +49 -35
- package/src/Scale/Array/U128Arr.sol +49 -35
- package/src/Scale/Array/U16Arr.sol +49 -35
- package/src/Scale/Array/U256Arr.sol +49 -35
- package/src/Scale/Array/U32Arr.sol +49 -35
- package/src/Scale/Array/U64Arr.sol +49 -35
- package/src/Scale/Array/U8Arr.sol +49 -35
- package/src/Scale/Array.sol +14 -15
- package/src/Scale/Bool/Bool.sol +1 -1
- package/src/Scale/Bool.sol +1 -1
- package/src/Scale/Compact/Compact.sol +89 -460
- package/src/Scale/Compact.sol +1 -1
- package/src/Scale/Signed/I128.sol +13 -6
- package/src/Scale/Signed/I16.sol +10 -5
- package/src/Scale/Signed/I256.sol +13 -6
- package/src/Scale/Signed/I32.sol +10 -5
- package/src/Scale/Signed/I64.sol +10 -5
- package/src/Scale/Signed/I8.sol +10 -5
- package/src/Scale/Signed.sol +7 -8
- package/src/Scale/Unsigned/U128.sol +15 -9
- package/src/Scale/Unsigned/U16.sol +15 -9
- package/src/Scale/Unsigned/U256.sol +15 -9
- package/src/Scale/Unsigned/U32.sol +15 -9
- package/src/Scale/Unsigned/U64.sol +15 -9
- package/src/Scale/Unsigned/U8.sol +13 -9
- package/src/Scale/Unsigned.sol +7 -8
- package/src/Utils/LittleEndian/LittleEndian.sol +0 -354
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
pragma solidity ^0.8.
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import {LittleEndianU16} from "../../LittleEndian/LittleEndianU16.sol";
|
|
5
|
+
import {LittleEndianU32} from "../../LittleEndian/LittleEndianU32.sol";
|
|
6
|
+
import {LittleEndianU256} from "../../LittleEndian/LittleEndianU256.sol";
|
|
3
7
|
|
|
4
8
|
/// @title Compact
|
|
5
9
|
/// @notice SCALE Compact encoding for space-efficient unsigned integer representation
|
|
@@ -13,96 +17,101 @@ library Compact {
|
|
|
13
17
|
error InvalidCompactEncoding();
|
|
14
18
|
error ValueOutOfRange();
|
|
15
19
|
error NonCanonicalEncoding();
|
|
20
|
+
error OffsetOutOfBounds();
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
uint256
|
|
20
|
-
uint256 private constant TWO_BYTE_MAX = 0x3FFF; // 16383
|
|
21
|
-
uint256 private constant FOUR_BYTE_MAX = 0x3FFFFFFF; // 1073741823
|
|
22
|
+
uint256 internal constant SINGLE_BYTE_BOUND = 64; // 2^6
|
|
23
|
+
uint256 internal constant TWO_BYTE_BOUND = 16384; // 2^14
|
|
24
|
+
uint256 internal constant FOUR_BYTE_BOUND = 1073741824; // 2^30
|
|
22
25
|
|
|
23
|
-
uint8
|
|
24
|
-
uint8
|
|
25
|
-
uint8
|
|
26
|
-
uint8
|
|
27
|
-
|
|
28
|
-
// ============ Encoding (generic) ============
|
|
26
|
+
uint8 internal constant MODE_SINGLE = 0x00;
|
|
27
|
+
uint8 internal constant MODE_TWO = 0x01;
|
|
28
|
+
uint8 internal constant MODE_FOUR = 0x02;
|
|
29
|
+
uint8 internal constant MODE_BIG = 0x03;
|
|
29
30
|
|
|
30
31
|
/// @notice Encodes a uint256 value to SCALE Compact format
|
|
31
32
|
/// @param value The value to encode
|
|
32
33
|
/// @return Compact-encoded bytes in little-endian
|
|
33
34
|
function encode(uint256 value) internal pure returns (bytes memory) {
|
|
34
|
-
if (value
|
|
35
|
+
if (value < SINGLE_BYTE_BOUND) {
|
|
35
36
|
return _encodeSingleByte(value);
|
|
36
|
-
} else if (value
|
|
37
|
+
} else if (value < TWO_BYTE_BOUND) {
|
|
37
38
|
return _encodeTwoByte(value);
|
|
38
|
-
} else if (value
|
|
39
|
+
} else if (value < FOUR_BYTE_BOUND) {
|
|
39
40
|
return _encodeFourByte(value);
|
|
40
41
|
} else {
|
|
41
42
|
return _encodeBigInt(value);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/// @
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
///@notice Decodes a uint256 value from SCALE Compact format
|
|
47
|
+
/// @dev Reverts if the encoding is invalid or non-canonical, or if the decoded value exceeds uint256 range
|
|
48
|
+
/// @param data The Compact-encoded byte sequence
|
|
49
|
+
/// @return value The decoded uint256 value
|
|
50
|
+
/// @return bytesRead The total number of bytes read during decoding
|
|
51
|
+
function decode(
|
|
52
|
+
bytes memory data
|
|
53
|
+
) internal pure returns (uint256 value, uint256 bytesRead) {
|
|
54
|
+
return decodeAt(data, 0);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/// @notice
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
/// @notice Decodes a uint256 value from SCALE Compact format
|
|
58
|
+
/// @dev Reverts if the encoding is invalid or non-canonical, or if the decoded value exceeds uint256 range
|
|
59
|
+
/// @param data The Compact-encoded byte sequence
|
|
60
|
+
/// @param offset The byte offset to start decoding from
|
|
61
|
+
/// @return value The decoded uint256 value
|
|
62
|
+
/// @return bytesRead The total number of bytes read during decoding
|
|
63
|
+
function decodeAt(
|
|
64
|
+
bytes memory data,
|
|
65
|
+
uint256 offset
|
|
66
|
+
) internal pure returns (uint256 value, uint256 bytesRead) {
|
|
67
|
+
if (offset >= data.length) revert OffsetOutOfBounds();
|
|
68
|
+
uint8 header;
|
|
69
|
+
assembly {
|
|
70
|
+
header := shr(248, mload(add(add(data, 32), offset)))
|
|
64
71
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
uint8 mode = header & 0x03;
|
|
73
|
+
if (mode == MODE_SINGLE) {
|
|
74
|
+
value = uint256(header) >> 2;
|
|
75
|
+
bytesRead = 1;
|
|
76
|
+
} else if (mode == MODE_TWO) {
|
|
77
|
+
value = uint256(LittleEndianU16.fromLE(data, offset)) >> 2;
|
|
78
|
+
bytesRead = 2;
|
|
79
|
+
} else if (mode == MODE_FOUR) {
|
|
80
|
+
value = uint256(LittleEndianU32.fromLE(data, offset)) >> 2;
|
|
81
|
+
bytesRead = 4;
|
|
75
82
|
} else {
|
|
76
|
-
|
|
83
|
+
uint8 m = (header >> 2) + 4;
|
|
84
|
+
if (m > 32) revert ValueOutOfRange();
|
|
85
|
+
value = LittleEndianU256.fromLE(data, offset + 1);
|
|
86
|
+
if (m < 32) {
|
|
87
|
+
value &= (uint256(1) << (uint256(m) * 8)) - 1; // zero out bytes beyond m
|
|
88
|
+
}
|
|
89
|
+
// MSB of payload must be non-zero for canonical encoding
|
|
90
|
+
uint8 msb;
|
|
91
|
+
assembly {
|
|
92
|
+
msb := shr(248, mload(add(add(data, 32), add(offset, m))))
|
|
93
|
+
}
|
|
94
|
+
if (msb == 0) revert NonCanonicalEncoding();
|
|
95
|
+
bytesRead = 1 + m;
|
|
77
96
|
}
|
|
97
|
+
_assertCanonicalEncoding(value, mode);
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
/// @
|
|
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
|
|
100
|
+
/// @dev Encodes values in the range [0, 63] using single-byte mode
|
|
93
101
|
function _encodeSingleByte(
|
|
94
102
|
uint256 value
|
|
95
103
|
) private pure returns (bytes memory) {
|
|
104
|
+
// << 2 already leaves 00 in the low bits.
|
|
96
105
|
return abi.encodePacked(uint8(value << 2));
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
/// @dev
|
|
108
|
+
/// @dev Encodes values in the range [64, 16383] using double-byte mode
|
|
100
109
|
function _encodeTwoByte(uint256 value) private pure returns (bytes memory) {
|
|
101
110
|
uint16 encoded = uint16((value << 2) | MODE_TWO);
|
|
102
111
|
return abi.encodePacked(uint8(encoded & 0xFF), uint8(encoded >> 8));
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
/// @dev
|
|
114
|
+
/// @dev Encodes values in the range [16384, 1073741823] using four-byte mode
|
|
106
115
|
function _encodeFourByte(
|
|
107
116
|
uint256 value
|
|
108
117
|
) private pure returns (bytes memory) {
|
|
@@ -116,7 +125,7 @@ library Compact {
|
|
|
116
125
|
);
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
/// @dev
|
|
128
|
+
/// @dev Encodes values in the range [1073741824, 2^536−1] using "big-int" mode
|
|
120
129
|
/// Header: ((bytesNeeded - 4) << 2) | 0b11
|
|
121
130
|
function _encodeBigInt(uint256 value) private pure returns (bytes memory) {
|
|
122
131
|
uint8 bytesNeeded = _bytesNeeded(value);
|
|
@@ -134,410 +143,30 @@ library Compact {
|
|
|
134
143
|
return result;
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
/// @dev Calculate minimum bytes needed to represent value
|
|
138
|
-
function _bytesNeeded(uint256 value) private pure returns (uint8) {
|
|
146
|
+
/// @dev Calculate minimum bytes needed to represent value in big-int mode (excluding header)
|
|
147
|
+
function _bytesNeeded(uint256 value) private pure returns (uint8 n) {
|
|
139
148
|
if (value == 0) return 1;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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;
|
|
149
|
+
uint256 tmp = value;
|
|
150
|
+
while (tmp > 0) {
|
|
151
|
+
tmp >>= 8;
|
|
152
|
+
n++;
|
|
441
153
|
}
|
|
442
154
|
}
|
|
443
155
|
|
|
444
|
-
/// @
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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;
|
|
156
|
+
/// @dev Asserts that the decoded value is using the canonical encoding for its mode
|
|
157
|
+
/// This ensures that values are not encoded in a longer format than necessary
|
|
158
|
+
function _assertCanonicalEncoding(uint256 value, uint8 mode) private pure {
|
|
159
|
+
if (mode == MODE_SINGLE && value >= SINGLE_BYTE_BOUND)
|
|
160
|
+
revert NonCanonicalEncoding();
|
|
161
|
+
if (
|
|
162
|
+
mode == MODE_TWO &&
|
|
163
|
+
(value < SINGLE_BYTE_BOUND || value >= TWO_BYTE_BOUND)
|
|
164
|
+
) revert NonCanonicalEncoding();
|
|
165
|
+
if (
|
|
166
|
+
mode == MODE_FOUR &&
|
|
167
|
+
(value < TWO_BYTE_BOUND || value >= FOUR_BYTE_BOUND)
|
|
168
|
+
) revert NonCanonicalEncoding();
|
|
169
|
+
if (mode == MODE_BIG && value < FOUR_BYTE_BOUND)
|
|
170
|
+
revert NonCanonicalEncoding();
|
|
542
171
|
}
|
|
543
172
|
}
|
package/src/Scale/Compact.sol
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
pragma solidity ^0.8.
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {U128} from "../Unsigned/U128.sol";
|
|
5
5
|
|
|
6
6
|
/// @title Scale Codec for the `int128` type.
|
|
7
7
|
/// @notice SCALE-compliant encoder/decoder for the `int128` type.
|
|
8
8
|
/// @dev SCALE reference: https://docs.polkadot.com/polkadot-protocol/basics/data-encoding
|
|
9
9
|
library I128 {
|
|
10
|
-
|
|
10
|
+
error OffsetOutOfBounds();
|
|
11
|
+
|
|
12
|
+
/// @notice Encodes an `int128` into SCALE format (16-byte two's-complement little-endian).
|
|
11
13
|
/// @param value The signed 128-bit integer to encode.
|
|
12
14
|
/// @return SCALE-encoded byte sequence.
|
|
13
15
|
function encode(int128 value) internal pure returns (bytes memory) {
|
|
@@ -29,11 +31,16 @@ library I128 {
|
|
|
29
31
|
bytes memory data,
|
|
30
32
|
uint256 offset
|
|
31
33
|
) internal pure returns (int128 value) {
|
|
34
|
+
// Safety Check is done in the unsigned decoder.
|
|
32
35
|
return int128(U128.decodeAt(data, offset));
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
/// @notice Converts an int128 to little-endian bytes16 (two's complement)
|
|
39
|
+
/// @param value The signed 128-bit integer to convert.
|
|
40
|
+
/// @return result Little-endian byte representation of the input value.
|
|
41
|
+
function toLittleEndian(
|
|
42
|
+
int128 value
|
|
43
|
+
) internal pure returns (bytes16 result) {
|
|
37
44
|
return U128.toLittleEndian(uint128(value));
|
|
38
45
|
}
|
|
39
|
-
}
|
|
46
|
+
}
|