diff --git a/packages/store/src/Memory.sol b/packages/store/src/Memory.sol index 8ce6c40a80..4789e9cfbf 100644 --- a/packages/store/src/Memory.sol +++ b/packages/store/src/Memory.sol @@ -3,11 +3,19 @@ pragma solidity >=0.8.21; import { leftMask } from "./leftMask.sol"; +/** + * @title Memory Operations + * @notice A library for performing low-level memory operations. + * @dev This library provides low-level memory operations with safety checks. + */ library Memory { /** - * In dynamic arrays the first word stores the length of data, after which comes the data. + * @notice Gets the actual data pointer of dynamic arrays. + * @dev In dynamic arrays, the first word stores the length of the data, after which comes the actual data. * Example: 0x40 0x01 0x02 * ^len ^data + * @param data The dynamic bytes data from which to get the pointer. + * @return memoryPointer The pointer to the actual data (skipping the length). */ function dataPointer(bytes memory data) internal pure returns (uint256 memoryPointer) { assembly { @@ -15,6 +23,13 @@ library Memory { } } + /** + * @notice Copies memory from one location to another. + * @dev Safely copies memory in chunks of 32 bytes, then handles any residual bytes. + * @param fromPointer The memory location to copy from. + * @param toPointer The memory location to copy to. + * @param length The number of bytes to copy. + */ function copy(uint256 fromPointer, uint256 toPointer, uint256 length) internal pure { // Copy 32-byte chunks while (length >= 32) { diff --git a/packages/store/src/PackedCounter.sol b/packages/store/src/PackedCounter.sol index 20a8a7df57..8f68f7277c 100644 --- a/packages/store/src/PackedCounter.sol +++ b/packages/store/src/PackedCounter.sol @@ -1,12 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; -// - Last 7 bytes (uint56) are used for the total byte length of the dynamic data -// - The next 5 byte (uint40) sections are used for the byte length of each field, indexed from right to left +/** + * @title PackedCounter Type Definition + * @dev Describes how the packed counter is structured. + * - 0x00-0x06 The least significant 7 bytes (uint56) represent the total byte length of dynamic (variable length) data. + * - 0x07-0xB The next five bytes (uint40) represent the length of the first dynamic field. + * - 0x0C-0x10 Followed by the length of the second dynamic field + * - 0x11-0x15 Length of the third dynamic field + * - 0x16-0x1A Length of fourth dynamic field + * - 0x1B-0x1F Length of fifth dynamic field + */ type PackedCounter is bytes32; using PackedCounterInstance for PackedCounter global; +// Constants for packed counter handling: + // Number of bits for the 7-byte accumulator uint256 constant ACC_BITS = 7 * 8; // Number of bits for the 5-byte sections @@ -15,10 +25,19 @@ uint256 constant VAL_BITS = 5 * 8; uint256 constant MAX_VAL = type(uint40).max; /** - * Static functions for PackedCounter - * The caller must ensure that the value arguments are <= MAX_VAL + * @title PackedCounter Library + * @notice Static functions for handling PackedCounter type. + * @dev Provides utility functions to pack values into a PackedCounter. + * The caller must ensure that the value arguments are <= MAX_VAL. */ library PackedCounterLib { + /** + * @notice Packs a single value into a PackedCounter. + * @dev Encodes the given value 'a' into the structure of a PackedCounter. The packed counter's accumulator + * will be set to 'a', and the first value slot of the PackedCounter will also be set to 'a'. + * @param a The length of the first dynamic field's data. + * @return The resulting PackedCounter containing the encoded value. + */ function pack(uint256 a) internal pure returns (PackedCounter) { uint256 packedCounter; unchecked { @@ -28,6 +47,13 @@ library PackedCounterLib { return PackedCounter.wrap(bytes32(packedCounter)); } + /** + * @notice Packs a single value into a PackedCounter. + * @dev Encodes the given values 'a'-'b' into the structure of a PackedCounter. + * @param a The length of the first dynamic field's data. + * @param b The length of the second dynamic field's data. + * @return The resulting PackedCounter containing the encoded values. + */ function pack(uint256 a, uint256 b) internal pure returns (PackedCounter) { uint256 packedCounter; unchecked { @@ -38,6 +64,14 @@ library PackedCounterLib { return PackedCounter.wrap(bytes32(packedCounter)); } + /** + * @notice Packs a single value into a PackedCounter. + * @dev Encodes the given values 'a'-'c' into the structure of a PackedCounter. + * @param a The length of the first dynamic field's data. + * @param b The length of the second dynamic field's data. + * @param c The length of the third dynamic field's data. + * @return The resulting PackedCounter containing the encoded values. + */ function pack(uint256 a, uint256 b, uint256 c) internal pure returns (PackedCounter) { uint256 packedCounter; unchecked { @@ -49,6 +83,15 @@ library PackedCounterLib { return PackedCounter.wrap(bytes32(packedCounter)); } + /** + * @notice Packs a single value into a PackedCounter. + * @dev Encodes the given values 'a'-'d' into the structure of a PackedCounter. + * @param a The length of the first dynamic field's data. + * @param b The length of the second dynamic field's data. + * @param c The length of the third dynamic field's data. + * @param d The length of the fourth dynamic field's data. + * @return The resulting PackedCounter containing the encoded values. + */ function pack(uint256 a, uint256 b, uint256 c, uint256 d) internal pure returns (PackedCounter) { uint256 packedCounter; unchecked { @@ -61,6 +104,16 @@ library PackedCounterLib { return PackedCounter.wrap(bytes32(packedCounter)); } + /** + * @notice Packs a single value into a PackedCounter. + * @dev Encodes the given values 'a'-'e' into the structure of a PackedCounter. + * @param a The length of the first dynamic field's data. + * @param b The length of the second dynamic field's data. + * @param c The length of the third dynamic field's data. + * @param d The length of the fourth dynamic field's data. + * @param e The length of the fourth dynamic field's data. + * @return The resulting PackedCounter containing the encoded values. + */ function pack(uint256 a, uint256 b, uint256 c, uint256 d, uint256 e) internal pure returns (PackedCounter) { uint256 packedCounter; unchecked { @@ -76,22 +129,29 @@ library PackedCounterLib { } /** - * Instance functions for PackedCounter + * @title PackedCounter Instance Library + * @notice Instance functions for handling a PackedCounter. + * @dev Offers decoding, extracting, and setting functionalities for a PackedCounter. */ library PackedCounterInstance { error PackedCounter_InvalidLength(uint256 length); /** - * Decode the accumulated counter - * (right-most 7 bytes of packed counter) + * @notice Decode the accumulated counter from a PackedCounter. + * @dev Extracts the right-most 7 bytes of a PackedCounter. + * @param packedCounter The packed counter to decode. + * @return The accumulated value from the PackedCounter. */ function total(PackedCounter packedCounter) internal pure returns (uint256) { return uint56(uint256(PackedCounter.unwrap(packedCounter))); } /** - * Decode the counter at the given index - * (right-to-left, 5 bytes per counter after the right-most 7 bytes) + * @notice Decode the dynamic field size at a specific index from a PackedCounter. + * @dev Extracts value right-to-left, with 5 bytes per dynamic field after the right-most 7 bytes. + * @param packedCounter The packed counter to decode. + * @param index The index to retrieve. + * @return The value at the given index from the PackedCounter. */ function atIndex(PackedCounter packedCounter, uint8 index) internal pure returns (uint256) { unchecked { @@ -100,7 +160,12 @@ library PackedCounterInstance { } /** - * Set a counter at the given index, return the new packed counter + * @notice Set a counter at a specific index in a PackedCounter. + * @dev Updates a value at a specific index and updates the accumulator field. + * @param packedCounter The packed counter to modify. + * @param index The index to set. + * @param newValueAtIndex The new value to set at the given index. + * @return The modified PackedCounter. */ function setAtIndex( PackedCounter packedCounter, @@ -144,8 +209,10 @@ library PackedCounterInstance { return PackedCounter.wrap(bytes32(rawPackedCounter)); } - /* - * Unwrap the packed counter + /** + * @notice Unwrap a PackedCounter to its raw bytes32 representation. + * @param packedCounter The packed counter to unwrap. + * @return The raw bytes32 value of the PackedCounter. */ function unwrap(PackedCounter packedCounter) internal pure returns (bytes32) { return PackedCounter.unwrap(packedCounter); diff --git a/packages/store/src/ResourceId.sol b/packages/store/src/ResourceId.sol index 9c7a8d3398..345c0cb823 100644 --- a/packages/store/src/ResourceId.sol +++ b/packages/store/src/ResourceId.sol @@ -1,20 +1,47 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.21; +/** + * @title ResourceId type definition and related utilities + * @dev A ResourceId is a bytes32 data structure that consists of a + * type and a name + */ type ResourceId is bytes32; +/// @dev Number of bits reserved for the type in the ResourceId. uint256 constant TYPE_BITS = 2 * 8; +/// @dev Number of bits reserved for the name in the ResourceId. uint256 constant NAME_BITS = 32 * 8 - TYPE_BITS; +/// @dev Bitmask to extract the type from the ResourceId. bytes32 constant TYPE_MASK = bytes32(hex"ffff"); +/** + * @title ResourceIdLib Library + * @dev Provides functions to encode data into the ResourceId + */ library ResourceIdLib { + /** + * @notice Encodes given typeId and name into a ResourceId. + * @param typeId The type identifier to be encoded. Must be 2 bytes. + * @param name The name to be encoded. Must be 30 bytes. + * @return A ResourceId containing the encoded typeId and name. + */ function encode(bytes2 typeId, bytes30 name) internal pure returns (ResourceId) { return ResourceId.wrap(bytes32(typeId) | (bytes32(name) >> TYPE_BITS)); } } +/** + * @title ResourceIdInstance Library + * @dev Provides functions to extract data from a ResourceId. + */ library ResourceIdInstance { + /** + * @notice Extracts the type identifier from a given ResourceId. + * @param resourceId The ResourceId from which the type identifier should be extracted. + * @return The extracted 2-byte type identifier. + */ function getType(ResourceId resourceId) internal pure returns (bytes2) { return bytes2(ResourceId.unwrap(resourceId)); } diff --git a/packages/store/src/Schema.sol b/packages/store/src/Schema.sol index 8e935196a7..cdd4c9e05d 100644 --- a/packages/store/src/Schema.sol +++ b/packages/store/src/Schema.sol @@ -5,23 +5,32 @@ import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol" import { WORD_LAST_INDEX, BYTE_TO_BITS, MAX_TOTAL_FIELDS, MAX_DYNAMIC_FIELDS, LayoutOffsets } from "./constants.sol"; -// - 2 bytes static length of the schema -// - 1 byte for number of static size fields -// - 1 byte for number of dynamic size fields -// - 28 bytes for 28 schema types (MAX_DYNAMIC_FIELDS allows us to pack the lengths into 1 word) +/** + * @title Schema handling in Lattice + * @dev Defines and handles the encoding/decoding of Schemas which describe the layout of data structures. + * 2 bytes length of all the static (in size) fields in the schema + * 1 byte for number of static size fields + * 1 byte for number of dynamic size fields + * 28 bytes for 28 schema types (MAX_DYNAMIC_FIELDS allows us to pack the lengths into 1 word) + */ type Schema is bytes32; using SchemaInstance for Schema global; /** - * Static functions for Schema + * @dev Static utility functions for handling Schemas. */ library SchemaLib { + /// @dev Error raised when the provided schema has an invalid length. error SchemaLib_InvalidLength(uint256 length); + + /// @dev Error raised when a static type is placed after a dynamic type in a schema. error SchemaLib_StaticTypeAfterDynamicType(); /** - * Encode the given schema into a single bytes32 + * @notice Encodes a given schema into a single bytes32. + * @param _schema The list of SchemaTypes that constitute the schema. + * @return The encoded Schema. */ function encode(SchemaType[] memory _schema) internal pure returns (Schema) { if (_schema.length > MAX_TOTAL_FIELDS) revert SchemaLib_InvalidLength(_schema.length); @@ -77,18 +86,23 @@ library SchemaLib { } /** - * Instance functions for Schema + * @dev Instance utility functions for handling a Schema instance. */ library SchemaInstance { /** - * Get the length of the static data for the given schema + * @notice Get the length of static data for the given schema. + * @param schema The schema to inspect. + * @return The static data length. */ function staticDataLength(Schema schema) internal pure returns (uint256) { return uint256(Schema.unwrap(schema)) >> LayoutOffsets.TOTAL_LENGTH; } /** - * Get the type of the data for the given schema at the given index + * @notice Get the SchemaType at a given index in the schema. + * @param schema The schema to inspect. + * @param index The index of the SchemaType to retrieve. + * @return The SchemaType at the given index. */ function atIndex(Schema schema, uint256 index) internal pure returns (SchemaType) { unchecked { @@ -97,21 +111,27 @@ library SchemaInstance { } /** - * Get the number of static fields for the given schema + * @notice Get the number of static (fixed length) fields in the schema. + * @param schema The schema to inspect. + * @return The number of static fields. */ function numStaticFields(Schema schema) internal pure returns (uint256) { return uint8(uint256(schema.unwrap()) >> LayoutOffsets.NUM_STATIC_FIELDS); } /** - * Get the number of dynamic length fields for the given schema + * @notice Get the number of dynamic length fields in the schema. + * @param schema The schema to inspect. + * @return The number of dynamic length fields. */ function numDynamicFields(Schema schema) internal pure returns (uint256) { return uint8(uint256(schema.unwrap()) >> LayoutOffsets.NUM_DYNAMIC_FIELDS); } /** - * Get the total number of fields for the given schema + * @notice Get the total number of fields in the schema. + * @param schema The schema to inspect. + * @return The total number of fields. */ function numFields(Schema schema) internal pure returns (uint256) { unchecked { @@ -122,12 +142,19 @@ library SchemaInstance { } /** - * Check if the given schema is empty + * @notice Checks if the provided schema is empty. + * @param schema The schema to check. + * @return true if the schema is empty, false otherwise. */ function isEmpty(Schema schema) internal pure returns (bool) { return Schema.unwrap(schema) == bytes32(0); } + /** + * @notice Validates the given schema. + * @param schema The schema to validate. + * @param allowEmpty Determines if an empty schema is valid or not. + */ function validate(Schema schema, bool allowEmpty) internal pure { // Schema must not be empty if (!allowEmpty && schema.isEmpty()) revert SchemaLib.SchemaLib_InvalidLength(0); @@ -171,7 +198,9 @@ library SchemaInstance { } /** - * Unwrap the schema + * @notice Unwraps the schema to its underlying bytes32 representation. + * @param schema The schema to unwrap. + * @return The bytes32 representation of the schema. */ function unwrap(Schema schema) internal pure returns (bytes32) { return Schema.unwrap(schema); diff --git a/packages/store/src/Slice.sol b/packages/store/src/Slice.sol index 85acce0ff1..d82d331a4a 100644 --- a/packages/store/src/Slice.sol +++ b/packages/store/src/Slice.sol @@ -23,7 +23,9 @@ library SliceLib { uint256 constant MASK_PTR = uint256(type(uint128).max) << 128; /** - * @dev Converts a bytes array to a slice (without copying data) + * @notice Converts a bytes array to a slice (without copying data) + * @param data The bytes array to be converted + * @return A new Slice representing the bytes array */ function fromBytes(bytes memory data) internal pure returns (Slice) { uint256 _pointer; @@ -36,15 +38,22 @@ library SliceLib { } /** - * @dev Subslice a bytes array using the given start index until the end of the array (without copying data) + * @notice Subslice a bytes array using the given start index until the end of the array (without copying data) + * @param data The bytes array to subslice + * @param start The start index for the subslice + * @return A new Slice representing the subslice */ function getSubslice(bytes memory data, uint256 start) internal pure returns (Slice) { return getSubslice(data, start, data.length); } /** - * @dev Subslice a bytes array using the given indexes (without copying data) - * The start index is inclusive, the end index is exclusive + * @notice Subslice a bytes array using the given indexes (without copying data) + * @dev The start index is inclusive, the end index is exclusive + * @param data The bytes array to subslice + * @param start The start index for the subslice + * @param end The end index for the subslice + * @return A new Slice representing the subslice */ function getSubslice(bytes memory data, uint256 start, uint256 end) internal pure returns (Slice) { // TODO this check helps catch bugs and can eventually be removed @@ -68,22 +77,28 @@ library SliceLib { */ library SliceInstance { /** - * @dev Returns the pointer to the start of a slice + * @notice Returns the pointer to the start of a slice + * @param self The slice whose pointer needs to be fetched + * @return The pointer to the start of the slice */ function pointer(Slice self) internal pure returns (uint256) { return Slice.unwrap(self) >> 128; } /** - * @dev Returns the slice length in bytes + * @notice Returns the slice length in bytes + * @param self The slice whose length needs to be fetched + * @return The length of the slice */ function length(Slice self) internal pure returns (uint256) { return Slice.unwrap(self) & SliceLib.MASK_LEN; } /** - * @dev Copies the slice to a new bytes array - * The slice will NOT point to the new bytes array + * @notice Converts a Slice to bytes + * @dev This function internally manages the conversion of a slice into a bytes format. + * @param self The Slice to be converted to bytes. + * @return data The bytes representation of the provided Slice. */ function toBytes(Slice self) internal pure returns (bytes memory data) { uint256 fromPointer = pointer(self); @@ -99,6 +114,12 @@ library SliceInstance { Memory.copy(fromPointer, toPointer, _length); } + /** + * @notice Converts a Slice to bytes32 + * @dev This function converts a slice into a fixed-length bytes32. Uses inline assembly for the conversion. + * @param self The Slice to be converted to bytes32. + * @return result The bytes32 representation of the provided Slice. + */ function toBytes32(Slice self) internal pure returns (bytes32 result) { uint256 memoryPointer = self.pointer(); /// @solidity memory-safe-assembly diff --git a/packages/store/src/Storage.sol b/packages/store/src/Storage.sol index 264a382599..80950c3ee9 100644 --- a/packages/store/src/Storage.sol +++ b/packages/store/src/Storage.sol @@ -4,19 +4,38 @@ pragma solidity >=0.8.21; import { leftMask } from "./leftMask.sol"; import { Memory } from "./Memory.sol"; +/** + * @title Storage Library + * @dev Provides functions for low-level storage manipulation, including storing and retrieving bytes. + */ library Storage { + /** + * @notice Store a single word of data at a specific storage pointer. + * @param storagePointer The location to store the data. + * @param data The 32-byte word of data to store. + */ function store(uint256 storagePointer, bytes32 data) internal { assembly { sstore(storagePointer, data) } } + /** + * @notice Store bytes of data at a specific storage pointer and offset. + * @param storagePointer The base storage location. + * @param offset Offset within the storage location. + * @param data Bytes to store. + */ function store(uint256 storagePointer, uint256 offset, bytes memory data) internal { store(storagePointer, offset, Memory.dataPointer(data), data.length); } /** - * Stores raw bytes to storage at the given storagePointer and offset (keeping the rest of the word intact) + * @notice Stores raw bytes to storage at a given pointer, offset, and length, keeping the rest of the word intact. + * @param storagePointer The base storage location. + * @param offset Offset within the storage location. + * @param memoryPointer Pointer to the start of the data in memory. + * @param length Length of the data in bytes. */ function store(uint256 storagePointer, uint256 offset, uint256 memoryPointer, uint256 length) internal { if (offset > 0) { @@ -99,6 +118,11 @@ library Storage { } } + /** + * @notice Set multiple storage locations to zero. + * @param storagePointer The starting storage location. + * @param length The number of storage locations to set to zero. + */ function zero(uint256 storagePointer, uint256 length) internal { // Ceil division to round up to the nearest word uint256 limit = storagePointer + (length + 31) / 32; @@ -111,6 +135,11 @@ library Storage { } } + /** + * @notice Load a single word of data from a specific storage pointer. + * @param storagePointer The location to load the data from. + * @return word The loaded 32-byte word of data. + */ function load(uint256 storagePointer) internal view returns (bytes32 word) { assembly { word := sload(storagePointer) @@ -118,7 +147,11 @@ library Storage { } /** - * Load raw bytes from storage at the given storagePointer, offset, and length + * @notice Load raw bytes from storage at a given pointer, offset, and length. + * @param storagePointer The base storage location. + * @param length Length of the data in bytes. + * @param offset Offset within the storage location. + * @return result The loaded bytes of data. */ function load(uint256 storagePointer, uint256 length, uint256 offset) internal view returns (bytes memory result) { uint256 memoryPointer; @@ -141,7 +174,11 @@ library Storage { } /** - * Append raw bytes from storage at the given storagePointer, offset, and length to the given memoryPointer + * @notice Append raw bytes from storage at a given pointer, offset, and length to a specific memory pointer. + * @param storagePointer The base storage location. + * @param length Length of the data in bytes. + * @param offset Offset within the storage location. + * @param memoryPointer Pointer to the location in memory to append the data. */ function load(uint256 storagePointer, uint256 length, uint256 offset, uint256 memoryPointer) internal view { if (offset > 0) { @@ -223,11 +260,13 @@ library Storage { } /** - * Load up to 32 bytes from storage at the given storagePointer and offset. - * The return value is left-aligned, the bytes beyond the length are not zeroed out, - * and the caller is expected to truncate as needed. - * Since fields are tightly packed, they can span more than one slot. + * @notice Load up to 32 bytes from storage at a given pointer and offset. + * @dev Since fields are tightly packed, they can span more than one slot. * Since the they're max 32 bytes, they can span at most 2 slots. + * @param storagePointer The base storage location. + * @param length Length of the data in bytes. + * @param offset Offset within the storage location. + * @return result The loaded bytes, left-aligned bytes. Bytes beyond the length are zeroed. */ function loadField(uint256 storagePointer, uint256 length, uint256 offset) internal view returns (bytes32 result) { if (offset >= 32) {