Skip to content

Commit

Permalink
Multiple collections per seller (#592)
Browse files Browse the repository at this point in the history
* create new collection

* Manage cloneAddress protocol wide

* Collection struct, domain test + old tests

* seller tests

* createOffer tests

* exchange handler tests

* update natspec

* FIx failing tests

* add missing tests

* review fixes

* minor optimization

* fix tests

---------

Co-authored-by: Ana Julia Bittencourt <anajuliabit@gmail.com>
Co-authored-by: Mischa <mischa@mmt.me.uk>
  • Loading branch information
3 people authored Jul 6, 2023
1 parent ad78b61 commit 6cdc94a
Show file tree
Hide file tree
Showing 28 changed files with 1,694 additions and 135 deletions.
1 change: 1 addition & 0 deletions contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ string constant INVALID_DISPUTE_RESOLVER = "Invalid dispute resolver";
string constant INVALID_QUANTITY_AVAILABLE = "Invalid quantity available";
string constant DR_UNSUPPORTED_FEE = "Dispute resolver does not accept this token";
string constant AGENT_FEE_AMOUNT_TOO_HIGH = "Sum of agent fee amount and protocol fee amount should be <= offer fee limit";
string constant NO_SUCH_COLLECTION = "No such collection";

// Revert Reasons: Group related
string constant NO_SUCH_GROUP = "No such group";
Expand Down
6 changes: 6 additions & 0 deletions contracts/domain/BosonTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ contract BosonTypes {
string metadataUri;
string metadataHash;
bool voided;
uint256 collectionIndex;
}

struct OfferDates {
Expand Down Expand Up @@ -285,4 +286,9 @@ contract BosonTypes {
string contractURI;
uint256 royaltyPercentage;
}

struct Collection {
address collectionAddress;
string externalId;
}
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IInitializableVoucherClone.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ interface IInitializableVoucherClone {
* @notice Initializes a voucher with the given parameters.
*
* @param _sellerId - The ID of the seller.
* @param _collectionIndex - The index of the collection.
* @param _newOwner - The address of the new owner.
* @param _voucherInitValues - The voucher initialization values.
*/
function initializeVoucher(
uint256 _sellerId,
uint256 _collectionIndex,
address _newOwner,
BosonTypes.VoucherInitValues calldata _voucherInitValues
) external;
Expand Down
7 changes: 7 additions & 0 deletions contracts/interfaces/events/IBosonAccountEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,11 @@ interface IBosonAccountEvents {
address indexed executedBy
);
event AgentCreated(uint256 indexed agentId, BosonTypes.Agent agent, address indexed executedBy);
event CollectionCreated(
uint256 indexed sellerId,
uint256 collectionIndex,
address collectionAddress,
string indexed externalId,
address indexed executedBy
);
}
30 changes: 29 additions & 1 deletion contracts/interfaces/handlers/IBosonAccountHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IBosonAccountEvents } from "../events/IBosonAccountEvents.sol";
*
* @notice Handles creation, update, retrieval of accounts within the protocol.
*
* The ERC-165 identifier for this interface is: 0x15335ed7
* The ERC-165 identifier for this interface is: 0x868de65b
*/
interface IBosonAccountHandler is IBosonAccountEvents {
/**
Expand Down Expand Up @@ -309,6 +309,23 @@ interface IBosonAccountHandler is IBosonAccountEvents {
*/
function removeSellersFromAllowList(uint256 _disputeResolverId, uint256[] calldata _sellerAllowList) external;

/**
* @notice Creates a new seller collection.
*
* Emits a CollectionCreated event if successful.
*
* Reverts if:
* - The offers region of protocol is paused
* - Caller is not the seller assistant
*
* @param _externalId - external collection id
* @param _voucherInitValues - the fully populated BosonTypes.VoucherInitValues struct
*/
function createNewCollection(
string calldata _externalId,
BosonTypes.VoucherInitValues calldata _voucherInitValues
) external;

/**
* @notice Gets the details about a seller.
*
Expand Down Expand Up @@ -353,6 +370,17 @@ interface IBosonAccountHandler is IBosonAccountEvents {
BosonTypes.AuthToken calldata _associatedAuthToken
) external view returns (bool exists, BosonTypes.Seller memory seller, BosonTypes.AuthToken memory authToken);

/**
* @notice Gets the details about a seller's collections.
*
* @param _sellerId - the id of the seller to check
* @return defaultVoucherAddress - the address of the default voucher contract for the seller
* @return additionalCollections - an array of additional collections that the seller has created
*/
function getSellersCollections(
uint256 _sellerId
) external view returns (address defaultVoucherAddress, BosonTypes.Collection[] memory additionalCollections);

/**
* @notice Gets the details about a buyer.
*
Expand Down
4 changes: 3 additions & 1 deletion contracts/interfaces/handlers/IBosonOfferHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IBosonOfferEvents } from "../events/IBosonOfferEvents.sol";
*
* @notice Handles creation, voiding, and querying of offers within the protocol.
*
* The ERC-165 identifier for this interface is: 0xa1598d02
* The ERC-165 identifier for this interface is: 0xa1e3b91c
*/
interface IBosonOfferHandler is IBosonOfferEvents {
/**
Expand All @@ -35,6 +35,7 @@ interface IBosonOfferHandler is IBosonOfferEvents {
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When agent id is non zero:
* - If Agent does not exist
* - If the sum of agent fee amount and protocol fee amount is greater than the offer fee limit
Expand Down Expand Up @@ -79,6 +80,7 @@ interface IBosonOfferHandler is IBosonOfferEvents {
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When agent ids are non zero:
* - If Agent does not exist
* - If the sum of agent fee amount and protocol fee amount is greater than the offer fee limit
Expand Down
18 changes: 17 additions & 1 deletion contracts/interfaces/handlers/IBosonOrchestrationHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IBosonBundleEvents } from "../events/IBosonBundleEvents.sol";
*
* @notice Combines creation of multiple entities (accounts, offers, groups, twins, bundles) in a single transaction
*
* The ERC-165 identifier for this interface is: 0x0c62d8e3
* The ERC-165 identifier for this interface is: 0xb2539c77
*/
interface IBosonOrchestrationHandler is
IBosonAccountEvents,
Expand Down Expand Up @@ -91,6 +91,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When agent id is non zero:
* - If Agent does not exist
* - If the sum of agent fee amount and protocol fee amount is greater than the offer fee limit
Expand Down Expand Up @@ -161,6 +162,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When agent id is non zero:
* - If Agent does not exist
* - If the sum of agent fee amount and protocol fee amount is greater than the offer fee limit
Expand Down Expand Up @@ -218,6 +220,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When agent id is non zero:
* - If Agent does not exist
Expand Down Expand Up @@ -269,6 +272,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When agent id is non zero:
* - If Agent does not exist
Expand Down Expand Up @@ -323,6 +327,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When adding to the group if:
* - Group does not exists
* - Caller is not the assistant of the group
Expand Down Expand Up @@ -377,6 +382,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When adding to the group if:
* - Group does not exists
* - Caller is not the assistant of the group
Expand Down Expand Up @@ -435,6 +441,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When creating twin if
* - Not approved to transfer the seller's token
* - SupplyAvailable is zero
Expand Down Expand Up @@ -494,6 +501,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When creating twin if
* - Not approved to transfer the seller's token
* - SupplyAvailable is zero
Expand Down Expand Up @@ -558,6 +566,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When creating twin if
* - Not approved to transfer the seller's token
Expand Down Expand Up @@ -622,6 +631,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When creating twin if
* - Not approved to transfer the seller's token
Expand Down Expand Up @@ -705,6 +715,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When agent id is non zero:
* - If Agent does not exist
Expand Down Expand Up @@ -781,6 +792,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When agent id is non zero:
* - If Agent does not exist
Expand Down Expand Up @@ -861,6 +873,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When creating twin if
* - Not approved to transfer the seller's token
* - SupplyAvailable is zero
Expand Down Expand Up @@ -945,6 +958,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - When creating twin if
* - Not approved to transfer the seller's token
* - SupplyAvailable is zero
Expand Down Expand Up @@ -1033,6 +1047,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When creating twin if
* - Not approved to transfer the seller's token
Expand Down Expand Up @@ -1122,6 +1137,7 @@ interface IBosonOrchestrationHandler is
* - Seller is not on dispute resolver's seller allow list
* - Dispute resolver does not accept fees in the exchange token
* - Buyer cancel penalty is greater than price
* - Collection does not exist
* - Condition includes invalid combination of parameters
* - When creating twin if
* - Not approved to transfer the seller's token
Expand Down
92 changes: 54 additions & 38 deletions contracts/protocol/bases/OfferBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -150,46 +150,59 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents {
// quantity must be greater than zero
require(_offer.quantityAvailable > 0, INVALID_QUANTITY_AVAILABLE);

// Specified resolver must be registered and active, except for absolute zero offers with unspecified dispute resolver.
// If price and sellerDeposit are 0, seller is not obliged to choose dispute resolver, which is done by setting _disputeResolverId to 0.
// In this case, there is no need to check the validity of the dispute resolver. However, if one (or more) of {price, sellerDeposit, _disputeResolverId}
// is different from 0, it must be checked that dispute resolver exists, supports the exchange token and seller is allowed to choose them.
DisputeResolutionTerms memory disputeResolutionTerms;
if (_offer.price != 0 || _offer.sellerDeposit != 0 || _disputeResolverId != 0) {
(
bool exists,
DisputeResolver storage disputeResolver,
DisputeResolverFee[] storage disputeResolverFees
) = fetchDisputeResolver(_disputeResolverId);
require(exists && disputeResolver.active, INVALID_DISPUTE_RESOLVER);

// Operate in a block to avoid "stack too deep" error
{
// Cache protocol lookups for reference
ProtocolLib.ProtocolLookups storage lookups = protocolLookups();

// check that seller is on the DR allow list
if (lookups.allowedSellers[_disputeResolverId].length > 0) {
// if length == 0, dispute resolver allows any seller
// if length > 0, we check that it is on allow list
require(lookups.allowedSellerIndex[_disputeResolverId][_offer.sellerId] > 0, SELLER_NOT_APPROVED);
{
// Cache protocol lookups for reference
ProtocolLib.ProtocolLookups storage lookups = protocolLookups();

// Specified resolver must be registered and active, except for absolute zero offers with unspecified dispute resolver.
// If price and sellerDeposit are 0, seller is not obliged to choose dispute resolver, which is done by setting _disputeResolverId to 0.
// In this case, there is no need to check the validity of the dispute resolver. However, if one (or more) of {price, sellerDeposit, _disputeResolverId}
// is different from 0, it must be checked that dispute resolver exists, supports the exchange token and seller is allowed to choose them.
if (_offer.price != 0 || _offer.sellerDeposit != 0 || _disputeResolverId != 0) {
(
bool exists,
DisputeResolver storage disputeResolver,
DisputeResolverFee[] storage disputeResolverFees
) = fetchDisputeResolver(_disputeResolverId);
require(exists && disputeResolver.active, INVALID_DISPUTE_RESOLVER);

// Operate in a block to avoid "stack too deep" error
{
// check that seller is on the DR allow list
if (lookups.allowedSellers[_disputeResolverId].length > 0) {
// if length == 0, dispute resolver allows any seller
// if length > 0, we check that it is on allow list
require(
lookups.allowedSellerIndex[_disputeResolverId][_offer.sellerId] > 0,
SELLER_NOT_APPROVED
);
}

// get the index of DisputeResolverFee and make sure DR supports the exchangeToken
uint256 feeIndex = lookups.disputeResolverFeeTokenIndex[_disputeResolverId][_offer.exchangeToken];
require(feeIndex > 0, DR_UNSUPPORTED_FEE);

uint256 feeAmount = disputeResolverFees[feeIndex - 1].feeAmount;

// store DR terms
disputeResolutionTerms.disputeResolverId = _disputeResolverId;
disputeResolutionTerms.escalationResponsePeriod = disputeResolver.escalationResponsePeriod;
disputeResolutionTerms.feeAmount = feeAmount;
disputeResolutionTerms.buyerEscalationDeposit =
(feeAmount * protocolFees().buyerEscalationDepositPercentage) /
10000;

protocolEntities().disputeResolutionTerms[_offer.id] = disputeResolutionTerms;
}
}

// get the index of DisputeResolverFee and make sure DR supports the exchangeToken
uint256 feeIndex = lookups.disputeResolverFeeTokenIndex[_disputeResolverId][_offer.exchangeToken];
require(feeIndex > 0, DR_UNSUPPORTED_FEE);

uint256 feeAmount = disputeResolverFees[feeIndex - 1].feeAmount;

// store DR terms
disputeResolutionTerms.disputeResolverId = _disputeResolverId;
disputeResolutionTerms.escalationResponsePeriod = disputeResolver.escalationResponsePeriod;
disputeResolutionTerms.feeAmount = feeAmount;
disputeResolutionTerms.buyerEscalationDeposit =
(feeAmount * protocolFees().buyerEscalationDepositPercentage) /
10000;

protocolEntities().disputeResolutionTerms[_offer.id] = disputeResolutionTerms;
// Collection must exist. Collections with index 0 exist by default.
if (_offer.collectionIndex > 0) {
require(
lookups.additionalCollections[_offer.sellerId].length >= _offer.collectionIndex,
NO_SUCH_COLLECTION
);
}
}

Expand Down Expand Up @@ -244,6 +257,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents {
offer.exchangeToken = _offer.exchangeToken;
offer.metadataUri = _offer.metadataUri;
offer.metadataHash = _offer.metadataHash;
offer.collectionIndex = _offer.collectionIndex;

// Get storage location for offer dates
OfferDates storage offerDates = fetchOfferDates(_offer.id);
Expand Down Expand Up @@ -316,7 +330,9 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents {
ProtocolLib.ProtocolCounters storage pc = protocolCounters();
uint256 _startId = pc.nextExchangeId;

IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]);
IBosonVoucher bosonVoucher = IBosonVoucher(
getCloneAddress(protocolLookups(), offer.sellerId, offer.collectionIndex)
);

address sender = msgSender();

Expand Down
Loading

0 comments on commit 6cdc94a

Please sign in to comment.