// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./EOARegistry.sol";
import "../interfaces/IOwnable.sol";
import "../interfaces/ICreatorTokenTransferValidator.sol";
import "@openzeppelin/contracts/access/IAccessControl.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* @title CreatorTokenTransferValidator
* @author Limit Break, Inc.
* @notice The CreatorTokenTransferValidator contract is designed to provide a customizable and secure transfer
* validation mechanism for NFT collections. This contract allows the owner of an NFT collection to configure
* the transfer security level, operator whitelist, and permitted contract receiver allowlist for each
* collection.
*
* @dev
Features
* - Transfer security levels: Provides different levels of transfer security,
* from open transfers to completely restricted transfers.
* - Operator whitelist: Allows the owner of a collection to whitelist specific operator addresses permitted
* to execute transfers on behalf of others.
* - Permitted contract receiver allowlist: Enables the owner of a collection to allow specific contract
* addresses to receive NFTs when otherwise disabled by security policy.
*
* @dev Benefits
* - Enhanced security: Allows creators to have more control over their NFT collections, ensuring the safety
* and integrity of their assets.
* - Flexibility: Provides collection owners the ability to customize transfer rules as per their requirements.
* - Compliance: Facilitates compliance with regulations by enabling creators to restrict transfers based on
* specific criteria.
*
* @dev Intended Usage
* - The CreatorTokenTransferValidator contract is intended to be used by NFT collection owners to manage and
* enforce transfer policies. This contract is integrated with the following varations of creator token
* NFT contracts to validate transfers according to the defined security policies.
*
* - ERC721-C: Creator token implenting OpenZeppelin's ERC-721 standard.
* - ERC721-AC: Creator token implenting Azuki's ERC-721A standard.
* - ERC721-CW: Creator token implementing OpenZeppelin's ERC-721 standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-721 collection.
* - ERC721-ACW: Creator token implementing Azuki's ERC721-A standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-721 collection.
* - ERC1155-C: Creator token implenting OpenZeppelin's ERC-1155 standard.
* - ERC1155-CW: Creator token implementing OpenZeppelin's ERC-1155 standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-1155 collection.
*
* Transfer Security Levels
* - Level 0 (Zero): No transfer restrictions.
* - Caller Constraints: None
* - Receiver Constraints: None
* - Level 1 (One): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading enabled.
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: None
* - Level 2 (Two): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading disabled.
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: None
* - Level 3 (Three): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading enabled. Transfers to contracts with code are not allowed.
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: NoCode
* - Level 4 (Four): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading enabled. Transfers are allowed only to Externally Owned Accounts (EOAs).
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: EOA
* - Level 5 (Five): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading disabled. Transfers to contracts with code are not allowed.
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: NoCode
* - Level 6 (Six): Only whitelisted operators can initiate transfers, with over-the-counter (OTC) trading disabled. Transfers are allowed only to Externally Owned Accounts (EOAs).
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: EOA
*/
contract CreatorTokenTransferValidator is EOARegistry, ICreatorTokenTransferValidator {
using EnumerableSet for EnumerableSet.AddressSet;
error CreatorTokenTransferValidator__AddressAlreadyAllowed();
error CreatorTokenTransferValidator__AddressNotAllowed();
error CreatorTokenTransferValidator__AllowlistDoesNotExist();
error CreatorTokenTransferValidator__AllowlistOwnershipCannotBeTransferredToZeroAddress();
error CreatorTokenTransferValidator__CallerDoesNotOwnAllowlist();
error CreatorTokenTransferValidator__CallerMustBeWhitelistedOperator();
error CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
error CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
error CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = TransferSecurityLevels.Zero;
uint120 private lastOperatorWhitelistId;
uint120 private lastPermittedContractReceiverAllowlistId;
mapping (TransferSecurityLevels => TransferSecurityPolicy) public transferSecurityPolicies;
mapping (address => CollectionSecurityPolicy) private collectionSecurityPolicies;
mapping (uint120 => address) public operatorWhitelistOwners;
mapping (uint120 => address) public permittedContractReceiverAllowlistOwners;
mapping (uint120 => EnumerableSet.AddressSet) private operatorWhitelists;
mapping (uint120 => EnumerableSet.AddressSet) private permittedContractReceiverAllowlists;
constructor(address defaultOwner) EOARegistry() {
transferSecurityPolicies[TransferSecurityLevels.Zero] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.None,
receiverConstraints: ReceiverConstraints.None
});
transferSecurityPolicies[TransferSecurityLevels.One] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
receiverConstraints: ReceiverConstraints.None
});
transferSecurityPolicies[TransferSecurityLevels.Two] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
receiverConstraints: ReceiverConstraints.None
});
transferSecurityPolicies[TransferSecurityLevels.Three] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
receiverConstraints: ReceiverConstraints.NoCode
});
transferSecurityPolicies[TransferSecurityLevels.Four] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistEnableOTC,
receiverConstraints: ReceiverConstraints.EOA
});
transferSecurityPolicies[TransferSecurityLevels.Five] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
receiverConstraints: ReceiverConstraints.NoCode
});
transferSecurityPolicies[TransferSecurityLevels.Six] = TransferSecurityPolicy({
callerConstraints: CallerConstraints.OperatorWhitelistDisableOTC,
receiverConstraints: ReceiverConstraints.EOA
});
uint120 id = ++lastOperatorWhitelistId;
operatorWhitelistOwners[id] = defaultOwner;
emit CreatedAllowlist(AllowlistTypes.Operators, id, "DEFAULT OPERATOR WHITELIST");
emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, defaultOwner);
}
/**
* @notice Apply the collection transfer policy to a transfer operation of a creator token.
*
* @dev Throws when the receiver has deployed code but is not in the permitted contract receiver allowlist,
* if the ReceiverConstraints is set to NoCode.
* @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
* is not in the permitted contract receiver allowlist, if the ReceiverConstraints is set to EOA.
* @dev Throws when `msg.sender` is not a whitelisted operator, if CallerConstraints is OperatorWhitelistDisableOTC.
* @dev Throws when `msg.sender` is neither a whitelisted operator nor the 'from' addresses,
* if CallerConstraints is OperatorWhitelistEnableOTC.
*
* @dev Postconditions:
* 1. Transfer is allowed or denied based on the applied transfer policy.
*
* @param caller The address initiating the transfer.
* @param from The address of the token owner.
* @param to The address of the token receiver.
*/
function applyCollectionTransferPolicy(address caller, address from, address to) external view override {
address collection = _msgSender();
CollectionSecurityPolicy memory collectionSecurityPolicy = collectionSecurityPolicies[collection];
TransferSecurityPolicy memory transferSecurityPolicy =
transferSecurityPolicies[collectionSecurityPolicy.transferSecurityLevel];
if (transferSecurityPolicy.receiverConstraints == ReceiverConstraints.NoCode) {
if (to.code.length > 0) {
if (!isContractReceiverPermitted(collectionSecurityPolicy.permittedContractReceiversId, to)) {
revert CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
}
}
} else if (transferSecurityPolicy.receiverConstraints == ReceiverConstraints.EOA) {
if (!isVerifiedEOA(to)) {
if (!isContractReceiverPermitted(collectionSecurityPolicy.permittedContractReceiversId, to)) {
revert CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
}
}
}
if (transferSecurityPolicy.callerConstraints != CallerConstraints.None) {
if(operatorWhitelists[collectionSecurityPolicy.operatorWhitelistId].length() > 0) {
if (!isOperatorWhitelisted(collectionSecurityPolicy.operatorWhitelistId, caller)) {
if (transferSecurityPolicy.callerConstraints == CallerConstraints.OperatorWhitelistEnableOTC) {
if (caller != from) {
revert CreatorTokenTransferValidator__CallerMustBeWhitelistedOperator();
}
} else {
revert CreatorTokenTransferValidator__CallerMustBeWhitelistedOperator();
}
}
}
}
}
/**
* @notice Create a new operator whitelist.
*
* @dev Postconditions:
* 1. A new operator whitelist with the specified name is created.
* 2. The caller is set as the owner of the new operator whitelist.
* 3. A `CreatedAllowlist` event is emitted.
* 4. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param name The name of the new operator whitelist.
* @return The id of the new operator whitelist.
*/
function createOperatorWhitelist(string calldata name) external override returns (uint120) {
uint120 id = ++lastOperatorWhitelistId;
operatorWhitelistOwners[id] = _msgSender();
emit CreatedAllowlist(AllowlistTypes.Operators, id, name);
emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, _msgSender());
return id;
}
/**
* @notice Create a new permitted contract receiver allowlist.
*
* @dev Postconditions:
* 1. A new permitted contract receiver allowlist with the specified name is created.
* 2. The caller is set as the owner of the new permitted contract receiver allowlist.
* 3. A `CreatedAllowlist` event is emitted.
* 4. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param name The name of the new permitted contract receiver allowlist.
* @return The id of the new permitted contract receiver allowlist.
*/
function createPermittedContractReceiverAllowlist(string calldata name) external override returns (uint120) {
uint120 id = ++lastPermittedContractReceiverAllowlistId;
permittedContractReceiverAllowlistOwners[id] = _msgSender();
emit CreatedAllowlist(AllowlistTypes.PermittedContractReceivers, id, name);
emit ReassignedAllowlistOwnership(AllowlistTypes.PermittedContractReceivers, id, _msgSender());
return id;
}
/**
* @notice Transfer ownership of an operator whitelist to a new owner.
*
* @dev Throws when the new owner is the zero address.
* @dev Throws when the caller does not own the specified operator whitelist.
*
* @dev Postconditions:
* 1. The operator whitelist ownership is transferred to the new owner.
* 2. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param id The id of the operator whitelist.
* @param newOwner The address of the new owner.
*/
function reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) external override {
if(newOwner == address(0)) {
revert CreatorTokenTransferValidator__AllowlistOwnershipCannotBeTransferredToZeroAddress();
}
_reassignOwnershipOfOperatorWhitelist(id, newOwner);
}
/**
* @notice Transfer ownership of a permitted contract receiver allowlist to a new owner.
*
* @dev Throws when the new owner is the zero address.
* @dev Throws when the caller does not own the specified permitted contract receiver allowlist.
*
* @dev Postconditions:
* 1. The permitted contract receiver allowlist ownership is transferred to the new owner.
* 2. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param id The id of the permitted contract receiver allowlist.
* @param newOwner The address of the new owner.
*/
function reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) external override {
if(newOwner == address(0)) {
revert CreatorTokenTransferValidator__AllowlistOwnershipCannotBeTransferredToZeroAddress();
}
_reassignOwnershipOfPermittedContractReceiverAllowlist(id, newOwner);
}
/**
* @notice Renounce the ownership of an operator whitelist, rendering the whitelist immutable.
*
* @dev Throws when the caller does not own the specified operator whitelist.
*
* @dev Postconditions:
* 1. The ownership of the specified operator whitelist is renounced.
* 2. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param id The id of the operator whitelist.
*/
function renounceOwnershipOfOperatorWhitelist(uint120 id) external override {
_reassignOwnershipOfOperatorWhitelist(id, address(0));
}
/**
* @notice Renounce the ownership of a permitted contract receiver allowlist, rendering the allowlist immutable.
*
* @dev Throws when the caller does not own the specified permitted contract receiver allowlist.
*
* @dev Postconditions:
* 1. The ownership of the specified permitted contract receiver allowlist is renounced.
* 2. A `ReassignedAllowlistOwnership` event is emitted.
*
* @param id The id of the permitted contract receiver allowlist.
*/
function renounceOwnershipOfPermittedContractReceiverAllowlist(uint120 id) external override {
_reassignOwnershipOfPermittedContractReceiverAllowlist(id, address(0));
}
/**
* @notice Set the transfer security level of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev Postconditions:
* 1. The transfer security level of the specified collection is set to the new value.
* 2. A `SetTransferSecurityLevel` event is emitted.
*
* @param collection The address of the collection.
* @param level The new transfer security level to apply.
*/
function setTransferSecurityLevelOfCollection(
address collection,
TransferSecurityLevels level) external override {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
collectionSecurityPolicies[collection].transferSecurityLevel = level;
emit SetTransferSecurityLevel(collection, level);
}
/**
* @notice Set the operator whitelist of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
* @dev Throws when the specified operator whitelist id does not exist.
*
* @dev Postconditions:
* 1. The operator whitelist of the specified collection is set to the new value.
* 2. A `SetAllowlist` event is emitted.
*
* @param collection The address of the collection.
* @param id The id of the operator whitelist.
*/
function setOperatorWhitelistOfCollection(address collection, uint120 id) external override {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
if (id > lastOperatorWhitelistId) {
revert CreatorTokenTransferValidator__AllowlistDoesNotExist();
}
collectionSecurityPolicies[collection].operatorWhitelistId = id;
emit SetAllowlist(AllowlistTypes.Operators, collection, id);
}
/**
* @notice Set the permitted contract receiver allowlist of a collection.
*
* @dev Throws when the caller does not own the specified collection.
* @dev Throws when the specified permitted contract receiver allowlist id does not exist.
*
* @dev Postconditions:
* 1. The permitted contract receiver allowlist of the specified collection is set to the new value.
* 2. A `PermittedContractReceiverAllowlistSet` event is emitted.
*
* @param collection The address of the collection.
* @param id The id of the permitted contract receiver allowlist.
*/
function setPermittedContractReceiverAllowlistOfCollection(address collection, uint120 id) external override {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
if (id > lastPermittedContractReceiverAllowlistId) {
revert CreatorTokenTransferValidator__AllowlistDoesNotExist();
}
collectionSecurityPolicies[collection].permittedContractReceiversId = id;
emit SetAllowlist(AllowlistTypes.PermittedContractReceivers, collection, id);
}
/**
* @notice Add an operator to an operator whitelist.
*
* @dev Throws when the caller does not own the specified operator whitelist.
* @dev Throws when the operator address is already allowed.
*
* @dev Postconditions:
* 1. The operator is added to the specified operator whitelist.
* 2. An `AddedToAllowlist` event is emitted.
*
* @param id The id of the operator whitelist.
* @param operator The address of the operator to add.
*/
function addOperatorToWhitelist(uint120 id, address operator) external override {
_requireCallerOwnsOperatorWhitelist(id);
if (!operatorWhitelists[id].add(operator)) {
revert CreatorTokenTransferValidator__AddressAlreadyAllowed();
}
emit AddedToAllowlist(AllowlistTypes.Operators, id, operator);
}
/**
* @notice Add a contract address to a permitted contract receiver allowlist.
*
* @dev Throws when the caller does not own the specified permitted contract receiver allowlist.
* @dev Throws when the contract address is already allowed.
*
* @dev Postconditions:
* 1. The contract address is added to the specified permitted contract receiver allowlist.
* 2. An `AddedToAllowlist` event is emitted.
*
* @param id The id of the permitted contract receiver allowlist.
* @param receiver The address of the contract to add.
*/
function addPermittedContractReceiverToAllowlist(uint120 id, address receiver) external override {
_requireCallerOwnsPermittedContractReceiverAllowlist(id);
if (!permittedContractReceiverAllowlists[id].add(receiver)) {
revert CreatorTokenTransferValidator__AddressAlreadyAllowed();
}
emit AddedToAllowlist(AllowlistTypes.PermittedContractReceivers, id, receiver);
}
/**
* @notice Remove an operator from an operator whitelist.
*
* @dev Throws when the caller does not own the specified operator whitelist.
* @dev Throws when the operator is not in the specified operator whitelist.
*
* @dev Postconditions:
* 1. The operator is removed from the specified operator whitelist.
* 2. A `RemovedFromAllowlist` event is emitted.
*
* @param id The id of the operator whitelist.
* @param operator The address of the operator to remove.
*/
function removeOperatorFromWhitelist(uint120 id, address operator) external override {
_requireCallerOwnsOperatorWhitelist(id);
if (!operatorWhitelists[id].remove(operator)) {
revert CreatorTokenTransferValidator__AddressNotAllowed();
}
emit RemovedFromAllowlist(AllowlistTypes.Operators, id, operator);
}
/**
* @notice Remove a contract address from a permitted contract receiver allowlist.
*
* @dev Throws when the caller does not own the specified permitted contract receiver allowlist.
* @dev Throws when the contract address is not in the specified permitted contract receiver allowlist.
*
* @dev Postconditions:
* 1. The contract address is removed from the specified permitted contract receiver allowlist.
* 2. A `RemovedFromAllowlist` event is emitted.
*
* @param id The id of the permitted contract receiver allowlist.
* @param receiver The address of the contract to remove.
*/
function removePermittedContractReceiverFromAllowlist(uint120 id, address receiver) external override {
_requireCallerOwnsPermittedContractReceiverAllowlist(id);
if (!permittedContractReceiverAllowlists[id].remove(receiver)) {
revert CreatorTokenTransferValidator__AddressNotAllowed();
}
emit RemovedFromAllowlist(AllowlistTypes.PermittedContractReceivers, id, receiver);
}
/**
* @notice Get the security policy of the specified collection.
* @param collection The address of the collection.
* @return The security policy of the specified collection, which includes:
* Transfer security level, operator whitelist id, permitted contract receiver allowlist id
*/
function getCollectionSecurityPolicy(address collection)
external view override returns (CollectionSecurityPolicy memory) {
return collectionSecurityPolicies[collection];
}
/**
* @notice Get the whitelisted operators in an operator whitelist.
* @param id The id of the operator whitelist.
* @return An array of whitelisted operator addresses.
*/
function getWhitelistedOperators(uint120 id) external view override returns (address[] memory) {
return operatorWhitelists[id].values();
}
/**
* @notice Get the permitted contract receivers in a permitted contract receiver allowlist.
* @param id The id of the permitted contract receiver allowlist.
* @return An array of contract addresses is the permitted contract receiver allowlist.
*/
function getPermittedContractReceivers(uint120 id) external view override returns (address[] memory) {
return permittedContractReceiverAllowlists[id].values();
}
/**
* @notice Check if an operator is in a specified operator whitelist.
* @param id The id of the operator whitelist.
* @param operator The address of the operator to check.
* @return True if the operator is in the specified operator whitelist, false otherwise.
*/
function isOperatorWhitelisted(uint120 id, address operator) public view override returns (bool) {
return operatorWhitelists[id].contains(operator);
}
/**
* @notice Check if a contract address is in a specified permitted contract receiver allowlist.
* @param id The id of the permitted contract receiver allowlist.
* @param receiver The address of the contract to check.
* @return True if the contract address is in the specified permitted contract receiver allowlist,
* false otherwise.
*/
function isContractReceiverPermitted(uint120 id, address receiver) public view override returns (bool) {
return permittedContractReceiverAllowlists[id].contains(receiver);
}
/// @notice ERC-165 Interface Support
function supportsInterface(bytes4 interfaceId) public view virtual override(EOARegistry, IERC165) returns (bool) {
return
interfaceId == type(ITransferValidator).interfaceId ||
interfaceId == type(ITransferSecurityRegistry).interfaceId ||
interfaceId == type(ICreatorTokenTransferValidator).interfaceId ||
super.supportsInterface(interfaceId);
}
function _requireCallerIsNFTOrContractOwnerOrAdmin(address tokenAddress) internal view {
bool callerHasPermissions = false;
if(tokenAddress.code.length > 0) {
callerHasPermissions = _msgSender() == tokenAddress;
if(!callerHasPermissions) {
try IOwnable(tokenAddress).owner() returns (address contractOwner) {
callerHasPermissions = _msgSender() == contractOwner;
} catch {}
if(!callerHasPermissions) {
try IAccessControl(tokenAddress).hasRole(DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, _msgSender())
returns (bool callerIsContractAdmin) {
callerHasPermissions = callerIsContractAdmin;
} catch {}
}
}
}
if(!callerHasPermissions) {
revert CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
}
}
function _reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) private {
_requireCallerOwnsOperatorWhitelist(id);
operatorWhitelistOwners[id] = newOwner;
emit ReassignedAllowlistOwnership(AllowlistTypes.Operators, id, newOwner);
}
function _reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) private {
_requireCallerOwnsPermittedContractReceiverAllowlist(id);
permittedContractReceiverAllowlistOwners[id] = newOwner;
emit ReassignedAllowlistOwnership(AllowlistTypes.PermittedContractReceivers, id, newOwner);
}
function _requireCallerOwnsOperatorWhitelist(uint120 id) private view {
if (_msgSender() != operatorWhitelistOwners[id]) {
revert CreatorTokenTransferValidator__CallerDoesNotOwnAllowlist();
}
}
function _requireCallerOwnsPermittedContractReceiverAllowlist(uint120 id) private view {
if (_msgSender() != permittedContractReceiverAllowlistOwners[id]) {
revert CreatorTokenTransferValidator__CallerDoesNotOwnAllowlist();
}
}
}