Skip to content

Commit

Permalink
Implement Batch Operations in IPAccountStorage (#134)
Browse files Browse the repository at this point in the history
* Add support of multicall
* IPAccountStorage support batch set/get data
  • Loading branch information
kingster-will authored Apr 26, 2024
1 parent 330b8b8 commit bfeb063
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 11 deletions.
91 changes: 80 additions & 11 deletions contracts/IPAccountStorage.sol
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

0 comments on commit bfeb063

Please sign in to comment.