//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;
import "./Constants.sol";
import "./Errors.sol";
import "./interfaces/IPaymentProcessorConfiguration.sol";
import "./interfaces/IPaymentProcessorEvents.sol";
import "./settings-registry/IPaymentProcessorSettings.sol";
import "./modules/PaymentProcessorModule.sol";
import "./storage/PaymentProcessorStorageAccess.sol";
import "./settings-registry/ICollectionSettingsRegistry.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";
import "@limitbreak/tm-core-lib/src/utils/security/RoleClient.sol";
import "@limitbreak/tm-core-lib/src/utils/security/TstorishReentrancyGuard.sol";
/*
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@(
@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@
#@@@@@@@@@@@@@@
@@@@@@@@@@@@
@@@@@@@@@@@@@@* @@@@@@@@@@@@
@@@@@@@@@@@@@@@ @ @@@@@@@@@@@@
@@@@@@@@@@@@@@@ @ @@@@@@@@@@@
@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@
@@@@@@@@@@@@@@@ #@@ @@@@@@@@@@@@/
@@@@@@@@@@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@
@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@&
@@@@@@@@@@@@@@ *@@@@@@@ (@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@% @@@@@@@@@@@@@@@@@@@@@@@@(
@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* @title Payment Processor
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/
contract PaymentProcessor is TstorishReentrancyGuard, RoleClient, PaymentProcessorStorageAccess, IPaymentProcessorEvents, IPaymentProcessorSettings, PaymentProcessorModule {
using EnumerableSet for EnumerableSet.AddressSet;
/// @dev The On-Chain Cancellation module implements of all on-chain cancellation-related functionality.
address private immutable _moduleOnChainCancellation;
/// @dev Implements buyListing and bulkBuyListings trade-related functionality.
address private immutable _moduleBuyListings;
/// @dev Implements acceptOffer and bulkAcceptOffers trade-related functionality.
address private immutable _moduleAcceptOffers;
/// @dev Implements collection sweep trade-related functionality.
address private immutable _moduleSweeps;
constructor(address configurationContract, address roleServer)
TstorishReentrancyGuard()
RoleClient(roleServer)
PaymentProcessorModule(configurationContract) {
(
address defaultContractOwner_,
PaymentProcessorModules memory paymentProcessorModules
) = IPaymentProcessorConfiguration(configurationContract).getPaymentProcessorDeploymentParams();
if (defaultContractOwner_ == address(0) ||
paymentProcessorModules.moduleOnChainCancellation == address(0) ||
paymentProcessorModules.moduleBuyListings == address(0) ||
paymentProcessorModules.moduleAcceptOffers == address(0) ||
paymentProcessorModules.moduleSweeps == address(0)) {
revert PaymentProcessor__InvalidConstructorArguments();
}
_moduleOnChainCancellation = paymentProcessorModules.moduleOnChainCancellation;
_moduleBuyListings = paymentProcessorModules.moduleBuyListings;
_moduleAcceptOffers = paymentProcessorModules.moduleAcceptOffers;
_moduleSweeps = paymentProcessorModules.moduleSweeps;
appStorage().protocolFeeVersions[0] = ProtocolFees({
protocolFeeReceiver: _defaultInfrastructureFeeReceiver,
minimumProtocolFeeBps: DEFAULT_PROTOCOL_FEE_MINIMUM_BPS,
marketplaceFeeProtocolTaxBps: DEFAULT_PROTOCOL_FEE_MARKETPLACE_TAX_BPS,
feeOnTopProtocolTaxBps: DEFAULT_PROTOCOL_FEE_FEE_ON_TOP_TAX_BPS,
versionExpiration: type(uint48).max
});
}
/**************************************************************/
/* MODIFIERS */
/**************************************************************/
/**
* @dev Function modifier that generates a delegatecall to `module` with `selector` as the calldata
* @dev This delegatecall is for functions that do not have parameters. The only calldata added is
* @dev the extra calldata from a trusted forwarder, when present.
*
* @param module The contract address being called in the delegatecall.
* @param selector The 4 byte function selector for the function to call in `module`.
*/
modifier delegateCallNoData(address module, bytes4 selector) {
assembly {
// This protocol is designed to work both via direct calls and calls from a trusted forwarder that
// preserves the original msg.sender by appending an extra 20 bytes to the calldata.
// The following code supports both cases.
let ptr := mload(0x40)
mstore(ptr, selector)
mstore(0x40, add(ptr, calldatasize()))
calldatacopy(add(ptr, 0x04), 0x04, sub(calldatasize(), 0x04))
let result := delegatecall(gas(), module, ptr, calldatasize(), 0, 0)
if iszero(result) {
// Call has failed, retrieve the error message and revert
let size := returndatasize()
returndatacopy(0, 0, size)
revert(0, size)
}
}
_;
}
/**
* @dev Function modifier that generates a delegatecall to `module` with `selector` and `data` as the
* @dev calldata. This delegatecall is for functions that have parameters but **DO NOT** take domain
* @dev separator as a parameter. Additional calldata from a trusted forwarder is appended to the end, when present.
*
* @param module The contract address being called in the delegatecall.
* @param selector The 4 byte function selector for the function to call in `module`.
* @param data The calldata to send to the `module`.
*/
modifier delegateCall(address module, bytes4 selector, bytes calldata data) {
assembly {
// This protocol is designed to work both via direct calls and calls from a trusted forwarder that
// preserves the original msg.sender by appending an extra 20 bytes to the calldata.
// The following code supports both cases. The magic number of 68 is:
// 4 bytes for the selector
// 32 bytes calldata offset to the data parameter
// 32 bytes for the length of the data parameter
let lengthWithAppendedCalldata := sub(calldatasize(), 68)
let ptr := mload(0x40)
mstore(ptr, selector)
calldatacopy(add(ptr,0x04), data.offset, lengthWithAppendedCalldata)
mstore(0x40, add(ptr,add(0x04, lengthWithAppendedCalldata)))
let result := delegatecall(gas(), module, ptr, add(lengthWithAppendedCalldata, 4), 0, 0)
if iszero(result) {
// Call has failed, retrieve the error message and revert
let size := returndatasize()
returndatacopy(0, 0, size)
revert(0, size)
}
}
_;
}
/**************************************************************/
/* READ ONLY ACCESSORS */
/**************************************************************/
/**
* @notice Returns the domain separator for the Payment Processor contract.
*/
function getDomainSeparator() public view returns (bytes32) {
return _cachedDomainSeparator;
}
/**
* @notice Returns the address of the wrapped native coin for the network.
*/
function wrappedNativeCoinAddress() public view returns (address) {
return _wrappedNativeCoinAddress;
}
/**
* @notice Returns the user-specific master nonce that allows order makers to efficiently cancel all listings or offers
* @notice they made previously. The master nonce for a user only changes when they explicitly request to revoke all
* @notice existing listings and offers.
*
* @dev When prompting makers to sign a listing or offer, marketplaces must query the current master nonce of
* @dev the user and include it in the listing/offer signature data.
*/
function masterNonces(address account) public view returns (uint256) {
return appStorage().masterNonces[account];
}
/**
* @notice Returns true if the nonce for the given account has been used or cancelled. In comparison to a master nonce for
* @notice a user, this nonce value is specific to a single order and may only be used or cancelled a single time.
*
* @dev When prompting makers to sign a listing or offer, marketplaces must generate a unique nonce value that
* @dev has not been previously used for filled, unfilled or cancelled orders. User nonces are unique to each
* @dev user but common to that user across all marketplaces that utilize Payment Processor and do not reset
* @dev when the master nonce is incremented. Nonces are stored in a BitMap for gas efficiency so it is recommended
* @dev to utilize sequential numbers that do not overlap with other marketplaces.
*/
function isNonceUsed(address account, uint256 nonce) public view returns (bool isUsed) {
// The following code is equivalent to, but saves gas:
//
// uint256 slot = nonce / 256;
// uint256 offset = nonce % 256;
// uint256 slotValue = appStorage().invalidatedSignatures[account][slot];
// isUsed = ((slotValue >> offset) & ONE) == ONE;
isUsed = ((appStorage().invalidatedSignatures[account][uint248(nonce >> 8)] >> uint8(nonce)) & ONE) == ONE;
}
/**
* @notice Returns the state and remaining fillable quantity of an order digest given the maker address.
*/
function remainingFillableQuantity(
address account,
bytes32 orderDigest
) external view returns (PartiallyFillableOrderStatus memory) {
return appStorage().partiallyFillableOrderStatuses[account][orderDigest];
}
/**
* @notice Returns the payment settings for a given collection.
*
* @notice paymentSettings: The payment setting type for a given collection
* (DefaultPaymentMethodWhitelist|AllowAnyPaymentMethod|CustomPaymentMethodWhitelist|PricingConstraints)
* @notice paymentMethodWhitelistId: The payment method whitelist id for a given collection.
* Applicable only when paymentSettings is CustomPaymentMethodWhitelist
* @notice constrainedPricingPaymentMethod: The payment method that min/max priced collections are priced in.
* Applicable only when paymentSettings is PricingConstraints.
* @notice royaltyBackfillNumerator: The royalty backfill percentage for a given collection. Used only as a
* fallback when a collection does not implement EIP-2981.
* @notice royaltyBountyNumerator: The royalty bounty percentage for a given collection. When set, this percentage
* is applied to the creator's royalty amount and paid to the maker marketplace as a bounty.
* @notice isRoyaltyBountyExclusive: When true, only the designated marketplace is eligible for royalty bounty.
* @notice blockTradesFromUntrustedChannels: When true, only transactions from channels that the collection
* authorizes will be allowed to execute.
*/
function collectionPaymentSettings(address tokenAddress) external view returns (CollectionPaymentSettings memory) {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
return collectionPaymentSettings_;
} else {
bytes32[] memory emptyBytes32;
(CollectionRegistryPaymentSettings memory collectionCoreSettings,,,,,) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
return CollectionPaymentSettings({
initialized: false,
paymentSettings: PaymentSettings(collectionCoreSettings.paymentSettingsType),
paymentMethodWhitelistId: collectionCoreSettings.paymentMethodWhitelistId,
royaltyBackfillReceiver: collectionCoreSettings.royaltyBackfillReceiver,
royaltyBackfillNumerator: collectionCoreSettings.royaltyBackfillNumerator,
royaltyBountyNumerator: collectionCoreSettings.royaltyBountyNumerator,
flags: uint8(collectionCoreSettings.extraData)
});
}
}
/**
* @notice Returns the optional creator-defined royalty bounty settings for a given collection.
*
* @return royaltyBountyNumerator The royalty bounty percentage for a given collection. When set, this percentage
* is applied to the creator's royalty amount and paid to the maker marketplace as a bounty.
* @return exclusiveBountyReceiver When non-zero, only the designated marketplace is eligible for royalty bounty.
*/
function collectionBountySettings(
address tokenAddress
) external view returns (uint16 royaltyBountyNumerator, address exclusiveBountyReceiver) {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
return (
collectionPaymentSettings_.royaltyBountyNumerator,
_isFlagSet(collectionPaymentSettings_.flags, FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE) ?
appStorage().collectionExclusiveBountyReceivers[tokenAddress] :
address(0));
} else {
bytes32[] memory emptyBytes32;
(
CollectionRegistryPaymentSettings memory collectionCoreSettings,,,
address exclusiveBountyReceiver_,,
) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
return (
collectionCoreSettings.royaltyBountyNumerator,
_isFlagSet(uint8(collectionCoreSettings.extraData), FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE) ?
exclusiveBountyReceiver_ : address(0));
}
}
/**
* @notice Returns the optional creator-defined royalty backfill settings for a given collection.
* This is useful for legacy collection lacking EIP-2981 support, as the collection owner can instruct
* PaymentProcessor to backfill missing on-chain royalties.
*
* @return royaltyBackfillNumerator The creator royalty percentage for a given collection.
* When set, this percentage is applied to the item sale price and paid to the creator if the attempt
* to query EIP-2981 royalties fails.
* @return royaltyBackfillReceiver When non-zero, this is the destination address for backfilled creator royalties.
*/
function collectionRoyaltyBackfillSettings(
address tokenAddress
) external view returns (uint16 royaltyBackfillNumerator, address royaltyBackfillReceiver) {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
royaltyBackfillNumerator = collectionPaymentSettings_.royaltyBackfillNumerator;
royaltyBackfillReceiver = collectionPaymentSettings_.royaltyBackfillReceiver;
} else {
bytes32[] memory emptyBytes32;
(
CollectionRegistryPaymentSettings memory collectionCoreSettings,,,,,
) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
royaltyBackfillNumerator = collectionCoreSettings.royaltyBackfillNumerator;
royaltyBackfillReceiver = collectionCoreSettings.royaltyBackfillReceiver;
}
}
/**
* @notice Returns true if the specified payment method is whitelisted for the specified payment method whitelist.
*/
function isPaymentMethodWhitelisted(uint32 paymentMethodWhitelistId, address paymentMethod) external view returns (bool) {
if (appStorage().collectionPaymentMethodWhitelists[paymentMethodWhitelistId].contains(paymentMethod)) {
return true;
}
if (ICollectionSettingsRegistry(collectionSettingsRegistry).isWhitelistedPaymentMethod(paymentMethodWhitelistId, paymentMethod)) {
return true;
}
return false;
}
/**
* @notice Returns the constrained payment method for a given collection, when applicable.
*
* @dev The constrained payment method is only enforced when the collection payment settings are set to
* the PricingContraints type.
*
* @param tokenAddress The address of the collection.
*/
function collectionConstrainedPaymentMethod(address tokenAddress) external view returns (address) {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
if (collectionPaymentSettings_.paymentSettings == PaymentSettings.PricingConstraints ||
collectionPaymentSettings_.paymentSettings == PaymentSettings.PricingConstraintsCollectionOnly) {
return appStorage().collectionConstrainedPricingPaymentMethods[tokenAddress];
}
} else {
bytes32[] memory emptyBytes32;
(
,,address constrainedPricingPaymentMethod,,,
) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
return constrainedPricingPaymentMethod;
}
return address(0);
}
/**
* @notice Returns the pricing bounds floor price for a given collection and token id, when applicable.
*
* @dev The pricing bounds floor price is only enforced when the collection payment settings are set to
* the PricingContraints type.
*
* @param tokenAddress The address of the collection.
* @param tokenId The token id of the item.
*/
function getFloorPrice(address tokenAddress, uint256 tokenId) external view returns (uint256) {
PricingBounds memory tokenLevelPricingBounds = appStorage().tokenPricingBounds[tokenAddress][tokenId];
if (!tokenLevelPricingBounds.initialized) {
RegistryPricingBounds memory registryPricingBounds = ICollectionSettingsRegistry(collectionSettingsRegistry).getTokenBoundPricing(tokenAddress, tokenId);
if (registryPricingBounds.isSet) {
return registryPricingBounds.floorPrice;
}
}
if (tokenLevelPricingBounds.isSet) {
return tokenLevelPricingBounds.floorPrice;
} else {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
PricingBounds memory collectionLevelPricingBounds = appStorage().collectionPricingBounds[tokenAddress];
if (collectionLevelPricingBounds.isSet) {
return collectionLevelPricingBounds.floorPrice;
}
} else {
bytes32[] memory emptyBytes32;
(
,RegistryPricingBounds memory collectionPricingBounds,,,,
) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
if (collectionPricingBounds.isSet) {
return collectionPricingBounds.floorPrice;
}
}
}
return 0;
}
/**
* @notice Returns the pricing bounds ceiling price for a given collection and token id, when applicable.
*
* @dev The pricing bounds ceiling price is only enforced when the collection payment settings are set to
* the PricingConstraints type.
*
* @param tokenAddress The address of the collection.
* @param tokenId The token id of the item.
*/
function getCeilingPrice(address tokenAddress, uint256 tokenId) external view returns (uint256) {
PricingBounds memory tokenLevelPricingBounds = appStorage().tokenPricingBounds[tokenAddress][tokenId];
if (!tokenLevelPricingBounds.initialized) {
RegistryPricingBounds memory registryPricingBounds = ICollectionSettingsRegistry(collectionSettingsRegistry).getTokenBoundPricing(tokenAddress, tokenId);
if (registryPricingBounds.isSet) {
return registryPricingBounds.ceilingPrice;
}
}
if (tokenLevelPricingBounds.isSet) {
return tokenLevelPricingBounds.ceilingPrice;
} else {
CollectionPaymentSettings memory collectionPaymentSettings_ =
appStorage().collectionPaymentSettings[tokenAddress];
if (collectionPaymentSettings_.initialized) {
PricingBounds memory collectionLevelPricingBounds = appStorage().collectionPricingBounds[tokenAddress];
if (collectionLevelPricingBounds.isSet) {
return collectionLevelPricingBounds.ceilingPrice;
}
} else {
bytes32[] memory emptyBytes32;
(
,RegistryPricingBounds memory collectionPricingBounds,,,,
) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, emptyBytes32, emptyBytes32);
if (collectionPricingBounds.isSet) {
return collectionPricingBounds.ceilingPrice;
}
}
}
return type(uint256).max;
}
/**************************************************************/
/* PAYMENT SETTINGS MANAGEMENT OPERATIONS */
/**************************************************************/
/**
* @notice Returns true if the specified payment method is on the deploy-time default payment method whitelist
* or post-deploy default payment method whitelist (id 0).
*
* @param paymentMethod The address of the payment method to check.
*/
function isDefaultPaymentMethod(address paymentMethod) external view returns (bool) {
address[] memory defaultPaymentMethods = _getDefaultPaymentMethods();
for (uint256 i = 0; i < defaultPaymentMethods.length;) {
if (paymentMethod == defaultPaymentMethods[i]) {
return true;
}
unchecked {
++i;
}
}
return ICollectionSettingsRegistry(collectionSettingsRegistry).isWhitelistedPaymentMethod(
DEFAULT_PAYMENT_METHOD_WHITELIST_ID,
paymentMethod
);
}
/**
* @notice Returns an array of the immutable default payment methods specified at deploy time.
* However, if any post-deployment default payment methods have been added, they are
* not returned here because using an enumerable payment method whitelist would make trades
* less gas efficient. For post-deployment default payment methods, exchanges should index
* the `PaymentMethodAddedToWhitelist` and `PaymentMethodRemovedFromWhitelist` events.
*/
function getDefaultPaymentMethods() external view returns (address[] memory) {
return _getDefaultPaymentMethods();
}
/**
* @notice Returns if the specified whitelistId contains the specified payment method.
*
* @param whitelistId The id of the whitelist to check.
* @param paymentMethod The address of the payment method to check.
*/
function checkWhitelistedPaymentMethod(
uint32 whitelistId,
address paymentMethod
) external returns (bool isWhitelisted) {
_requireCallerIsCollectionSettingsRegistryOrSelf();
if (ICollectionSettingsRegistry(collectionSettingsRegistry).isWhitelistedPaymentMethod(whitelistId, paymentMethod)) {
_addWhitelistedPaymentMethod(appStorage().collectionPaymentMethodWhitelists[whitelistId], whitelistId, paymentMethod);
return true;
}
return false;
}
/**
* @notice Updates the payment method whitelist with the specified payment methods.
*
* @param paymentMethodWhitelistId The id of the whitelist to update.
* @param paymentMethods The array of payment methods to add or remove.
* @param paymentMethodsAdded True if the payment methods are being added, false if they are being removed.
*/
function registryUpdateWhitelistPaymentMethods(
uint32 paymentMethodWhitelistId,
address[] calldata paymentMethods,
bool paymentMethodsAdded
) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
EnumerableSet.AddressSet storage ptrPaymentMethods = appStorage().collectionPaymentMethodWhitelists[paymentMethodWhitelistId];
if (paymentMethodsAdded) {
for (uint256 i; i < paymentMethods.length;) {
_addWhitelistedPaymentMethod(ptrPaymentMethods, paymentMethodWhitelistId, paymentMethods[i]);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < paymentMethods.length;) {
_removeWhitelistedPaymentMethod(ptrPaymentMethods, paymentMethodWhitelistId, paymentMethods[i]);
unchecked {
++i;
}
}
}
}
/**
* @notice Checks the collection settings registry if the provided `tokenAddress` has been initialized.
* @notice If it has, pulls the collection settings from the registry and updates the cache, if not,
* @notice initializes the cache with the default settings.
*
* @param tokenAddress The address of the collection to check.
*/
function checkSyncCollectionSettings(address tokenAddress) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
if (ICollectionSettingsRegistry(collectionSettingsRegistry).isCollectionSettingsInitialized(tokenAddress)) {
_syncCollectionSettings(tokenAddress);
} else {
appStorage().collectionPaymentSettings[tokenAddress].initialized = true;
}
}
/**
* @notice Force syncs the collection settings for the specified token address.
*
* @param tokenAddress The address of the collection to sync.
*/
function registrySyncSettings(address tokenAddress) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
_syncCollectionSettings(tokenAddress);
}
/**
* @notice Returns if the specified channel is a trusted channel for the specified collection and caches the result.
*
* @param tokenAddress The address of the collection to check.
* @param channel The address of the channel to check.
*/
function checkCollectionTrustedChannels(
address tokenAddress,
address channel
) external returns (bool isAllowed) {
_requireCallerIsCollectionSettingsRegistryOrSelf();
if (ICollectionSettingsRegistry(collectionSettingsRegistry).isTrustedChannelForCollection(tokenAddress, channel)) {
_addCollectionTrustedChannel(appStorage().collectionTrustedChannels[tokenAddress], tokenAddress, channel);
return true;
}
return false;
}
/**
* @notice Updates the local cache of trusted channels to add or remove the provided `channelsToUpdate`.
*
* @param tokenAddress The address of the collection to update.
* @param channelsToUpdate The array of channels to add or remove.
* @param channelsAdded True if the channels are being added, false if they are being removed.
*/
function registryUpdateTrustedChannels(
address tokenAddress,
address[] calldata channelsToUpdate,
bool channelsAdded
) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
EnumerableSet.AddressSet storage ptrTrustedChannels = appStorage().collectionTrustedChannels[tokenAddress];
if (channelsAdded) {
for (uint256 i; i < channelsToUpdate.length;) {
_addCollectionTrustedChannel(ptrTrustedChannels, tokenAddress, channelsToUpdate[i]);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < channelsToUpdate.length;) {
_removeCollectionTrustedChannel(ptrTrustedChannels, tokenAddress, channelsToUpdate[i]);
unchecked {
++i;
}
}
}
}
/**
* @notice Returns if the specified permit processor is a trusted permit processor for the specified collection
* @notice and caches the result.
*
* @param permitProcessor The address of the permit processor to check.
*/
function checkTrustedPermitProcessors(
address permitProcessor
) external returns (bool isTrusted) {
_requireCallerIsCollectionSettingsRegistryOrSelf();
if (ICollectionSettingsRegistry(collectionSettingsRegistry).isTrustedPermitProcessor(permitProcessor)) {
_addTrustedPermitProcessor(permitProcessor);
return true;
}
return false;
}
/**
* @notice Updates the local cache of trusted permit processors to add or remove the provided `permitProcessors`.
*
* @param permitProcessors The array of permit processors to add or remove.
* @param permitProcessorsAdded True if the permit processors are being added, false if they are being removed.
*/
function registryUpdateTrustedPermitProcessors(
address[] calldata permitProcessors,
bool permitProcessorsAdded
) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
if (permitProcessorsAdded) {
for (uint256 i; i < permitProcessors.length;) {
_addTrustedPermitProcessor(permitProcessors[i]);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < permitProcessors.length;) {
_removeTrustedPermitProcessor(permitProcessors[i]);
unchecked {
++i;
}
}
}
}
/**
* @notice Returns the token pricing bounds for the specified token id and caches the result.
*
* @param tokenAddress The address of the collection to check.
* @param tokenId The token id of the item.
*/
function checkSyncTokenPricingBounds(
address tokenAddress,
uint256 tokenId
) external returns (uint256, uint256) {
_requireCallerIsCollectionSettingsRegistryOrSelf();
PricingBounds storage tokenLevelPricingBounds = appStorage().tokenPricingBounds[tokenAddress][tokenId];
RegistryPricingBounds memory registryPricingBounds = ICollectionSettingsRegistry(collectionSettingsRegistry).getTokenBoundPricing(tokenAddress, tokenId);
if (registryPricingBounds.isSet) {
tokenLevelPricingBounds.initialized = true;
tokenLevelPricingBounds.isSet = registryPricingBounds.isSet;
tokenLevelPricingBounds.floorPrice = registryPricingBounds.floorPrice;
tokenLevelPricingBounds.ceilingPrice = registryPricingBounds.ceilingPrice;
emit UpdatedTokenLevelPricingBoundaries(
tokenAddress,
tokenId,
registryPricingBounds.floorPrice,
registryPricingBounds.ceilingPrice);
} else {
tokenLevelPricingBounds.initialized = true;
}
if (tokenLevelPricingBounds.isSet) {
return (tokenLevelPricingBounds.floorPrice, tokenLevelPricingBounds.ceilingPrice);
} else {
PricingBounds memory collectionLevelPricingBounds = appStorage().collectionPricingBounds[tokenAddress];
if (collectionLevelPricingBounds.isSet) {
return (collectionLevelPricingBounds.floorPrice, collectionLevelPricingBounds.ceilingPrice);
}
}
return (0, type(uint256).max);
}
/**
* @notice Updates the local cache of token pricing bounds to add or remove the provided `tokenIds`.
*
* @param tokenAddress The address of the collection to update.
* @param tokenIds The array of token ids to update.
* @param pricingBounds The array of pricing bounds to update.
*/
function registryUpdateTokenPricingBounds(
address tokenAddress,
uint256[] calldata tokenIds,
RegistryPricingBounds[] calldata pricingBounds
) external {
_requireCallerIsCollectionSettingsRegistryOrSelf();
mapping (uint256 => PricingBounds) storage ptrTokenPricingBounds =
appStorage().tokenPricingBounds[tokenAddress];
uint256 tokenId;
for(uint256 i = 0; i < tokenIds.length;) {
tokenId = tokenIds[i];
RegistryPricingBounds calldata pricingBounds_ = pricingBounds[i];
PricingBounds memory currentPricingBounds = ptrTokenPricingBounds[tokenId];
if(!(currentPricingBounds.isSet == pricingBounds_.isSet &&
currentPricingBounds.floorPrice == pricingBounds_.floorPrice &&
currentPricingBounds.ceilingPrice == pricingBounds_.ceilingPrice)
) {
ptrTokenPricingBounds[tokenId] = PricingBounds({
initialized: true,
isSet: pricingBounds_.isSet,
floorPrice: pricingBounds_.floorPrice,
ceilingPrice: pricingBounds_.ceilingPrice
});
emit UpdatedTokenLevelPricingBoundaries(
tokenAddress,
tokenId,
pricingBounds_.floorPrice,
pricingBounds_.ceilingPrice);
}
unchecked {
++i;
}
}
}
/**************************************************************/
/* ON-CHAIN CANCELLATION OPERATIONS */
/**************************************************************/
/**
* @notice Allows a cosigner to destroy itself, never to be used again. This is a fail-safe in case of a failure
* to secure the co-signer private key in a Web2 co-signing service. In case of suspected cosigner key
* compromise, or when a co-signer key is rotated, the cosigner MUST destroy itself to prevent past listings
* that were cancelled off-chain from being used by a malicious actor.
*
* @dev Throws when the cosigner did not sign an authorization to self-destruct.
*
* @dev
Postconditions:
* @dev 1. The cosigner can never be used to co-sign orders again.
* @dev 2. A `DestroyedCosigner` event has been emitted.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `destroyCosigner(address cosigner, SignatureECDSA signature)`
*/
function destroyCosigner(bytes calldata data) external
delegateCall(_moduleOnChainCancellation, SELECTOR_DESTROY_COSIGNER, data) {}
/**
* @notice Allows a maker to revoke/cancel all prior signatures of their listings and offers.
*
* @dev Postconditions:
* @dev 1. The maker's master nonce has been incremented by `1` in contract storage, rendering all signed
* approvals using the prior nonce unusable.
* @dev 2. A `MasterNonceInvalidated` event has been emitted.
*/
function revokeMasterNonce() external
delegateCallNoData(_moduleOnChainCancellation, SELECTOR_REVOKE_MASTER_NONCE) {}
/**
* @notice Allows a maker to revoke/cancel a single, previously signed listing or offer by specifying the
* nonce of the listing or offer.
*
* @dev Throws when the maker has already revoked the nonce.
* @dev Throws when the nonce was already used by the maker to successfully buy or sell an NFT.
*
* @dev Postconditions:
* @dev 1. The specified `nonce` for the `_msgSender()` has been revoked and can
* no longer be used to execute a sale or purchase.
* @dev 2. A `NonceInvalidated` event has been emitted.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `revokeSingleNonce(uint256 nonce)`
*/
function revokeSingleNonce(bytes calldata data) external
delegateCall(_moduleOnChainCancellation, SELECTOR_REVOKE_SINGLE_NONCE, data) {}
/**
* @notice Allows a maker to revoke/cancel a partially fillable order by specifying the order digest hash.
*
* @dev Throws when the maker has already revoked the order digest.
* @dev Throws when the order digest was already used by the maker and has been fully filled.
*
* @dev Postconditions:
* @dev 1. The specified `orderDigest` for the `_msgSender()` has been revoked and can
* no longer be used to execute a sale or purchase.
* @dev 2. An `OrderDigestInvalidated` event has been emitted.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `revokeOrderDigest(bytes32 orderDigest)`
*/
function revokeOrderDigest(bytes calldata data) external
delegateCall(_moduleOnChainCancellation, SELECTOR_REVOKE_ORDER_DIGEST, data) {}
/**************************************************************/
/* TAKER OPERATIONS */
/**************************************************************/
/**
* @notice Executes a buy listing transaction for a single order item.
*
* @dev Throws when the maker's nonce has already been used or has been cancelled.
* @dev Throws when the order has expired.
* @dev Throws when the combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the item sale price.
* @dev Throws when the maker's master nonce does not match the order details.
* @dev Throws when the order does not comply with the collection payment settings.
* @dev Throws when the maker's signature is invalid.
* @dev Throws when the order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Throws when the token transfer fails for any reason such as lack of approvals or token no longer owned by maker.
* @dev Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. An `BuyListingERC721` event has been emitted for a ERC721 purchase.
* @dev 6. An `BuyListingERC1155` event has been emitted for a ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function buyListing(
* bytes32 domainSeparator,
* Order memory saleDetails,
* SignatureECDSA memory sellerSignature,
* Cosignature memory cosignature,
* FeeOnTop memory feeOnTop)`
*/
function buyListing(bytes calldata data) external payable
nonReentrant delegateCall(_moduleBuyListings, SELECTOR_BUY_LISTING, data) {}
/**
* @notice Executes an offer accept transaction for a single order item.
*
* @dev Throws when the maker's nonce has already been used or has been cancelled.
* @dev Throws when the order has expired.
* @dev Throws when the combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the item sale price.
* @dev Throws when the maker's master nonce does not match the order details.
* @dev Throws when the order does not comply with the collection payment settings.
* @dev Throws when the maker's signature is invalid.
* @dev Throws when the order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the maker does not have sufficient funds to complete the purchase.
* @dev Throws when the token transfer fails for any reason such as lack of approvals or token not owned by the taker.
* @dev Throws when the token the offer is being accepted for does not match the conditions set by the maker.
* @dev Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
* @dev 6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
* @dev 7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function acceptOffer(
* bytes32 domainSeparator,
* uint256 offerType,
* Order memory saleDetails,
* SignatureECDSA memory buyerSignature,
* TokenSetProof memory tokenSetProof,
* Cosignature memory cosignature,
* FeeOnTop memory feeOnTop)`
*/
function acceptOffer(bytes calldata data) external payable
nonReentrant delegateCall(_moduleAcceptOffers, SELECTOR_ACCEPT_OFFER, data) {}
/**
* @notice Executes a buy listing transaction for multiple order items.
*
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when any taker fee on top exceeds 100% of the item sale price.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
* @dev 6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function bulkBuyListings(
* bytes32 domainSeparator,
* Order[] calldata saleDetailsArray,
* SignatureECDSA[] calldata sellerSignatures,
* Cosignature[] calldata cosignatures,
* FeeOnTop[] calldata feesOnTop)`
*/
function bulkBuyListings(bytes calldata data) external payable
nonReentrant delegateCall(_moduleBuyListings, SELECTOR_BULK_BUY_LISTINGS, data) {}
/**
* @notice Executes an accept offer transaction for multiple order items.
*
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when any taker fee on top exceeds 100% of the item sale price.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when a maker does not have sufficient funds to complete the purchase.
* @dev Throws when the token an offer is being accepted for does not match the conditions set by the maker.
* @dev Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. `AcceptOfferERC721` events have been emitted for each ERC721 sale.
* @dev 6. `AcceptOfferERC1155` events have been emitted for each ERC1155 sale.
* @dev 7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function bulkAcceptOffers(
* bytes32 domainSeparator,
* BulkAcceptOffersParams memory params)`
*/
function bulkAcceptOffers(bytes calldata data) external payable
nonReentrant delegateCall(_moduleAcceptOffers, SELECTOR_BULK_ACCEPT_OFFERS, data) {}
/**
* @notice Executes a sweep transaction for buying multiple items from the same collection.
*
* @dev Throws when the sweep order protocol is ERC1155_PARTIAL_FILL (unsupported).
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the combined item sale prices.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
* @dev 6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function sweepCollection(
* bytes32 domainSeparator,
* FeeOnTop memory feeOnTop,
* SweepOrder memory sweepOrder,
* SweepItem[] calldata items,
* SignatureECDSA[] calldata signedSellOrders,
* Cosignature[] memory cosignatures)`
*/
function sweepCollection(bytes calldata data) external payable
nonReentrant delegateCall(_moduleSweeps, SELECTOR_SWEEP_COLLECTION, data) {}
/**
* @notice Executes an advanced buy listing transaction for a single order item.
*
* @dev Throws when the maker's nonce has already been used or has been cancelled.
* @dev Throws when the order has expired.
* @dev Throws when the combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the item sale price.
* @dev Throws when the maker's master nonce does not match the order details.
* @dev Throws when the order does not comply with the collection payment settings.
* @dev Throws when the maker's signature is invalid.
* @dev Throws when the order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Throws when the token transfer fails for any reason such as lack of approvals or token no longer owned by maker.
* @dev Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
* @dev Throws when the provided bulk order merkle proof and order index are invalid.
* @dev Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. An `BuyListingERC721` event has been emitted for a ERC721 purchase.
* @dev 6. An `BuyListingERC1155` event has been emitted for a ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function buyListingAdvanced(
* bytes32 domainSeparator,
* AdvancedListing memory advancedListing,
* BulkOrderProof memory bulkOrderProof,
* FeeOnTop memory feeOnTop)`
*/
function buyListingAdvanced(bytes calldata data) external payable
nonReentrant delegateCall(_moduleBuyListings, SELECTOR_BUY_LISTING_ADVANCED, data) {}
/**
* @notice Executes an advanced offer accept transaction for a single order item.
*
* @dev Throws when the maker's nonce has already been used or has been cancelled.
* @dev Throws when the order has expired.
* @dev Throws when the combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the item sale price.
* @dev Throws when the maker's master nonce does not match the order details.
* @dev Throws when the order does not comply with the collection payment settings.
* @dev Throws when the maker's signature is invalid.
* @dev Throws when the order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the maker does not have sufficient funds to complete the purchase.
* @dev Throws when the token transfer fails for any reason such as lack of approvals or token not owned by the taker.
* @dev Throws when the token the offer is being accepted for does not match the conditions set by the maker.
* @dev Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Throws when the provided bulk order merkle proof and order index are invalid.
* @dev Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
* @dev 6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
* @dev 7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function acceptOfferAdvanced(
* bytes32 domainSeparator,
* AdvancedBid memory advancedBid,
* BulkOrderProof memory bulkOrderProof,
* FeeOnTop memory feeOnTop,
* TokenSetProof memory tokenSetProof)`
*/
function acceptOfferAdvanced(bytes calldata data) external payable
nonReentrant delegateCall(_moduleAcceptOffers, SELECTOR_ACCEPT_OFFER_ADVANCED, data) {}
/**
* @notice Executes an advanced buy listing transaction for multiple order items.
*
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when any taker fee on top exceeds 100% of the item sale price.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Throws when any provided bulk order merkle proof and order index are invalid.
* @dev Throws when any provided permit processor does not have approval to move the token or receives an invalid signature.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
* @dev 6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function bulkBuyListingsAdvanced(
* bytes32 domainSeparator,
* AdvancedListing[] calldata advancedListingsArray,
* BulkOrderProof[] calldata bulkOrderProofs,
* FeeOnTop[] calldata feesOnTop)`
*/
function bulkBuyListingsAdvanced(bytes calldata data) external payable
nonReentrant delegateCall(_moduleBuyListings, SELECTOR_BULK_BUY_LISTINGS_ADVANCED, data) {}
/**
* @notice Executes an advanced offer accept transaction for multiple order items.
*
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when any taker fee on top exceeds 100% of the item sale price.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when a maker does not have sufficient funds to complete the purchase.
* @dev Throws when any token transfer fails for any reason such as lack of approvals or token not owned by the taker.
* @dev Throws when the token an offer is being accepted for does not match the conditions set by the maker.
* @dev Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
* @dev Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
* @dev Throws when the provided bulk order merkle proof and order index are invalid.
* @dev Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
* @dev 6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
* @dev 7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
* @dev 8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function bulkAcceptOffersAdvanced(
* bytes32 domainSeparator,
* AdvancedBid[] calldata advancedBids,
* BulkOrderProof[] calldata bulkOrderProofs,
* FeeOnTop[] calldata feesOnTop,
* TokenSetProof[] calldata tokenSetProofs)`
*/
function bulkAcceptOffersAdvanced(bytes calldata data) external payable
nonReentrant delegateCall(_moduleAcceptOffers, SELECTOR_BULK_ACCEPT_OFFERS_ADVANCED, data) {}
/**
* @notice Executes an advanced sweep transaction for buying multiple items from the same collection.
*
* @dev Throws when the sweep order protocol is ERC1155_PARTIAL_FILL (unsupported).
* @dev Throws when a maker's nonce has already been used or has been cancelled.
* @dev Throws when any order has expired.
* @dev Throws when any combined marketplace and royalty fee exceeds 100%.
* @dev Throws when the taker fee on top exceeds 100% of the combined item sale prices.
* @dev Throws when a maker's master nonce does not match the order details.
* @dev Throws when an order does not comply with the collection payment settings.
* @dev Throws when a maker's signature is invalid.
* @dev Throws when an order is a cosigned order and the cosignature is invalid.
* @dev Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
* @dev Throws when the taker does not have or did not send sufficient funds to complete the purchase.
* @dev Throws when the provided bulk order merkle proof and order index are invalid.
* @dev Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
* @dev Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
* @dev Any unused native token payment will be returned to the taker as wrapped native token.
*
* @dev Postconditions:
* @dev 1. Payment amounts and fees are sent to their respective recipients.
* @dev 2. Purchased tokens are sent to the beneficiary.
* @dev 3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
* @dev 4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
* @dev 5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
* @dev 6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
* @dev 7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
*
* @param data Calldata encoded with PaymentProcessorEncoder. Matches calldata for:
* `function sweepCollectionAdvanced(
* bytes32 domainSeparator,
* AdvancedSweep calldata advancedSweep)`
*/
function sweepCollectionAdvanced(bytes calldata data) external payable
nonReentrant delegateCall(_moduleSweeps, SELECTOR_SWEEP_COLLECTION_ADVANCED, data) {}
/**************************************************************/
/* PROTOCOL FEES */
/**************************************************************/
/**
* @notice Sets the protocol fees for the marketplace.
* @dev If either the fee receiver is set to the zero address or all fees are set to zero, the protocol fees
* will be disabled entirely.
*
* @dev Throws when `msg.sender` does not have the `FEE_MANAGER` role.
* @dev Throws when the grace period, in seconds, exceeds uint24 max (about 194 days).
* @dev Throws when the marketplace fee or fee on top tax rate exceeds the cap.
*
* @dev Postconditions:
* @dev 1. The protocol fees have been updated.
* @dev 2. A `ProtocolFeesUpdated` event has been emitted.
*
* @param protocolFees The new protocol fees to set.
*/
function setProtocolFees(ProtocolFees calldata protocolFees, uint256 gracePeriodSeconds) external callerHasRole(FEE_MANAGER) {
if (gracePeriodSeconds > type(uint24).max) {
revert PaymentProcessor__MaxGracePeriodExceeded();
}
ProtocolFees memory protocolFeesUpdated = protocolFees;
protocolFeesUpdated.versionExpiration = type(uint48).max;
if (protocolFeesUpdated.protocolFeeReceiver == address(0)) {
protocolFeesUpdated.minimumProtocolFeeBps = 0;
protocolFeesUpdated.marketplaceFeeProtocolTaxBps = 0;
protocolFeesUpdated.feeOnTopProtocolTaxBps = 0;
} else {
if (protocolFeesUpdated.marketplaceFeeProtocolTaxBps > FEE_DENOMINATOR) {
revert PaymentProcessor__ProtocolFeeOrTaxExceedsCap();
}
if (protocolFeesUpdated.feeOnTopProtocolTaxBps > FEE_DENOMINATOR) {
revert PaymentProcessor__ProtocolFeeOrTaxExceedsCap();
}
if (protocolFeesUpdated.minimumProtocolFeeBps |
protocolFeesUpdated.marketplaceFeeProtocolTaxBps |
protocolFeesUpdated.feeOnTopProtocolTaxBps == 0) {
protocolFeesUpdated.protocolFeeReceiver = address(0);
}
}
unchecked {
uint48 gracePeriod = uint48(block.timestamp + gracePeriodSeconds);
uint256 nextVersion = ++appStorage().currentProtocolFeeVersion;
appStorage().protocolFeeVersions[nextVersion - 1].versionExpiration = gracePeriod;
appStorage().protocolFeeVersions[nextVersion] = protocolFeesUpdated;
emit ProtocolFeesUpdated(
protocolFeesUpdated.protocolFeeReceiver,
protocolFeesUpdated.minimumProtocolFeeBps,
protocolFeesUpdated.marketplaceFeeProtocolTaxBps,
protocolFeesUpdated.feeOnTopProtocolTaxBps,
gracePeriod);
}
}
/**
* @notice Returns the current protocol fee version.
*/
function getProtocolFeeVersion() external view returns (uint256) {
return appStorage().currentProtocolFeeVersion;
}
/**
* @notice Returns the current protocol fees for the marketplace.
*/
function getProtocolFees() external view returns (ProtocolFees memory protocolFees) {
protocolFees = appStorage().protocolFeeVersions[appStorage().currentProtocolFeeVersion];
}
/**
* @notice Returns the protocol fees for a specific historical version.
*/
function getProtocolFees(uint256 version) external view returns (ProtocolFees memory protocolFees) {
protocolFees = appStorage().protocolFeeVersions[version];
}
function _setupRoles() internal virtual override {
_setupRole(FEE_MANAGER, 0);
}
}