diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 157a5324b..00925fd24 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -11,6 +11,9 @@ bytes32 constant CLIENT = keccak256("CLIENT"); // Role for clients of the Protoc bytes32 constant UPGRADER = keccak256("UPGRADER"); // Role for performing contract and config upgrades bytes32 constant FEE_COLLECTOR = keccak256("FEE_COLLECTOR"); // Role for collecting fees from the protocol +// Generic +uint256 constant HUNDRED_PERCENT = 10000; // 100% in basis points + // Pause Handler uint256 constant ALL_REGIONS_MASK = (1 << (uint256(type(BosonTypes.PausableRegion).max) + 1)) - 1; diff --git a/contracts/protocol/bases/BuyerBase.sol b/contracts/protocol/bases/BuyerBase.sol index 38be80ef7..8c335570f 100644 --- a/contracts/protocol/bases/BuyerBase.sol +++ b/contracts/protocol/bases/BuyerBase.sol @@ -28,15 +28,9 @@ contract BuyerBase is ProtocolBase, IBosonAccountEvents { //Check for zero address if (_buyer.wallet == address(0)) revert InvalidAddress(); - //Check active is not set to false - if (!_buyer.active) revert MustBeActive(); - // Get the next account id and increment the counter uint256 buyerId = protocolCounters().nextAccountId++; - //check that the wallet address is unique to one buyer id - if (protocolLookups().buyerIdByWallet[_buyer.wallet] != 0) revert BuyerAddressMustBeUnique(); - _buyer.id = buyerId; storeBuyer(_buyer); diff --git a/contracts/protocol/bases/OfferBase.sol b/contracts/protocol/bases/OfferBase.sol index 4332cc416..34df901fa 100644 --- a/contracts/protocol/bases/OfferBase.sol +++ b/contracts/protocol/bases/OfferBase.sol @@ -191,7 +191,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { disputeResolutionTerms.feeAmount = feeAmount; disputeResolutionTerms.buyerEscalationDeposit = (feeAmount * fees.buyerEscalationDepositPercentage) / - 10000; + HUNDRED_PERCENT; } protocolEntities().disputeResolutionTerms[_offer.id] = disputeResolutionTerms; } @@ -236,9 +236,9 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { uint256 protocolFee = getProtocolFee(_offer.exchangeToken, offerPrice); // Calculate the agent fee amount - uint256 agentFeeAmount = (agent.feePercentage * offerPrice) / 10000; + uint256 agentFeeAmount = (agent.feePercentage * offerPrice) / HUNDRED_PERCENT; - uint256 totalOfferFeeLimit = (limits.maxTotalOfferFeePercentage * offerPrice) / 10000; + uint256 totalOfferFeeLimit = (limits.maxTotalOfferFeePercentage * offerPrice) / HUNDRED_PERCENT; // Sum of agent fee amount and protocol fee amount should be <= offer fee limit and less that fee limit set by seller uint256 totalFeeAmount = agentFeeAmount + protocolFee; diff --git a/contracts/protocol/bases/ProtocolBase.sol b/contracts/protocol/bases/ProtocolBase.sol index 369a3ad25..3ed3ac111 100644 --- a/contracts/protocol/bases/ProtocolBase.sol +++ b/contracts/protocol/bases/ProtocolBase.sol @@ -698,7 +698,7 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase, BosonErrors return _exchangeToken == protocolAddresses().token ? protocolFees().flatBoson - : (protocolFees().percentage * _price) / 10000; + : (protocolFees().percentage * _price) / HUNDRED_PERCENT; } /** diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index f0f2ee8e5..039238e23 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -634,7 +634,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable !isPreminted ); - royaltyAmount = (_salePrice * royaltyPercentage) / 10000; + royaltyAmount = (_salePrice * royaltyPercentage) / HUNDRED_PERCENT; } /** diff --git a/contracts/protocol/facets/BuyerHandlerFacet.sol b/contracts/protocol/facets/BuyerHandlerFacet.sol index f490323d8..28f289c87 100644 --- a/contracts/protocol/facets/BuyerHandlerFacet.sol +++ b/contracts/protocol/facets/BuyerHandlerFacet.sol @@ -34,6 +34,12 @@ contract BuyerHandlerFacet is BuyerBase { * @param _buyer - the fully populated struct with buyer id set to 0x0 */ function createBuyer(Buyer memory _buyer) external buyersNotPaused nonReentrant { + //Check active is not set to false + if (!_buyer.active) revert MustBeActive(); + + //check that the wallet address is unique to one buyer id + if (protocolLookups().buyerIdByWallet[_buyer.wallet] != 0) revert BuyerAddressMustBeUnique(); + createBuyerInternal(_buyer); } diff --git a/contracts/protocol/facets/ConfigHandlerFacet.sol b/contracts/protocol/facets/ConfigHandlerFacet.sol index 53e0d4eb9..d81f736fe 100644 --- a/contracts/protocol/facets/ConfigHandlerFacet.sol +++ b/contracts/protocol/facets/ConfigHandlerFacet.sol @@ -555,6 +555,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { * Reverts if the value more than 10000 */ function checkMaxPercententage(uint256 _percentage) internal pure { - if (_percentage > 10000) revert InvalidFeePercentage(); + if (_percentage > HUNDRED_PERCENT) revert InvalidFeePercentage(); } } diff --git a/contracts/protocol/facets/DisputeHandlerFacet.sol b/contracts/protocol/facets/DisputeHandlerFacet.sol index f84e2a15c..365a915cc 100644 --- a/contracts/protocol/facets/DisputeHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeHandlerFacet.sol @@ -238,7 +238,7 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { uint8 _sigV ) external override nonReentrant { // buyer should get at most 100% - if (_buyerPercent > 10000) revert InvalidBuyerPercent(); + if (_buyerPercent > HUNDRED_PERCENT) revert InvalidBuyerPercent(); // Get the exchange, should be in disputed state (Exchange storage exchange, ) = getValidExchange(_exchangeId, ExchangeState.Disputed); @@ -340,7 +340,7 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { */ function decideDispute(uint256 _exchangeId, uint256 _buyerPercent) external override nonReentrant { // Buyer should get at most 100% - if (_buyerPercent > 10000) revert InvalidBuyerPercent(); + if (_buyerPercent > HUNDRED_PERCENT) revert InvalidBuyerPercent(); // Make sure the dispute is valid and the caller is the dispute resolver (Exchange storage exchange, Dispute storage dispute, DisputeDates storage disputeDates) = disputeResolverChecks( diff --git a/contracts/protocol/facets/OfferHandlerFacet.sol b/contracts/protocol/facets/OfferHandlerFacet.sol index 977734877..6668e6a23 100644 --- a/contracts/protocol/facets/OfferHandlerFacet.sol +++ b/contracts/protocol/facets/OfferHandlerFacet.sol @@ -186,15 +186,8 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * @param _offerId - the id of the offer to void */ - function voidOffer(uint256 _offerId) public override offersNotPaused nonReentrant { - // Get offer. Make sure caller is assistant - Offer storage offer = getValidOfferWithSellerCheck(_offerId); - - // Void the offer - offer.voided = true; - - // Notify listeners of state change - emit OfferVoided(_offerId, offer.sellerId, msgSender()); + function voidOffer(uint256 _offerId) external override offersNotPaused nonReentrant { + voidOfferInternal(_offerId); } /** @@ -212,9 +205,9 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * @param _offerIds - list of ids of offers to void */ - function voidOfferBatch(uint256[] calldata _offerIds) external override offersNotPaused { + function voidOfferBatch(uint256[] calldata _offerIds) external override offersNotPaused nonReentrant { for (uint256 i = 0; i < _offerIds.length; ) { - voidOffer(_offerIds[i]); + voidOfferInternal(_offerIds[i]); unchecked { i++; @@ -237,26 +230,8 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @param _offerId - the id of the offer to extend * @param _validUntilDate - new valid until date */ - function extendOffer(uint256 _offerId, uint256 _validUntilDate) public override offersNotPaused nonReentrant { - // Make sure the caller is the assistant, offer exists and is not voided - Offer storage offer = getValidOfferWithSellerCheck(_offerId); - - // Fetch the offer dates - OfferDates storage offerDates = fetchOfferDates(_offerId); - - // New valid until date must be greater than existing one - if (offerDates.validUntil >= _validUntilDate) revert InvalidOfferPeriod(); - - // If voucherRedeemableUntil is set, _validUntilDate must be less or equal than that - if (offerDates.voucherRedeemableUntil > 0) { - if (_validUntilDate > offerDates.voucherRedeemableUntil) revert InvalidOfferPeriod(); - } - - // Update the valid until property - offerDates.validUntil = _validUntilDate; - - // Notify watchers of state change - emit OfferExtended(_offerId, offer.sellerId, _validUntilDate, msgSender()); + function extendOffer(uint256 _offerId, uint256 _validUntilDate) external override offersNotPaused nonReentrant { + extendOfferInternal(_offerId, _validUntilDate); } /** @@ -275,9 +250,12 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @param _offerIds - list of ids of the offers to extend * @param _validUntilDate - new valid until date */ - function extendOfferBatch(uint256[] calldata _offerIds, uint256 _validUntilDate) external override offersNotPaused { + function extendOfferBatch( + uint256[] calldata _offerIds, + uint256 _validUntilDate + ) external override offersNotPaused nonReentrant { for (uint256 i = 0; i < _offerIds.length; ) { - extendOffer(_offerIds[i], _validUntilDate); + extendOfferInternal(_offerIds[i], _validUntilDate); unchecked { i++; @@ -302,17 +280,8 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { function updateOfferRoyaltyRecipients( uint256 _offerId, RoyaltyInfo calldata _royaltyInfo - ) public override offersNotPaused nonReentrant { - // Make sure the caller is the assistant, offer exists and is not voided - Offer storage offer = getValidOfferWithSellerCheck(_offerId); - - validateRoyaltyInfo(protocolLookups(), protocolLimits(), offer.sellerId, _royaltyInfo); - - // Add new entry to the royaltyInfo array - offer.royaltyInfo.push(_royaltyInfo); - - // Notify watchers of state change - emit OfferRoyaltyInfoUpdated(_offerId, offer.sellerId, _royaltyInfo, msgSender()); + ) external override offersNotPaused nonReentrant { + updateOfferRoyaltyRecipientsInternal(_offerId, _royaltyInfo); } /** @@ -333,9 +302,9 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { function updateOfferRoyaltyRecipientsBatch( uint256[] calldata _offerIds, BosonTypes.RoyaltyInfo calldata _royaltyInfo - ) external override offersNotPaused { + ) external override offersNotPaused nonReentrant { for (uint256 i = 0; i < _offerIds.length; ) { - updateOfferRoyaltyRecipients(_offerIds[i], _royaltyInfo); + updateOfferRoyaltyRecipientsInternal(_offerIds[i], _royaltyInfo); unchecked { i++; @@ -343,6 +312,96 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { } } + /** + * @notice Internal function to void a given offer, used by both single and batch void functions. + * Existing exchanges are not affected. + * No further vouchers can be issued against a voided offer. + * + * Emits an OfferVoided event if successful. + * + * Reverts if: + * - The offers region of protocol is paused + * - Offer id is invalid + * - Caller is not the assistant of the offer + * - Offer has already been voided + * + * @param _offerId - the id of the offer to void + */ + function voidOfferInternal(uint256 _offerId) internal { + // Get offer. Make sure caller is assistant + Offer storage offer = getValidOfferWithSellerCheck(_offerId); + + // Void the offer + offer.voided = true; + + // Notify listeners of state change + emit OfferVoided(_offerId, offer.sellerId, msgSender()); + } + + /** + * @notice Internal function to set new valid until date, used by both single and batch extend functions. + * + * Emits an OfferExtended event if successful. + * + * Reverts if: + * - The offers region of protocol is paused + * - Offer does not exist + * - Caller is not the assistant of the offer + * - New valid until date is before existing valid until dates + * - Offer has voucherRedeemableUntil set and new valid until date is greater than that + * + * @param _offerId - the id of the offer to extend + * @param _validUntilDate - new valid until date + */ + function extendOfferInternal(uint256 _offerId, uint256 _validUntilDate) internal { + // Make sure the caller is the assistant, offer exists and is not voided + Offer storage offer = getValidOfferWithSellerCheck(_offerId); + + // Fetch the offer dates + OfferDates storage offerDates = fetchOfferDates(_offerId); + + // New valid until date must be greater than existing one + if (offerDates.validUntil >= _validUntilDate) revert InvalidOfferPeriod(); + + // If voucherRedeemableUntil is set, _validUntilDate must be less or equal than that + if (offerDates.voucherRedeemableUntil > 0) { + if (_validUntilDate > offerDates.voucherRedeemableUntil) revert InvalidOfferPeriod(); + } + + // Update the valid until property + offerDates.validUntil = _validUntilDate; + + // Notify watchers of state change + emit OfferExtended(_offerId, offer.sellerId, _validUntilDate, msgSender()); + } + + /** + * @notice Internal function to update the royalty recipients, used by both single and batch update functions. + * + * Emits an OfferRoyaltyInfoUpdated event if successful. + * + * Reverts if: + * - The offers region of protocol is paused + * - Offer does not exist + * - Caller is not the assistant of the offer + * - New royalty info is invalid + * + * @param _offerId - the id of the offer to be updated + * @param _royaltyInfo - new royalty info + */ + function updateOfferRoyaltyRecipientsInternal(uint256 _offerId, RoyaltyInfo calldata _royaltyInfo) internal { + // Make sure the caller is the assistant, offer exists and is not voided + Offer storage offer = getValidOfferWithSellerCheck(_offerId); + + validateRoyaltyInfo(protocolLookups(), protocolLimits(), offer.sellerId, _royaltyInfo); + + // Add new entry to the royaltyInfo array + offer.royaltyInfo.push(_royaltyInfo); + + // Notify watchers of state change + emit OfferRoyaltyInfoUpdated(_offerId, offer.sellerId, _royaltyInfo, msgSender()); + } + /** * @notice Gets the details about a given offer. * @@ -385,7 +444,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * @return nextOfferId - the next offer id */ - function getNextOfferId() public view override returns (uint256 nextOfferId) { + function getNextOfferId() external view override returns (uint256 nextOfferId) { nextOfferId = protocolCounters().nextOfferId; } @@ -396,7 +455,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @return exists - the offer was found * @return offerVoided - true if voided, false otherwise */ - function isOfferVoided(uint256 _offerId) public view override returns (bool exists, bool offerVoided) { + function isOfferVoided(uint256 _offerId) external view override returns (bool exists, bool offerVoided) { Offer storage offer; (exists, offer) = fetchOffer(_offerId); offerVoided = offer.voided; diff --git a/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol b/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol index b4fc00eaa..9c4ef038d 100644 --- a/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol +++ b/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol @@ -122,7 +122,7 @@ contract PriceDiscoveryHandlerFacet is IBosonPriceDiscoveryHandler, PriceDiscove { // Calculate royalties (RoyaltyInfo storage royaltyInfo, uint256 royaltyInfoIndex, ) = fetchRoyalties(offerId, false); - uint256 royaltyAmount = (getTotalRoyaltyPercentage(royaltyInfo.bps) * actualPrice) / 10000; + uint256 royaltyAmount = (getTotalRoyaltyPercentage(royaltyInfo.bps) * actualPrice) / HUNDRED_PERCENT; // Verify that fees and royalties are not higher than the price. if (protocolFeeAmount + royaltyAmount > actualPrice) revert FeeAmountTooHigh(); diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index e13b46437..270ab9c73 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -126,7 +126,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis (royaltyInfo, exchangeCost.royaltyInfoIndex, ) = fetchRoyalties(offerId, false); exchangeCost.royaltyAmount = (getTotalRoyaltyPercentage(royaltyInfo.bps) * exchangeCost.price) / - 10000; + HUNDRED_PERCENT; } // Verify that fees and royalties are not higher than the price. diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 9c2848fc7..5fbd7738f 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -175,7 +175,7 @@ library FundsLib { } else { // RESOLVED or DECIDED uint256 pot = price + sellerDeposit + buyerEscalationDeposit; - buyerPayoff = (pot * dispute.buyerPercent) / 10000; + buyerPayoff = (pot * dispute.buyerPercent) / HUNDRED_PERCENT; sellerPayoff = pot - buyerPayoff; } } @@ -257,7 +257,7 @@ library FundsLib { { if (_exchangeState == BosonTypes.ExchangeState.Completed) { // COMPLETED, buyer pays full price - effectivePriceMultiplier = 10000; + effectivePriceMultiplier = HUNDRED_PERCENT; } else if ( _exchangeState == BosonTypes.ExchangeState.Revoked || _exchangeState == BosonTypes.ExchangeState.Canceled @@ -272,13 +272,13 @@ library FundsLib { if (disputeState == BosonTypes.DisputeState.Retracted) { // RETRACTED - same as "COMPLETED" - effectivePriceMultiplier = 10000; + effectivePriceMultiplier = HUNDRED_PERCENT; } else if (disputeState == BosonTypes.DisputeState.Refused) { // REFUSED, buyer pays nothing effectivePriceMultiplier = 0; } else { // RESOLVED or DECIDED - effectivePriceMultiplier = 10000 - dispute.buyerPercent; + effectivePriceMultiplier = HUNDRED_PERCENT - dispute.buyerPercent; } } } @@ -318,9 +318,9 @@ library FundsLib { ( reducedSecondaryPrice > resellerBuyPrice ? effectivePriceMultiplier * (reducedSecondaryPrice - resellerBuyPrice) - : (10000 - effectivePriceMultiplier) * (resellerBuyPrice - reducedSecondaryPrice) + : (HUNDRED_PERCENT - effectivePriceMultiplier) * (resellerBuyPrice - reducedSecondaryPrice) ) / - 10000; + HUNDRED_PERCENT; resellerBuyPrice = price; } @@ -341,7 +341,7 @@ library FundsLib { } // protocolFee and sellerRoyalties can be multiplied by effectivePriceMultiplier just at the end - protocolFee = (protocolFee * effectivePriceMultiplier) / 10000; + protocolFee = (protocolFee * effectivePriceMultiplier) / HUNDRED_PERCENT; } /** @@ -561,10 +561,10 @@ library FundsLib { BosonTypes.RoyaltyInfo storage _royaltyInfo = _offer.royaltyInfo[_royaltyInfoIndex]; uint256 len = _royaltyInfo.recipients.length; uint256 totalAmount; - uint256 effectivePrice = (_price * _effectivePriceMultiplier) / 10000; + uint256 effectivePrice = (_price * _effectivePriceMultiplier) / HUNDRED_PERCENT; for (uint256 i = 0; i < len; ) { address payable recipient = _royaltyInfo.recipients[i]; - uint256 amount = (_royaltyInfo.bps[i] * effectivePrice) / 10000; + uint256 amount = (_royaltyInfo.bps[i] * effectivePrice) / HUNDRED_PERCENT; totalAmount += amount; if (recipient == address(0)) { // goes to seller's treasury @@ -580,6 +580,6 @@ library FundsLib { } // if there is a remainder due to rounding, it goes to the seller's treasury - sellerRoyalties += (_effectivePriceMultiplier * _escrowedRoyaltyAmount) / 10000 - totalAmount; + sellerRoyalties += (_effectivePriceMultiplier * _escrowedRoyaltyAmount) / HUNDRED_PERCENT - totalAmount; } }