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.
Files changed (41) hide show
  1. package/README.md +2 -0
  2. package/package.json +3 -1
  3. package/src/LittleEndian/LittleEndianU128.sol +49 -0
  4. package/src/LittleEndian/LittleEndianU16.sol +29 -0
  5. package/src/LittleEndian/LittleEndianU256.sol +101 -0
  6. package/src/LittleEndian/LittleEndianU32.sol +42 -0
  7. package/src/LittleEndian/LittleEndianU64.sol +45 -0
  8. package/src/LittleEndian/LittleEndianU8.sol +26 -0
  9. package/src/Scale/Array/BoolArr.sol +49 -35
  10. package/src/Scale/Array/I128Arr.sol +49 -35
  11. package/src/Scale/Array/I16Arr.sol +49 -35
  12. package/src/Scale/Array/I256Arr.sol +49 -35
  13. package/src/Scale/Array/I32Arr.sol +49 -35
  14. package/src/Scale/Array/I64Arr.sol +49 -35
  15. package/src/Scale/Array/I8Arr.sol +49 -35
  16. package/src/Scale/Array/U128Arr.sol +49 -35
  17. package/src/Scale/Array/U16Arr.sol +49 -35
  18. package/src/Scale/Array/U256Arr.sol +49 -35
  19. package/src/Scale/Array/U32Arr.sol +49 -35
  20. package/src/Scale/Array/U64Arr.sol +49 -35
  21. package/src/Scale/Array/U8Arr.sol +49 -35
  22. package/src/Scale/Array.sol +14 -15
  23. package/src/Scale/Bool/Bool.sol +1 -1
  24. package/src/Scale/Bool.sol +1 -1
  25. package/src/Scale/Compact/Compact.sol +89 -460
  26. package/src/Scale/Compact.sol +1 -1
  27. package/src/Scale/Signed/I128.sol +13 -6
  28. package/src/Scale/Signed/I16.sol +10 -5
  29. package/src/Scale/Signed/I256.sol +13 -6
  30. package/src/Scale/Signed/I32.sol +10 -5
  31. package/src/Scale/Signed/I64.sol +10 -5
  32. package/src/Scale/Signed/I8.sol +10 -5
  33. package/src/Scale/Signed.sol +7 -8
  34. package/src/Scale/Unsigned/U128.sol +15 -9
  35. package/src/Scale/Unsigned/U16.sol +15 -9
  36. package/src/Scale/Unsigned/U256.sol +15 -9
  37. package/src/Scale/Unsigned/U32.sol +15 -9
  38. package/src/Scale/Unsigned/U64.sol +15 -9
  39. package/src/Scale/Unsigned/U8.sol +13 -9
  40. package/src/Scale/Unsigned.sol +7 -8
  41. 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.20;
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
- // ============ 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
+ 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 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) ============
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 <= SINGLE_BYTE_MAX) {
35
+ if (value < SINGLE_BYTE_BOUND) {
35
36
  return _encodeSingleByte(value);
36
- } else if (value <= TWO_BYTE_MAX) {
37
+ } else if (value < TWO_BYTE_BOUND) {
37
38
  return _encodeTwoByte(value);
38
- } else if (value <= FOUR_BYTE_MAX) {
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
- // ============ 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
- }
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 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);
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
- /// @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);
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
- return _encodeBigInt(uint256(value));
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
- /// @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
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 Two-byte mode: value << 2 | 0b01, little-endian
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 Four-byte mode: value << 2 | 0b10, little-endian
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 Big-integer mode: header byte + raw value bytes
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
- 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;
149
+ uint256 tmp = value;
150
+ while (tmp > 0) {
151
+ tmp >>= 8;
152
+ n++;
441
153
  }
442
154
  }
443
155
 
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;
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
  }
@@ -1,4 +1,4 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- pragma solidity ^0.8.20;
2
+ pragma solidity ^0.8.28;
3
3
 
4
4
  import {Compact} from "./Compact/Compact.sol";
@@ -1,13 +1,15 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- pragma solidity ^0.8.20;
2
+ pragma solidity ^0.8.28;
3
3
 
4
- import { U128 } from "../Unsigned/U128.sol";
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
- /// @notice Encodes an `int128` into SCALE format (16-byte two's-complement little-endian).
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
- /// @notice Converts an int128 to little-endian bytes16 (two's complement)
36
- function toLittleEndian(int128 value) internal pure returns (bytes16 result) {
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
+ }