From 870eb5cd697900c43910045d9543cd1c10e96e08 Mon Sep 17 00:00:00 2001 From: Bulalu Date: Sat, 5 Aug 2023 12:16:36 +0300 Subject: [PATCH 1/3] =?UTF-8?q?added=20ownerOf=20to=20enum=20HookType=20?= =?UTF-8?q?=F0=9F=A7=9E=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/IERC721ACH.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IERC721ACH.sol b/src/interfaces/IERC721ACH.sol index 4b2ee59..a0f7493 100644 --- a/src/interfaces/IERC721ACH.sol +++ b/src/interfaces/IERC721ACH.sol @@ -13,8 +13,9 @@ interface IERC721ACH { /// @notice Hook for custom logic before a token transfer occurs. BeforeTokenTransfers, /// @notice Hook for custom logic after a token transfer occurs. - AfterTokenTransfers - + AfterTokenTransfers, + /// @notice Hook for custom logic for ownerOf() function. + OwnerOf } From 97a9b1a4af611a50965a4b3c4e36a39a05d2eea3 Mon Sep 17 00:00:00 2001 From: Bulalu Date: Sat, 5 Aug 2023 13:16:23 +0300 Subject: [PATCH 2/3] =?UTF-8?q?added=20interface=20&=20tests=20for=20Owner?= =?UTF-8?q?Of=20hook=20=F0=9F=A7=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ERC721ACH.sol | 15 ++++++++ src/interfaces/IOwnerOfHook.sol | 35 ++++++++++++++++++ test/hooks/OwnerOfHook.t.sol | 54 ++++++++++++++++++++++++++++ test/utils/hooks/OwnerOfHookMock.sol | 37 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 src/interfaces/IOwnerOfHook.sol create mode 100644 test/hooks/OwnerOfHook.t.sol create mode 100644 test/utils/hooks/OwnerOfHookMock.sol diff --git a/src/ERC721ACH.sol b/src/ERC721ACH.sol index cbae07a..aeae851 100644 --- a/src/ERC721ACH.sol +++ b/src/ERC721ACH.sol @@ -5,6 +5,7 @@ import {ERC721AC} from "ERC721C/erc721c/ERC721AC.sol"; import {IERC721A} from "erc721a/contracts/IERC721A.sol"; import {IBeforeTokenTransfersHook} from "./interfaces/IBeforeTokenTransfersHook.sol"; import {IAfterTokenTransfersHook} from "./interfaces/IAfterTokenTransfersHook.sol"; +import {IOwnerOfHook} from "./interfaces/IOwnerOfHook.sol"; import {IERC721ACH} from "./interfaces/IERC721ACH.sol"; /** @@ -93,6 +94,20 @@ contract ERC721ACH is ERC721AC, IERC721ACH { } } + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + + IOwnerOfHook ownerOfHook = IOwnerOfHook(hooks[HookType.OwnerOf]); + + if ( + address(ownerOfHook) != address(0) && + ownerOfHook.useOwnerOfHook(tokenId) + ) { + ownerOfHook.ownerOfOverrideHook(tokenId); + } + + return super.ownerOf(tokenId); + } + /** * @notice Returns the contract address for a specified hook type. diff --git a/src/interfaces/IOwnerOfHook.sol b/src/interfaces/IOwnerOfHook.sol new file mode 100644 index 0000000..74e009d --- /dev/null +++ b/src/interfaces/IOwnerOfHook.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/// @title IOwnerOfHook +/// @dev Interface that defines hooks for retrieving the owner of a token. +interface IOwnerOfHook { + + /** + @notice Emitted when the owner of hook is used. + @param tokenId The ID of the token whose owner is being retrieved. + @param owner The address of the owner of the token. + */ + event OwnerOfHookUsed( + uint256 tokenId, + address owner + ); + + /** + @notice Checks if the owner retrieval function should use the custom hook. + @param tokenId The ID of the token whose owner is being retrieved. + @return A boolean indicating whether or not to use the custom hook for the owner retrieval function. + */ + function useOwnerOfHook( + uint256 tokenId + ) external view returns (bool); + + /** + @notice Provides a custom implementation for the owner retrieval process. + @param tokenId The ID of the token whose owner is being retrieved. + @return The address of the owner of the token. + */ + function ownerOfOverrideHook( + uint256 tokenId + ) external view returns (address); +} diff --git a/test/hooks/OwnerOfHook.t.sol b/test/hooks/OwnerOfHook.t.sol new file mode 100644 index 0000000..8e997ba --- /dev/null +++ b/test/hooks/OwnerOfHook.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Vm} from "forge-std/Vm.sol"; +import {DSTest} from "ds-test/test.sol"; +import {ERC721ACHMock} from "../utils/ERC721ACHMock.sol"; +import {IERC721A} from "lib/ERC721A/contracts/IERC721A.sol"; +import {OwnerOfHookMock} from "../utils/hooks/OwnerOfHookMock.sol"; +import {IERC721ACH} from "../../src/interfaces/IERC721ACH.sol"; + +contract OwnerOfHookTest is DSTest { + Vm public constant vm = Vm(HEVM_ADDRESS); + address public constant DEFAULT_OWNER_ADDRESS = address(0xC0FFEE); + address public constant DEFAULT_BUYER_ADDRESS = address(0xBABE); + ERC721ACHMock erc721Mock; + OwnerOfHookMock hookMock; + + IERC721ACH.HookType constant OwnerOf = IERC721ACH.HookType.OwnerOf; + + function setUp() public { + erc721Mock = new ERC721ACHMock(DEFAULT_OWNER_ADDRESS); + hookMock = new OwnerOfHookMock(); + } + + function test_ownerOfHook() public { + assertEq(address(0), address(erc721Mock.getHook(OwnerOf))); + } + + function test_setOwnerOfHook() public { + assertEq(address(0), address(erc721Mock.getHook(OwnerOf))); + + // calling an admin function without being the contract owner should revert + vm.expectRevert(); + erc721Mock.setHook(OwnerOf, address(hookMock)); + + vm.prank(DEFAULT_OWNER_ADDRESS); + erc721Mock.setHook(OwnerOf, address(hookMock)); + assertEq(address(hookMock), address(erc721Mock.getHook(OwnerOf))); + } + + function test_ownerOfHook(uint256 tokenId) public { + vm.assume(tokenId > 0); + vm.assume(tokenId < 10); + + test_setOwnerOfHook(); + erc721Mock.mint(DEFAULT_BUYER_ADDRESS, tokenId); + + assertEq(DEFAULT_BUYER_ADDRESS, erc721Mock.ownerOf(tokenId)); + + hookMock.setHooksEnabled(true); + vm.expectRevert(OwnerOfHookMock.OwnerOfHook_Executed.selector); + erc721Mock.ownerOf(tokenId); + } +} diff --git a/test/utils/hooks/OwnerOfHookMock.sol b/test/utils/hooks/OwnerOfHookMock.sol new file mode 100644 index 0000000..8b97ab1 --- /dev/null +++ b/test/utils/hooks/OwnerOfHookMock.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {IOwnerOfHook} from "../../../src/interfaces/IOwnerOfHook.sol"; + +contract OwnerOfHookMock is IOwnerOfHook { + /// @notice hook was executed + error OwnerOfHook_Executed(); + + bool public hooksEnabled; + address public fixedOwner; + + /// @notice toggle ownerOf hook. + function setHooksEnabled(bool _enabled) public { + hooksEnabled = _enabled; + } + + /// @notice set fixed owner returned by the hook. + function setFixedOwner(address _fixedOwner) public { + fixedOwner = _fixedOwner; + } + + /// @notice Check if the ownerOf function should use hook. + /// @dev Returns whether or not to use the hook for ownerOf function + function useOwnerOfHook( + uint256 + ) external view override returns (bool) { + return hooksEnabled; + } + + /// @notice custom implementation for ownerOf Hook. + function ownerOfOverrideHook( + uint256 + ) external view override returns (address) { + revert OwnerOfHook_Executed(); + } +} From 67d8cb7811b49bcadde35a234ddcf0e2f60f84c7 Mon Sep 17 00:00:00 2001 From: Bulalu Date: Sat, 5 Aug 2023 13:42:34 +0300 Subject: [PATCH 3/3] =?UTF-8?q?updated=20ownerOf=20fn=20=F0=9F=A5=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ERC721ACH.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ERC721ACH.sol b/src/ERC721ACH.sol index aeae851..666ddef 100644 --- a/src/ERC721ACH.sol +++ b/src/ERC721ACH.sol @@ -102,8 +102,8 @@ contract ERC721ACH is ERC721AC, IERC721ACH { address(ownerOfHook) != address(0) && ownerOfHook.useOwnerOfHook(tokenId) ) { - ownerOfHook.ownerOfOverrideHook(tokenId); - } + return ownerOfHook.ownerOfOverrideHook(tokenId); + } return super.ownerOf(tokenId); }