Skip to content

Commit

Permalink
refactor(registry): create a registry interface and rename functions …
Browse files Browse the repository at this point in the history
…to better describe return value
  • Loading branch information
ashhanai committed Feb 14, 2024
1 parent 94fbbaf commit e73ce76
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 54 deletions.
44 changes: 9 additions & 35 deletions src/MultiTokenCategoryRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,35 @@ pragma solidity 0.8.16;
import { Ownable2Step } from "@openzeppelin/access/Ownable2Step.sol";
import { ERC165 } from "@openzeppelin/utils/introspection/ERC165.sol";

import { IMultiTokenCategoryRegistry } from "@MT/interfaces/IMultiTokenCategoryRegistry.sol";

/**
* @title MultiToken Category Registry
* @notice Contract to register known MultiToken Categories for assets.
* @dev Categories are stored as incremented by one to distinguish between 0 category value and category not registered.
*/
contract MultiTokenCategoryRegistry is Ownable2Step, ERC165 {
contract MultiTokenCategoryRegistry is Ownable2Step, ERC165, IMultiTokenCategoryRegistry {

/**
* @notice A reserved value for a category not registered.
*/
uint8 public constant CATEGORY_NOT_REGISTERED = type(uint8).max;

/**
* @notice Interface ID for the MultiToken Category Registry.
* @dev Category Registry Interface ID is 0xc37a4a01.
*/
bytes4 public constant CATEGORY_REGISTRY_INTERFACE_ID =
this.registerCategory.selector ^
this.unregisterCategory.selector ^
this.registeredCategory.selector;

/**
* @notice Mapping of assets address to its known category.
* @dev Categories are incremented by one before being stored to distinguish between 0 category value and category not registered.
*/
mapping (address => uint8) private _registeredCategory;

/**
* @notice Emitted when a category is registered for an asset address.
* @param assetAddress Address of an asset to which category is registered.
* @param category A raw value of a MultiToken Category registered for an asset.
*/
event CategoryRegistered(address indexed assetAddress, uint8 indexed category);

/**
* @notice Emitted when a category is unregistered for an asset address.
* @param assetAddress Address of an asset to which category is unregistered.
*/
event CategoryUnregistered(address indexed assetAddress);

/**
* @notice Thrown when a reserved category value is used to register a category.
*/
error ReservedCategoryValue();

/**
* @notice Register a MultiToken Category to an asset address.
* @param assetAddress Address of an asset to which category is registered.
* @param category A raw value of a MultiToken Category to register for an asset.
* @inheritdoc IMultiTokenCategoryRegistry
*/
function registerCategory(address assetAddress, uint8 category) external onlyOwner {
function registerCategoryValue(address assetAddress, uint8 category) external onlyOwner {
if (category == CATEGORY_NOT_REGISTERED)
revert ReservedCategoryValue(); // Note: to unregister a category, use `unregisterCategory` method.

Expand All @@ -65,21 +42,18 @@ contract MultiTokenCategoryRegistry is Ownable2Step, ERC165 {
}

/**
* @notice Clear the stored category for the asset address.
* @param assetAddress Address of an asset to which category is unregistered.
* @inheritdoc IMultiTokenCategoryRegistry
*/
function unregisterCategory(address assetAddress) external onlyOwner {
function unregisterCategoryValue(address assetAddress) external onlyOwner {
delete _registeredCategory[assetAddress];

emit CategoryUnregistered(assetAddress);
}

/**
* @notice Getter for a registered category of a given asset address.
* @param assetAddress Address of an asset to which category is requested.
* @return Category value registered for the asset address.
* @inheritdoc IMultiTokenCategoryRegistry
*/
function registeredCategory(address assetAddress) external view returns (uint8) {
function registeredCategoryValue(address assetAddress) external view returns (uint8) {
uint8 category = _registeredCategory[assetAddress];
return category == 0 ? CATEGORY_NOT_REGISTERED : category - 1;
}
Expand All @@ -91,7 +65,7 @@ contract MultiTokenCategoryRegistry is Ownable2Step, ERC165 {
*/
function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
return
interfaceId == CATEGORY_REGISTRY_INTERFACE_ID ||
interfaceId == type(IMultiTokenCategoryRegistry).interfaceId ||
super.supportsInterface(interfaceId);
}

Expand Down
44 changes: 44 additions & 0 deletions src/interfaces/IMultiTokenCategoryRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
* @title MultiToken Category Registry Interface
* @notice Interface for the MultiToken Category Registry.
* @dev Category Registry Interface ID is 0xc37a4a01.
*/
interface IMultiTokenCategoryRegistry {

/**
* @notice Emitted when a category is registered for an asset address.
* @param assetAddress Address of an asset to which category is registered.
* @param category A raw value of a MultiToken Category registered for an asset.
*/
event CategoryRegistered(address indexed assetAddress, uint8 indexed category);

/**
* @notice Emitted when a category is unregistered for an asset address.
* @param assetAddress Address of an asset to which category is unregistered.
*/
event CategoryUnregistered(address indexed assetAddress);

/**
* @notice Register a MultiToken Category value to an asset address.
* @param assetAddress Address of an asset to which category is registered.
* @param category A raw value of a MultiToken Category to register for an asset.
*/
function registerCategoryValue(address assetAddress, uint8 category) external;

/**
* @notice Clear the stored category for the asset address.
* @param assetAddress Address of an asset to which category is unregistered.
*/
function unregisterCategoryValue(address assetAddress) external;

/**
* @notice Getter for a registered category value of a given asset address.
* @param assetAddress Address of an asset to which category is requested.
* @return Raw category value registered for the asset address.
*/
function registeredCategoryValue(address assetAddress) external view returns (uint8);

}
38 changes: 19 additions & 19 deletions test/unit/MultiTokenCategoryRegistry.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.16;

import { Test } from "forge-std/Test.sol";

import { MultiTokenCategoryRegistry } from "@MT/MultiTokenCategoryRegistry.sol";
import { MultiTokenCategoryRegistry, IMultiTokenCategoryRegistry } from "@MT/MultiTokenCategoryRegistry.sol";


abstract contract MultiTokenCategoryRegistryTest is Test {
Expand All @@ -27,32 +27,32 @@ abstract contract MultiTokenCategoryRegistryTest is Test {


/*----------------------------------------------------------*|
|* # REGISTER CATEGORY *|
|* # REGISTER CATEGORY VALUE *|
|*----------------------------------------------------------*/

contract MultiTokenCategoryRegistry_RegisterCategory_Test is MultiTokenCategoryRegistryTest {
contract MultiTokenCategoryRegistry_RegisterCategoryValue_Test is MultiTokenCategoryRegistryTest {

function test_shouldFail_whenCallerNotOwner() external {
address notOwner = makeAddr("notOwner");

vm.expectRevert("Ownable: caller is not the owner");
vm.prank(notOwner);
registry.registerCategory(assetAddress, 7);
registry.registerCategoryValue(assetAddress, 7);
}

function test_shouldFail_whenCategoryMaxUint8Value() external {
uint8 CATEGORY_NOT_REGISTERED = registry.CATEGORY_NOT_REGISTERED();

vm.expectRevert(abi.encodeWithSelector(MultiTokenCategoryRegistry.ReservedCategoryValue.selector));
vm.prank(owner);
registry.registerCategory(assetAddress, CATEGORY_NOT_REGISTERED);
registry.registerCategoryValue(assetAddress, CATEGORY_NOT_REGISTERED);
}

function testFuzz_shouldStore_incrementedCategoryValue(address _assetAddress, uint8 category) external {
vm.assume(category != type(uint8).max);

vm.prank(owner);
registry.registerCategory(_assetAddress, category);
registry.registerCategoryValue(_assetAddress, category);

bytes32 categorySlot = keccak256(abi.encode(_assetAddress, REGISTER_CATEGORY_SLOT));
bytes32 categoryValue = vm.load(address(registry), categorySlot);
Expand All @@ -66,24 +66,24 @@ contract MultiTokenCategoryRegistry_RegisterCategory_Test is MultiTokenCategoryR
emit CategoryRegistered(_assetAddress, category);

vm.prank(owner);
registry.registerCategory(_assetAddress, category);
registry.registerCategoryValue(_assetAddress, category);
}

}


/*----------------------------------------------------------*|
|* # UNREGISTER CATEGORY *|
|* # UNREGISTER CATEGORY VALUE *|
|*----------------------------------------------------------*/

contract MultiTokenCategoryRegistry_UnregisterCategory_Test is MultiTokenCategoryRegistryTest {
contract MultiTokenCategoryRegistry_UnregisterCategoryValue_Test is MultiTokenCategoryRegistryTest {

function test_shouldFail_whenCallerNotOwner() external {
address notOwner = makeAddr("notOwner");

vm.expectRevert("Ownable: caller is not the owner");
vm.prank(notOwner);
registry.unregisterCategory(assetAddress);
registry.unregisterCategoryValue(assetAddress);
}

function testFuzz_shouldClearStore(address _assetAddress, uint256 storedCategory) external {
Expand All @@ -93,7 +93,7 @@ contract MultiTokenCategoryRegistry_UnregisterCategory_Test is MultiTokenCategor
vm.store(address(registry), categorySlot, bytes32(storedCategory));

vm.prank(owner);
registry.unregisterCategory(_assetAddress);
registry.unregisterCategoryValue(_assetAddress);

bytes32 categoryValue = vm.load(address(registry), categorySlot);
assertEq(categoryValue, bytes32(0));
Expand All @@ -104,31 +104,31 @@ contract MultiTokenCategoryRegistry_UnregisterCategory_Test is MultiTokenCategor
emit CategoryUnregistered(_assetAddress);

vm.prank(owner);
registry.unregisterCategory(_assetAddress);
registry.unregisterCategoryValue(_assetAddress);
}

}


/*----------------------------------------------------------*|
|* # REGISTERED CATEGORY *|
|* # REGISTERED CATEGORY VALUE *|
|*----------------------------------------------------------*/

contract MultiTokenCategoryRegistry_RegisteredCategory_Test is MultiTokenCategoryRegistryTest {
contract MultiTokenCategoryRegistry_RegisteredCategoryValue_Test is MultiTokenCategoryRegistryTest {

function testFuzz_shouldReturnCategory_whenRegistered(uint256 storedCategory) external {
function testFuzz_shouldReturnCategoryValue_whenRegistered(uint256 storedCategory) external {
vm.assume(storedCategory > 0 && storedCategory <= type(uint8).max);

bytes32 categorySlot = keccak256(abi.encode(assetAddress, REGISTER_CATEGORY_SLOT));
vm.store(address(registry), categorySlot, bytes32(storedCategory));

uint8 category = registry.registeredCategory(assetAddress);
uint8 category = registry.registeredCategoryValue(assetAddress);

assertEq(category, storedCategory - 1);
}

function testFuzz_shouldReturnCategoryNotRegistered_whenNotRegistered(address _assetAddress) external {
uint8 category = registry.registeredCategory(_assetAddress);
function testFuzz_shouldReturnCategoryNotRegisteredValue_whenNotRegistered(address _assetAddress) external {
uint8 category = registry.registeredCategoryValue(_assetAddress);

assertEq(category, registry.CATEGORY_NOT_REGISTERED());
}
Expand All @@ -143,7 +143,7 @@ contract MultiTokenCategoryRegistry_RegisteredCategory_Test is MultiTokenCategor
contract MultiTokenCategoryRegistry_SupportsInterface_Test is MultiTokenCategoryRegistryTest {

function test_shouldReturnTrue_whenCategoryRegistryInterfaceId() external {
bytes4 interfaceId = registry.CATEGORY_REGISTRY_INTERFACE_ID();
bytes4 interfaceId = type(IMultiTokenCategoryRegistry).interfaceId;

assertTrue(registry.supportsInterface(interfaceId));
}
Expand Down

0 comments on commit e73ce76

Please sign in to comment.