Skip to content

Commit

Permalink
docs(store): add NatSpec to Memory and Storage libraries (#1656)
Browse files Browse the repository at this point in the history
Co-authored-by: alvarius <[email protected]>
  • Loading branch information
qbzzt and alvrs authored Sep 30, 2023
1 parent 25b2b79 commit a38a9f2
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 42 deletions.
17 changes: 16 additions & 1 deletion packages/store/src/Memory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ 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 {
memoryPointer := add(data, 0x20)
}
}

/**
* @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) {
Expand Down
91 changes: 79 additions & 12 deletions packages/store/src/PackedCounter.sol
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions packages/store/src/ResourceId.sol
Original file line number Diff line number Diff line change
@@ -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));
}
Expand Down
57 changes: 43 additions & 14 deletions packages/store/src/Schema.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit a38a9f2

Please sign in to comment.