From f25da218d7b40878a61f6feb09f39c7fb06433f5 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 23 Jan 2025 18:43:10 +0530 Subject: [PATCH] feat: add LibPrefixLengthEncodedByteArray with tests --- .../lib/LibPrefixLengthEncodedByteArray.sol | 74 +++++++++++++ .../LibPrefixLengthEncodedByteArray.t.sol | 104 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 foundry/src/lib/LibPrefixLengthEncodedByteArray.sol create mode 100644 foundry/test/LibPrefixLengthEncodedByteArray.t.sol diff --git a/foundry/src/lib/LibPrefixLengthEncodedByteArray.sol b/foundry/src/lib/LibPrefixLengthEncodedByteArray.sol new file mode 100644 index 0000000..f8053b4 --- /dev/null +++ b/foundry/src/lib/LibPrefixLengthEncodedByteArray.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +error LibPrefixLengthEncodedByteArray__InvalidEncoding(); + +library LibPrefixLengthEncodedByteArray { + /** + * @dev Pop the first element of an array and returns it with the remaining data. + */ + function next(bytes calldata encoded) + internal + pure + returns (bytes calldata elem, bytes calldata res) + { + // Handle empty input + if (encoded.length == 0) { + return (encoded[:0], encoded[:0]); + } + + // Ensure we have at least 2 bytes for length prefix + if (encoded.length < 2) revert LibPrefixLengthEncodedByteArray__InvalidEncoding(); + // Extract the length prefix (first 2 bytes) + uint16 length = uint16(bytes2(encoded[:2])); + + // Check if length is valid + if (2 + length > encoded.length) revert LibPrefixLengthEncodedByteArray__InvalidEncoding(); + + // Extract the element (after length prefix) + elem = encoded[2:2+length]; + + // Extract the remaining data + res = encoded[2+length:]; + + return (elem, res); + } + + /** + * @dev Gets the size of the encoded array. + */ + function size(bytes calldata encoded) internal pure returns (uint256 s) { + uint256 offset = 0; + + while (offset < encoded.length) { + // Ensure we have at least 2 bytes for length prefix + if (offset + 2 > encoded.length) revert LibPrefixLengthEncodedByteArray__InvalidEncoding(); + + uint16 length = uint16(bytes2(encoded[offset:offset + 2])); + + // Check if length is valid + if (offset + 2 + length > encoded.length) revert LibPrefixLengthEncodedByteArray__InvalidEncoding(); + + offset += length + 2; + s++; + } + } + + /** + * @dev Cast an encoded array into a Solidity array. + */ + function toArray(bytes calldata encoded) + internal + pure + returns (bytes[] memory arr) + { + bytes calldata elem; + uint256 idx = 0; + arr = new bytes[](LibPrefixLengthEncodedByteArray.size(encoded)); + while (encoded.length > 0) { + (elem, encoded) = LibPrefixLengthEncodedByteArray.next(encoded); + arr[idx] = elem; + idx++; + } + } +} \ No newline at end of file diff --git a/foundry/test/LibPrefixLengthEncodedByteArray.t.sol b/foundry/test/LibPrefixLengthEncodedByteArray.t.sol new file mode 100644 index 0000000..7ea6d79 --- /dev/null +++ b/foundry/test/LibPrefixLengthEncodedByteArray.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {LibPrefixLengthEncodedByteArray} from "../src/lib/LibPrefixLengthEncodedByteArray.sol"; + +contract LibPrefixLengthEncodedByteArrayTest is Test { + using LibPrefixLengthEncodedByteArray for bytes; + + function testNextEmpty() public { + bytes memory encoded = ""; + (bytes memory elem, bytes memory remaining) = this.next(encoded); + assertEq(elem.length, 0); + assertEq(remaining.length, 0); + } + + function testNextSingleElement() public { + // Create encoded data: length prefix (0003) followed by "ABC" + bytes memory encoded = hex"0003414243"; + (bytes memory elem, bytes memory remaining) = this.next(encoded); + + assertEq(elem.length, 3); + assertEq(elem, hex"414243"); // "ABC" + assertEq(remaining.length, 0); + } + + function testNextMultipleElements() public { + // Encoded data: [0003]ABC[0002]DE + bytes memory encoded = hex"000341424300024445"; + + // First next() + (bytes memory elem1, bytes memory remaining1) = this.next(encoded); + assertEq(elem1, hex"414243"); // "ABC" + assertEq(remaining1, hex"00024445"); + + // Second next() + (bytes memory elem2, bytes memory remaining2) = this.next(remaining1); + assertEq(elem2, hex"4445"); // "DE" + assertEq(remaining2.length, 0); + } + + function testSize() public { + bytes memory empty = ""; + assertEq(this.size(empty), 0); + + bytes memory single = hex"0003414243"; + assertEq(this.size(single), 1); + + bytes memory multiple = hex"0003414243000244450001FF"; + assertEq(this.size(multiple), 3); + } + + function testFailInvalidLength() public { + // Length prefix larger than remaining data + bytes memory invalid = hex"0004414243"; + this.next(invalid); + } + + function testFailIncompletePrefix() public { + // Only 1 byte instead of 2 bytes prefix + bytes memory invalid = hex"01"; + this.next(invalid); + } + + function testLargeElement() public { + // Test with a large but manageable size (1000 bytes) + bytes memory large = new bytes(1002); // 2 bytes prefix + 1000 bytes data + large[0] = bytes1(uint8(0x03)); // 03 + large[1] = bytes1(uint8(0xe8)); // E8 (1000 in hex) + + // Fill data bytes + for (uint i = 2; i < large.length; i++) { + large[i] = bytes1(uint8(0x01)); + } + + (bytes memory elem, bytes memory remaining) = this.next(large); + assertEq(elem.length, 1000); + assertEq(remaining.length, 0); + } + + function testSizeWithLargeElements() public { + // Two elements: 1000 bytes + 500 bytes + bytes memory data = new bytes(1504); // 1000 + 2 + 500 + 2 + + // First element (1000 bytes) + data[0] = bytes1(uint8(0x03)); // 03 + data[1] = bytes1(uint8(0xe8)); // E8 (1000 in hex) + + // Second element (500 bytes) + data[1002] = bytes1(uint8(0x01)); // 01 + data[1003] = bytes1(uint8(0xf4)); // F4 (500 in hex) + + assertEq(this.size(data), 2); + } + + function next(bytes calldata data) external pure returns (bytes memory, bytes memory) { + return data.next(); + } + + function size(bytes calldata data) external pure returns (uint256) { + return data.size(); + } + +}