Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Batch Operations in IPAccountStorage #134

Merged
merged 7 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 80 additions & 11 deletions contracts/IPAccountStorage.sol
kingster-will marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IIPAccountStorage } from "./interfaces/IIPAccountStorage.sol";
import { IModuleRegistry } from "./interfaces/registries/IModuleRegistry.sol";
import { Errors } from "./lib/Errors.sol";
import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol";
import { ShortStrings } from "@openzeppelin/contracts/utils/ShortStrings.sol";
/// @title IPAccount Storage
/// @dev Implements the IIPAccountStorage interface for managing IPAccount's state using a namespaced storage pattern.
/// Inherits all functionalities from IIPAccountStorage, providing concrete implementations for the interface's methods.
Expand Down Expand Up @@ -39,28 +39,101 @@ contract IPAccountStorage is ERC165, IIPAccountStorage {
IP_ASSET_REGISTRY = ipAssetRegistry;
}

/// @inheritdoc IIPAccountStorage
/// @dev Sets a bytes value under a given key within the default namespace, determined by `msg.sender`.
/// @param key The key under which to store the value.
/// @param value The bytes value to be stored.
function setBytes(bytes32 key, bytes calldata value) external onlyRegisteredModule {
bytesData[_toBytes32(msg.sender)][key] = value;
}
/// @inheritdoc IIPAccountStorage

/// @notice Sets multiple `bytes` values for an array of keys within the namespace of the caller (`msg.sender`).
/// @param keys An array of `bytes32` keys under which the `bytes` values will be stored.
/// @param values An array of `bytes` values corresponding to the keys to be stored.
/// @dev The function requires that the arrays `keys` and `values` have the same length.
function setBytesBatch(bytes32[] calldata keys, bytes[] calldata values) external onlyRegisteredModule {
if (keys.length != values.length) revert Errors.IPAccountStorage__InvalidBatchLengths();
for (uint256 i = 0; i < keys.length; i++) {
bytesData[_toBytes32(msg.sender)][keys[i]] = values[i];
}
}

/// @notice Retrieves an array of `bytes` values corresponding to an array of keys from specified namespaces.
/// @param namespaces An array of `bytes32` representing the namespaces from which values are to be retrieved.
/// @param keys An array of `bytes32` representing the keys corresponding to the values to be retrieved.
/// @return values An array of `bytes` containing the values associated with the specified keys
/// across the given namespaces.
/// @dev Requires that the length of `namespaces` and `keys` arrays be the same to ensure correct data retrieval.
function getBytesBatch(
bytes32[] calldata namespaces,
bytes32[] calldata keys
) external view returns (bytes[] memory values) {
if (namespaces.length != keys.length) revert Errors.IPAccountStorage__InvalidBatchLengths();
values = new bytes[](keys.length);
for (uint256 i = 0; i < keys.length; i++) {
values[i] = bytesData[namespaces[i]][keys[i]];
}
}

/// @dev Retrieves a bytes value by a given key from the default namespace.
/// @param key The key whose value is to be retrieved.
/// @return The bytes value stored under the specified key.
function getBytes(bytes32 key) external view returns (bytes memory) {
return bytesData[_toBytes32(msg.sender)][key];
}
/// @inheritdoc IIPAccountStorage

/// @dev Retrieves a bytes value by a given key from a specified namespace.
/// @param namespace The namespace from which to retrieve the value.
/// @param key The key whose value is to be retrieved.
/// @return The bytes value stored under the specified key in the given namespace.
function getBytes(bytes32 namespace, bytes32 key) external view returns (bytes memory) {
return bytesData[namespace][key];
}

/// @inheritdoc IIPAccountStorage
/// @dev Sets a bytes32 value under a given key within the default namespace, determined by `msg.sender`.
/// @param key The key under which to store the value.
/// @param value The bytes32 value to be stored.
function setBytes32(bytes32 key, bytes32 value) external onlyRegisteredModule {
bytes32Data[_toBytes32(msg.sender)][key] = value;
}
/// @inheritdoc IIPAccountStorage

/// @notice Sets an array of `bytes32` values for corresponding keys within the caller's (`msg.sender`) namespace.
/// @param keys An array of `bytes32` keys under which the values will be stored.
/// @param values An array of `bytes32` values to be stored under the specified keys.
/// @dev The function requires that the `keys` and `values` arrays have the same length for correct mapping.
function setBytes32Batch(bytes32[] calldata keys, bytes32[] calldata values) external onlyRegisteredModule {
if (keys.length != values.length) revert Errors.IPAccountStorage__InvalidBatchLengths();
for (uint256 i = 0; i < keys.length; i++) {
bytes32Data[_toBytes32(msg.sender)][keys[i]] = values[i];
}
}

/// @notice Retrieves an array of `bytes32` values corresponding to specified keys across multiple namespaces.
/// @param namespaces An array of `bytes32` representing the namespaces from which to retrieve the values.
/// @param keys An array of `bytes32` keys for which values are to be retrieved.
/// @return values An array of `bytes32` values retrieved from the specified keys within the given namespaces.
/// @dev The `namespaces` and `keys` arrays must be the same length.
function getBytes32Batch(
bytes32[] calldata namespaces,
bytes32[] calldata keys
) external view returns (bytes32[] memory values) {
if (namespaces.length != keys.length) revert Errors.IPAccountStorage__InvalidBatchLengths();
values = new bytes32[](keys.length);
for (uint256 i = 0; i < keys.length; i++) {
values[i] = bytes32Data[namespaces[i]][keys[i]];
}
}

/// @dev Retrieves a bytes32 value by a given key from the default namespace.
/// @param key The key whose value is to be retrieved.
/// @return The bytes32 value stored under the specified key.
function getBytes32(bytes32 key) external view returns (bytes32) {
return bytes32Data[_toBytes32(msg.sender)][key];
}
/// @inheritdoc IIPAccountStorage

/// @dev Retrieves a bytes32 value by a given key from a specified namespace.
/// @param namespace The namespace from which to retrieve the value.
/// @param key The key whose value is to be retrieved.
/// @return The bytes32 value stored under the specified key in the given namespace.
function getBytes32(bytes32 namespace, bytes32 key) external view returns (bytes32) {
return bytes32Data[namespace][key];
}
Expand All @@ -70,10 +143,6 @@ contract IPAccountStorage is ERC165, IIPAccountStorage {
return interfaceId == type(IIPAccountStorage).interfaceId || super.supportsInterface(interfaceId);
}

function _toBytes32(string memory s) internal pure returns (bytes32) {
return ShortString.unwrap(s.toShortString());
}

function _toBytes32(address a) internal pure returns (bytes32) {
return bytes32(uint256(uint160(a)));
}
Expand Down
33 changes: 33 additions & 0 deletions contracts/interfaces/IIPAccountStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ interface IIPAccountStorage is IERC165 {
/// @param value The bytes value to be stored.
function setBytes(bytes32 key, bytes calldata value) external;

/// @notice Sets multiple `bytes` values for an array of keys within the namespace of the caller (`msg.sender`).
/// @param keys An array of `bytes32` keys under which the `bytes` values will be stored.
/// @param values An array of `bytes` values corresponding to the keys to be stored.
/// @dev The function requires that the arrays `keys` and `values` have the same length.
function setBytesBatch(bytes32[] calldata keys, bytes[] calldata values) external;

/// @notice Retrieves an array of `bytes` values corresponding to an array of keys from specified namespaces.
/// @param namespaces An array of `bytes32` representing the namespaces from which values are to be retrieved.
/// @param keys An array of `bytes32` representing the keys corresponding to the values to be retrieved.
/// @return values An array of `bytes` containing the values associated with the specified keys
/// across the given namespaces.
/// @dev Requires that the length of `namespaces` and `keys` arrays be the same to ensure correct data retrieval.
function getBytesBatch(
bytes32[] calldata namespaces,
bytes32[] calldata keys
) external view returns (bytes[] memory values);

/// @dev Retrieves a bytes value by a given key from the default namespace.
/// @param key The key whose value is to be retrieved.
/// @return The bytes value stored under the specified key.
Expand All @@ -39,6 +56,22 @@ interface IIPAccountStorage is IERC165 {
/// @param value The bytes32 value to be stored.
function setBytes32(bytes32 key, bytes32 value) external;

/// @notice Sets an array of `bytes32` values for corresponding keys within the caller's (`msg.sender`) namespace.
/// @param keys An array of `bytes32` keys under which the values will be stored.
/// @param values An array of `bytes32` values to be stored under the specified keys.
/// @dev The function requires that the `keys` and `values` arrays have the same length for correct mapping.
function setBytes32Batch(bytes32[] calldata keys, bytes32[] calldata values) external;

/// @notice Retrieves an array of `bytes32` values corresponding to specified keys across multiple namespaces.
/// @param namespaces An array of `bytes32` representing the namespaces from which to retrieve the values.
/// @param keys An array of `bytes32` keys for which values are to be retrieved.
/// @return values An array of `bytes32` values retrieved from the specified keys within the given namespaces.
/// @dev The `namespaces` and `keys` arrays must be the same length.
function getBytes32Batch(
bytes32[] calldata namespaces,
bytes32[] calldata keys
) external view returns (bytes32[] memory values);

/// @dev Retrieves a bytes32 value by a given key from the default namespace.
/// @param key The key whose value is to be retrieved.
/// @return The bytes32 value stored under the specified key.
Expand Down
3 changes: 3 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ library Errors {
/// @notice Caller writing to IP Account storage is not a registered module.
error IPAccountStorage__NotRegisteredModule(address module);

/// @notice Invalid batch lengths provided.
error IPAccountStorage__InvalidBatchLengths();

////////////////////////////////////////////////////////////////////////////
// IP Account Registry //
////////////////////////////////////////////////////////////////////////////
Expand Down
68 changes: 68 additions & 0 deletions test/foundry/IPAccountStorage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,74 @@ contract IPAccountStorageTest is BaseTest, BaseModule {
assertEq(ipAccount.getBytes32(_toBytes32(address(licenseRegistry)), "test"), "testData");
}

function test_IPAccountStorage_BatchSetAndGetBytes() public {
bytes32[] memory keys = new bytes32[](2);
keys[0] = "test1";
keys[1] = "test2";
bytes[] memory values = new bytes[](2);
values[0] = abi.encodePacked("test1Data");
values[1] = abi.encodePacked("test2Data");
ipAccount.setBytesBatch(keys, values);
assertEq(ipAccount.getBytes("test1"), "test1Data");
assertEq(ipAccount.getBytes("test2"), "test2Data");

bytes32[] memory namespaces = new bytes32[](2);
namespaces[0] = _toBytes32(address(this));
namespaces[1] = _toBytes32(address(this));
bytes[] memory results = ipAccount.getBytesBatch(namespaces, keys);
assertEq(results[0], "test1Data");
assertEq(results[1], "test2Data");
}

function test_IPAccountStorage_revert_BatchSetAndGetBytes() public {
bytes32[] memory keys = new bytes32[](2);
keys[0] = "test1";
keys[1] = "test2";
bytes[] memory values = new bytes[](1);
values[0] = abi.encodePacked("test1Data");
vm.expectRevert(Errors.IPAccountStorage__InvalidBatchLengths.selector);
ipAccount.setBytesBatch(keys, values);

bytes32[] memory namespaces = new bytes32[](1);
namespaces[0] = _toBytes32(address(this));
vm.expectRevert(Errors.IPAccountStorage__InvalidBatchLengths.selector);
ipAccount.getBytesBatch(namespaces, keys);
}

function test_IPAccountStorage_BatchSetAndGetBytes32() public {
bytes32[] memory keys = new bytes32[](2);
keys[0] = "test1";
keys[1] = "test2";
bytes32[] memory values = new bytes32[](2);
values[0] = bytes32(uint256(111));
values[1] = bytes32(uint256(222));
ipAccount.setBytes32Batch(keys, values);
assertEq(ipAccount.getBytes32("test1"), bytes32(uint256(111)));
assertEq(ipAccount.getBytes32("test2"), bytes32(uint256(222)));

bytes32[] memory namespaces = new bytes32[](2);
namespaces[0] = _toBytes32(address(this));
namespaces[1] = _toBytes32(address(this));
bytes32[] memory results = ipAccount.getBytes32Batch(namespaces, keys);
assertEq(results[0], bytes32(uint256(111)));
assertEq(results[1], bytes32(uint256(222)));
}

function test_IPAccountStorage_revert_BatchSetAndGetBytes32() public {
bytes32[] memory keys = new bytes32[](2);
keys[0] = "test1";
keys[1] = "test2";
bytes32[] memory values = new bytes32[](1);
values[0] = bytes32(uint256(111));
vm.expectRevert(Errors.IPAccountStorage__InvalidBatchLengths.selector);
ipAccount.setBytes32Batch(keys, values);

bytes32[] memory namespaces = new bytes32[](1);
namespaces[0] = _toBytes32(address(this));
vm.expectRevert(Errors.IPAccountStorage__InvalidBatchLengths.selector);
ipAccount.getBytes32Batch(namespaces, keys);
}

function _toBytes32(address a) internal pure returns (bytes32) {
return bytes32(uint256(uint160(a)));
}
Expand Down
Loading