diff --git a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol index 3e9b96e9..3ecaa0d0 100644 --- a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol +++ b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.24; // Access Control - import {Role} from "../../../../src/Role.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; @@ -58,6 +57,7 @@ contract OperatorAllowlist is ERC165, OwnableRoles, IOperatorAllowlist { * @param admin the address to grant `Role._MANAGER_ROLE` to */ constructor(address admin) { + _initializeOwner(admin); _grantRoles(admin, Role._MANAGER_ROLE); } diff --git a/test/module/immutable/ImmutableAllowlistERC721.t.sol b/test/module/immutable/ImmutableAllowlistERC721.t.sol index 1622c773..3f09b74f 100644 --- a/test/module/immutable/ImmutableAllowlistERC721.t.sol +++ b/test/module/immutable/ImmutableAllowlistERC721.t.sol @@ -35,12 +35,36 @@ contract Core is ERC721Core { } -contract TransferableERC721Test is Test { +contract DummyContract { + + ERC721Core public immutable erc721Core; + + constructor(address payable _erc721Core) { + erc721Core = ERC721Core(_erc721Core); + } + + function approve(address _to, uint256 _tokenId) external { + erc721Core.approve(_to, _tokenId); + } + + function setApprovalForAll(address _operator) external { + erc721Core.setApprovalForAll(_operator, true); + } + + function transfer(address _to, uint256 _tokenId) external { + erc721Core.transferFrom(address(this), _to, _tokenId); + } + +} + +contract ImmutableAllowlistERC721Test is Test { Core public core; ImmutableAllowlistERC721 public immutableAllowlistModule; OperatorAllowlist public operatorAllowlist; + DummyContract public dummyContract1; + DummyContract public dummyContract2; address public owner = address(0x1); address public actorOne = address(0x2); @@ -55,6 +79,8 @@ contract TransferableERC721Test is Test { core = new Core("test", "TEST", "", owner, modules, moduleData); immutableAllowlistModule = new ImmutableAllowlistERC721(); + + vm.prank(owner); operatorAllowlist = new OperatorAllowlist(owner); // install module @@ -64,6 +90,14 @@ contract TransferableERC721Test is Test { core.installModule(address(immutableAllowlistModule), encodedOperatorAllowlist); vm.stopPrank(); + // set registrar role for owner + vm.prank(owner); + operatorAllowlist.grantRegistrarRole(owner); + + // deploy dummy contract + dummyContract1 = new DummyContract(payable(address(core))); + dummyContract2 = new DummyContract(payable(address(core))); + // mint tokens core.mint(actorOne, 1, string(""), ""); // tokenId 0 core.mint(actorTwo, 1, string(""), ""); // tokenId 1 @@ -73,6 +107,13 @@ contract TransferableERC721Test is Test { core.grantRoles(owner, Role._MANAGER_ROLE); } + function allowlist(address _target) internal { + address[] memory allowlist = new address[](1); + allowlist[0] = _target; + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + } + /*/////////////////////////////////////////////////////////////// Unit tests: `setOperatorAllowlistRegistry` //////////////////////////////////////////////////////////////*/ @@ -97,14 +138,126 @@ contract TransferableERC721Test is Test { } /*/////////////////////////////////////////////////////////////// - Unit tests: `setTransferable` + Unit tests: `beforeApproveERC721` //////////////////////////////////////////////////////////////*/ - /// @notice Callback function for ERC721.approve - function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve) - external - returns (bytes memory) - {} + function test_state_beforeApproveERC721() public { + // passes when msg.sender is an EOA and targetApproval is an EOA + vm.prank(actorOne); + core.approve(actorTwo, 0); + + // set allowlist for dummy contract + address[] memory allowlist = new address[](3); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.startPrank(actorThree); + core.mint(actorThree, 2, string(""), ""); // tokenId 3 + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.approve(address(dummyContract2), 3); + } + + function test_revert_beforeApproveERC721() public { + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.ApproveTargetNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.approve(address(dummyContract1), 0); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeApproveForAll` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeApproveForAllERC721() public { + // passes when msg.sender is an EOA and targetApproval is an EOA + vm.prank(actorOne); + core.setApprovalForAll(actorTwo, true); + + // set allowlist for dummy contract + address[] memory allowlist = new address[](3); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.startPrank(actorThree); + core.mint(actorThree, 1, string(""), ""); // tokenId 3 + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.setApprovalForAll(address(dummyContract2)); + } + + function test_revert_beforeApproveForAllERC721() public { + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.ApproveTargetNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.setApprovalForAll(address(dummyContract1), true); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeTransferERC721` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeTransferERC721() public { + // set allowlist + address[] memory allowlist = new address[](5); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorOne); + allowlist[3] = address(actorTwo); + allowlist[4] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.prank(actorOne); + core.transferFrom(actorOne, actorTwo, 0); + + // passes when msg.sender is an EOA and targetApproval is a contract and is allowlisted + core.mint(actorThree, 1, string(""), ""); // tokenId 3 + vm.startPrank(actorThree); + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.transfer(address(dummyContract2), 3); + } + + function test_revert_beforeTransferERC721() public { + // fails when msg.sender is not allowlisted + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector(OperatorAllowlistEnforcementErrors.CallerNotInAllowlist.selector, actorOne) + ); + core.transferFrom(actorOne, actorTwo, 0); + + // fails when target is not allowlisted + allowlist(actorOne); + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.TransferToNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.transferFrom(actorOne, address(dummyContract1), 0); + } /// @notice Callback function for ERC721.setApprovalForAll function beforeApproveForAll(address _from, address _to, bool _approved) external returns (bytes memory) {}