From 5a5f687ea3dd3da77af10d3d8e0ae5ab4be80792 Mon Sep 17 00:00:00 2001 From: tate Date: Thu, 11 Sep 2025 12:26:14 +1000 Subject: [PATCH 1/7] feat(wip): basic hca --- contracts/src/common/BaseRegistry.sol | 4 +- contracts/src/common/ERC1155Singleton.sol | 4 +- contracts/src/common/EjectionController.sol | 2 +- .../src/common/EnhancedAccessControl.sol | 207 ++++++++++++++---- contracts/src/common/OwnedResolver.sol | 1 + contracts/src/common/PermissionedRegistry.sol | 9 +- contracts/src/hca/HCAContext.sol | 36 +++ contracts/src/hca/IHCAFactoryBasic.sol | 10 + contracts/test/hca/HCAContext.t.sol | 90 ++++++++ contracts/test/mocks/MockHCAFactoryBasic.sol | 16 ++ 10 files changed, 323 insertions(+), 56 deletions(-) create mode 100644 contracts/src/hca/HCAContext.sol create mode 100644 contracts/src/hca/IHCAFactoryBasic.sol create mode 100644 contracts/test/hca/HCAContext.t.sol create mode 100644 contracts/test/mocks/MockHCAFactoryBasic.sol diff --git a/contracts/src/common/BaseRegistry.sol b/contracts/src/common/BaseRegistry.sol index 6f69ad0a..453521c8 100644 --- a/contracts/src/common/BaseRegistry.sol +++ b/contracts/src/common/BaseRegistry.sol @@ -25,8 +25,8 @@ abstract contract BaseRegistry is IRegistry, ERC1155Singleton { modifier onlyTokenOwner(uint256 tokenId) { address owner = ownerOf(tokenId); - if (owner != msg.sender) { - revert AccessDenied(tokenId, owner, msg.sender); + if (owner != _msgSender()) { + revert AccessDenied(tokenId, owner, _msgSender()); } _; } diff --git a/contracts/src/common/ERC1155Singleton.sol b/contracts/src/common/ERC1155Singleton.sol index 821f1b5e..327b4e89 100644 --- a/contracts/src/common/ERC1155Singleton.sol +++ b/contracts/src/common/ERC1155Singleton.sol @@ -12,10 +12,10 @@ import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensi import {IERC1155Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {ERC1155Utils} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Utils.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +import {HCAContext} from "../hca/HCAContext.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; -abstract contract ERC1155Singleton is Context, ERC165, IERC1155Singleton, IERC1155Errors, IERC1155MetadataURI { +abstract contract ERC1155Singleton is HCAContext, ERC165, IERC1155Singleton, IERC1155Errors, IERC1155MetadataURI { using Arrays for uint256[]; using Arrays for address[]; diff --git a/contracts/src/common/EjectionController.sol b/contracts/src/common/EjectionController.sol index 68c6cb04..772474b6 100644 --- a/contracts/src/common/EjectionController.sol +++ b/contracts/src/common/EjectionController.sol @@ -36,7 +36,7 @@ abstract contract EjectionController is IERC1155Receiver, ERC165, EnhancedAccess bridge = _bridge; // Grant admin roles to the deployer so they can manage bridge roles - _grantRoles(ROOT_RESOURCE, LibBridgeRoles.ROLE_EJECTOR_ADMIN, msg.sender, true); + _grantRoles(ROOT_RESOURCE, LibBridgeRoles.ROLE_EJECTOR_ADMIN, _msgSender(), true); } /** diff --git a/contracts/src/common/EnhancedAccessControl.sol b/contracts/src/common/EnhancedAccessControl.sol index aae4d278..cb70f2b7 100644 --- a/contracts/src/common/EnhancedAccessControl.sol +++ b/contracts/src/common/EnhancedAccessControl.sol @@ -3,36 +3,43 @@ pragma solidity ^0.8.20; -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +import {HCAContext} from "../hca/HCAContext.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IEnhancedAccessControl} from "./IEnhancedAccessControl.sol"; library LibEACBaseRoles { - uint256 constant public ALL_ROLES = 0x1111111111111111111111111111111111111111111111111111111111111111; - uint256 constant public ADMIN_ROLES = 0x1111111111111111111111111111111100000000000000000000000000000000; + uint256 public constant ALL_ROLES = + 0x1111111111111111111111111111111111111111111111111111111111111111; + uint256 public constant ADMIN_ROLES = + 0x1111111111111111111111111111111100000000000000000000000000000000; } /** * @dev Access control system that allows for: - * + * * - Resource-based roles. * - Obtaining assignee count for each role in each resource. * - Root resource override (0x0) - role assignments in the `ROOT_RESOURCE` auto-apply to all resources. * - Up to 32 roles and 32 corresponding admin roles - stored as a bitmap in uint256 (see below). * - Up to 15 assignees per role - stored as a bitmap in uint256 (64 * 4 bits = 256 bits) (see below). - * + * * Role representation: * - A role bitmap is a uint256, where the lower 128 bits represent the regular roles (0-31), and the upper 128 bits represent the admin roles (32-63) for those roles. * - Each role is represented by a nybble (4 bits), in little-endian order. * - If a given role left-most nybble bit is located at index N then the corresponding admin role nybble starts at bit position N << 128. */ -abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessControl { +abstract contract EnhancedAccessControl is + HCAContext, + ERC165, + IEnhancedAccessControl +{ /** * @dev user roles within a resource stored as a bitmap. * Resource -> User -> RoleBitmap */ - mapping(uint256 resource => mapping(address account => uint256 roleBitmap)) private _roles; + mapping(uint256 resource => mapping(address account => uint256 roleBitmap)) + private _roles; /** * @dev The number of assignees for a given role in a given resource. @@ -50,7 +57,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Returns the roles bitmap for an account in a resource. */ - function roles(uint256 resource, address account) public view virtual returns (uint256) { + function roles( + uint256 resource, + address account + ) public view virtual returns (uint256) { return _roles[resource][account]; } @@ -60,10 +70,9 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr function roleCount(uint256 resource) public view virtual returns (uint256) { return _roleCount[resource]; } - /** - * @dev Modifier that checks that sender has the admin roles for all the given roles. + * @dev Modifier that checks that sender has the admin roles for all the given roles. */ modifier canGrantRoles(uint256 resource, uint256 roleBitmap) { _checkCanGrantRoles(resource, roleBitmap, _msgSender()); @@ -71,7 +80,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr } /** - * @dev Modifier that checks that sender has all the given roles within the given resource. + * @dev Modifier that checks that sender has all the given roles within the given resource. */ modifier onlyRoles(uint256 resource, uint256 roleBitmap) { _checkRoles(resource, roleBitmap, _msgSender()); @@ -79,7 +88,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr } /** - * @dev Modifier that checks that sender has all the given roles within the `ROOT_RESOURCE`. + * @dev Modifier that checks that sender has all the given roles within the `ROOT_RESOURCE`. */ modifier onlyRootRoles(uint256 roleBitmap) { _checkRoles(ROOT_RESOURCE, roleBitmap, _msgSender()); @@ -89,8 +98,12 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev See {IERC165-supportsInterface}. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IEnhancedAccessControl).interfaceId || super.supportsInterface(interfaceId); + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IEnhancedAccessControl).interfaceId || + super.supportsInterface(interfaceId); } /** @@ -100,7 +113,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to check. * @return `true` if `account` has been granted all the given roles in the `ROOT_RESOURCE`, `false` otherwise. */ - function hasRootRoles(uint256 rolesBitmap, address account) public view virtual returns (bool) { + function hasRootRoles( + uint256 rolesBitmap, + address account + ) public view virtual returns (bool) { return _roles[ROOT_RESOURCE][account] & rolesBitmap == rolesBitmap; } @@ -112,11 +128,17 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to check. * @return `true` if `account` has been granted all the given roles in either `resource` or the `ROOT_RESOURCE`, `false` otherwise. */ - function hasRoles(uint256 resource, uint256 rolesBitmap, address account) public view virtual returns (bool) { - return (_roles[ROOT_RESOURCE][account] | _roles[resource][account]) & rolesBitmap == rolesBitmap; + function hasRoles( + uint256 resource, + uint256 rolesBitmap, + address account + ) public view virtual returns (bool) { + return + (_roles[ROOT_RESOURCE][account] | _roles[resource][account]) & + rolesBitmap == + rolesBitmap; } - /** * @dev Get if any of the roles in the given role bitmap has assignees. * @@ -124,7 +146,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The roles bitmap to check. * @return `true` if any of the roles in the given role bitmap has assignees, `false` otherwise. */ - function hasAssignees(uint256 resource, uint256 roleBitmap) public view virtual returns (bool) { + function hasAssignees( + uint256 resource, + uint256 roleBitmap + ) public view virtual returns (bool) { (uint256 counts, ) = getAssigneeCount(resource, roleBitmap); return counts != 0; } @@ -137,7 +162,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @return counts The no. of assignees for each of the roles in the given role bitmap, expressed as a packed array of 4-bit ints. * @return mask The mask for the given role bitmap. */ - function getAssigneeCount(uint256 resource, uint256 roleBitmap) public view virtual returns (uint256 counts, uint256 mask) { + function getAssigneeCount( + uint256 resource, + uint256 roleBitmap + ) public view virtual returns (uint256 counts, uint256 mask) { mask = _roleBitmapToMask(roleBitmap); counts = _roleCount[resource] & mask; } @@ -154,7 +182,11 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to grant roles to. * @return `true` if the roles were granted, `false` otherwise. */ - function grantRoles(uint256 resource, uint256 roleBitmap, address account) public virtual canGrantRoles(resource, roleBitmap) returns (bool) { + function grantRoles( + uint256 resource, + uint256 roleBitmap, + address account + ) public virtual canGrantRoles(resource, roleBitmap) returns (bool) { if (resource == ROOT_RESOURCE) { revert EACRootResourceNotAllowed(); } @@ -171,7 +203,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to grant roles to. * @return `true` if the roles were granted, `false` otherwise. */ - function grantRootRoles(uint256 roleBitmap, address account) public virtual canGrantRoles(ROOT_RESOURCE, roleBitmap) returns (bool) { + function grantRootRoles( + uint256 roleBitmap, + address account + ) public virtual canGrantRoles(ROOT_RESOURCE, roleBitmap) returns (bool) { return _grantRoles(ROOT_RESOURCE, roleBitmap, account, true); } @@ -187,7 +222,11 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to revoke roles from. * @return `true` if the roles were revoked, `false` otherwise. */ - function revokeRoles(uint256 resource, uint256 roleBitmap, address account) public virtual canGrantRoles(resource, roleBitmap) returns (bool) { + function revokeRoles( + uint256 resource, + uint256 roleBitmap, + address account + ) public virtual canGrantRoles(resource, roleBitmap) returns (bool) { if (resource == ROOT_RESOURCE) { revert EACRootResourceNotAllowed(); } @@ -204,7 +243,10 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to revoke roles from. * @return `true` if the roles were revoked, `false` otherwise. */ - function revokeRootRoles(uint256 roleBitmap, address account) public virtual canGrantRoles(ROOT_RESOURCE, roleBitmap) returns (bool) { + function revokeRootRoles( + uint256 roleBitmap, + address account + ) public virtual canGrantRoles(ROOT_RESOURCE, roleBitmap) returns (bool) { return _revokeRoles(ROOT_RESOURCE, roleBitmap, account, true); } @@ -213,7 +255,11 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Reverts if `account` does not have all the given roles. */ - function _checkRoles(uint256 resource, uint256 roleBitmap, address account) internal view virtual { + function _checkRoles( + uint256 resource, + uint256 roleBitmap, + address account + ) internal view virtual { if (!hasRoles(resource, roleBitmap, account)) { revert EACUnauthorizedAccountRoles(resource, roleBitmap, account); } @@ -222,7 +268,11 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Reverts if `account` does not have the admin roles for all the given roles. */ - function _checkCanGrantRoles(uint256 resource, uint256 roleBitmap, address account) internal view virtual { + function _checkCanGrantRoles( + uint256 resource, + uint256 roleBitmap, + address account + ) internal view virtual { uint256 settableRoles = _getSettableRoles(resource, account); if ((roleBitmap & ~settableRoles) != 0) { revert EACCannotGrantRoles(resource, roleBitmap, account); @@ -231,7 +281,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Transfers all roles from `srcAccount` to `dstAccount` within the same resource. - * + * * This function first revokes all roles from the source account, then grants them to the * destination account. This prevents exceeding max assignees limits during transfer. * @@ -240,7 +290,12 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param dstAccount The account to transfer roles to. * @param executeCallbacks Whether to execute the callbacks. */ - function _transferRoles(uint256 resource, address srcAccount, address dstAccount, bool executeCallbacks) internal virtual { + function _transferRoles( + uint256 resource, + address srcAccount, + address dstAccount, + bool executeCallbacks + ) internal virtual { uint256 srcRoles = _roles[resource][srcAccount]; if (srcRoles != 0) { // First revoke roles from source account to free up assignee slots @@ -259,7 +314,12 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param executeCallbacks Whether to execute the callbacks. * @return `true` if the roles were granted, `false` otherwise. */ - function _grantRoles(uint256 resource, uint256 roleBitmap, address account, bool executeCallbacks) internal virtual returns (bool) { + function _grantRoles( + uint256 resource, + uint256 roleBitmap, + address account, + bool executeCallbacks + ) internal virtual returns (bool) { _checkRoleBitmap(roleBitmap); uint256 currentRoles = _roles[resource][account]; uint256 updatedRoles = currentRoles | roleBitmap; @@ -269,7 +329,13 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr uint256 newlyAddedRoles = roleBitmap & ~currentRoles; _updateRoleCounts(resource, newlyAddedRoles, true); if (executeCallbacks) { - _onRolesGranted(resource, account, currentRoles, updatedRoles, roleBitmap); + _onRolesGranted( + resource, + account, + currentRoles, + updatedRoles, + roleBitmap + ); } emit EACRolesGranted(resource, roleBitmap, account); return true; @@ -287,17 +353,28 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param executeCallbacks Whether to execute the callbacks. * @return `true` if the roles were revoked, `false` otherwise. */ - function _revokeRoles(uint256 resource, uint256 roleBitmap, address account, bool executeCallbacks) internal virtual returns (bool) { + function _revokeRoles( + uint256 resource, + uint256 roleBitmap, + address account, + bool executeCallbacks + ) internal virtual returns (bool) { _checkRoleBitmap(roleBitmap); uint256 currentRoles = _roles[resource][account]; uint256 updatedRoles = currentRoles & ~roleBitmap; - + if (currentRoles != updatedRoles) { _roles[resource][account] = updatedRoles; uint256 newlyRemovedRoles = roleBitmap & currentRoles; - _updateRoleCounts(resource, newlyRemovedRoles, false); + _updateRoleCounts(resource, newlyRemovedRoles, false); if (executeCallbacks) { - _onRolesRevoked(resource, account, currentRoles, updatedRoles, roleBitmap); + _onRolesRevoked( + resource, + account, + currentRoles, + updatedRoles, + roleBitmap + ); } emit EACRolesRevoked(resource, roleBitmap, account); return true; @@ -309,8 +386,18 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Revoke all roles for account within resource. */ - function _revokeAllRoles(uint256 resource, address account, bool executeCallbacks) internal virtual returns (bool) { - return _revokeRoles(resource, LibEACBaseRoles.ALL_ROLES, account, executeCallbacks); + function _revokeAllRoles( + uint256 resource, + address account, + bool executeCallbacks + ) internal virtual returns (bool) { + return + _revokeRoles( + resource, + LibEACBaseRoles.ALL_ROLES, + account, + executeCallbacks + ); } /** @@ -319,7 +406,11 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The roles being modified * @param isGrant true for grant, false for revoke */ - function _updateRoleCounts(uint256 resource, uint256 roleBitmap, bool isGrant) internal { + function _updateRoleCounts( + uint256 resource, + uint256 roleBitmap, + bool isGrant + ) internal { uint256 roleMask = _roleBitmapToMask(roleBitmap); if (isGrant) { @@ -339,15 +430,19 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr /** * @dev Returns the settable roles for `account` within `resource`. - * + * * The settable roles are the roles that the account can grant/revoke. - * + * * @param resource The resource to get settable roles for. * @param account The account to get settable roles for. * @return The settable roles for `account` within `resource`. */ - function _getSettableRoles(uint256 resource, address account) internal view virtual returns (uint256) { - uint256 adminRoleBitmap = (_roles[resource][account] | _roles[ROOT_RESOURCE][account]) & LibEACBaseRoles.ADMIN_ROLES; + function _getSettableRoles( + uint256 resource, + address account + ) internal view virtual returns (uint256) { + uint256 adminRoleBitmap = (_roles[resource][account] | + _roles[ROOT_RESOURCE][account]) & LibEACBaseRoles.ADMIN_ROLES; return adminRoleBitmap >> 128; } @@ -360,7 +455,13 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param newRoles The new roles for the account. * @param roleBitmap The roles that were granted. */ - function _onRolesGranted(uint256 resource, address account, uint256 oldRoles, uint256 newRoles, uint256 roleBitmap) internal virtual {} + function _onRolesGranted( + uint256 resource, + address account, + uint256 oldRoles, + uint256 newRoles, + uint256 roleBitmap + ) internal virtual {} /** * @dev Callback for when roles are revoked. @@ -371,7 +472,13 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param newRoles The new roles for the account. * @param roleBitmap The roles that were revoked. */ - function _onRolesRevoked(uint256 resource, address account, uint256 oldRoles, uint256 newRoles, uint256 roleBitmap) internal virtual {} + function _onRolesRevoked( + uint256 resource, + address account, + uint256 oldRoles, + uint256 newRoles, + uint256 roleBitmap + ) internal virtual {} // Private methods @@ -386,8 +493,6 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr } } - - /** * @dev Converts a role bitmap to a mask. * @@ -396,7 +501,9 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The role bitmap to convert. * @return roleMask The mask for the role bitmap. */ - function _roleBitmapToMask(uint256 roleBitmap) private pure returns (uint256 roleMask) { + function _roleBitmapToMask( + uint256 roleBitmap + ) private pure returns (uint256 roleMask) { _checkRoleBitmap(roleBitmap); roleMask = roleBitmap | (roleBitmap << 1); roleMask |= roleMask << 2; @@ -412,8 +519,12 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr // Algorithm source: https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord uint256 hasZeroNybbles; unchecked { - hasZeroNybbles = (value - 0x1111111111111111111111111111111111111111111111111111111111111111) & ~value & 0x8888888888888888888888888888888888888888888888888888888888888888; + hasZeroNybbles = + (value - + 0x1111111111111111111111111111111111111111111111111111111111111111) & + ~value & + 0x8888888888888888888888888888888888888888888888888888888888888888; } return hasZeroNybbles != 0; } -} \ No newline at end of file +} diff --git a/contracts/src/common/OwnedResolver.sol b/contracts/src/common/OwnedResolver.sol index 679e37d6..3094dc59 100644 --- a/contracts/src/common/OwnedResolver.sol +++ b/contracts/src/common/OwnedResolver.sol @@ -38,6 +38,7 @@ contract OwnedResolver is } function isAuthorised(bytes32) internal view override returns (bool) { + // TODO(tate): replace with _msgSender() for HCA context? or maybe we don't need this contract at all return msg.sender == owner(); } diff --git a/contracts/src/common/PermissionedRegistry.sol b/contracts/src/common/PermissionedRegistry.sol index 067c3afc..4f84c39e 100644 --- a/contracts/src/common/PermissionedRegistry.sol +++ b/contracts/src/common/PermissionedRegistry.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.13; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +import {IHCAFactoryBasic} from "../hca/IHCAFactoryBasic.sol"; import {ERC1155Singleton} from "./ERC1155Singleton.sol"; import {IERC1155Singleton} from "./IERC1155Singleton.sol"; import {IRegistry} from "./IRegistry.sol"; @@ -34,7 +36,7 @@ contract PermissionedRegistry is BaseRegistry, EnhancedAccessControl, IPermissio _; } - constructor(IRegistryDatastore _datastore, IRegistryMetadata _metadata, address _ownerAddress, uint256 _ownerRoles) BaseRegistry(_datastore) MetadataMixin(_metadata) { + constructor(IHCAFactoryBasic hcaFactory_, IRegistryDatastore _datastore, IRegistryMetadata _metadata, address _ownerAddress, uint256 _ownerRoles) EnhancedAccessControl(hcaFactory_) BaseRegistry(_datastore) MetadataMixin(_metadata) { _grantRoles(ROOT_RESOURCE, _ownerRoles, _ownerAddress, false); if (address(_metadata) == address(0)) { @@ -114,10 +116,11 @@ contract PermissionedRegistry is BaseRegistry, EnhancedAccessControl, IPermissio ITokenObserver observer = tokenObservers[tokenId]; if (address(observer) != address(0)) { - observer.onRenew(tokenId, expires, msg.sender); + // TODO(tate): clarify if the observer will be HCA aware or not + observer.onRenew(tokenId, expires, _msgSender()); } - emit NameRenewed(tokenId, expires, msg.sender); + emit NameRenewed(tokenId, expires, _msgSender()); } /** diff --git a/contracts/src/hca/HCAContext.sol b/contracts/src/hca/HCAContext.sol new file mode 100644 index 00000000..d6307e9e --- /dev/null +++ b/contracts/src/hca/HCAContext.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) + +pragma solidity ^0.8.25; + +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; + +import {IHCAFactoryBasic} from "./IHCAFactoryBasic.sol"; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract HCAContext is Context { + /// @notice The HCA factory contract + IHCAFactoryBasic public immutable HCA_FACTORY; + + /// @notice Initializes the HCA factory contract + /// @param hcaFactory_ The address of the HCA factory contract + constructor(address hcaFactory_) { + HCA_FACTORY = IHCAFactoryBasic(hcaFactory_); + } + + /// @notice Returns either the account owner of an HCA or the original sender + function _msgSender() internal view virtual override returns (address) { + address accountOwner = HCA_FACTORY.getAccountOwner(msg.sender); + if (accountOwner == address(0)) return msg.sender; + return accountOwner; + } +} diff --git a/contracts/src/hca/IHCAFactoryBasic.sol b/contracts/src/hca/IHCAFactoryBasic.sol new file mode 100644 index 00000000..3a0950d5 --- /dev/null +++ b/contracts/src/hca/IHCAFactoryBasic.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/// @dev Basic interface for the HCA factory. +interface IHCAFactoryBasic { + /// @notice Returns the account owner of the given HCA + /// @param hca The HCA to get the account owner of + /// @return The account owner of the given HCA + function getAccountOwner(address hca) external view returns (address); +} diff --git a/contracts/test/hca/HCAContext.t.sol b/contracts/test/hca/HCAContext.t.sol new file mode 100644 index 00000000..9b86ca41 --- /dev/null +++ b/contracts/test/hca/HCAContext.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "forge-std/Test.sol"; + +import {HCAContext} from "../../src/hca/HCAContext.sol"; +import {IHCAFactoryBasic} from "../../src/hca/IHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "../mocks/MockHCAFactoryBasic.sol"; + +contract HCAContextHarness is HCAContext { + constructor(address factory) HCAContext(factory) {} + + function exposedMsgSender() external view returns (address) { + return _msgSender(); + } +} + +contract HCAContextTest is Test { + MockHCAFactoryBasic factory; + HCAContextHarness harness; + + address user = address(0x1111); + address hca = address(0xAAAA); + address owner = address(0xBEEF); + + function setUp() public { + factory = new MockHCAFactoryBasic(); + harness = new HCAContextHarness(address(factory)); + } + + function test_constructor_sets_factory() public view { + // HCA_FACTORY is public immutable on the base, accessible via harness + assertEq(address(harness.HCA_FACTORY()), address(factory)); + } + + function test_msgSender_returns_original_when_not_hca() public { + vm.prank(user); + address sender = harness.exposedMsgSender(); + assertEq( + sender, + user, + "_msgSender should return original sender when not HCA" + ); + } + + function test_msgSender_returns_owner_when_sender_is_hca() public { + factory.setAccountOwner(hca, owner); + + vm.prank(hca); + address sender = harness.exposedMsgSender(); + assertEq( + sender, + owner, + "_msgSender should return account owner for HCA senders" + ); + } + + function test_msgSender_zero_owner_treated_as_eoa() public { + // Ensure no owner configured for `user` + vm.prank(user); + address sender = harness.exposedMsgSender(); + assertEq(sender, user); + } + + function test_msgSender_unrelated_mapping_does_not_affect_eoa() public { + // Configure a different HCA mapping, but call from an unrelated EOA + factory.setAccountOwner(hca, owner); + + vm.prank(user); + address sender = harness.exposedMsgSender(); + assertEq( + sender, + user, + "Unrelated mapping should not affect EOA sender" + ); + } + + function test_msgSender_owner_same_as_hca_returns_hca() public { + // Edge: if factory maps HCA to itself, _msgSender returns that same address + factory.setAccountOwner(hca, hca); + + vm.prank(hca); + address sender = harness.exposedMsgSender(); + assertEq( + sender, + hca, + "When owner == HCA, _msgSender should be the HCA address" + ); + } +} diff --git a/contracts/test/mocks/MockHCAFactoryBasic.sol b/contracts/test/mocks/MockHCAFactoryBasic.sol new file mode 100644 index 00000000..76cdad98 --- /dev/null +++ b/contracts/test/mocks/MockHCAFactoryBasic.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IHCAFactoryBasic} from "../../src/hca/IHCAFactoryBasic.sol"; + +contract MockHCAFactoryBasic is IHCAFactoryBasic { + mapping(address => address) internal _ownerOf; + + function setAccountOwner(address hca, address owner) external { + _ownerOf[hca] = owner; + } + + function getAccountOwner(address hca) external view returns (address) { + return _ownerOf[hca]; + } +} From 980a943c0242ce468135ed8622b5d99995756556 Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 3 Oct 2025 11:04:46 +1000 Subject: [PATCH 2/7] full implementation --- .../registry/MigratedWrappedNameRegistry.sol | 4 +- contracts/src/L2/registrar/ETHRegistrar.sol | 4 +- contracts/src/L2/registry/UserRegistry.sol | 4 +- .../access-control/EnhancedAccessControl.sol | 41 ++++------------ .../src/common/bridge/EjectionController.sol | 7 ++- .../src/common/erc1155/ERC1155Singleton.sol | 5 +- contracts/src/common/hca/HCAContext.sol | 14 ++++++ .../src/common/hca/HCAContextUpgradable.sol | 14 ++++++ contracts/src/common/hca/HCAEquivalence.sol | 22 +++++++++ .../hca/interfaces}/IHCAFactoryBasic.sol | 0 .../registry/BaseUriRegistryMetadata.sol | 4 +- .../common/registry/PermissionedRegistry.sol | 7 ++- .../registry/SimpleRegistryMetadata.sol | 4 +- contracts/src/hca/HCAContext.sol | 36 -------------- .../test/integration/bridge/BridgeTest.t.sol | 14 +++--- contracts/test/mocks/MockHCAFactoryBasic.sol | 4 +- .../test/mocks/MockPermissionedRegistry.sol | 4 +- .../unit/L1/bridge/L1BridgeController.t.sol | 13 ++--- .../test/unit/L1/dns/DNSTLDResolver.t.sol | 2 + .../L1LockedMigrationController.t.sol | 5 ++ .../L1UnlockedMigrationController.t.sol | 4 ++ .../MigratedWrappedNameRegistry.t.sol | 15 ++++++ .../unit/L2/bridge/L2BridgeController.t.sol | 4 ++ .../test/unit/L2/registrar/ETHRegistrar.t.sol | 6 ++- .../registrar/StandardRentPriceOracle.t.sol | 6 ++- .../test/unit/L2/registry/UserRegistry.t.sol | 25 ++++++++-- .../EnhancedAccessControl.t.sol | 9 +++- .../unit/common/bridge/BridgeMessages.t.sol | 4 ++ .../test/unit/common/hca/HCAContext.t.sol | 48 +++++++++++++++++++ .../common/hca/HCAContextUpgradable.t.sol | 48 +++++++++++++++++++ .../common/hca/HCAEquivalence.t.sol} | 46 +++++++----------- .../registry/BaseUriRegistryMetadata.t.sol | 13 ++++- .../registry/PermissionedRegistry.t.sol | 24 +++++++--- .../unit/common/registry/RootRegistry.t.sol | 13 ++++- .../registry/SimpleRegistryMetadata.t.sol | 13 ++++- .../libraries/LibRegistry.sol | 2 + 36 files changed, 344 insertions(+), 144 deletions(-) create mode 100644 contracts/src/common/hca/HCAContext.sol create mode 100644 contracts/src/common/hca/HCAContextUpgradable.sol create mode 100644 contracts/src/common/hca/HCAEquivalence.sol rename contracts/src/{hca => common/hca/interfaces}/IHCAFactoryBasic.sol (100%) delete mode 100644 contracts/src/hca/HCAContext.sol create mode 100644 contracts/test/unit/common/hca/HCAContext.t.sol create mode 100644 contracts/test/unit/common/hca/HCAContextUpgradable.t.sol rename contracts/test/{hca/HCAContext.t.sol => unit/common/hca/HCAEquivalence.t.sol} (61%) diff --git a/contracts/src/L1/registry/MigratedWrappedNameRegistry.sol b/contracts/src/L1/registry/MigratedWrappedNameRegistry.sol index 1f4878f7..7e1ddff1 100644 --- a/contracts/src/L1/registry/MigratedWrappedNameRegistry.sol +++ b/contracts/src/L1/registry/MigratedWrappedNameRegistry.sol @@ -14,6 +14,7 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {MigrationData} from "../../common/bridge/types/TransferData.sol"; import {UnauthorizedCaller} from "../../common/CommonErrors.sol"; +import {IHCAFactoryBasic} from "../../common/hca/interfaces/IHCAFactoryBasic.sol"; import {IPermissionedRegistry} from "../../common/registry/interfaces/IPermissionedRegistry.sol"; import {IRegistry} from "../../common/registry/interfaces/IRegistry.sol"; import {IRegistryDatastore} from "../../common/registry/interfaces/IRegistryDatastore.sol"; @@ -77,8 +78,9 @@ contract MigratedWrappedNameRegistry is VerifiableFactory factory_, IPermissionedRegistry ethRegistry_, IRegistryDatastore datastore_, + IHCAFactoryBasic hcaFactory_, IRegistryMetadata metadataProvider_ - ) PermissionedRegistry(datastore_, metadataProvider_, _msgSender(), 0) { + ) PermissionedRegistry(datastore_, hcaFactory_, metadataProvider_, _msgSender(), 0) { NAME_WRAPPER = nameWrapper_; ENS_REGISTRY = ensRegistry_; FACTORY = factory_; diff --git a/contracts/src/L2/registrar/ETHRegistrar.sol b/contracts/src/L2/registrar/ETHRegistrar.sol index 95c87cd2..f9a9bf60 100644 --- a/contracts/src/L2/registrar/ETHRegistrar.sol +++ b/contracts/src/L2/registrar/ETHRegistrar.sol @@ -5,6 +5,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {EnhancedAccessControl} from "../../common/access-control/EnhancedAccessControl.sol"; import {EACBaseRolesLib} from "../../common/access-control/libraries/EACBaseRolesLib.sol"; +import {HCAEquivalence} from "../../common/hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../../common/hca/interfaces/IHCAFactoryBasic.sol"; import {IPermissionedRegistry} from "../../common/registry/interfaces/IPermissionedRegistry.sol"; import {IRegistry} from "../../common/registry/interfaces/IRegistry.sol"; import {IRegistryDatastore} from "../../common/registry/interfaces/IRegistryDatastore.sol"; @@ -61,7 +63,7 @@ contract ETHRegistrar is IETHRegistrar, EnhancedAccessControl { uint64 maxCommitmentAge_, uint64 minRegisterDuration_, IRentPriceOracle rentPriceOracle_ - ) { + ) HCAEquivalence(IHCAFactoryBasic(address(0))) { if (maxCommitmentAge_ <= minCommitmentAge_) { revert MaxCommitmentAgeTooLow(); } diff --git a/contracts/src/L2/registry/UserRegistry.sol b/contracts/src/L2/registry/UserRegistry.sol index 556a9749..a2a16071 100644 --- a/contracts/src/L2/registry/UserRegistry.sol +++ b/contracts/src/L2/registry/UserRegistry.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.13; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IHCAFactoryBasic} from "../../common/hca/interfaces/IHCAFactoryBasic.sol"; import {IRegistryDatastore} from "../../common/registry/interfaces/IRegistryDatastore.sol"; import {IRegistryMetadata} from "../../common/registry/interfaces/IRegistryMetadata.sol"; import {PermissionedRegistry} from "../../common/registry/PermissionedRegistry.sol"; @@ -27,8 +28,9 @@ contract UserRegistry is Initializable, PermissionedRegistry, UUPSUpgradeable { constructor( IRegistryDatastore datastore_, + IHCAFactoryBasic hcaFactory_, IRegistryMetadata metadataProvider_ - ) PermissionedRegistry(datastore_, metadataProvider_, _msgSender(), 0) { + ) PermissionedRegistry(datastore_, hcaFactory_, metadataProvider_, _msgSender(), 0) { // This disables initialization for the implementation contract _disableInitializers(); } diff --git a/contracts/src/common/access-control/EnhancedAccessControl.sol b/contracts/src/common/access-control/EnhancedAccessControl.sol index 28af71c1..610316ff 100644 --- a/contracts/src/common/access-control/EnhancedAccessControl.sol +++ b/contracts/src/common/access-control/EnhancedAccessControl.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.20; -import {HCAContext} from "../hca/HCAContext.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {HCAContext} from "../hca/HCAContext.sol"; + import {IEnhancedAccessControl} from "./interfaces/IEnhancedAccessControl.sol"; import {EACBaseRolesLib} from "./libraries/EACBaseRolesLib.sol"; @@ -22,7 +23,7 @@ import {EACBaseRolesLib} from "./libraries/EACBaseRolesLib.sol"; /// - A role bitmap is a uint256, where the lower 128 bits represent the regular roles (0-31), and the upper 128 bits represent the admin roles (32-63) for those roles. /// - Each role is represented by a nybble (4 bits), in little-endian order. /// - If a given role left-most nybble bit is located at index N then the corresponding admin role nybble starts at bit position N << 128. -abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessControl { +abstract contract EnhancedAccessControl is HCAContext, ERC165, IEnhancedAccessControl { //////////////////////////////////////////////////////////////////////// // Constants //////////////////////////////////////////////////////////////////////// @@ -192,10 +193,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param account The account to check. * @return `true` if `account` has been granted all the given roles in the `ROOT_RESOURCE`, `false` otherwise. */ - function hasRootRoles( - uint256 rolesBitmap, - address account - ) public view virtual returns (bool) { + function hasRootRoles(uint256 rolesBitmap, address account) public view virtual returns (bool) { return _roles[ROOT_RESOURCE][account] & rolesBitmap == rolesBitmap; } @@ -224,10 +222,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The roles bitmap to check. * @return `true` if any of the roles in the given role bitmap has assignees, `false` otherwise. */ - function hasAssignees( - uint256 resource, - uint256 roleBitmap - ) public view virtual returns (bool) { + function hasAssignees(uint256 resource, uint256 roleBitmap) public view virtual returns (bool) { (uint256 counts, ) = getAssigneeCount(resource, roleBitmap); return counts != 0; } @@ -302,13 +297,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr uint256 newlyAddedRoles = roleBitmap & ~currentRoles; _updateRoleCounts(resource, newlyAddedRoles, true); if (executeCallbacks) { - _onRolesGranted( - resource, - account, - currentRoles, - updatedRoles, - roleBitmap - ); + _onRolesGranted(resource, account, currentRoles, updatedRoles, roleBitmap); } emit EACRolesGranted(resource, roleBitmap, account); return true; @@ -341,13 +330,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr uint256 newlyRemovedRoles = roleBitmap & currentRoles; _updateRoleCounts(resource, newlyRemovedRoles, false); if (executeCallbacks) { - _onRolesRevoked( - resource, - account, - currentRoles, - updatedRoles, - roleBitmap - ); + _onRolesRevoked(resource, account, currentRoles, updatedRoles, roleBitmap); } emit EACRolesRevoked(resource, roleBitmap, account); return true; @@ -373,11 +356,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The roles being modified * @param isGrant true for grant, false for revoke */ - function _updateRoleCounts( - uint256 resource, - uint256 roleBitmap, - bool isGrant - ) internal { + function _updateRoleCounts(uint256 resource, uint256 roleBitmap, bool isGrant) internal { uint256 roleMask = _roleBitmapToMask(roleBitmap); if (isGrant) { @@ -531,9 +510,7 @@ abstract contract EnhancedAccessControl is Context, ERC165, IEnhancedAccessContr * @param roleBitmap The role bitmap to convert. * @return roleMask The mask for the role bitmap. */ - function _roleBitmapToMask( - uint256 roleBitmap - ) private pure returns (uint256 roleMask) { + function _roleBitmapToMask(uint256 roleBitmap) private pure returns (uint256 roleMask) { _checkRoleBitmap(roleBitmap); roleMask = roleBitmap | (roleBitmap << 1); roleMask |= roleMask << 2; diff --git a/contracts/src/common/bridge/EjectionController.sol b/contracts/src/common/bridge/EjectionController.sol index 5339b391..78eae2b5 100644 --- a/contracts/src/common/bridge/EjectionController.sol +++ b/contracts/src/common/bridge/EjectionController.sol @@ -6,6 +6,8 @@ import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC16 import {EnhancedAccessControl} from "../access-control/EnhancedAccessControl.sol"; import {UnauthorizedCaller} from "../CommonErrors.sol"; +import {HCAEquivalence} from "../hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../hca/interfaces/IHCAFactoryBasic.sol"; import {IPermissionedRegistry} from "../registry/interfaces/IPermissionedRegistry.sol"; import {LibLabel} from "../utils/LibLabel.sol"; @@ -55,7 +57,10 @@ abstract contract EjectionController is IERC1155Receiver, ERC165, EnhancedAccess // Initialization //////////////////////////////////////////////////////////////////////// - constructor(IPermissionedRegistry registry_, IBridge bridge_) { + constructor( + IPermissionedRegistry registry_, + IBridge bridge_ + ) HCAEquivalence(IHCAFactoryBasic(address(0))) { REGISTRY = registry_; BRIDGE = bridge_; diff --git a/contracts/src/common/erc1155/ERC1155Singleton.sol b/contracts/src/common/erc1155/ERC1155Singleton.sol index d1853e04..9967f02b 100644 --- a/contracts/src/common/erc1155/ERC1155Singleton.sol +++ b/contracts/src/common/erc1155/ERC1155Singleton.sol @@ -12,14 +12,15 @@ import { import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import {ERC1155Utils} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Utils.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {HCAContext} from "../hca/HCAContext.sol"; + import {IERC1155Singleton} from "./interfaces/IERC1155Singleton.sol"; abstract contract ERC1155Singleton is - Context, + HCAContext, ERC165, IERC1155Singleton, IERC1155Errors, diff --git a/contracts/src/common/hca/HCAContext.sol b/contracts/src/common/hca/HCAContext.sol new file mode 100644 index 00000000..6f498aeb --- /dev/null +++ b/contracts/src/common/hca/HCAContext.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; + +import {HCAEquivalence} from "./HCAEquivalence.sol"; + +/// @dev Replaces msg.sender +abstract contract HCAContext is Context, HCAEquivalence { + /// @notice Returns either the account owner of an HCA or the original sender + function _msgSender() internal view virtual override returns (address) { + return _msgSenderWithHcaEquivalence(); + } +} diff --git a/contracts/src/common/hca/HCAContextUpgradable.sol b/contracts/src/common/hca/HCAContextUpgradable.sol new file mode 100644 index 00000000..1e2bf8ab --- /dev/null +++ b/contracts/src/common/hca/HCAContextUpgradable.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; + +import {HCAEquivalence} from "./HCAEquivalence.sol"; + +/// @dev Replaces msg.sender +abstract contract HCAContextUpgradable is ContextUpgradeable, HCAEquivalence { + /// @notice Returns either the account owner of an HCA or the original sender + function _msgSender() internal view virtual override returns (address) { + return _msgSenderWithHcaEquivalence(); + } +} diff --git a/contracts/src/common/hca/HCAEquivalence.sol b/contracts/src/common/hca/HCAEquivalence.sol new file mode 100644 index 00000000..78b5a110 --- /dev/null +++ b/contracts/src/common/hca/HCAEquivalence.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IHCAFactoryBasic} from "./interfaces/IHCAFactoryBasic.sol"; + +/// @dev Replaces msg.sender +abstract contract HCAEquivalence { + /// @notice The HCA factory contract + IHCAFactoryBasic public immutable HCA_FACTORY; + + constructor(IHCAFactoryBasic hcaFactory) { + HCA_FACTORY = hcaFactory; + } + + /// @notice Returns either the account owner of an HCA or the original sender + function _msgSenderWithHcaEquivalence() internal view returns (address) { + if (address(HCA_FACTORY) == address(0)) return msg.sender; + address accountOwner = HCA_FACTORY.getAccountOwner(msg.sender); + if (accountOwner == address(0)) return msg.sender; + return accountOwner; + } +} diff --git a/contracts/src/hca/IHCAFactoryBasic.sol b/contracts/src/common/hca/interfaces/IHCAFactoryBasic.sol similarity index 100% rename from contracts/src/hca/IHCAFactoryBasic.sol rename to contracts/src/common/hca/interfaces/IHCAFactoryBasic.sol diff --git a/contracts/src/common/registry/BaseUriRegistryMetadata.sol b/contracts/src/common/registry/BaseUriRegistryMetadata.sol index 6413cca8..6a63c37a 100644 --- a/contracts/src/common/registry/BaseUriRegistryMetadata.sol +++ b/contracts/src/common/registry/BaseUriRegistryMetadata.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.13; import {EnhancedAccessControl} from "../access-control/EnhancedAccessControl.sol"; import {EACBaseRolesLib} from "../access-control/libraries/EACBaseRolesLib.sol"; +import {HCAEquivalence} from "../hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../hca/interfaces/IHCAFactoryBasic.sol"; import {IRegistryMetadata} from "./interfaces/IRegistryMetadata.sol"; @@ -25,7 +27,7 @@ contract BaseUriRegistryMetadata is EnhancedAccessControl, IRegistryMetadata { // Initialization //////////////////////////////////////////////////////////////////////// - constructor() { + constructor(IHCAFactoryBasic hcaFactory) HCAEquivalence(hcaFactory) { _grantRoles(ROOT_RESOURCE, EACBaseRolesLib.ALL_ROLES, _msgSender(), true); } diff --git a/contracts/src/common/registry/PermissionedRegistry.sol b/contracts/src/common/registry/PermissionedRegistry.sol index c4b54e6a..d822aebd 100644 --- a/contracts/src/common/registry/PermissionedRegistry.sol +++ b/contracts/src/common/registry/PermissionedRegistry.sol @@ -7,6 +7,8 @@ import {EnhancedAccessControl} from "../access-control/EnhancedAccessControl.sol import {IEnhancedAccessControl} from "../access-control/interfaces/IEnhancedAccessControl.sol"; import {ERC1155Singleton} from "../erc1155/ERC1155Singleton.sol"; import {IERC1155Singleton} from "../erc1155/interfaces/IERC1155Singleton.sol"; +import {HCAEquivalence} from "../hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../hca/interfaces/IHCAFactoryBasic.sol"; import {LibLabel} from "../utils/LibLabel.sol"; import {BaseRegistry} from "./BaseRegistry.sol"; @@ -58,10 +60,11 @@ contract PermissionedRegistry is constructor( IRegistryDatastore datastore_, + IHCAFactoryBasic hcaFactory_, IRegistryMetadata metadata_, address ownerAddress_, uint256 ownerRoles_ - ) BaseRegistry(datastore_) MetadataMixin(metadata_) { + ) BaseRegistry(datastore_) HCAEquivalence(hcaFactory_) MetadataMixin(metadata_) { _grantRoles(ROOT_RESOURCE, ownerRoles_, ownerAddress_, false); } @@ -102,7 +105,7 @@ contract PermissionedRegistry is emit SubregistryUpdate(tokenId, address(0)); emit ResolverUpdate(tokenId, address(0)); - emit NameBurned(tokenId, msg.sender); + emit NameBurned(tokenId, _msgSender()); } function setSubregistry( diff --git a/contracts/src/common/registry/SimpleRegistryMetadata.sol b/contracts/src/common/registry/SimpleRegistryMetadata.sol index 4f94fa70..4dce0ed3 100644 --- a/contracts/src/common/registry/SimpleRegistryMetadata.sol +++ b/contracts/src/common/registry/SimpleRegistryMetadata.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.13; import {EnhancedAccessControl} from "../access-control/EnhancedAccessControl.sol"; import {EACBaseRolesLib} from "../access-control/libraries/EACBaseRolesLib.sol"; +import {HCAEquivalence} from "../hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../hca/interfaces/IHCAFactoryBasic.sol"; import {IRegistryMetadata} from "./interfaces/IRegistryMetadata.sol"; @@ -24,7 +26,7 @@ contract SimpleRegistryMetadata is EnhancedAccessControl, IRegistryMetadata { // Initialization //////////////////////////////////////////////////////////////////////// - constructor() { + constructor(IHCAFactoryBasic hcaFactory) HCAEquivalence(hcaFactory) { _grantRoles(ROOT_RESOURCE, EACBaseRolesLib.ALL_ROLES, _msgSender(), true); } diff --git a/contracts/src/hca/HCAContext.sol b/contracts/src/hca/HCAContext.sol deleted file mode 100644 index d6307e9e..00000000 --- a/contracts/src/hca/HCAContext.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) - -pragma solidity ^0.8.25; - -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; - -import {IHCAFactoryBasic} from "./IHCAFactoryBasic.sol"; - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract HCAContext is Context { - /// @notice The HCA factory contract - IHCAFactoryBasic public immutable HCA_FACTORY; - - /// @notice Initializes the HCA factory contract - /// @param hcaFactory_ The address of the HCA factory contract - constructor(address hcaFactory_) { - HCA_FACTORY = IHCAFactoryBasic(hcaFactory_); - } - - /// @notice Returns either the account owner of an HCA or the original sender - function _msgSender() internal view virtual override returns (address) { - address accountOwner = HCA_FACTORY.getAccountOwner(msg.sender); - if (accountOwner == address(0)) return msg.sender; - return accountOwner; - } -} diff --git a/contracts/test/integration/bridge/BridgeTest.t.sol b/contracts/test/integration/bridge/BridgeTest.t.sol index 4463f6c8..7963fb4c 100644 --- a/contracts/test/integration/bridge/BridgeTest.t.sol +++ b/contracts/test/integration/bridge/BridgeTest.t.sol @@ -5,10 +5,7 @@ pragma solidity >=0.8.13; import {Test} from "forge-std/Test.sol"; -import { - EnhancedAccessControl, - EACBaseRolesLib -} from "~src/common/access-control/EnhancedAccessControl.sol"; +import {EACBaseRolesLib} from "~src/common/access-control/EnhancedAccessControl.sol"; import {BridgeEncoderLib} from "~src/common/bridge/libraries/BridgeEncoderLib.sol"; import {BridgeRolesLib} from "~src/common/bridge/libraries/BridgeRolesLib.sol"; import {TransferData} from "~src/common/bridge/types/TransferData.sol"; @@ -21,10 +18,12 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockL2Bridge} from "~src/mocks/MockL2Bridge.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; -contract BridgeTest is Test, EnhancedAccessControl { +contract BridgeTest is Test { RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; MockPermissionedRegistry l1Registry; MockPermissionedRegistry l2Registry; @@ -40,15 +39,18 @@ contract BridgeTest is Test, EnhancedAccessControl { function setUp() public { // Deploy registries datastore = new RegistryDatastore(); - SimpleRegistryMetadata metadata = new SimpleRegistryMetadata(); + hcaFactory = new MockHCAFactoryBasic(); + SimpleRegistryMetadata metadata = new SimpleRegistryMetadata(hcaFactory); l1Registry = new MockPermissionedRegistry( datastore, + hcaFactory, metadata, address(this), EACBaseRolesLib.ALL_ROLES ); l2Registry = new MockPermissionedRegistry( datastore, + hcaFactory, metadata, address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/mocks/MockHCAFactoryBasic.sol b/contracts/test/mocks/MockHCAFactoryBasic.sol index 76cdad98..749a26cd 100644 --- a/contracts/test/mocks/MockHCAFactoryBasic.sol +++ b/contracts/test/mocks/MockHCAFactoryBasic.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IHCAFactoryBasic} from "../../src/hca/IHCAFactoryBasic.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/IHCAFactoryBasic.sol"; contract MockHCAFactoryBasic is IHCAFactoryBasic { - mapping(address => address) internal _ownerOf; + mapping(address hca => address owner) internal _ownerOf; function setAccountOwner(address hca, address owner) external { _ownerOf[hca] = owner; diff --git a/contracts/test/mocks/MockPermissionedRegistry.sol b/contracts/test/mocks/MockPermissionedRegistry.sol index f2ff6bd5..c7fca438 100644 --- a/contracts/test/mocks/MockPermissionedRegistry.sol +++ b/contracts/test/mocks/MockPermissionedRegistry.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.13; // solhint-disable no-console, private-vars-leading-underscore, state-visibility, func-name-mixedcase, namechain/ordering, one-contract-per-file +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; import { PermissionedRegistry, IRegistryDatastore, @@ -20,10 +21,11 @@ contract MockPermissionedRegistry is PermissionedRegistry { // Pass through all constructor arguments constructor( IRegistryDatastore datastore_, + IHCAFactoryBasic hcaFactory_, IRegistryMetadata metadata_, address ownerAddress_, uint256 ownerRoles_ - ) PermissionedRegistry(datastore_, metadata_, ownerAddress_, ownerRoles_) {} + ) PermissionedRegistry(datastore_, hcaFactory_, metadata_, ownerAddress_, ownerRoles_) {} /** * @dev Public wrapper for getResourceFromTokenId - for testing only diff --git a/contracts/test/unit/L1/bridge/L1BridgeController.t.sol b/contracts/test/unit/L1/bridge/L1BridgeController.t.sol index ce72a98a..030aca9a 100644 --- a/contracts/test/unit/L1/bridge/L1BridgeController.t.sol +++ b/contracts/test/unit/L1/bridge/L1BridgeController.t.sol @@ -7,10 +7,7 @@ import {Test, Vm} from "forge-std/Test.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import { - EnhancedAccessControl, - EACBaseRolesLib -} from "~src/common/access-control/EnhancedAccessControl.sol"; +import {EACBaseRolesLib} from "~src/common/access-control/EnhancedAccessControl.sol"; import { IEnhancedAccessControl } from "~src/common/access-control/interfaces/IEnhancedAccessControl.sol"; @@ -25,6 +22,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract MockRegistryMetadata is IRegistryMetadata { @@ -37,9 +35,10 @@ contract MockBridge is IBridge { function sendMessage(bytes memory) external override {} } -contract L1BridgeControllerTest is Test, ERC1155Holder, EnhancedAccessControl { +contract L1BridgeControllerTest is Test, ERC1155Holder { RegistryDatastore datastore; MockPermissionedRegistry registry; + MockHCAFactoryBasic hcaFactory; L1BridgeController bridgeController; MockRegistryMetadata registryMetadata; MockBridge bridge; @@ -51,7 +50,7 @@ contract L1BridgeControllerTest is Test, ERC1155Holder, EnhancedAccessControl { function supportsInterface( bytes4 /*interfaceId*/ - ) public pure override(ERC1155Holder, EnhancedAccessControl) returns (bool) { + ) public pure override(ERC1155Holder) returns (bool) { return true; } @@ -359,12 +358,14 @@ contract L1BridgeControllerTest is Test, ERC1155Holder, EnhancedAccessControl { function setUp() public { datastore = new RegistryDatastore(); + hcaFactory = new MockHCAFactoryBasic(); registryMetadata = new MockRegistryMetadata(); bridge = new MockBridge(); // Deploy the eth registry registry = new MockPermissionedRegistry( datastore, + hcaFactory, registryMetadata, address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/L1/dns/DNSTLDResolver.t.sol b/contracts/test/unit/L1/dns/DNSTLDResolver.t.sol index 206af189..04859518 100644 --- a/contracts/test/unit/L1/dns/DNSTLDResolver.t.sol +++ b/contracts/test/unit/L1/dns/DNSTLDResolver.t.sol @@ -10,6 +10,7 @@ import {IAddrResolver} from "@ens/contracts/resolvers/profiles/IAddrResolver.sol import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import {EACBaseRolesLib} from "~src/common/access-control/EnhancedAccessControl.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; import { PermissionedRegistry, IRegistryMetadata @@ -58,6 +59,7 @@ contract DNSTLDResolverTest is Test, ERC1155Holder, IAddrResolver { datastore = new RegistryDatastore(); rootRegistry = new PermissionedRegistry( datastore, + IHCAFactoryBasic(address(0)), IRegistryMetadata(address(0)), address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol b/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol index 0bf4bce6..825f4801 100644 --- a/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol +++ b/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol @@ -36,6 +36,7 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L1LockedMigrationController} from "~src/L1/migration/L1LockedMigrationController.sol"; import {LockedNamesLib} from "~src/L1/migration/libraries/LockedNamesLib.sol"; import {MigratedWrappedNameRegistry} from "~src/L1/registry/MigratedWrappedNameRegistry.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract MockNameWrapper { @@ -106,6 +107,7 @@ contract L1LockedMigrationControllerTest is Test, ERC1155Holder { MockPermissionedRegistry registry; VerifiableFactory factory; MigratedWrappedNameRegistry implementation; + MockHCAFactoryBasic hcaFactory; address owner = address(this); address user = address(0x1234); @@ -119,6 +121,7 @@ contract L1LockedMigrationControllerTest is Test, ERC1155Holder { bridge = new MockBridge(); datastore = new RegistryDatastore(); metadata = new MockRegistryMetadata(); + hcaFactory = new MockHCAFactoryBasic(); // Deploy factory and implementation factory = new VerifiableFactory(); @@ -128,12 +131,14 @@ contract L1LockedMigrationControllerTest is Test, ERC1155Holder { factory, IPermissionedRegistry(address(registry)), // ethRegistry datastore, + hcaFactory, metadata ); // Setup eth registry registry = new MockPermissionedRegistry( datastore, + hcaFactory, metadata, owner, EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol b/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol index 58930f55..601e4318 100644 --- a/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol +++ b/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol @@ -27,6 +27,7 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L1UnlockedMigrationController} from "~src/L1/migration/L1UnlockedMigrationController.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockBaseRegistrar} from "~src/mocks/v1/MockBaseRegistrar.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; // Simple mock that implements IRegistryMetadata contract MockRegistryMetadata is IRegistryMetadata { @@ -107,6 +108,7 @@ contract L1UnlockedMigrationControllerTest is Test, ERC1155Holder, ERC721Holder RegistryDatastore datastore; PermissionedRegistry registry; MockRegistryMetadata registryMetadata; + MockHCAFactoryBasic hcaFactory; address user = address(0x1234); address controller = address(0x5678); @@ -284,11 +286,13 @@ contract L1UnlockedMigrationControllerTest is Test, ERC1155Holder, ERC721Holder function setUp() public { // Set up real registry infrastructure datastore = new RegistryDatastore(); + hcaFactory = new MockHCAFactoryBasic(); registryMetadata = new MockRegistryMetadata(); // Deploy the real registry registry = new PermissionedRegistry( datastore, + hcaFactory, registryMetadata, address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol b/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol index 4b24e3f2..dfafce27 100644 --- a/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol +++ b/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol @@ -36,6 +36,7 @@ import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {LockedNamesLib} from "~src/L1/migration/libraries/LockedNamesLib.sol"; import {ParentNotMigrated, LabelNotMigrated} from "~src/L1/migration/MigrationErrors.sol"; import {MigratedWrappedNameRegistry} from "~src/L1/registry/MigratedWrappedNameRegistry.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract MockRegistryMetadata is IRegistryMetadata { function tokenUri(uint256) external pure override returns (string memory) { @@ -124,6 +125,7 @@ contract MigratedWrappedNameRegistryTest is Test { MigratedWrappedNameRegistry implementation; MigratedWrappedNameRegistry registry; RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; MockRegistryMetadata metadata; MockENS ensRegistry; MockNameWrapper nameWrapper; @@ -139,6 +141,7 @@ contract MigratedWrappedNameRegistryTest is Test { function setUp() public { datastore = new RegistryDatastore(); + hcaFactory = new MockHCAFactoryBasic(); metadata = new MockRegistryMetadata(); ensRegistry = new MockENS(); nameWrapper = new MockNameWrapper(); @@ -151,6 +154,7 @@ contract MigratedWrappedNameRegistryTest is Test { factory, IPermissionedRegistry(address(0)), // mock ethRegistry datastore, + hcaFactory, metadata ); @@ -566,6 +570,7 @@ contract MigratedWrappedNameRegistryTest is Test { realFactory, IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -593,6 +598,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1306,6 +1312,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1333,6 +1340,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1355,6 +1363,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1389,6 +1398,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1456,6 +1466,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1478,6 +1489,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1511,6 +1523,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1544,6 +1557,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(0)), datastore, + hcaFactory, metadata ); @@ -1587,6 +1601,7 @@ contract MigratedWrappedNameRegistryTest is Test { VerifiableFactory(address(0)), IPermissionedRegistry(address(mockEthRegistry)), datastore, + hcaFactory, metadata ); diff --git a/contracts/test/unit/L2/bridge/L2BridgeController.t.sol b/contracts/test/unit/L2/bridge/L2BridgeController.t.sol index 27d0c7b2..33500e53 100644 --- a/contracts/test/unit/L2/bridge/L2BridgeController.t.sol +++ b/contracts/test/unit/L2/bridge/L2BridgeController.t.sol @@ -25,6 +25,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; // Mock implementation of IRegistryMetadata @@ -54,6 +55,7 @@ contract TestL2BridgeController is Test, ERC1155Holder { L2BridgeController controller; MockPermissionedRegistry ethRegistry; RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; MockRegistryMetadata registryMetadata; MockBridge bridge; @@ -77,12 +79,14 @@ contract TestL2BridgeController is Test, ERC1155Holder { function setUp() public { // Deploy dependencies datastore = new RegistryDatastore(); + hcaFactory = new MockHCAFactoryBasic(); registryMetadata = new MockRegistryMetadata(); bridge = new MockBridge(); // Deploy ETH registry ethRegistry = new MockPermissionedRegistry( datastore, + hcaFactory, registryMetadata, address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol b/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol index 0d91c2ed..0119dd7b 100644 --- a/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol +++ b/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol @@ -34,9 +34,11 @@ import { DiscountPoint } from "~src/L2/registrar/StandardRentPriceOracle.sol"; import {MockERC20, MockERC20Blacklist} from "~src/mocks/MockERC20.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract ETHRegistrarTest is Test { PermissionedRegistry ethRegistry; + MockHCAFactoryBasic hcaFactory; StandardRentPriceOracle rentPriceOracle; ETHRegistrar ethRegistrar; @@ -49,9 +51,11 @@ contract ETHRegistrarTest is Test { address beneficiary = makeAddr("beneficiary"); function setUp() external { + hcaFactory = new MockHCAFactoryBasic(); ethRegistry = new PermissionedRegistry( new RegistryDatastore(), - new SimpleRegistryMetadata(), + hcaFactory, + new SimpleRegistryMetadata(hcaFactory), address(this), EACBaseRolesLib.ALL_ROLES ); diff --git a/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol b/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol index c4deb962..af564819 100644 --- a/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol +++ b/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol @@ -25,9 +25,11 @@ import { DiscountPoint } from "~src/L2/registrar/StandardRentPriceOracle.sol"; import {MockERC20} from "~src/mocks/MockERC20.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract StandardRentPriceOracleTest is Test, ERC1155Holder { PermissionedRegistry ethRegistry; + MockHCAFactoryBasic hcaFactory; StandardRentPriceOracle rentPriceOracle; @@ -37,9 +39,11 @@ contract StandardRentPriceOracleTest is Test, ERC1155Holder { address user = makeAddr("user"); function setUp() external { + hcaFactory = new MockHCAFactoryBasic(); ethRegistry = new PermissionedRegistry( new RegistryDatastore(), - new SimpleRegistryMetadata(), + hcaFactory, + new SimpleRegistryMetadata(hcaFactory), address(this), EACBaseRolesLib.ALL_ROLES ); diff --git a/contracts/test/unit/L2/registry/UserRegistry.t.sol b/contracts/test/unit/L2/registry/UserRegistry.t.sol index f839b32e..91b3c512 100644 --- a/contracts/test/unit/L2/registry/UserRegistry.t.sol +++ b/contracts/test/unit/L2/registry/UserRegistry.t.sol @@ -12,6 +12,7 @@ import {EACBaseRolesLib} from "~src/common/access-control/EnhancedAccessControl. import { IEnhancedAccessControl } from "~src/common/access-control/interfaces/IEnhancedAccessControl.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; import {IRegistry} from "~src/common/registry/interfaces/IRegistry.sol"; import {IRegistryDatastore} from "~src/common/registry/interfaces/IRegistryDatastore.sol"; import {IRegistryMetadata} from "~src/common/registry/interfaces/IRegistryMetadata.sol"; @@ -19,6 +20,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; import {UserRegistry} from "~src/L2/registry/UserRegistry.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract UserRegistryTest is Test, ERC1155Holder { // Test constants @@ -31,6 +33,7 @@ contract UserRegistryTest is Test, ERC1155Holder { // Contracts VerifiableFactory factory; RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; SimpleRegistryMetadata metadata; UserRegistry implementation; UserRegistry proxy; @@ -47,11 +50,14 @@ contract UserRegistryTest is Test, ERC1155Holder { // Deploy the datastore datastore = new RegistryDatastore(); + // Deploy the HCA factory + hcaFactory = new MockHCAFactoryBasic(); + // Deploy metadata provider - metadata = new SimpleRegistryMetadata(); + metadata = new SimpleRegistryMetadata(hcaFactory); // Deploy the implementation - implementation = new UserRegistry(datastore, metadata); + implementation = new UserRegistry(datastore, hcaFactory, metadata); // Create initialization data bytes memory initData = abi.encodeWithSelector( @@ -258,7 +264,11 @@ contract UserRegistryTest is Test, ERC1155Holder { // Test for contract upgradeability function test_upgrade() public { // Deploy a new implementation - UserRegistryV2Mock newImplementation = new UserRegistryV2Mock(datastore, metadata); + UserRegistryV2Mock newImplementation = new UserRegistryV2Mock( + datastore, + hcaFactory, + metadata + ); // Upgrade the proxy vm.prank(admin); @@ -271,7 +281,11 @@ contract UserRegistryTest is Test, ERC1155Holder { function test_Revert_unauthorized_upgrade() public { // Deploy a new implementation - UserRegistryV2Mock newImplementation = new UserRegistryV2Mock(datastore, metadata); + UserRegistryV2Mock newImplementation = new UserRegistryV2Mock( + datastore, + hcaFactory, + metadata + ); // User1 tries to upgrade without permission vm.expectRevert( @@ -332,8 +346,9 @@ contract UserRegistryTest is Test, ERC1155Holder { contract UserRegistryV2Mock is UserRegistry { constructor( IRegistryDatastore _datastore, + IHCAFactoryBasic _hcaFactory, IRegistryMetadata _metadataProvider - ) UserRegistry(_datastore, _metadataProvider) {} + ) UserRegistry(_datastore, _hcaFactory, _metadataProvider) {} function version() public pure returns (uint256) { return 2; } diff --git a/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol b/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol index 656a6efd..72bf5e7a 100644 --- a/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol +++ b/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol @@ -10,6 +10,9 @@ import { IEnhancedAccessControl } from "~src/common/access-control/interfaces/IEnhancedAccessControl.sol"; import {EACBaseRolesLib} from "~src/common/access-control/libraries/EACBaseRolesLib.sol"; +import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; abstract contract MockRoles { uint256 public constant RESOURCE_1 = uint256(keccak256("RESOURCE_1")); @@ -42,7 +45,7 @@ contract MockEnhancedAccessControl is EnhancedAccessControl, MockRoles { address public lastRevokedAccount; uint256 public lastRevokedResource; - constructor() EnhancedAccessControl() { + constructor(IHCAFactoryBasic hcaFactory) HCAEquivalence(hcaFactory) { _grantRoles( ROOT_RESOURCE, ROLE_A | @@ -172,6 +175,7 @@ contract MockEnhancedAccessControl is EnhancedAccessControl, MockRoles { contract EnhancedAccessControlTest is Test, MockRoles { MockEnhancedAccessControl access; + MockHCAFactoryBasic hcaFactory; address admin; address user1; address user2; @@ -182,7 +186,8 @@ contract EnhancedAccessControlTest is Test, MockRoles { user1 = makeAddr("user1"); user2 = makeAddr("user2"); superuser = makeAddr("superuser"); - access = new MockEnhancedAccessControl(); + hcaFactory = new MockHCAFactoryBasic(); + access = new MockEnhancedAccessControl(hcaFactory); } function test_initial_roles() public view { diff --git a/contracts/test/unit/common/bridge/BridgeMessages.t.sol b/contracts/test/unit/common/bridge/BridgeMessages.t.sol index 49774a67..c18acdcd 100644 --- a/contracts/test/unit/common/bridge/BridgeMessages.t.sol +++ b/contracts/test/unit/common/bridge/BridgeMessages.t.sol @@ -19,6 +19,7 @@ import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; import {MockBridgeBase} from "~src/mocks/MockBridgeBase.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockL2Bridge} from "~src/mocks/MockL2Bridge.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract MockRegistryMetadata is IRegistryMetadata { function tokenUri(uint256) external pure override returns (string memory) { @@ -34,6 +35,7 @@ contract BridgeMessagesTest is Test { PermissionedRegistry registry; RegistryDatastore datastore; MockRegistryMetadata registryMetadata; + MockHCAFactoryBasic hcaFactory; string testLabel = "test"; address testOwner = address(0x1234); @@ -44,11 +46,13 @@ contract BridgeMessagesTest is Test { function setUp() public { // Deploy dependencies datastore = new RegistryDatastore(); + hcaFactory = new MockHCAFactoryBasic(); registryMetadata = new MockRegistryMetadata(); // Deploy registry registry = new PermissionedRegistry( datastore, + hcaFactory, registryMetadata, address(this), EACBaseRolesLib.ALL_ROLES diff --git a/contracts/test/unit/common/hca/HCAContext.t.sol b/contracts/test/unit/common/hca/HCAContext.t.sol new file mode 100644 index 00000000..2763789a --- /dev/null +++ b/contracts/test/unit/common/hca/HCAContext.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +// solhint-disable no-console, private-vars-leading-underscore, state-visibility, func-name-mixedcase, namechain/ordering, one-contract-per-file + +import {Test} from "forge-std/Test.sol"; + +import {HCAContext} from "~src/common/hca/HCAContext.sol"; +import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; + +contract HCAContextHarness is HCAContext { + constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} + + function exposedMsgSender() external view returns (address) { + return _msgSender(); + } +} + +contract HCAContextTest is Test { + MockHCAFactoryBasic factory; + HCAContextHarness harness; + + address user = address(0x1111); + address hca = address(0xAAAA); + address owner = address(0xBEEF); + + function setUp() public { + factory = new MockHCAFactoryBasic(); + harness = new HCAContextHarness(factory); + } + + function test_constructor_sets_factory() public view { + // HCA_FACTORY is public immutable on the base, accessible via harness + assertEq(address(harness.HCA_FACTORY()), address(factory)); + } + + function test_msgSender_calls_HCAEquivalence() public { + vm.prank(user); + vm.expectCall( + address(factory), + abi.encodeWithSelector(factory.getAccountOwner.selector, user) + ); + address sender = harness.exposedMsgSender(); + assertEq(sender, user); + } +} diff --git a/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol b/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol new file mode 100644 index 00000000..7995272d --- /dev/null +++ b/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +// solhint-disable no-console, private-vars-leading-underscore, state-visibility, func-name-mixedcase, namechain/ordering, one-contract-per-file + +import {Test} from "forge-std/Test.sol"; + +import {HCAContextUpgradable} from "~src/common/hca/HCAContextUpgradable.sol"; +import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; + +contract HCAContextUpgradableHarness is HCAContextUpgradable { + constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} + + function exposedMsgSender() external view returns (address) { + return _msgSender(); + } +} + +contract HCAContextUpgradableTest is Test { + MockHCAFactoryBasic factory; + HCAContextUpgradableHarness harness; + + address user = address(0x1111); + address hca = address(0xAAAA); + address owner = address(0xBEEF); + + function setUp() public { + factory = new MockHCAFactoryBasic(); + harness = new HCAContextUpgradableHarness(factory); + } + + function test_constructor_sets_factory() public view { + // HCA_FACTORY is public immutable on the base, accessible via harness + assertEq(address(harness.HCA_FACTORY()), address(factory)); + } + + function test_msgSender_calls_HCAEquivalence() public { + vm.prank(user); + vm.expectCall( + address(factory), + abi.encodeWithSelector(factory.getAccountOwner.selector, user) + ); + address sender = harness.exposedMsgSender(); + assertEq(sender, user); + } +} diff --git a/contracts/test/hca/HCAContext.t.sol b/contracts/test/unit/common/hca/HCAEquivalence.t.sol similarity index 61% rename from contracts/test/hca/HCAContext.t.sol rename to contracts/test/unit/common/hca/HCAEquivalence.t.sol index 9b86ca41..0fa0a9ad 100644 --- a/contracts/test/hca/HCAContext.t.sol +++ b/contracts/test/unit/common/hca/HCAEquivalence.t.sol @@ -1,23 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import "forge-std/Test.sol"; +// solhint-disable no-console, private-vars-leading-underscore, state-visibility, func-name-mixedcase, namechain/ordering, one-contract-per-file -import {HCAContext} from "../../src/hca/HCAContext.sol"; -import {IHCAFactoryBasic} from "../../src/hca/IHCAFactoryBasic.sol"; -import {MockHCAFactoryBasic} from "../mocks/MockHCAFactoryBasic.sol"; +import {Test} from "forge-std/Test.sol"; -contract HCAContextHarness is HCAContext { - constructor(address factory) HCAContext(factory) {} +import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; + +contract HCAEquivalenceHarness is HCAEquivalence { + constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} function exposedMsgSender() external view returns (address) { - return _msgSender(); + return _msgSenderWithHcaEquivalence(); } } -contract HCAContextTest is Test { +contract HCAEquivalenceTest is Test { MockHCAFactoryBasic factory; - HCAContextHarness harness; + HCAEquivalenceHarness harness; address user = address(0x1111); address hca = address(0xAAAA); @@ -25,7 +27,7 @@ contract HCAContextTest is Test { function setUp() public { factory = new MockHCAFactoryBasic(); - harness = new HCAContextHarness(address(factory)); + harness = new HCAEquivalenceHarness(IHCAFactoryBasic(address(factory))); } function test_constructor_sets_factory() public view { @@ -36,11 +38,7 @@ contract HCAContextTest is Test { function test_msgSender_returns_original_when_not_hca() public { vm.prank(user); address sender = harness.exposedMsgSender(); - assertEq( - sender, - user, - "_msgSender should return original sender when not HCA" - ); + assertEq(sender, user, "_msgSender should return original sender when not HCA"); } function test_msgSender_returns_owner_when_sender_is_hca() public { @@ -48,11 +46,7 @@ contract HCAContextTest is Test { vm.prank(hca); address sender = harness.exposedMsgSender(); - assertEq( - sender, - owner, - "_msgSender should return account owner for HCA senders" - ); + assertEq(sender, owner, "_msgSender should return account owner for HCA senders"); } function test_msgSender_zero_owner_treated_as_eoa() public { @@ -68,11 +62,7 @@ contract HCAContextTest is Test { vm.prank(user); address sender = harness.exposedMsgSender(); - assertEq( - sender, - user, - "Unrelated mapping should not affect EOA sender" - ); + assertEq(sender, user, "Unrelated mapping should not affect EOA sender"); } function test_msgSender_owner_same_as_hca_returns_hca() public { @@ -81,10 +71,6 @@ contract HCAContextTest is Test { vm.prank(hca); address sender = harness.exposedMsgSender(); - assertEq( - sender, - hca, - "When owner == HCA, _msgSender should be the HCA address" - ); + assertEq(sender, hca, "When owner == HCA, _msgSender should be the HCA address"); } } diff --git a/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol b/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol index 82c69bfa..1f820c26 100644 --- a/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol +++ b/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol @@ -17,9 +17,11 @@ import {IRegistryMetadata} from "~src/common/registry/interfaces/IRegistryMetada import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib.sol"; import {PermissionedRegistry} from "~src/common/registry/PermissionedRegistry.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract BaseUriRegistryMetadataTest is Test, ERC1155Holder { RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; PermissionedRegistry registry; PermissionedRegistry parentRegistry; BaseUriRegistryMetadata metadata; @@ -33,11 +35,18 @@ contract BaseUriRegistryMetadataTest is Test, ERC1155Holder { function setUp() public { datastore = new RegistryDatastore(); - metadata = new BaseUriRegistryMetadata(); + hcaFactory = new MockHCAFactoryBasic(); + metadata = new BaseUriRegistryMetadata(hcaFactory); // Use the valid ALL_ROLES value for deployer roles uint256 deployerRoles = EACBaseRolesLib.ALL_ROLES; - registry = new PermissionedRegistry(datastore, metadata, address(this), deployerRoles); + registry = new PermissionedRegistry( + datastore, + hcaFactory, + metadata, + address(this), + deployerRoles + ); } function test_registry_metadata_base_uri() public { diff --git a/contracts/test/unit/common/registry/PermissionedRegistry.t.sol b/contracts/test/unit/common/registry/PermissionedRegistry.t.sol index eec94cf4..31e88eed 100644 --- a/contracts/test/unit/common/registry/PermissionedRegistry.t.sol +++ b/contracts/test/unit/common/registry/PermissionedRegistry.t.sol @@ -21,6 +21,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract PermissionedRegistryTest is Test, ERC1155Holder { @@ -34,6 +35,7 @@ contract PermissionedRegistryTest is Test, ERC1155Holder { RegistryDatastore datastore; MockPermissionedRegistry registry; + MockHCAFactoryBasic hcaFactory; MockTokenObserver observer; RevertingTokenObserver revertingObserver; IRegistryMetadata metadata; @@ -58,8 +60,15 @@ contract PermissionedRegistryTest is Test, ERC1155Holder { function setUp() public { datastore = new RegistryDatastore(); - metadata = new SimpleRegistryMetadata(); - registry = new MockPermissionedRegistry(datastore, metadata, address(this), deployerRoles); + hcaFactory = new MockHCAFactoryBasic(); + metadata = new SimpleRegistryMetadata(hcaFactory); + registry = new MockPermissionedRegistry( + datastore, + hcaFactory, + metadata, + address(this), + deployerRoles + ); observer = new MockTokenObserver(); revertingObserver = new RevertingTokenObserver(); } @@ -1130,10 +1139,9 @@ contract PermissionedRegistryTest is Test, ERC1155Holder { function test_first_time_registration_no_eacVersionId_increment() public { string memory label = "firsttime"; - address owner = makeAddr("owner"); // Verify name has never been registered (entry.expiry should be 0) - (uint256 tokenId, IRegistryDatastore.Entry memory entry) = registry.getNameData(label); + (, IRegistryDatastore.Entry memory entry) = registry.getNameData(label); assertEq(entry.expiry, 0, "Name should never have been registered before"); assertEq(entry.eacVersionId, 0, "Initial eacVersionId should be 0"); @@ -2517,8 +2525,12 @@ contract PermissionedRegistryTest is Test, ERC1155Holder { assertEq(reconstructedTokenId, tokenId, "Round-trip conversion should work"); // Check that the owner is correctly recognized - address owner = registry.ownerOf(reconstructedTokenId); - assertEq(owner, user1, "Owner should be found for reconstructed token ID"); + address ownerOfReconstructedTokenId = registry.ownerOf(reconstructedTokenId); + assertEq( + ownerOfReconstructedTokenId, + user1, + "Owner should be found for reconstructed token ID" + ); } function test_getNameData_returns_correct_tokenId() public { diff --git a/contracts/test/unit/common/registry/RootRegistry.t.sol b/contracts/test/unit/common/registry/RootRegistry.t.sol index ef77a21b..12dd0aab 100644 --- a/contracts/test/unit/common/registry/RootRegistry.t.sol +++ b/contracts/test/unit/common/registry/RootRegistry.t.sol @@ -15,6 +15,7 @@ import {IRegistry} from "~src/common/registry/interfaces/IRegistry.sol"; import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract RootRegistryTest is Test, ERC1155Holder { @@ -30,6 +31,7 @@ contract RootRegistryTest is Test, ERC1155Holder { RegistryDatastore datastore; MockPermissionedRegistry registry; + MockHCAFactoryBasic hcaFactory; SimpleRegistryMetadata metadata; // Hardcoded role constants @@ -49,10 +51,17 @@ contract RootRegistryTest is Test, ERC1155Holder { function setUp() public { datastore = new RegistryDatastore(); - metadata = new SimpleRegistryMetadata(); + hcaFactory = new MockHCAFactoryBasic(); + metadata = new SimpleRegistryMetadata(hcaFactory); // Use the valid ALL_ROLES value for deployer roles uint256 deployerRoles = EACBaseRolesLib.ALL_ROLES; - registry = new MockPermissionedRegistry(datastore, metadata, address(this), deployerRoles); + registry = new MockPermissionedRegistry( + datastore, + hcaFactory, + metadata, + address(this), + deployerRoles + ); metadata.grantRootRoles(RegistryRolesLib.ROLE_REGISTRAR, address(registry)); } diff --git a/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol b/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol index 4ad54a98..f3cf79ed 100644 --- a/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol +++ b/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol @@ -17,9 +17,11 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {PermissionedRegistry} from "~src/common/registry/PermissionedRegistry.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; +import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; contract SimpleRegistryMetadataTest is Test, ERC1155Holder { RegistryDatastore datastore; + MockHCAFactoryBasic hcaFactory; PermissionedRegistry registry; SimpleRegistryMetadata metadata; @@ -33,10 +35,17 @@ contract SimpleRegistryMetadataTest is Test, ERC1155Holder { function setUp() public { datastore = new RegistryDatastore(); - metadata = new SimpleRegistryMetadata(); + hcaFactory = new MockHCAFactoryBasic(); + metadata = new SimpleRegistryMetadata(hcaFactory); // Use the valid ALL_ROLES value for deployer roles uint256 deployerRoles = EACBaseRolesLib.ALL_ROLES; - registry = new PermissionedRegistry(datastore, metadata, address(this), deployerRoles); + registry = new PermissionedRegistry( + datastore, + hcaFactory, + metadata, + address(this), + deployerRoles + ); } function test_registry_metadata_token_uri() public { diff --git a/contracts/test/unit/universalResolver/libraries/LibRegistry.sol b/contracts/test/unit/universalResolver/libraries/LibRegistry.sol index 400a62bd..0bf25a23 100755 --- a/contracts/test/unit/universalResolver/libraries/LibRegistry.sol +++ b/contracts/test/unit/universalResolver/libraries/LibRegistry.sol @@ -8,6 +8,7 @@ import {Test} from "forge-std/Test.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import {EACBaseRolesLib} from "~src/common/access-control/EnhancedAccessControl.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; import { PermissionedRegistry, IRegistryMetadata @@ -24,6 +25,7 @@ contract LibRegistryTest is Test, ERC1155Holder { return new PermissionedRegistry( datastore, + IHCAFactoryBasic(address(0)), IRegistryMetadata(address(0)), address(this), EACBaseRolesLib.ALL_ROLES From d771a56118bd72d68ab699034f934c23aaf67869 Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 3 Oct 2025 11:08:31 +1000 Subject: [PATCH 3/7] oops --- contracts/test/mocks/MockHCAFactoryBasic.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/mocks/MockHCAFactoryBasic.sol b/contracts/test/mocks/MockHCAFactoryBasic.sol index 749a26cd..ac12ad79 100644 --- a/contracts/test/mocks/MockHCAFactoryBasic.sol +++ b/contracts/test/mocks/MockHCAFactoryBasic.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IHCAFactoryBasic} from "~src/common/hca/IHCAFactoryBasic.sol"; +import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; contract MockHCAFactoryBasic is IHCAFactoryBasic { mapping(address hca => address owner) internal _ownerOf; From 3b888aa4f73fd091c2c25faa28e9cbc73bf6f8d6 Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 3 Oct 2025 11:15:19 +1000 Subject: [PATCH 4/7] hh test fix --- .../mocks/MockHCAFactoryBasic.sol | 0 .../test/integration/bridge/BridgeTest.t.sol | 2 +- .../integration/fixtures/deployV2Fixture.ts | 19 +++++++++++++++++-- .../unit/L1/bridge/L1BridgeController.t.sol | 2 +- .../L1LockedMigrationController.t.sol | 2 +- .../L1UnlockedMigrationController.t.sol | 2 +- .../MigratedWrappedNameRegistry.t.sol | 2 +- .../unit/L2/bridge/L2BridgeController.t.sol | 2 +- .../test/unit/L2/registrar/ETHRegistrar.t.sol | 2 +- .../registrar/StandardRentPriceOracle.t.sol | 2 +- .../test/unit/L2/registry/UserRegistry.t.sol | 2 +- .../EnhancedAccessControl.t.sol | 2 +- .../unit/common/bridge/BridgeMessages.t.sol | 2 +- .../test/unit/common/hca/HCAContext.t.sol | 2 +- .../common/hca/HCAContextUpgradable.t.sol | 2 +- .../test/unit/common/hca/HCAEquivalence.t.sol | 2 +- .../registry/BaseUriRegistryMetadata.t.sol | 2 +- .../registry/PermissionedRegistry.t.sol | 2 +- .../unit/common/registry/RootRegistry.t.sol | 2 +- .../registry/SimpleRegistryMetadata.t.sol | 2 +- 20 files changed, 35 insertions(+), 20 deletions(-) rename contracts/{test => src}/mocks/MockHCAFactoryBasic.sol (100%) diff --git a/contracts/test/mocks/MockHCAFactoryBasic.sol b/contracts/src/mocks/MockHCAFactoryBasic.sol similarity index 100% rename from contracts/test/mocks/MockHCAFactoryBasic.sol rename to contracts/src/mocks/MockHCAFactoryBasic.sol diff --git a/contracts/test/integration/bridge/BridgeTest.t.sol b/contracts/test/integration/bridge/BridgeTest.t.sol index 7963fb4c..abe5486f 100644 --- a/contracts/test/integration/bridge/BridgeTest.t.sol +++ b/contracts/test/integration/bridge/BridgeTest.t.sol @@ -18,7 +18,7 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockL2Bridge} from "~src/mocks/MockL2Bridge.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract BridgeTest is Test { diff --git a/contracts/test/integration/fixtures/deployV2Fixture.ts b/contracts/test/integration/fixtures/deployV2Fixture.ts index ca256594..8d600d43 100644 --- a/contracts/test/integration/fixtures/deployV2Fixture.ts +++ b/contracts/test/integration/fixtures/deployV2Fixture.ts @@ -16,13 +16,26 @@ export async function deployV2Fixture( }); const [walletClient] = await network.viem.getWalletClients(); const datastore = await network.viem.deployContract("RegistryDatastore"); + const hcaFactory = await network.viem.deployContract("MockHCAFactoryBasic"); const rootRegistry = await network.viem.deployContract( "PermissionedRegistry", - [datastore.address, zeroAddress, walletClient.account.address, ROLES.ALL], + [ + datastore.address, + hcaFactory.address, + zeroAddress, + walletClient.account.address, + ROLES.ALL, + ], ); const ethRegistry = await network.viem.deployContract( "PermissionedRegistry", - [datastore.address, zeroAddress, walletClient.account.address, ROLES.ALL], + [ + datastore.address, + hcaFactory.address, + zeroAddress, + walletClient.account.address, + ROLES.ALL, + ], ); const batchGatewayProvider = await network.viem.deployContract( "GatewayProvider", @@ -50,6 +63,7 @@ export async function deployV2Fixture( publicClient, walletClient, datastore, + hcaFactory, rootRegistry, ethRegistry, batchGatewayProvider, @@ -119,6 +133,7 @@ export async function deployV2Fixture( "PermissionedRegistry", [ datastore.address, + hcaFactory.address, metadataAddress, walletClient.account.address, roles, diff --git a/contracts/test/unit/L1/bridge/L1BridgeController.t.sol b/contracts/test/unit/L1/bridge/L1BridgeController.t.sol index 030aca9a..2f60379a 100644 --- a/contracts/test/unit/L1/bridge/L1BridgeController.t.sol +++ b/contracts/test/unit/L1/bridge/L1BridgeController.t.sol @@ -22,7 +22,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract MockRegistryMetadata is IRegistryMetadata { diff --git a/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol b/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol index 825f4801..65870305 100644 --- a/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol +++ b/contracts/test/unit/L1/migration/L1LockedMigrationController.t.sol @@ -36,7 +36,7 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L1LockedMigrationController} from "~src/L1/migration/L1LockedMigrationController.sol"; import {LockedNamesLib} from "~src/L1/migration/libraries/LockedNamesLib.sol"; import {MigratedWrappedNameRegistry} from "~src/L1/registry/MigratedWrappedNameRegistry.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract MockNameWrapper { diff --git a/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol b/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol index 601e4318..6361667c 100644 --- a/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol +++ b/contracts/test/unit/L1/migration/L1UnlockedMigrationController.t.sol @@ -27,7 +27,7 @@ import {L1BridgeController} from "~src/L1/bridge/L1BridgeController.sol"; import {L1UnlockedMigrationController} from "~src/L1/migration/L1UnlockedMigrationController.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockBaseRegistrar} from "~src/mocks/v1/MockBaseRegistrar.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; // Simple mock that implements IRegistryMetadata contract MockRegistryMetadata is IRegistryMetadata { diff --git a/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol b/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol index dfafce27..a9c7ea1d 100644 --- a/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol +++ b/contracts/test/unit/L1/registry/MigratedWrappedNameRegistry.t.sol @@ -36,7 +36,7 @@ import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {LockedNamesLib} from "~src/L1/migration/libraries/LockedNamesLib.sol"; import {ParentNotMigrated, LabelNotMigrated} from "~src/L1/migration/MigrationErrors.sol"; import {MigratedWrappedNameRegistry} from "~src/L1/registry/MigratedWrappedNameRegistry.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract MockRegistryMetadata is IRegistryMetadata { function tokenUri(uint256) external pure override returns (string memory) { diff --git a/contracts/test/unit/L2/bridge/L2BridgeController.t.sol b/contracts/test/unit/L2/bridge/L2BridgeController.t.sol index 33500e53..33f65528 100644 --- a/contracts/test/unit/L2/bridge/L2BridgeController.t.sol +++ b/contracts/test/unit/L2/bridge/L2BridgeController.t.sol @@ -25,7 +25,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; // Mock implementation of IRegistryMetadata diff --git a/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol b/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol index 0119dd7b..14a9ee8a 100644 --- a/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol +++ b/contracts/test/unit/L2/registrar/ETHRegistrar.t.sol @@ -34,7 +34,7 @@ import { DiscountPoint } from "~src/L2/registrar/StandardRentPriceOracle.sol"; import {MockERC20, MockERC20Blacklist} from "~src/mocks/MockERC20.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract ETHRegistrarTest is Test { PermissionedRegistry ethRegistry; diff --git a/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol b/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol index af564819..704e9902 100644 --- a/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol +++ b/contracts/test/unit/L2/registrar/StandardRentPriceOracle.t.sol @@ -25,7 +25,7 @@ import { DiscountPoint } from "~src/L2/registrar/StandardRentPriceOracle.sol"; import {MockERC20} from "~src/mocks/MockERC20.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract StandardRentPriceOracleTest is Test, ERC1155Holder { PermissionedRegistry ethRegistry; diff --git a/contracts/test/unit/L2/registry/UserRegistry.t.sol b/contracts/test/unit/L2/registry/UserRegistry.t.sol index 91b3c512..fc9c4ec9 100644 --- a/contracts/test/unit/L2/registry/UserRegistry.t.sol +++ b/contracts/test/unit/L2/registry/UserRegistry.t.sol @@ -20,7 +20,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; import {UserRegistry} from "~src/L2/registry/UserRegistry.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract UserRegistryTest is Test, ERC1155Holder { // Test constants diff --git a/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol b/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol index 72bf5e7a..3c2c0967 100644 --- a/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol +++ b/contracts/test/unit/common/access-control/EnhancedAccessControl.t.sol @@ -12,7 +12,7 @@ import { import {EACBaseRolesLib} from "~src/common/access-control/libraries/EACBaseRolesLib.sol"; import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; abstract contract MockRoles { uint256 public constant RESOURCE_1 = uint256(keccak256("RESOURCE_1")); diff --git a/contracts/test/unit/common/bridge/BridgeMessages.t.sol b/contracts/test/unit/common/bridge/BridgeMessages.t.sol index c18acdcd..7efdd7aa 100644 --- a/contracts/test/unit/common/bridge/BridgeMessages.t.sol +++ b/contracts/test/unit/common/bridge/BridgeMessages.t.sol @@ -19,7 +19,7 @@ import {L2BridgeController} from "~src/L2/bridge/L2BridgeController.sol"; import {MockBridgeBase} from "~src/mocks/MockBridgeBase.sol"; import {MockL1Bridge} from "~src/mocks/MockL1Bridge.sol"; import {MockL2Bridge} from "~src/mocks/MockL2Bridge.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract MockRegistryMetadata is IRegistryMetadata { function tokenUri(uint256) external pure override returns (string memory) { diff --git a/contracts/test/unit/common/hca/HCAContext.t.sol b/contracts/test/unit/common/hca/HCAContext.t.sol index 2763789a..b054ca33 100644 --- a/contracts/test/unit/common/hca/HCAContext.t.sol +++ b/contracts/test/unit/common/hca/HCAContext.t.sol @@ -8,7 +8,7 @@ import {Test} from "forge-std/Test.sol"; import {HCAContext} from "~src/common/hca/HCAContext.sol"; import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract HCAContextHarness is HCAContext { constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} diff --git a/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol b/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol index 7995272d..e25fbeb8 100644 --- a/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol +++ b/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol @@ -8,7 +8,7 @@ import {Test} from "forge-std/Test.sol"; import {HCAContextUpgradable} from "~src/common/hca/HCAContextUpgradable.sol"; import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract HCAContextUpgradableHarness is HCAContextUpgradable { constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} diff --git a/contracts/test/unit/common/hca/HCAEquivalence.t.sol b/contracts/test/unit/common/hca/HCAEquivalence.t.sol index 0fa0a9ad..2eac8573 100644 --- a/contracts/test/unit/common/hca/HCAEquivalence.t.sol +++ b/contracts/test/unit/common/hca/HCAEquivalence.t.sol @@ -7,7 +7,7 @@ import {Test} from "forge-std/Test.sol"; import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract HCAEquivalenceHarness is HCAEquivalence { constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} diff --git a/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol b/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol index 1f820c26..0252e83c 100644 --- a/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol +++ b/contracts/test/unit/common/registry/BaseUriRegistryMetadata.t.sol @@ -17,7 +17,7 @@ import {IRegistryMetadata} from "~src/common/registry/interfaces/IRegistryMetada import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib.sol"; import {PermissionedRegistry} from "~src/common/registry/PermissionedRegistry.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract BaseUriRegistryMetadataTest is Test, ERC1155Holder { RegistryDatastore datastore; diff --git a/contracts/test/unit/common/registry/PermissionedRegistry.t.sol b/contracts/test/unit/common/registry/PermissionedRegistry.t.sol index 31e88eed..cb050301 100644 --- a/contracts/test/unit/common/registry/PermissionedRegistry.t.sol +++ b/contracts/test/unit/common/registry/PermissionedRegistry.t.sol @@ -21,7 +21,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; import {LibLabel} from "~src/common/utils/LibLabel.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract PermissionedRegistryTest is Test, ERC1155Holder { diff --git a/contracts/test/unit/common/registry/RootRegistry.t.sol b/contracts/test/unit/common/registry/RootRegistry.t.sol index 12dd0aab..37cd83b2 100644 --- a/contracts/test/unit/common/registry/RootRegistry.t.sol +++ b/contracts/test/unit/common/registry/RootRegistry.t.sol @@ -15,7 +15,7 @@ import {IRegistry} from "~src/common/registry/interfaces/IRegistry.sol"; import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; import {MockPermissionedRegistry} from "~test/mocks/MockPermissionedRegistry.sol"; contract RootRegistryTest is Test, ERC1155Holder { diff --git a/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol b/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol index f3cf79ed..d05d5404 100644 --- a/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol +++ b/contracts/test/unit/common/registry/SimpleRegistryMetadata.t.sol @@ -17,7 +17,7 @@ import {RegistryRolesLib} from "~src/common/registry/libraries/RegistryRolesLib. import {PermissionedRegistry} from "~src/common/registry/PermissionedRegistry.sol"; import {RegistryDatastore} from "~src/common/registry/RegistryDatastore.sol"; import {SimpleRegistryMetadata} from "~src/common/registry/SimpleRegistryMetadata.sol"; -import {MockHCAFactoryBasic} from "~test/mocks/MockHCAFactoryBasic.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract SimpleRegistryMetadataTest is Test, ERC1155Holder { RegistryDatastore datastore; From bfcc556a0cd31edc730725955fd7bb08537f9b3e Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 3 Oct 2025 12:24:52 +1000 Subject: [PATCH 5/7] deploy script fix --- contracts/deploy/l1/00_RootRegistry.ts | 6 +++++- contracts/deploy/l1/01_ReverseRegistry.ts | 5 +++++ contracts/deploy/l1/02_ETHRegistry.ts | 9 +++++++-- contracts/deploy/l2/01_ETHRegistry.ts | 6 +++++- .../{00_RegistryMetadata.ts => 00_HCAFactory.ts} | 9 ++++++--- contracts/deploy/shared/01_RegistryMetadata.ts | 15 +++++++++++++++ contracts/script/setup.ts | 3 +++ 7 files changed, 46 insertions(+), 7 deletions(-) rename contracts/deploy/shared/{00_RegistryMetadata.ts => 00_HCAFactory.ts} (52%) create mode 100644 contracts/deploy/shared/01_RegistryMetadata.ts diff --git a/contracts/deploy/l1/00_RootRegistry.ts b/contracts/deploy/l1/00_RootRegistry.ts index d4f31af6..36000de5 100644 --- a/contracts/deploy/l1/00_RootRegistry.ts +++ b/contracts/deploy/l1/00_RootRegistry.ts @@ -6,6 +6,9 @@ export default execute( const registryDatastore = get<(typeof artifacts.RegistryDatastore)["abi"]>("RegistryDatastore"); + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); + const registryMetadata = get< (typeof artifacts.SimpleRegistryMetadata)["abi"] >("SimpleRegistryMetadata"); @@ -15,6 +18,7 @@ export default execute( artifact: artifacts.PermissionedRegistry, args: [ registryDatastore.address, + hcaFactory.address, registryMetadata.address, deployer, ROLES.ALL, @@ -23,6 +27,6 @@ export default execute( }, { tags: ["RootRegistry", "l1"], - dependencies: ["RegistryDatastore", "RegistryMetadata"], + dependencies: ["RegistryDatastore", "HCAFactory", "RegistryMetadata"], }, ); diff --git a/contracts/deploy/l1/01_ReverseRegistry.ts b/contracts/deploy/l1/01_ReverseRegistry.ts index 9d301a9e..0d44cbe6 100644 --- a/contracts/deploy/l1/01_ReverseRegistry.ts +++ b/contracts/deploy/l1/01_ReverseRegistry.ts @@ -14,6 +14,9 @@ export default execute( const registryDatastore = get<(typeof artifacts.RegistryDatastore)["abi"]>("RegistryDatastore"); + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); + const registryMetadata = get< (typeof artifacts.SimpleRegistryMetadata)["abi"] >("SimpleRegistryMetadata"); @@ -24,6 +27,7 @@ export default execute( artifact: artifacts.PermissionedRegistry, args: [ registryDatastore.address, + hcaFactory.address, registryMetadata.address, deployer, ROLES.ALL, @@ -50,6 +54,7 @@ export default execute( "DefaultReverseResolver", "RootRegistry", "RegistryDatastore", + "HCAFactory", "SimpleRegistryMetadata", ], }, diff --git a/contracts/deploy/l1/02_ETHRegistry.ts b/contracts/deploy/l1/02_ETHRegistry.ts index 1e6ff90b..72040c36 100644 --- a/contracts/deploy/l1/02_ETHRegistry.ts +++ b/contracts/deploy/l1/02_ETHRegistry.ts @@ -1,7 +1,7 @@ import { artifacts, execute } from "@rocketh"; import { MAX_EXPIRY, ROLES } from "../constants.js"; - // TODO: ownership +// TODO: ownership export default execute( async ({ deploy, execute: write, get, namedAccounts: { deployer } }) => { const rootRegistry = @@ -10,6 +10,9 @@ export default execute( const registryDatastore = get<(typeof artifacts.RegistryDatastore)["abi"]>("RegistryDatastore"); + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); + const registryMetadata = get< (typeof artifacts.SimpleRegistryMetadata)["abi"] >("SimpleRegistryMetadata"); @@ -22,6 +25,7 @@ export default execute( artifact: artifacts.PermissionedRegistry, args: [ registryDatastore.address, + hcaFactory.address, registryMetadata.address, deployer, ROLES.ALL, @@ -33,7 +37,7 @@ export default execute( functionName: "register", args: [ "eth", - deployer, + deployer, ethRegistry.address, ethTLDResolver.address, 0n, @@ -46,6 +50,7 @@ export default execute( dependencies: [ "RootRegistry", "RegistryDatastore", + "HCAFactory", "RegistryMetadata", "ETHTLDResolver", ], diff --git a/contracts/deploy/l2/01_ETHRegistry.ts b/contracts/deploy/l2/01_ETHRegistry.ts index 7f2995cd..fa35f0cd 100644 --- a/contracts/deploy/l2/01_ETHRegistry.ts +++ b/contracts/deploy/l2/01_ETHRegistry.ts @@ -6,6 +6,9 @@ export default execute( const registryDatastore = get<(typeof artifacts.RegistryDatastore)["abi"]>("RegistryDatastore"); + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); + const registryMetadata = get< (typeof artifacts.SimpleRegistryMetadata)["abi"] >("SimpleRegistryMetadata"); @@ -15,6 +18,7 @@ export default execute( artifact: artifacts.PermissionedRegistry, args: [ registryDatastore.address, + hcaFactory.address, registryMetadata.address, deployer, ROLES.ALL, @@ -23,6 +27,6 @@ export default execute( }, { tags: ["ETHRegistry", "l2"], - dependencies: ["RegistryDatastore", "RegistryMetadata"], + dependencies: ["RegistryDatastore", "HCAFactory", "RegistryMetadata"], }, ); diff --git a/contracts/deploy/shared/00_RegistryMetadata.ts b/contracts/deploy/shared/00_HCAFactory.ts similarity index 52% rename from contracts/deploy/shared/00_RegistryMetadata.ts rename to contracts/deploy/shared/00_HCAFactory.ts index d1a32964..11cd5351 100644 --- a/contracts/deploy/shared/00_RegistryMetadata.ts +++ b/contracts/deploy/shared/00_HCAFactory.ts @@ -2,11 +2,14 @@ import { artifacts, execute } from "@rocketh"; export default execute( async ({ deploy, namedAccounts: { deployer } }) => { - await deploy("SimpleRegistryMetadata", { + // TODO: deploy the actual HCAFactory + await deploy("HCAFactory", { account: deployer, - artifact: artifacts.SimpleRegistryMetadata, + artifact: artifacts.MockHCAFactoryBasic, args: [], }); }, - { tags: ["RegistryMetadata", "shared"] }, + { + tags: ["HCAFactory", "shared"], + }, ); diff --git a/contracts/deploy/shared/01_RegistryMetadata.ts b/contracts/deploy/shared/01_RegistryMetadata.ts new file mode 100644 index 00000000..9d009eb8 --- /dev/null +++ b/contracts/deploy/shared/01_RegistryMetadata.ts @@ -0,0 +1,15 @@ +import { artifacts, execute } from "@rocketh"; + +export default execute( + async ({ deploy, get, namedAccounts: { deployer } }) => { + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); + + await deploy("SimpleRegistryMetadata", { + account: deployer, + artifact: artifacts.SimpleRegistryMetadata, + args: [hcaFactory.address], + }); + }, + { tags: ["RegistryMetadata", "HCAFactory", "shared"] }, +); diff --git a/contracts/script/setup.ts b/contracts/script/setup.ts index 7d7ab8a0..1426be30 100644 --- a/contracts/script/setup.ts +++ b/contracts/script/setup.ts @@ -296,6 +296,7 @@ export async function setupCrossChainEnvironment({ universalResolver: l1Contracts("UniversalResolverV2"), // shared registryDatastore: l1Contracts("RegistryDatastore"), + hcaFactory: l1Contracts("HCAFactory"), simpleRegistryMetadata: l1Contracts("SimpleRegistryMetadata"), dedicatedResolverFactory: l1Contracts( "VerifiableFactory", @@ -328,6 +329,7 @@ export async function setupCrossChainEnvironment({ mockDAI: l2Contracts("src/mocks/MockERC20.sol/MockERC20", "MockDAI"), // shared registryDatastore: l2Contracts("RegistryDatastore"), + hcaFactory: l2Contracts("HCAFactory"), simpleRegistryMetadata: l2Contracts("SimpleRegistryMetadata"), dedicatedResolverFactory: l2Contracts( "VerifiableFactory", @@ -425,6 +427,7 @@ export async function setupCrossChainEnvironment({ bytecode: artifacts.PermissionedRegistry.bytecode, args: [ this.contracts.registryDatastore.address, + this.contracts.hcaFactory.address, this.contracts.simpleRegistryMetadata.address, account.address, roles, From a7559d924b589e926a9ed04871f6e827eca228eb Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 3 Oct 2025 17:28:40 +1000 Subject: [PATCH 6/7] fix setup type --- contracts/script/setup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/script/setup.ts b/contracts/script/setup.ts index 1426be30..2adddc69 100644 --- a/contracts/script/setup.ts +++ b/contracts/script/setup.ts @@ -296,7 +296,7 @@ export async function setupCrossChainEnvironment({ universalResolver: l1Contracts("UniversalResolverV2"), // shared registryDatastore: l1Contracts("RegistryDatastore"), - hcaFactory: l1Contracts("HCAFactory"), + hcaFactory: l1Contracts("MockHCAFactoryBasic", "HCAFactory"), simpleRegistryMetadata: l1Contracts("SimpleRegistryMetadata"), dedicatedResolverFactory: l1Contracts( "VerifiableFactory", @@ -329,7 +329,7 @@ export async function setupCrossChainEnvironment({ mockDAI: l2Contracts("src/mocks/MockERC20.sol/MockERC20", "MockDAI"), // shared registryDatastore: l2Contracts("RegistryDatastore"), - hcaFactory: l2Contracts("HCAFactory"), + hcaFactory: l2Contracts("MockHCAFactoryBasic", "HCAFactory"), simpleRegistryMetadata: l2Contracts("SimpleRegistryMetadata"), dedicatedResolverFactory: l2Contracts( "VerifiableFactory", From c239c8cd57dc5fefe881069423b251d658e21e06 Mon Sep 17 00:00:00 2001 From: tate Date: Wed, 8 Oct 2025 10:04:16 +1100 Subject: [PATCH 7/7] add to dedicatedresolver --- ...atedResolver.ts => 01_DedicatedResolver.ts} | 6 +++++- ...pgradable.sol => HCAContextUpgradeable.sol} | 4 ++-- .../src/common/resolver/DedicatedResolver.sol | 18 ++++++++++++++++++ .../integration/fixtures/deployV2Fixture.ts | 6 ++++-- ...dable.t.sol => HCAContextUpgradeable.t.sol} | 10 +++++----- .../common/resolver/DedicatedResolver.t.sol | 5 ++++- 6 files changed, 38 insertions(+), 11 deletions(-) rename contracts/deploy/shared/{00_DedicatedResolver.ts => 01_DedicatedResolver.ts} (63%) rename contracts/src/common/hca/{HCAContextUpgradable.sol => HCAContextUpgradeable.sol} (68%) rename contracts/test/unit/common/hca/{HCAContextUpgradable.t.sol => HCAContextUpgradeable.t.sol} (82%) diff --git a/contracts/deploy/shared/00_DedicatedResolver.ts b/contracts/deploy/shared/01_DedicatedResolver.ts similarity index 63% rename from contracts/deploy/shared/00_DedicatedResolver.ts rename to contracts/deploy/shared/01_DedicatedResolver.ts index ecec7507..538eb96a 100755 --- a/contracts/deploy/shared/00_DedicatedResolver.ts +++ b/contracts/deploy/shared/01_DedicatedResolver.ts @@ -1,7 +1,9 @@ import { artifacts, execute } from "@rocketh"; export default execute( - async ({ deploy, namedAccounts: { deployer } }) => { + async ({ deploy, get, namedAccounts: { deployer } }) => { + const hcaFactory = + get<(typeof artifacts.MockHCAFactoryBasic)["abi"]>("HCAFactory"); await deploy("DedicatedResolverFactory", { account: deployer, artifact: artifacts.VerifiableFactory, @@ -10,9 +12,11 @@ export default execute( await deploy("DedicatedResolverImpl", { account: deployer, artifact: artifacts.DedicatedResolver, + args: [hcaFactory.address], }); }, { tags: ["DedicatedResolver", "shared"], + dependencies: ["HCAFactory"], }, ); diff --git a/contracts/src/common/hca/HCAContextUpgradable.sol b/contracts/src/common/hca/HCAContextUpgradeable.sol similarity index 68% rename from contracts/src/common/hca/HCAContextUpgradable.sol rename to contracts/src/common/hca/HCAContextUpgradeable.sol index 1e2bf8ab..99cb0045 100644 --- a/contracts/src/common/hca/HCAContextUpgradable.sol +++ b/contracts/src/common/hca/HCAContextUpgradeable.sol @@ -6,9 +6,9 @@ import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/Cont import {HCAEquivalence} from "./HCAEquivalence.sol"; /// @dev Replaces msg.sender -abstract contract HCAContextUpgradable is ContextUpgradeable, HCAEquivalence { +abstract contract HCAContextUpgradeable is ContextUpgradeable, HCAEquivalence { /// @notice Returns either the account owner of an HCA or the original sender - function _msgSender() internal view virtual override returns (address) { + function _msgSender() internal view virtual override(ContextUpgradeable) returns (address) { return _msgSenderWithHcaEquivalence(); } } diff --git a/contracts/src/common/resolver/DedicatedResolver.sol b/contracts/src/common/resolver/DedicatedResolver.sol index b243ddc9..3463713a 100644 --- a/contracts/src/common/resolver/DedicatedResolver.sol +++ b/contracts/src/common/resolver/DedicatedResolver.sol @@ -18,9 +18,14 @@ import {IERC7996} from "@ens/contracts/utils/IERC7996.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {HCAContextUpgradeable} from "../hca/HCAContextUpgradeable.sol"; +import {HCAEquivalence} from "../hca/HCAEquivalence.sol"; +import {IHCAFactoryBasic} from "../hca/interfaces/IHCAFactoryBasic.sol"; + import {IDedicatedResolverSetters, NODE_ANY} from "./interfaces/IDedicatedResolverSetters.sol"; /// @title DedicatedResolver @@ -28,6 +33,7 @@ import {IDedicatedResolverSetters, NODE_ANY} from "./interfaces/IDedicatedResolv contract DedicatedResolver is ERC165, OwnableUpgradeable, + HCAContextUpgradeable, IDedicatedResolverSetters, IERC7996, IExtendedResolver, @@ -78,6 +84,8 @@ contract DedicatedResolver is // Initialization //////////////////////////////////////////////////////////////////////// + constructor(IHCAFactoryBasic hcaFactory) HCAEquivalence(hcaFactory) {} + /// @dev Initialize the contract. /// @param owner The owner of the resolver. function initialize(address owner) public initializer { @@ -296,6 +304,16 @@ contract DedicatedResolver is // Internal Functions //////////////////////////////////////////////////////////////////////// + function _msgSender() + internal + view + virtual + override(ContextUpgradeable, HCAContextUpgradeable) + returns (address) + { + return HCAContextUpgradeable._msgSender(); + } + /// @dev Returns true if `x` has a single bit set. function _isPowerOf2(uint256 x) internal pure returns (bool) { return x > 0 && (x - 1) & x == 0; diff --git a/contracts/test/integration/fixtures/deployV2Fixture.ts b/contracts/test/integration/fixtures/deployV2Fixture.ts index 8d600d43..896698c0 100644 --- a/contracts/test/integration/fixtures/deployV2Fixture.ts +++ b/contracts/test/integration/fixtures/deployV2Fixture.ts @@ -56,8 +56,10 @@ export async function deployV2Fixture( ]); const dedicatedResolverFactory = await network.viem.deployContract("VerifiableFactory"); - const dedicatedResolverImpl = - await network.viem.deployContract("DedicatedResolver"); + const dedicatedResolverImpl = await network.viem.deployContract( + "DedicatedResolver", + [hcaFactory.address], + ); return { network, publicClient, diff --git a/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol b/contracts/test/unit/common/hca/HCAContextUpgradeable.t.sol similarity index 82% rename from contracts/test/unit/common/hca/HCAContextUpgradable.t.sol rename to contracts/test/unit/common/hca/HCAContextUpgradeable.t.sol index e25fbeb8..e9044b46 100644 --- a/contracts/test/unit/common/hca/HCAContextUpgradable.t.sol +++ b/contracts/test/unit/common/hca/HCAContextUpgradeable.t.sol @@ -5,12 +5,12 @@ pragma solidity ^0.8.25; import {Test} from "forge-std/Test.sol"; -import {HCAContextUpgradable} from "~src/common/hca/HCAContextUpgradable.sol"; +import {HCAContextUpgradeable} from "~src/common/hca/HCAContextUpgradeable.sol"; import {HCAEquivalence} from "~src/common/hca/HCAEquivalence.sol"; import {IHCAFactoryBasic} from "~src/common/hca/interfaces/IHCAFactoryBasic.sol"; import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; -contract HCAContextUpgradableHarness is HCAContextUpgradable { +contract HCAContextUpgradeableHarness is HCAContextUpgradeable { constructor(IHCAFactoryBasic factory) HCAEquivalence(factory) {} function exposedMsgSender() external view returns (address) { @@ -18,9 +18,9 @@ contract HCAContextUpgradableHarness is HCAContextUpgradable { } } -contract HCAContextUpgradableTest is Test { +contract HCAContextUpgradeableTest is Test { MockHCAFactoryBasic factory; - HCAContextUpgradableHarness harness; + HCAContextUpgradeableHarness harness; address user = address(0x1111); address hca = address(0xAAAA); @@ -28,7 +28,7 @@ contract HCAContextUpgradableTest is Test { function setUp() public { factory = new MockHCAFactoryBasic(); - harness = new HCAContextUpgradableHarness(factory); + harness = new HCAContextUpgradeableHarness(factory); } function test_constructor_sets_factory() public view { diff --git a/contracts/test/unit/common/resolver/DedicatedResolver.t.sol b/contracts/test/unit/common/resolver/DedicatedResolver.t.sol index 4a250a35..08ef1249 100644 --- a/contracts/test/unit/common/resolver/DedicatedResolver.t.sol +++ b/contracts/test/unit/common/resolver/DedicatedResolver.t.sol @@ -27,6 +27,7 @@ import { IDedicatedResolverSetters, NODE_ANY } from "~src/common/resolver/interfaces/IDedicatedResolverSetters.sol"; +import {MockHCAFactoryBasic} from "~src/mocks/MockHCAFactoryBasic.sol"; contract DedicatedResolverTest is Test { struct I { @@ -53,6 +54,7 @@ contract DedicatedResolverTest is Test { } address owner; + MockHCAFactoryBasic hcaFactory; DedicatedResolver resolver; string testName = "test.eth"; @@ -60,7 +62,8 @@ contract DedicatedResolverTest is Test { function setUp() external { VerifiableFactory factory = new VerifiableFactory(); - DedicatedResolver resolverImpl = new DedicatedResolver(); + hcaFactory = new MockHCAFactoryBasic(); + DedicatedResolver resolverImpl = new DedicatedResolver(hcaFactory); owner = makeAddr("owner"); bytes memory initData = abi.encodeCall(DedicatedResolver.initialize, (owner));