1+ // SPDX-License-Identifier: MIT
2+ pragma solidity ^ 0.8.24 ;
3+
4+ import "forge-std/Test.sol " ;
5+ import "forge-std/console2.sol " ;
6+ import "../src/libraries/BytePackLib.sol " ;
7+
8+ /// @title TestWrapper
9+ /// @notice Wrapper contract to expose internal library functions for testing
10+ contract TestWrapper {
11+ function packCalldata (bytes calldata data ) external pure returns (bytes32 ) {
12+ return BytePackLib.packCalldata (data);
13+ }
14+
15+ function unpack (bytes32 packed ) external pure returns (bytes memory ) {
16+ return BytePackLib.unpack (packed);
17+ }
18+
19+ function isPacked (bytes32 value ) external pure returns (bool ) {
20+ return BytePackLib.isPacked (value);
21+ }
22+
23+ function packedLength (bytes32 packed ) external pure returns (uint256 ) {
24+ return BytePackLib.packedLength (packed);
25+ }
26+ }
27+
28+ contract BytePackLibTest is Test {
29+ TestWrapper wrapper;
30+
31+ function setUp () public {
32+ wrapper = new TestWrapper ();
33+ }
34+
35+ /// @notice Test packing and unpacking for all valid sizes (0-31 bytes)
36+ function test_AllValidSizes () public {
37+ for (uint256 i = 0 ; i <= 31 ; i++ ) {
38+ // Test with incrementing pattern
39+ bytes memory data = new bytes (i);
40+ for (uint256 j = 0 ; j < i; j++ ) {
41+ data[j] = bytes1 (uint8 (j % 256 ));
42+ }
43+ this .helperPackUnpack (data);
44+
45+ // Also test with all zeros
46+ bytes memory zeros = new bytes (i);
47+ this .helperPackUnpack (zeros);
48+
49+ // Also test with all 0xFF
50+ bytes memory ones = new bytes (i);
51+ for (uint256 j = 0 ; j < i; j++ ) {
52+ ones[j] = bytes1 (0xFF );
53+ }
54+ this .helperPackUnpack (ones);
55+ }
56+ }
57+
58+ function helperPackUnpack (bytes calldata data ) external view {
59+ require (data.length < 32 , "Data must be less than 32 bytes " );
60+
61+ bytes32 packed = wrapper.packCalldata (data);
62+
63+ // Verify it's marked as packed
64+ assertTrue (wrapper.isPacked (packed), "Should be marked as packed " );
65+
66+ // Verify the length is correct
67+ assertEq (wrapper.packedLength (packed), data.length , "Length should match " );
68+
69+ // Verify unpacking gives back the original data
70+ bytes memory unpacked = wrapper.unpack (packed);
71+ assertEq (unpacked, data, "Unpacked data should match original " );
72+
73+ // Verify the tag byte is correct (length + 1)
74+ uint8 tag = uint8 (uint256 (packed >> 248 ));
75+ assertEq (tag, data.length + 1 , "Tag should be length + 1 " );
76+ }
77+
78+ /// @notice Test that packing 32+ bytes reverts
79+ function test_PackingTooLarge_Reverts () public {
80+ for (uint256 size = 32 ; size <= 40 ; size++ ) {
81+ bytes memory data = new bytes (size);
82+
83+ vm.expectRevert (abi.encodeWithSelector (BytePackLib.ContentTooLarge.selector , size));
84+ this .helperPackLarge (data);
85+ }
86+ }
87+
88+ function helperPackLarge (bytes calldata data ) external view {
89+ wrapper.packCalldata (data);
90+ }
91+
92+ /// @notice Test that unpacking non-packed data reverts
93+ function test_UnpackNonPacked_Reverts () public {
94+ // Test with zero bytes32 (tag = 0)
95+ bytes32 zero = bytes32 (0 );
96+ assertFalse (wrapper.isPacked (zero), "Zero should not be packed " );
97+ vm.expectRevert (BytePackLib.NotPackedData.selector );
98+ wrapper.unpack (zero);
99+
100+ // Test with an address-like value (no tag byte)
101+ bytes32 addressLike = bytes32 (uint256 (uint160 (address (0x1234567890123456789012345678901234567890 ))));
102+ assertFalse (wrapper.isPacked (addressLike), "Address should not be packed " );
103+ vm.expectRevert (BytePackLib.NotPackedData.selector );
104+ wrapper.unpack (addressLike);
105+
106+ // Test with tag byte > 32 (e.g., 0x21 = 33)
107+ bytes32 invalidTag = bytes32 (uint256 (0x21 ) << 248 );
108+ assertFalse (wrapper.isPacked (invalidTag), "Tag > 32 should not be packed " );
109+ vm.expectRevert (BytePackLib.NotPackedData.selector );
110+ wrapper.unpack (invalidTag);
111+
112+ // Test with tag byte = 255 (maximum uint8)
113+ bytes32 maxTag = bytes32 (uint256 (0xFF ) << 248 );
114+ assertFalse (wrapper.isPacked (maxTag), "Tag = 255 should not be packed " );
115+ vm.expectRevert (BytePackLib.NotPackedData.selector );
116+ wrapper.unpack (maxTag);
117+
118+ // Test getting length of non-packed data
119+ vm.expectRevert (BytePackLib.NotPackedData.selector );
120+ wrapper.packedLength (zero);
121+
122+ vm.expectRevert (BytePackLib.NotPackedData.selector );
123+ wrapper.packedLength (addressLike);
124+
125+ vm.expectRevert (BytePackLib.NotPackedData.selector );
126+ wrapper.packedLength (invalidTag);
127+
128+ vm.expectRevert (BytePackLib.NotPackedData.selector );
129+ wrapper.packedLength (maxTag);
130+ }
131+
132+ /// @notice Test isPacked detection
133+ function test_IsPacked_Detection () public {
134+ // Pack some data and verify detection
135+ bytes memory testData = hex "74657374 " ; // "test"
136+ bytes32 packed = wrapper.packCalldata (testData);
137+ assertTrue (wrapper.isPacked (packed), "Packed data should be detected " );
138+
139+ // Regular addresses should not be detected as packed
140+ address addr = address (0x1234567890123456789012345678901234567890 );
141+ bytes32 addrBytes = bytes32 (uint256 (uint160 (addr)));
142+ assertFalse (wrapper.isPacked (addrBytes), "Address should not be detected as packed " );
143+
144+ // Zero should not be detected as packed
145+ assertFalse (wrapper.isPacked (bytes32 (0 )), "Zero should not be detected as packed " );
146+
147+ // Random data without tag byte (first byte is 0x00) should not be detected as packed
148+ bytes32 randomData = bytes32 (uint256 (0x00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff ));
149+ assertFalse (wrapper.isPacked (randomData), "Random data should not be detected as packed " );
150+ }
151+
152+ /// @notice Test the exact packed format
153+ function test_PackedFormat () public {
154+ // Test empty bytes
155+ bytes memory empty = "" ;
156+ bytes32 packedEmpty = wrapper.packCalldata (empty);
157+ assertEq (uint256 (packedEmpty), uint256 (bytes32 (bytes1 (0x01 ))), "Empty should pack to 0x01 followed by zeros " );
158+
159+ // Test single byte 'A' (0x41)
160+ bytes memory single = hex "41 " ;
161+ bytes32 packedSingle = wrapper.packCalldata (single);
162+ // Should be: tag=0x02, data=0x41, rest zeros
163+ assertEq (uint8 (uint256 (packedSingle >> 248 )), 0x02 , "Tag for 1 byte should be 2 " );
164+ assertEq (uint8 (uint256 (packedSingle >> 240 )), 0x41 , "Data should be 0x41 " );
165+
166+ // Test "ABC" (0x414243)
167+ bytes memory abc = hex "414243 " ;
168+ bytes32 packedAbc = wrapper.packCalldata (abc);
169+ // Should be: tag=0x04, data=0x414243, rest zeros
170+ assertEq (uint8 (uint256 (packedAbc >> 248 )), 0x04 , "Tag for 3 bytes should be 4 " );
171+ assertEq (uint8 (uint256 (packedAbc >> 240 )), 0x41 , "First byte should be 0x41 " );
172+ assertEq (uint8 (uint256 (packedAbc >> 232 )), 0x42 , "Second byte should be 0x42 " );
173+ assertEq (uint8 (uint256 (packedAbc >> 224 )), 0x43 , "Third byte should be 0x43 " );
174+ }
175+
176+ /// @notice Test edge cases
177+ function test_EdgeCases () public {
178+ // Test 31 bytes (maximum packable)
179+ bytes memory max = new bytes (31 );
180+ for (uint i = 0 ; i < 31 ; i++ ) {
181+ max[i] = bytes1 (uint8 (i));
182+ }
183+
184+ bytes32 packed = wrapper.packCalldata (max);
185+ assertTrue (wrapper.isPacked (packed), "31 bytes should be packed " );
186+ assertEq (wrapper.packedLength (packed), 31 , "Length should be 31 " );
187+
188+ bytes memory unpacked = wrapper.unpack (packed);
189+ assertEq (unpacked, max, "31 bytes should unpack correctly " );
190+
191+ // Verify tag is 32 (31 + 1) - the maximum valid tag
192+ assertEq (uint8 (uint256 (packed >> 248 )), 32 , "Tag for 31 bytes should be 32 " );
193+
194+ // Manually create a bytes32 with tag = 32 (boundary case) and verify it's valid
195+ bytes32 dataBytes;
196+ assembly {
197+ dataBytes := mload (add (max, 0x20 ))
198+ }
199+ bytes32 boundaryTag = bytes32 (uint256 (32 ) << 248 ) | (dataBytes >> 8 );
200+ assertTrue (wrapper.isPacked (boundaryTag), "Tag = 32 should be valid packed data " );
201+ assertEq (wrapper.packedLength (boundaryTag), 31 , "Tag = 32 means 31 bytes of data " );
202+ }
203+ }
0 commit comments