diff --git a/.gitmodules b/.gitmodules index 8d69764..64e850c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/creator-token-standards"] + path = lib/creator-token-standards + url = https://github.com/limitbreakinc/creator-token-standards diff --git a/foundry.toml b/foundry.toml index 1563a5d..0445dba 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,8 +2,9 @@ src = 'src' out = 'out' libs = ['lib'] -solc = "0.8.19" -solc-version = "0.8.19" +solc = "0.8.24" +solc-version = "0.8.24" +evm_version = "cancun" via_ir = true optimizer-runs = 20_000 diff --git a/lib/creator-token-standards b/lib/creator-token-standards new file mode 160000 index 0000000..14eb0cb --- /dev/null +++ b/lib/creator-token-standards @@ -0,0 +1 @@ +Subproject commit 14eb0cb8d486692e8b522d46cc2a06eea800286a diff --git a/remappings.txt b/remappings.txt index 0458d00..c993b31 100644 --- a/remappings.txt +++ b/remappings.txt @@ -9,3 +9,4 @@ erc721a-upgradeable/=lib/chiru-labs/erc721a-upgradeable/ @openzeppelin/=lib/openzeppelin/ @openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/ solady/=lib/solady/src/ +@limitbreak/creator-token-standards/=lib/creator-token-standards/src/ diff --git a/scripts/constants.ts b/scripts/constants.ts index 0aad577..a2935b9 100644 --- a/scripts/constants.ts +++ b/scripts/constants.ts @@ -2,6 +2,7 @@ export const BUILD_DIR = 'build' export const DEPLOYABLE_CONTRACT_NAMES = [ 'ERC20ItemsFactory', 'ERC721ItemsFactory', + 'ERC721CItemsFactory', 'ERC721SaleFactory', 'ERC1155ItemsFactory', 'ERC1155SaleFactory', @@ -10,6 +11,7 @@ export const DEPLOYABLE_CONTRACT_NAMES = [ export const TOKEN_CONTRACT_NAMES = [ 'ERC20Items', 'ERC721Items', + 'ERC721CItems', 'ERC721Sale', 'ERC1155Items', 'ERC1155Sale', diff --git a/src/tokens/ERC721/presets/c_items/ERC721CItems.sol b/src/tokens/ERC721/presets/c_items/ERC721CItems.sol new file mode 100644 index 0000000..efcf901 --- /dev/null +++ b/src/tokens/ERC721/presets/c_items/ERC721CItems.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {ERC721Items} from "@0xsequence/contracts-library/tokens/ERC721/presets/items/ERC721Items.sol"; +import {CreatorTokenBase, ICreatorToken} from "@limitbreak/creator-token-standards/utils/CreatorTokenBase.sol"; +import {TOKEN_TYPE_ERC721} from "@limitbreak/permit-c/Constants.sol"; + +/** + * An implementation of ERC-721 capable of minting when role provided. + */ +contract ERC721CItems is ERC721Items, CreatorTokenBase { + bytes32 internal constant _TRANSFER_ADMIN_ROLE = keccak256("TRANSFER_ADMIN_ROLE"); + + /// @inheritdoc ERC721Items + function initialize( + address owner, + string memory tokenName, + string memory tokenSymbol, + string memory tokenBaseURI, + string memory tokenContractURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) public virtual override { + _grantRole(_TRANSFER_ADMIN_ROLE, owner); + + super.initialize( + owner, tokenName, tokenSymbol, tokenBaseURI, tokenContractURI, royaltyReceiver, royaltyFeeNumerator + ); + } + + function _tokenType() internal pure override returns (uint16) { + return uint16(TOKEN_TYPE_ERC721); + } + + function _requireCallerIsContractOwner() internal view override { + _checkRole(_TRANSFER_ADMIN_ROLE); + } + + function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) { + functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)")); + isViewFunction = true; + } + + /* FIXME + /// @inheritdoc CreatorTokenBase + function getTransferValidator() public view override returns (address validator) { + validator = transferValidator; + // Do not use the default validator + } + */ + + function _beforeTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity) internal virtual override { + for (uint256 i = 0; i < quantity;) { + _validateBeforeTransfer(from, to, startTokenId + i); + unchecked { + ++i; + } + } + } + + function _afterTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity) internal virtual override { + for (uint256 i = 0; i < quantity;) { + _validateAfterTransfer(from, to, startTokenId + i); + unchecked { + ++i; + } + } + } + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return type(ICreatorToken).interfaceId == interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/src/tokens/ERC721/presets/c_items/ERC721CItemsFactory.sol b/src/tokens/ERC721/presets/c_items/ERC721CItemsFactory.sol new file mode 100644 index 0000000..60d1474 --- /dev/null +++ b/src/tokens/ERC721/presets/c_items/ERC721CItemsFactory.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import {IERC721CItemsFactory, IERC721CItemsFactoryFunctions} from + "@0xsequence/contracts-library/tokens/ERC721/presets/c_items/IERC721CItemsFactory.sol"; +import {ERC721CItems} from "@0xsequence/contracts-library/tokens/ERC721/presets/c_items/ERC721CItems.sol"; +import {SequenceProxyFactory} from "@0xsequence/contracts-library/proxies/SequenceProxyFactory.sol"; + +/** + * Deployer of ERC-721C Items proxies. + */ +contract ERC721CItemsFactory is IERC721CItemsFactory, SequenceProxyFactory { + /** + * Creates an ERC-721C Items Factory. + * @param factoryOwner The owner of the ERC-721C Items Factory + */ + constructor(address factoryOwner) { + ERC721CItems impl = new ERC721CItems(); + SequenceProxyFactory._initialize(address(impl), factoryOwner); + } + + /// @inheritdoc IERC721CItemsFactoryFunctions + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + string memory contractURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) + external + returns (address proxyAddr) + { + bytes32 salt = + keccak256(abi.encode(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator)); + proxyAddr = _createProxy(salt, proxyOwner, ""); + ERC721CItems(proxyAddr).initialize(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator); + emit ERC721CItemsDeployed(proxyAddr); + return proxyAddr; + } + + /// @inheritdoc IERC721CItemsFactoryFunctions + function determineAddress( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + string memory contractURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) external view returns (address proxyAddr) + { + bytes32 salt = + keccak256(abi.encode(tokenOwner, name, symbol, baseURI, contractURI, royaltyReceiver, royaltyFeeNumerator)); + return _computeProxyAddress(salt, proxyOwner, ""); + } +} diff --git a/src/tokens/ERC721/presets/c_items/IERC721CItemsFactory.sol b/src/tokens/ERC721/presets/c_items/IERC721CItemsFactory.sol new file mode 100644 index 0000000..80d01b5 --- /dev/null +++ b/src/tokens/ERC721/presets/c_items/IERC721CItemsFactory.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +interface IERC721CItemsFactoryFunctions { + /** + * Creates an ERC-721C Items proxy. + * @param proxyOwner The owner of the ERC-721C Items proxy + * @param tokenOwner The owner of the ERC-721C Items implementation + * @param name The name of the ERC-721C Items proxy + * @param symbol The symbol of the ERC-721C Items proxy + * @param baseURI The base URI of the ERC-721C Items proxy + * @param contractURI The contract URI of the ERC-721C Items proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @return proxyAddr The address of the ERC-721C Items Proxy + */ + function deploy( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + string memory contractURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) + external + returns (address proxyAddr); + + /** + * Computes the address of a proxy instance. + * @param proxyOwner The owner of the ERC-721C Items proxy + * @param tokenOwner The owner of the ERC-721C Items implementation + * @param name The name of the ERC-721C Items proxy + * @param symbol The symbol of the ERC-721C Items proxy + * @param baseURI The base URI of the ERC-721C Items proxy + * @param contractURI The contract URI of the ERC-721C Items proxy + * @param royaltyReceiver Address of who should be sent the royalty payment + * @param royaltyFeeNumerator The royalty fee numerator in basis points (e.g. 15% would be 1500) + * @return proxyAddr The address of the ERC-721C Items Proxy + */ + function determineAddress( + address proxyOwner, + address tokenOwner, + string memory name, + string memory symbol, + string memory baseURI, + string memory contractURI, + address royaltyReceiver, + uint96 royaltyFeeNumerator + ) + external + returns (address proxyAddr); +} + +interface IERC721CItemsFactorySignals { + /** + * Event emitted when a new ERC-721C Items proxy contract is deployed. + * @param proxyAddr The address of the deployed proxy. + */ + event ERC721CItemsDeployed(address proxyAddr); +} + +interface IERC721CItemsFactory is IERC721CItemsFactoryFunctions, IERC721CItemsFactorySignals {} diff --git a/test/_mocks/WalletMock.sol b/test/_mocks/WalletMock.sol new file mode 100644 index 0000000..d593b25 --- /dev/null +++ b/test/_mocks/WalletMock.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +contract WalletMock { + function execute(address to, uint256 value, bytes memory data) external returns (bool, bytes memory) { + (bool success, bytes memory result) = to.call{value: value}(data); + return (success, result); + } +} diff --git a/test/tokens/ERC721/presets/ERC721CItems.t.sol b/test/tokens/ERC721/presets/ERC721CItems.t.sol new file mode 100644 index 0000000..e73d249 --- /dev/null +++ b/test/tokens/ERC721/presets/ERC721CItems.t.sol @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.24; + +import {TestHelper} from "../../../TestHelper.sol"; +import {WalletMock} from "../../../_mocks/WalletMock.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import {ERC721CItems} from "src/tokens/ERC721/presets/c_items/ERC721CItems.sol"; +import {ERC721CItemsFactory} from "src/tokens/ERC721/presets/c_items/ERC721CItemsFactory.sol"; + +import {CreatorTokenTransferValidatorConfiguration} from "@limitbreak/creator-token-standards/utils/CreatorTokenTransferValidatorConfiguration.sol"; +import {CreatorTokenTransferValidator} from "@limitbreak/creator-token-standards/utils/CreatorTokenTransferValidator.sol"; +import {EOARegistry} from "@limitbreak/creator-token-standards/utils/EOARegistry.sol"; + +contract ERC721CItemsTransfersTest is TestHelper { + // Redeclare events + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + ERC721CItems private token; + EOARegistry private eoaRegistry; + CreatorTokenTransferValidatorConfiguration private config; + CreatorTokenTransferValidator private validator; + + address private proxyOwner; + address private owner; + address private holder; + address private operator; + address private blacklisted; + address private whitelisted; + address private wallet; + + uint256 private holderPk; + uint256 private operatorPk; + + uint120 private listId; + + function setUp() public { + owner = makeAddr("owner"); + proxyOwner = makeAddr("proxyOwner"); + (holder, holderPk) = makeAddrAndKey("holder"); + (operator, operatorPk) = makeAddrAndKey("operator"); + blacklisted = makeAddr("blacklisted"); + whitelisted = makeAddr("whitelisted"); + + wallet = address(new WalletMock()); + + vm.deal(address(this), 100 ether); + vm.deal(owner, 100 ether); + + ERC721CItemsFactory factory = new ERC721CItemsFactory(address(this)); + token = ERC721CItems( + factory.deploy(proxyOwner, owner, "name", "symbol", "baseURI", "contractURI", address(this), 0) + ); + + eoaRegistry = new EOARegistry(); + config = new CreatorTokenTransferValidatorConfiguration(address(this)); + config.setNativeValueToCheckPauseState(0); + validator = new CreatorTokenTransferValidator( + address(this), address(eoaRegistry), "CreatorTokenTransferValidator", "3", address(config) + ); + + vm.prank(owner); + token.setTransferValidator(address(validator)); + + // Sign EOAs in registry + bytes32 hashToSign = ECDSA.toEthSignedMessageHash(bytes(eoaRegistry.MESSAGE_TO_SIGN())); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(holderPk, hashToSign); + eoaRegistry.verifySignatureVRS(v, r, s); + (v, r, s) = vm.sign(operatorPk, hashToSign); + eoaRegistry.verifySignatureVRS(v, r, s); + + // Prep for transfer tests + + vm.startPrank(owner); + token.mint(holder, 1); + token.mint(wallet, 1); + vm.stopPrank(); + + vm.startPrank(holder); + token.setApprovalForAll(operator, true); + token.setApprovalForAll(blacklisted, true); + token.setApprovalForAll(whitelisted, true); + token.setApprovalForAll(wallet, true); + vm.stopPrank(); + + vm.startPrank(owner); + validator.setTransferSecurityLevelOfCollection(address(token), 2, true, true, false); + listId = validator.createList("TOKEN"); + address[] memory addressList = new address[](1); + addressList[0] = blacklisted; + validator.addAccountsToBlacklist(listId, addressList); + addressList[0] = whitelisted; + validator.addAccountsToWhitelist(listId, addressList); + // bytes32[] memory codehashes = new bytes32[](1); + // codehashes[0] = wallet.codehash; + // validator.addCodeHashesToWhitelist(listId, codehashes); + validator.applyListToCollection(address(token), listId); + vm.stopPrank(); + } + + function _setLevel(uint8 level) private { + vm.startPrank(owner); + validator.setTransferSecurityLevelOfCollection(address(token), level, true, true, false); + + if (level == 5 || level == 6) { + bytes32[] memory codehashes = new bytes32[](1); + codehashes[0] = wallet.codehash; + validator.addCodeHashesToWhitelist(listId, codehashes); + } + vm.stopPrank(); + } + + function testTransferLevelDefault() public { + vm.prank(holder); + token.transferFrom(holder, owner, 0); + vm.assertEq(token.ownerOf(0), owner); + } + + // No protection + function testTransferLevel1OTC() public { + _setLevel(1); + + vm.prank(holder); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel1WalletOTC() public { + _setLevel(1); + + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + vm.assertEq(token.ownerOf(1), operator); + } + function testTransferLevel1OTCWalletReceiver() public { + _setLevel(1); + + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel1Operator() public { + _setLevel(1); + + vm.prank(operator); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel1WalletOperator() public { + _setLevel(1); + + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + + // Blacklist + OTC + function testTransferLevel2OTC() public { + _setLevel(2); + + vm.prank(holder); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel2WalletOTC() public { + _setLevel(2); + + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + vm.assertEq(token.ownerOf(1), operator); + } + function testTransferLevel2OTCWalletReceiver() public { + _setLevel(2); + + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel2Operator() public { + _setLevel(2); + + vm.prank(operator); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel2WalletOperator() public { + _setLevel(2); + + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel2Blacklisted() public { + _setLevel(2); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + + // Whitelist + OTC + function testTransferLevel3OTC() public { + _setLevel(3); + + vm.prank(holder); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel3WalletOTC() public { + _setLevel(3); + + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + vm.assertEq(token.ownerOf(1), operator); + } + function testTransferLevel3OTCWalletReceiver() public { + _setLevel(3); + + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel3Operator() public { + _setLevel(3); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel3WalletOperator() public { + _setLevel(3); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel3Blacklisted() public { + _setLevel(3); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel3Whitelisted() public { + _setLevel(3); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Whitelist + No OTC + function testTransferLevel4OTC() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel4WalletOTC() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + } + function testTransferLevel4OTCWalletReceiver() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel4Operator() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel4WalletOperator() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel4Blacklisted() public { + _setLevel(4); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel4Whitelisted() public { + _setLevel(4); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Whitelist + OTC + No code receiver + function testTransferLevel5OTC() public { + _setLevel(5); + + vm.prank(holder); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel5WalletOTC() public { + _setLevel(5); + + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + vm.assertEq(token.ownerOf(1), operator); + } + function testTransferLevel5OTCWalletReceiver() public { + _setLevel(5); + + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel5Operator() public { + _setLevel(5); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel5WalletOperator() public { + _setLevel(5); + + vm.expectRevert(); //FIXME This passes as code hash is whitelisted to enable Wallet OTC transfers + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel5Blacklisted() public { + _setLevel(5); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel5Whitelisted() public { + _setLevel(5); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Whitelist + OTC + EOA Receiver + function testTransferLevel6OTC() public { + _setLevel(6); + + vm.prank(holder); + token.transferFrom(holder, operator, 0); + vm.assertEq(token.ownerOf(0), operator); + } + function testTransferLevel6WalletOTC() public { + _setLevel(6); + + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + vm.assertEq(token.ownerOf(1), operator); + } + function testTransferLevel6OTCWalletReceiver() public { + _setLevel(6); + + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + vm.assertEq(token.ownerOf(0), wallet); + } + function testTransferLevel6Operator() public { + _setLevel(6); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel6WalletOperator() public { + _setLevel(6); + + vm.expectRevert(); //FIXME This passes as code hash is whitelisted to enable Wallet OTC transfers + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel6Blacklisted() public { + _setLevel(6); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel6Whitelisted() public { + _setLevel(6); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Whitelist + No OTC + No code receiver + function testTransferLevel7OTC() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel7WalletOTC() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + } + function testTransferLevel7OTCWalletReceiver() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel7Operator() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel7WalletOperator() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel7Blacklisted() public { + _setLevel(7); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel7Whitelisted() public { + _setLevel(7); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Whitelist + No OTC + EOA Receiver + function testTransferLevel8OTC() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel8WalletOTC() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + } + function testTransferLevel8OTCWalletReceiver() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel8Operator() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel8WalletOperator() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel8Blacklisted() public { + _setLevel(8); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel8Whitelisted() public { + _setLevel(8); + + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + vm.assertEq(token.ownerOf(0), whitelisted); + } + + // Soul bound + function testTransferLevel9OTC() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel9WalletOTC() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(wallet, operator, 1); + } + function testTransferLevel9OTCWalletReceiver() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(holder); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel9Operator() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(operator); + token.transferFrom(holder, operator, 0); + } + function testTransferLevel9WalletOperator() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(wallet); + token.transferFrom(holder, wallet, 0); + } + function testTransferLevel9Blacklisted() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(blacklisted); + token.transferFrom(holder, blacklisted, 0); + } + function testTransferLevel9Whitelisted() public { + _setLevel(9); + + vm.expectRevert(); + vm.prank(whitelisted); + token.transferFrom(holder, whitelisted, 0); + } +}