From c387b22fd93e64a9f05427333c783b0dd27ee22f Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Thu, 8 Jun 2023 14:41:11 -0300 Subject: [PATCH 01/22] Remove protocol gas limits --- .../interfaces/events/IBosonConfigEvents.sol | 10 - .../handlers/IBosonConfigHandler.sol | 196 ---- contracts/protocol/bases/BundleBase.sol | 9 - .../protocol/clients/voucher/BosonVoucher.sol | 17 - .../protocol/facets/ConfigHandlerFacet.sol | 287 ----- .../protocol/facets/ExchangeHandlerFacet.sol | 4 - .../protocol/facets/FundsHandlerFacet.sol | 5 +- .../protocol/facets/GroupHandlerFacet.sol | 4 - .../ProtocolInitializationHandlerFacet.sol | 1 - scripts/config/limit-estimation.js | 54 - scripts/util/estimate-limits.js | 1003 ----------------- test/protocol/ConfigHandlerTest.js | 432 ------- 12 files changed, 1 insertion(+), 2021 deletions(-) delete mode 100644 scripts/config/limit-estimation.js delete mode 100644 scripts/util/estimate-limits.js diff --git a/contracts/interfaces/events/IBosonConfigEvents.sol b/contracts/interfaces/events/IBosonConfigEvents.sol index 626201afc..03e3ce8d5 100644 --- a/contracts/interfaces/events/IBosonConfigEvents.sol +++ b/contracts/interfaces/events/IBosonConfigEvents.sol @@ -15,16 +15,7 @@ interface IBosonConfigEvents { event BeaconProxyAddressChanged(address indexed beaconProxyAddress, address indexed executedBy); event ProtocolFeePercentageChanged(uint256 feePercentage, address indexed executedBy); event ProtocolFeeFlatBosonChanged(uint256 feeFlatBoson, address indexed executedBy); - event MaxExchangesPerBatchChanged(uint16 maxExchangesPerBatch, address indexed executedBy); - event MaxOffersPerGroupChanged(uint16 maxOffersPerGroup, address indexed executedBy); - event MaxOffersPerBatchChanged(uint16 maxOffersPerBatch, address indexed executedBy); - event MaxTwinsPerBundleChanged(uint16 maxTwinsPerBundle, address indexed executedBy); - event MaxOffersPerBundleChanged(uint16 maxOffersPerBundle, address indexed executedBy); - event MaxTokensPerWithdrawalChanged(uint16 maxTokensPerWithdrawal, address indexed executedBy); - event MaxFeesPerDisputeResolverChanged(uint16 maxFeesPerDisputeResolver, address indexed executedBy); event MaxEscalationResponsePeriodChanged(uint256 maxEscalationResponsePeriod, address indexed executedBy); - event MaxDisputesPerBatchChanged(uint16 maxDisputesPerBatch, address indexed executedBy); - event MaxAllowedSellersChanged(uint16 maxAllowedSellers, address indexed executedBy); event BuyerEscalationFeePercentageChanged(uint256 buyerEscalationFeePercentage, address indexed executedBy); event AuthTokenContractChanged( BosonTypes.AuthTokenType indexed authTokenType, @@ -35,6 +26,5 @@ interface IBosonConfigEvents { event MaxRoyaltyPercentageChanged(uint16 maxRoyaltyPecentage, address indexed executedBy); event MaxResolutionPeriodChanged(uint256 maxResolutionPeriod, address indexed executedBy); event MinDisputePeriodChanged(uint256 minDisputePeriod, address indexed executedBy); - event MaxPremintedVouchersChanged(uint256 maxPremintedVouchers, address indexed executedBy); event AccessControllerAddressChanged(address indexed accessControllerAddress, address indexed executedBy); } diff --git a/contracts/interfaces/handlers/IBosonConfigHandler.sol b/contracts/interfaces/handlers/IBosonConfigHandler.sol index 056832cac..7130fc714 100644 --- a/contracts/interfaces/handlers/IBosonConfigHandler.sol +++ b/contracts/interfaces/handlers/IBosonConfigHandler.sol @@ -134,126 +134,6 @@ interface IBosonConfigHandler is IBosonConfigEvents { */ function getProtocolFeeFlatBoson() external view returns (uint256); - /** - * @notice Sets the maximum numbers of offers that can be created in a single transaction. - * - * Emits a MaxOffersPerBatchChanged event. - * - * Reverts if _maxOffersPerBatch is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerBatch - the maximum length of {BosonTypes.Offer[]} - */ - function setMaxOffersPerBatch(uint16 _maxOffersPerBatch) external; - - /** - * @notice Gets the maximum numbers of offers that can be created in a single transaction. - * - * @return the maximum numbers of offers that can be created in a single transaction - */ - function getMaxOffersPerBatch() external view returns (uint16); - - /** - * @notice Sets the maximum numbers of offers that can be added to a group in a single transaction. - * - * Emits a MaxOffersPerGroupChanged event. - * - * Reverts if _maxOffersPerGroup is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerGroup - the maximum length of {BosonTypes.Group.offerIds} - */ - function setMaxOffersPerGroup(uint16 _maxOffersPerGroup) external; - - /** - * @notice Gets the maximum numbers of offers that can be added to a group in a single transaction. - * - * @return the maximum numbers of offers that can be added to a group in a single transaction - */ - function getMaxOffersPerGroup() external view returns (uint16); - - /** - * @notice Sets the maximum numbers of twins that can be added to a bundle in a single transaction. - * - * Emits a MaxTwinsPerBundleChanged event. - * - * Reverts if _maxTwinsPerBundle is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxTwinsPerBundle - the maximum length of {BosonTypes.Bundle.twinIds} - */ - function setMaxTwinsPerBundle(uint16 _maxTwinsPerBundle) external; - - /** - * @notice Gets the maximum numbers of twins that can be added to a bundle in a single transaction. - * - * @return the maximum numbers of twins that can be added to a bundle in a single transaction. - */ - function getMaxTwinsPerBundle() external view returns (uint16); - - /** - * @notice Sets the maximum numbers of offers that can be added to a bundle in a single transaction. - * - * Emits a MaxOffersPerBundleChanged event. - * - * Reverts if _maxOffersPerBundle is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerBundle - the maximum length of {BosonTypes.Bundle.offerIds} - */ - function setMaxOffersPerBundle(uint16 _maxOffersPerBundle) external; - - /** - * @notice Gets the maximum numbers of offers that can be added to a bundle in a single transaction. - * - * @return the maximum numbers of offers that can be added to a bundle in a single transaction - */ - function getMaxOffersPerBundle() external view returns (uint16); - - /** - * @notice Sets the maximum numbers of tokens that can be withdrawn in a single transaction. - * - * Emits a MaxTokensPerWithdrawalChanged event. - * - * Reverts if _maxTokensPerWithdrawal is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxTokensPerWithdrawal - the maximum length of token list when calling {FundsHandlerFacet.withdraw} - */ - function setMaxTokensPerWithdrawal(uint16 _maxTokensPerWithdrawal) external; - - /** - * @notice Gets the maximum numbers of tokens that can be withdrawn in a single transaction. - * - * @return the maximum length of token list when calling {FundsHandlerFacet.withdraw} - */ - function getMaxTokensPerWithdrawal() external view returns (uint16); - - /** - * @notice Sets the maximum number of dispute resolver fee structs that can be processed in a single transaction. - * - * Emits a MaxFeesPerDisputeResolverChanged event. - * - * Reverts if _maxFeesPerDisputeResolver is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxFeesPerDisputeResolver - the maximum length of dispute resolver fees list when calling {AccountHandlerFacet.createDisputeResolver} or {AccountHandlerFacet.updateDisputeResolver} - */ - function setMaxFeesPerDisputeResolver(uint16 _maxFeesPerDisputeResolver) external; - - /** - * @notice Gets the maximum number of dispute resolver fee structs that can be processed in a single transaction. - * - * @return the maximum number of dispute resolver fee structs that can be processed in a single transaction - */ - function getMaxFeesPerDisputeResolver() external view returns (uint16); - /** * @notice Sets the maximum escalation response period a dispute resolver can specify. * @@ -274,26 +154,6 @@ interface IBosonConfigHandler is IBosonConfigEvents { */ function getMaxEscalationResponsePeriod() external view returns (uint256); - /** - * @notice Sets the maximum number of disputes that can be expired in a single transaction. - * - * Emits a MaxDisputesPerBatchChanged event. - * - * Reverts if _maxDisputesPerBatch is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxDisputesPerBatch - the maximum number of disputes that can be expired - */ - function setMaxDisputesPerBatch(uint16 _maxDisputesPerBatch) external; - - /** - * @notice Gets the maximum number of disputes that can be expired in a single transaction. - * - * @return the maximum number of disputes that can be expired - */ - function getMaxDisputesPerBatch() external view returns (uint16); - /** * @notice Sets the total offer fee percentage limit which will validate the sum of (Protocol Fee percentage + Agent Fee percentage) of an offer fee. * @@ -317,26 +177,6 @@ interface IBosonConfigHandler is IBosonConfigEvents { */ function getMaxTotalOfferFeePercentage() external view returns (uint16); - /** - * @notice Sets the maximum number of seller ids that can be added to or removed from dispute resolver seller allow list in a single transaction. - * - * Emits a MaxAllowedSellersChanged event. - * - * Reverts if _maxAllowedSellers is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxAllowedSellers - the maximum number of seller ids that can be added or removed - */ - function setMaxAllowedSellers(uint16 _maxAllowedSellers) external; - - /** - * @notice Gets the maximum number of seller ids that can be added to or removed from dispute resolver seller allow list in a single transaction. - * - * @return the maximum number of seller ids that can be added or removed - */ - function getMaxAllowedSellers() external view returns (uint16); - /** * @notice Sets the buyer escalation fee percentage. * @@ -384,26 +224,6 @@ interface IBosonConfigHandler is IBosonConfigEvents { */ function getAuthTokenContract(BosonTypes.AuthTokenType _authTokenType) external view returns (address); - /** - * @notice Sets the maximum number of exchanges that can be created in a single transaction. - * - * Emits a MaxExchangesPerBatchChanged event. - * - * Reverts if _maxExchangesPerBatch is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxExchangesPerBatch - the maximum length of {BosonTypes.Exchange[]} - */ - function setMaxExchangesPerBatch(uint16 _maxExchangesPerBatch) external; - - /** - * @notice Gets the maximum number of exchanges that can be created in a single transaction. - * - * @return the maximum length of {BosonTypes.Exchange[]} - */ - function getMaxExchangesPerBatch() external view returns (uint16); - /** * @notice Sets the maximum royalty percentage that can be set by the seller. * @@ -465,22 +285,6 @@ interface IBosonConfigHandler is IBosonConfigEvents { */ function getMinDisputePeriod() external view returns (uint256); - /** - * @notice Sets the maximum number of vouchers that can be preminted in a single transaction. - * - * Emits a MaxPremintedVouchersChanged event if successful. - * - * Reverts if the _maxPremintedVouchers is zero. - * - * @param _maxPremintedVouchers - the maximum number of vouchers - */ - function setMaxPremintedVouchers(uint256 _maxPremintedVouchers) external; - - /** - * @notice Gets the maximum number of vouchers that can be preminted in a single transaction. - */ - function getMaxPremintedVouchers() external view returns (uint256); - /** * @notice Sets the access controller address. * diff --git a/contracts/protocol/bases/BundleBase.sol b/contracts/protocol/bases/BundleBase.sol index 7b63f161a..892a648e2 100644 --- a/contracts/protocol/bases/BundleBase.sol +++ b/contracts/protocol/bases/BundleBase.sol @@ -24,10 +24,8 @@ contract BundleBase is ProtocolBase, IBosonBundleEvents { * - Any of the offers belongs to different seller * - Any of the offers does not exist * - Offer exists in a different bundle - * - Number of offers exceeds maximum allowed number per bundle * - Any of the twins belongs to different seller * - Any of the twins does not exist - * - Number of twins exceeds maximum allowed number per bundle * - Duplicate twins added in same bundle * - Exchange already exists for the offer id in bundle * - Offers' total quantity is greater than twin supply when token is nonfungible @@ -38,7 +36,6 @@ contract BundleBase is ProtocolBase, IBosonBundleEvents { function createBundleInternal(Bundle memory _bundle) internal { // Cache protocol lookups and limits for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); - ProtocolLib.ProtocolLimits storage limits = protocolLimits(); // get message sender address sender = msgSender(); @@ -53,12 +50,6 @@ contract BundleBase is ProtocolBase, IBosonBundleEvents { BUNDLE_REQUIRES_AT_LEAST_ONE_TWIN_AND_ONE_OFFER ); - // limit maximum number of offers to avoid running into block gas limit in a loop - require(_bundle.offerIds.length <= limits.maxOffersPerBundle, TOO_MANY_OFFERS); - - // limit maximum number of twins to avoid running into block gas limit in a loop - require(_bundle.twinIds.length <= limits.maxTwinsPerBundle, TOO_MANY_TWINS); - // Get the next bundle and increment the counter uint256 bundleId = protocolCounters().nextBundleId++; // Sum of offers quantity available diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 3ea7bab82..7e2e14461 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -200,7 +200,6 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * Reverts if: * - Offer id is not associated with a range * - Amount to mint is more than remaining un-minted in range - * - Too many to mint in a single transaction, given current block gas limit * - Offer already expired * - Offer is voided * @@ -217,13 +216,6 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable // Revert if no more to mint in range require(range.length >= range.minted + _amount, INVALID_AMOUNT_TO_MINT); - // Get max amount that can be minted in a single transaction - address protocolDiamond = IClientExternalAddresses(BeaconClientLib._beacon()).getProtocolAddress(); - uint256 maxPremintedVouchers = IBosonConfigHandler(protocolDiamond).getMaxPremintedVouchers(); - - // Revert if too many to mint in a single transaction - require(_amount <= maxPremintedVouchers, TOO_MANY_TO_MINT); - // Make sure that offer is not expired or voided (Offer memory offer, OfferDates memory offerDates) = getBosonOffer(_offerId); require(!offer.voided && (offerDates.validUntil > block.timestamp), OFFER_EXPIRED_OR_VOIDED); @@ -283,10 +275,6 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable (Offer memory offer, OfferDates memory offerDates) = getBosonOffer(_offerId); require(offer.voided || (offerDates.validUntil <= block.timestamp), OFFER_STILL_VALID); - // Get max amount that can be burned in a single transaction - address protocolDiamond = IClientExternalAddresses(BeaconClientLib._beacon()).getProtocolAddress(); - uint256 maxPremintedVouchers = IBosonConfigHandler(protocolDiamond).getMaxPremintedVouchers(); - // Get the first token to burn uint256 start = (range.lastBurnedTokenId == 0) ? range.start : (range.lastBurnedTokenId + 1); @@ -296,11 +284,6 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable // End should be greater than start require(end > start, NOTHING_TO_BURN); - // If amount to burn is more than maxPremintedVouchers, burn only maxPremintedVouchers - if (end > start + maxPremintedVouchers) { - end = start + maxPremintedVouchers; - } - // Burn the range address rangeOwner = range.owner; uint256 burned; diff --git a/contracts/protocol/facets/ConfigHandlerFacet.sol b/contracts/protocol/facets/ConfigHandlerFacet.sol index 1a0ab5d07..92630491b 100644 --- a/contracts/protocol/facets/ConfigHandlerFacet.sol +++ b/contracts/protocol/facets/ConfigHandlerFacet.sol @@ -38,22 +38,11 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { setBeaconProxyAddress(_addresses.beaconProxy); setProtocolFeePercentage(_fees.percentage); setProtocolFeeFlatBoson(_fees.flatBoson); - setMaxExchangesPerBatch(_limits.maxExchangesPerBatch); - setMaxOffersPerGroup(_limits.maxOffersPerGroup); - setMaxTwinsPerBundle(_limits.maxTwinsPerBundle); - setMaxOffersPerBundle(_limits.maxOffersPerBundle); - setMaxOffersPerBatch(_limits.maxOffersPerBatch); - setMaxTokensPerWithdrawal(_limits.maxTokensPerWithdrawal); - setMaxFeesPerDisputeResolver(_limits.maxFeesPerDisputeResolver); - setMaxEscalationResponsePeriod(_limits.maxEscalationResponsePeriod); - setMaxDisputesPerBatch(_limits.maxDisputesPerBatch); - setMaxAllowedSellers(_limits.maxAllowedSellers); setBuyerEscalationDepositPercentage(_fees.buyerEscalationDepositPercentage); setMaxTotalOfferFeePercentage(_limits.maxTotalOfferFeePercentage); setMaxRoyaltyPecentage(_limits.maxRoyaltyPecentage); setMaxResolutionPeriod(_limits.maxResolutionPeriod); setMinDisputePeriod(_limits.minDisputePeriod); - setMaxPremintedVouchers(_limits.maxPremintedVouchers); // Initialize protocol counters ProtocolLib.ProtocolCounters storage pc = protocolCounters(); @@ -238,176 +227,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { return protocolFees().flatBoson; } - /** - * @notice Sets the maximum numbers of offers that can be added to a group in a single transaction. - * - * Emits a MaxOffersPerGroupChanged event if successful. - * - * Reverts if the _maxOffersPerGroup is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerGroup - the maximum length of {BosonTypes.Group.offerIds} - */ - function setMaxOffersPerGroup(uint16 _maxOffersPerGroup) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxOffersPerGroup is greater than 0 - checkNonZero(_maxOffersPerGroup); - - protocolLimits().maxOffersPerGroup = _maxOffersPerGroup; - emit MaxOffersPerGroupChanged(_maxOffersPerGroup, msgSender()); - } - - /** - * @notice Gets the maximum numbers of offers that can be added to a group in a single transaction. - * - * @return the maximum numbers of offers that can be added to a group in a single transaction - */ - function getMaxOffersPerGroup() external view override returns (uint16) { - return protocolLimits().maxOffersPerGroup; - } - - /** - * @notice Sets the maximum numbers of twins that can be added to a bundle in a single transaction. - * - * Emits a MaxTwinsPerBundleChanged event if successful. - * - * Reverts if the _maxTwinsPerBundle is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxTwinsPerBundle - the maximum length of {BosonTypes.Bundle.twinIds} - */ - function setMaxTwinsPerBundle(uint16 _maxTwinsPerBundle) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxTwinsPerBundle is greater than 0 - checkNonZero(_maxTwinsPerBundle); - - protocolLimits().maxTwinsPerBundle = _maxTwinsPerBundle; - emit MaxTwinsPerBundleChanged(_maxTwinsPerBundle, msgSender()); - } - - /** - * @notice Gets the maximum numbers of twins that can be added to a bundle in a single transaction. - * - * @return the maximum numbers of twins that can be added to a bundle in a single transaction. - */ - function getMaxTwinsPerBundle() external view override returns (uint16) { - return protocolLimits().maxTwinsPerBundle; - } - - /** - * @notice Sets the maximum numbers of offers that can be added to a bundle in a single transaction. - * - * Emits a MaxOffersPerBundleChanged event if successful. - * - * Reverts if the _maxOffersPerBundle is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerBundle - the maximum length of {BosonTypes.Bundle.offerIds} - */ - function setMaxOffersPerBundle(uint16 _maxOffersPerBundle) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxOffersPerBundle is greater than 0 - checkNonZero(_maxOffersPerBundle); - - protocolLimits().maxOffersPerBundle = _maxOffersPerBundle; - emit MaxOffersPerBundleChanged(_maxOffersPerBundle, msgSender()); - } - - /** - * @notice Gets the maximum numbers of offers that can be added to a bundle in a single transaction. - * - * @return the maximum numbers of offers that can be added to a bundle in a single transaction - */ - function getMaxOffersPerBundle() external view override returns (uint16) { - return protocolLimits().maxOffersPerBundle; - } - - /** - * @notice Sets the maximum numbers of offers that can be created in a single transaction. - * - * Emits a MaxOffersPerBatchChanged event if successful. - * - * Reverts if the _maxOffersPerBatch is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxOffersPerBatch - the maximum length of {BosonTypes.Offer[]} - */ - function setMaxOffersPerBatch(uint16 _maxOffersPerBatch) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxOffersPerBatch is greater than 0 - checkNonZero(_maxOffersPerBatch); - - protocolLimits().maxOffersPerBatch = _maxOffersPerBatch; - emit MaxOffersPerBatchChanged(_maxOffersPerBatch, msgSender()); - } - - /** - * @notice Gets the maximum numbers of offers that can be created in a single transaction. - * - * @return the maximum numbers of offers that can be created in a single transaction - */ - function getMaxOffersPerBatch() external view override returns (uint16) { - return protocolLimits().maxOffersPerBatch; - } - - /** - * @notice Sets the maximum numbers of tokens that can be withdrawn in a single transaction. - * - * Emits a MaxTokensPerWithdrawalChanged event if successful. - * - * Reverts if the _maxTokensPerWithdrawal is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxTokensPerWithdrawal - the maximum length of token list when calling {FundsHandlerFacet.withdraw} - */ - function setMaxTokensPerWithdrawal(uint16 _maxTokensPerWithdrawal) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxTokensPerWithdrawal is greater than 0 - checkNonZero(_maxTokensPerWithdrawal); - - protocolLimits().maxTokensPerWithdrawal = _maxTokensPerWithdrawal; - emit MaxTokensPerWithdrawalChanged(_maxTokensPerWithdrawal, msgSender()); - } - - /** - * @notice Gets the maximum numbers of tokens that can be withdrawn in a single transaction. - * - * @return the maximum length of token list when calling {FundsHandlerFacet.withdraw} - */ - function getMaxTokensPerWithdrawal() external view override returns (uint16) { - return protocolLimits().maxTokensPerWithdrawal; - } - - /** - * @notice Sets the maximum number of dispute resolver fee structs that can be processed in a single transaction. - * - * Emits a MaxFeesPerDisputeResolverChanged event if successful. - * - * Reverts if the _maxFeesPerDisputeResolver is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxFeesPerDisputeResolver - the maximum length of dispute resolver fees list when calling {AccountHandlerFacet.createDisputeResolver} or {AccountHandlerFacet.updateDisputeResolver} - */ - function setMaxFeesPerDisputeResolver( - uint16 _maxFeesPerDisputeResolver - ) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxFeesPerDisputeResolver is greater than 0 - checkNonZero(_maxFeesPerDisputeResolver); - - protocolLimits().maxFeesPerDisputeResolver = _maxFeesPerDisputeResolver; - emit MaxFeesPerDisputeResolverChanged(_maxFeesPerDisputeResolver, msgSender()); - } - - /** - * @notice Gets the maximum number of dispute resolver fee structs that can be processed in a single transaction. - * - * @return the maximum number of dispute resolver fee structs that can be processed in a single transaction - */ - function getMaxFeesPerDisputeResolver() external view override returns (uint16) { - return protocolLimits().maxFeesPerDisputeResolver; - } - /** * @notice Sets the maximum escalation response period a dispute resolver can specify. * @@ -438,32 +257,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { return protocolLimits().maxEscalationResponsePeriod; } - /** - * @notice Sets the maximum number of disputes that can be expired in a single transaction. - * - * Emits a MaxDisputesPerBatchChanged event if successful. - * - * @dev Caller must have ADMIN role. - * - * @param _maxDisputesPerBatch - the maximum number of disputes that can be expired - */ - function setMaxDisputesPerBatch(uint16 _maxDisputesPerBatch) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxDisputesPerBatch is greater than 0 - checkNonZero(_maxDisputesPerBatch); - - protocolLimits().maxDisputesPerBatch = _maxDisputesPerBatch; - emit MaxDisputesPerBatchChanged(_maxDisputesPerBatch, msgSender()); - } - - /** - * @notice Gets the maximum number of disputes that can be expired in a single transaction. - * - * @return the maximum number of disputes that can be expired - */ - function getMaxDisputesPerBatch() external view override returns (uint16) { - return protocolLimits().maxDisputesPerBatch; - } - /** * @notice Sets the total offer fee percentage limit that will validate the sum of (Protocol Fee percentage + Agent Fee percentage) of an offer fee. * @@ -539,34 +332,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { return protocolLimits().maxRoyaltyPecentage; } - /** - * @notice Sets the maximum number of seller ids that can be added to or removed from dispute resolver seller allow list in a single transaction. - * - * Emits a MaxAllowedSellersChanged event if successful. - * - * Reverts if the _maxAllowedSellers is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxAllowedSellers - the maximum number of seller ids that can be added or removed - */ - function setMaxAllowedSellers(uint16 _maxAllowedSellers) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxAllowedSellers greater than 0 - checkNonZero(_maxAllowedSellers); - - protocolLimits().maxAllowedSellers = _maxAllowedSellers; - emit MaxAllowedSellersChanged(_maxAllowedSellers, msgSender()); - } - - /** - * @notice Gets the maximum number of seller ids that can be added to or removed from dispute resolver seller allow list in a single transaction. - * - * @return the maximum number of seller ids that can be added or removed - */ - function getMaxAllowedSellers() external view override returns (uint16) { - return protocolLimits().maxAllowedSellers; - } - /** * @notice Sets the buyer escalation fee percentage. * @@ -641,34 +406,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { return protocolLookups().authTokenContracts[_authTokenType]; } - /** - * @notice Sets the maximum number of exchanges that can be created in a single transaction. - * - * Emits a MaxExchangesPerBatchChanged event if successful. - * - * Reverts if the _maxExchangesPerBatch is zero. - * - * @dev Caller must have ADMIN role. - * - * @param _maxExchangesPerBatch - the maximum length of {BosonTypes.Exchange[]} - */ - function setMaxExchangesPerBatch(uint16 _maxExchangesPerBatch) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxExchangePerBatch is greater than 0 - checkNonZero(_maxExchangesPerBatch); - - protocolLimits().maxExchangesPerBatch = _maxExchangesPerBatch; - emit MaxExchangesPerBatchChanged(_maxExchangesPerBatch, msgSender()); - } - - /** - * @notice Gets the maximum number of exchanges that can be created in a single transaction. - * - * @return the maximum length of {BosonTypes.Exchange[]} - */ - function getMaxExchangesPerBatch() external view override returns (uint16) { - return protocolLimits().maxExchangesPerBatch; - } - /** * @notice Sets the maximum resolution period a seller can specify. * @@ -721,30 +458,6 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { return protocolLimits().minDisputePeriod; } - /** - * @notice Sets the maximum number of vouchers that can be preminted in a single transaction. - * - * Emits a MaxPremintedVouchersChanged event if successful. - * - * Reverts if the _maxPremintedVouchers is zero. - * - * @param _maxPremintedVouchers - the maximum number of vouchers - */ - function setMaxPremintedVouchers(uint256 _maxPremintedVouchers) public override onlyRole(ADMIN) nonReentrant { - // Make sure _maxPremintedVouchers is greater than 0 - checkNonZero(_maxPremintedVouchers); - - protocolLimits().maxPremintedVouchers = _maxPremintedVouchers; - emit MaxPremintedVouchersChanged(_maxPremintedVouchers, msgSender()); - } - - /** - * @notice Gets the maximum number of vouchers that can be preminted in a single transaction. - */ - function getMaxPremintedVouchers() external view override returns (uint256) { - return protocolLimits().maxPremintedVouchers; - } - /** * @notice Sets the access controller address. * diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 53c051fa0..09c47339d 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -286,7 +286,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * * Reverts if: * - The exchanges region of protocol is paused - * - Number of exchanges exceeds maximum allowed number per batch * - For any exchange: * - Exchange does not exist * - Exchange is not in Redeemed state @@ -295,9 +294,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * @param _exchangeIds - the array of exchanges ids */ function completeExchangeBatch(uint256[] calldata _exchangeIds) external override exchangesNotPaused { - // limit maximum number of exchanges to avoid running into block gas limit in a loop - require(_exchangeIds.length <= protocolLimits().maxExchangesPerBatch, TOO_MANY_EXCHANGES); - for (uint256 i = 0; i < _exchangeIds.length; i++) { // complete the exchange completeExchange(_exchangeIds[i]); diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 58f613321..99e972497 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -235,14 +235,11 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { // Make sure that at least something will be withdrawn require(tokenList.length != 0, NOTHING_TO_WITHDRAW); - // Make sure that tokenList is not too long - uint256 len = maxTokensPerWithdrawal <= tokenList.length ? maxTokensPerWithdrawal : tokenList.length; - // Get entity's availableFunds storage pointer mapping(address => uint256) storage entityFunds = lookups.availableFunds[_entityId]; // Transfer funds - for (uint256 i = 0; i < len; i++) { + for (uint256 i = 0; i < tokenList.length; i++) { // Get available funds from storage uint256 availableFunds = entityFunds[tokenList[i]]; FundsLib.transferFundsFromProtocol(_entityId, tokenList[i], _destinationAddress, availableFunds); diff --git a/contracts/protocol/facets/GroupHandlerFacet.sol b/contracts/protocol/facets/GroupHandlerFacet.sol index 547a92465..68c4ded8b 100644 --- a/contracts/protocol/facets/GroupHandlerFacet.sol +++ b/contracts/protocol/facets/GroupHandlerFacet.sol @@ -77,7 +77,6 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { * - The groups region of protocol is paused * - Caller is not the seller * - Offer ids param is an empty list - * - Number of offers exceeds maximum allowed number per group * - Group does not exist * - Any offer is not part of the group * @@ -94,9 +93,6 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { // Check if group can be updated (uint256 sellerId, Group storage group) = preUpdateChecks(_groupId, _offerIds); - // limit maximum number of offers to avoid running into block gas limit in a loop - require(_offerIds.length <= protocolLimits().maxOffersPerGroup, TOO_MANY_OFFERS); - for (uint256 i = 0; i < _offerIds.length; i++) { uint256 offerId = _offerIds[i]; diff --git a/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol b/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol index 376e1723a..553c372f8 100644 --- a/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol +++ b/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol @@ -122,7 +122,6 @@ contract ProtocolInitializationHandlerFacet is IBosonProtocolInitializationHandl uint256 _maxPremintedVouchers = abi.decode(_initializationData, (uint256)); require(_maxPremintedVouchers != 0, VALUE_ZERO_NOT_ALLOWED); protocolLimits().maxPremintedVouchers = _maxPremintedVouchers; - emit MaxPremintedVouchersChanged(_maxPremintedVouchers, msgSender()); } /** diff --git a/scripts/config/limit-estimation.js b/scripts/config/limit-estimation.js deleted file mode 100644 index 5754d4cf2..000000000 --- a/scripts/config/limit-estimation.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * List of limits to test and methods that depend on them. - * This is used to test the upper limit of elements in array, passed as input arguments, before the gas block limit is hit. - * - * If a new limit is added to the smart contracts, this list should be updated. - */ -exports.limitsToEstimate = { - blockGasLimit: 30000000, - safeGasLimitPercent: 60, - maxArrayLength: 100, // length of the array used during the estimation. - limits: [ - { name: "maxExchangesPerBatch", methods: { completeExchangeBatch: "IBosonExchangeHandler" } }, - { - name: "maxOffersPerGroup", - methods: { - createGroup: "IBosonGroupHandler", - addOffersToGroup: "IBosonGroupHandler", - removeOffersFromGroup: "IBosonGroupHandler", - }, - }, - { name: "maxOffersPerBundle", methods: { createBundle: "IBosonBundleHandler" } }, - { name: "maxTwinsPerBundle", methods: { createBundle: "IBosonBundleHandler" } }, - { - name: "maxOffersPerBatch", - methods: { - createOfferBatch: "IBosonOfferHandler", - voidOfferBatch: "IBosonOfferHandler", - extendOfferBatch: "IBosonOfferHandler", - }, - }, - { - name: "maxTokensPerWithdrawal", - methods: { withdrawFunds: "IBosonFundsHandler", withdrawProtocolFees: "IBosonFundsHandler" }, - }, - { - name: "maxFeesPerDisputeResolver", - methods: { - createDisputeResolver: "IBosonAccountHandler", - addFeesToDisputeResolver: "IBosonAccountHandler", - removeFeesFromDisputeResolver: "IBosonAccountHandler", - }, - }, - { name: "maxDisputesPerBatch", methods: { expireDisputeBatch: "IBosonDisputeHandler" } }, - { - name: "maxAllowedSellers", - methods: { - createDisputeResolver: "IBosonAccountHandler", - addSellersToAllowList: "IBosonAccountHandler", - removeSellersFromAllowList: "IBosonAccountHandler", - }, - }, - { name: "maxPremintedVouchers", methods: { preMint: "IBosonVoucher" } }, - ], -}; diff --git a/scripts/util/estimate-limits.js b/scripts/util/estimate-limits.js deleted file mode 100644 index e05a0e7b7..000000000 --- a/scripts/util/estimate-limits.js +++ /dev/null @@ -1,1003 +0,0 @@ -const hre = require("hardhat"); -const ethers = hre.ethers; -const simpleStatistic = require("simple-statistics"); -const fs = require("fs"); - -const { limitsToEstimate } = require("../config/limit-estimation"); -const gasLimit = limitsToEstimate.blockGasLimit; -hre.network.config.blockGasLimit = gasLimit; - -const Role = require("../domain/Role"); -const Bundle = require("../domain/Bundle"); -const Group = require("../domain/Group"); -const EvaluationMethod = require("../domain/EvaluationMethod"); -const { DisputeResolverFee } = require("../domain/DisputeResolverFee"); -const { deployProtocolDiamond } = require("../util/deploy-protocol-diamond.js"); -const { deployAndCutFacets } = require("../util/deploy-protocol-handler-facets.js"); -const { deployProtocolClients } = require("../util/deploy-protocol-clients"); -const { deployMockTokens } = require("../util/deploy-mock-tokens"); -const { oneWeek, oneMonth } = require("../../test/util/constants"); -const { - mockSeller, - mockDisputeResolver, - mockVoucherInitValues, - mockAuthToken, - mockCondition, - mockOffer, - mockTwin, - accountId, -} = require("../../test/util/mock"); -const { setNextBlockTimestamp, getFacetsWithArgs, calculateContractAddress } = require("../../test/util/utils.js"); - -// Common vars -let deployer, - sellerWallet1, - sellerWallet2, - sellerWallet3, - dr1, - dr2, - dr3, - buyer, - rando, - other1, - other2, - other3, - protocolAdmin, - feeCollector; -let protocolDiamond, - accessController, - accountHandler, - bundleHandler, - disputeHandler, - exchangeHandler, - fundsHandler, - groupHandler, - offerHandler, - twinHandler; -let bosonVoucher; -let protocolFeePercentage, protocolFeeFlatBoson, buyerEscalationDepositPercentage; -let handlers = {}; -let result = {}; - -let setupEnvironment = {}; - -/* -For each limit from limitsToEstimate, a full setup is needed before a function that depends on a limit can be estimated. -The function that prepares an environment must return the object with invocation details for all methods that depend on a limit. -{ method_1: invocationDetails_1, method_2: invocationDetails_2, ..., method_n: invocationDetails_2} - -Invocation details contain -- account: account that calls the method (important if access is restiricted) -- args: array of arguments that needs to be passed into method -- arrayIndex: index that tells which parameter's length should be varied during the estimation -- structField: if array is part of a struct, specify the field name -*/ - -/* -Setup the environment for "maxAllowedSellers". The following functions depend on it: -- createDisputeResolver -- addSellersToAllowList -- removeSellersFromAllowList -*/ -setupEnvironment["maxAllowedSellers"] = async function (sellerCount = 10) { - // AuthToken - const emptyAuthToken = mockAuthToken(); - const voucherInitValues = mockVoucherInitValues(); - - for (let i = 0; i < sellerCount; i++) { - const wallet = ethers.Wallet.createRandom(); - - //Random wallet has no provider. Connect wallet to ethers provider. The connected wallet will have no ETH - const connectedWallet = wallet.connect(ethers.provider); - - //Fund the new wallet - let tx = { - to: connectedWallet.address, - // Convert currency unit from ether to wei - value: ethers.utils.parseEther("1"), - }; - - await other1.sendTransaction(tx); - const seller = mockSeller(wallet.address, wallet.address, wallet.address, wallet.address); - await accountHandler.connect(connectedWallet).createSeller(seller, emptyAuthToken, voucherInitValues); - } - - //Create DisputeResolverFee array - const disputeResolverFees = [ - new DisputeResolverFee(other1.address, "MockToken1", "0"), - new DisputeResolverFee(other2.address, "MockToken2", "0"), - new DisputeResolverFee(other3.address, "MockToken3", "0"), - ]; - - const sellerAllowList = [...Array(sellerCount + 1).keys()].slice(1); - - // Dispute resolver 2 - used in "addSellersToAllowList" - const disputeResolver2 = mockDisputeResolver(dr2.address, dr2.address, dr2.address, dr2.address); - await accountHandler.connect(dr2).createDisputeResolver(disputeResolver2, disputeResolverFees, []); - const args_2 = [disputeResolver2.id, sellerAllowList]; - const arrayIndex_2 = 1; - - // Dispute resolver 3 - used in "removeSellersFromAllowList" - const disputeResolver3 = mockDisputeResolver(dr3.address, dr3.address, dr3.address, dr3.address); - await accountHandler.connect(dr3).createDisputeResolver(disputeResolver3, disputeResolverFees, sellerAllowList); - const args_3 = [disputeResolver3.id, sellerAllowList]; - const arrayIndex_3 = 1; - - const disputeResolver1 = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address); - const args_1 = [disputeResolver1, disputeResolverFees, sellerAllowList]; - const arrayIndex_1 = 2; - - return { - createDisputeResolver: { account: dr1, args: args_1, arrayIndex: arrayIndex_1 }, - addSellersToAllowList: { account: dr2, args: args_2, arrayIndex: arrayIndex_2 }, - removeSellersFromAllowList: { account: dr3, args: args_3, arrayIndex: arrayIndex_3 }, - }; -}; - -/* -Setup the environment for "maxFeesPerDisputeResolver". The following functions depend on it: -- createDisputeResolver -- addFeesToDisputeResolver -- removeFeesFromDisputeResolver -*/ -setupEnvironment["maxFeesPerDisputeResolver"] = async function (feesCount = 10) { - //Create DisputeResolverFee array - let disputeResolverFees = []; - for (let i = 0; i < feesCount; i++) { - const wallet = ethers.Wallet.createRandom(); - disputeResolverFees.push(new DisputeResolverFee(wallet.address, `MockToken${i}`, "0")); - } - - // Dispute resolver 2 - used in "addFeesToDisputeResolver" - const disputeResolver2 = mockDisputeResolver(dr2.address, dr2.address, dr2.address, dr2.address); - await accountHandler.connect(dr2).createDisputeResolver(disputeResolver2, [], []); - const args_2 = [disputeResolver2.id, disputeResolverFees]; - const arrayIndex_2 = 1; - - // Dispute resolver 3 - used in "removeFeesFromDisputeResolver" - const disputeResolver3 = mockDisputeResolver(dr3.address, dr3.address, dr3.address, dr3.address); - await accountHandler.connect(dr3).createDisputeResolver(disputeResolver3, disputeResolverFees, [], { gasLimit }); - const feeTokenAddressesToRemove = disputeResolverFees.map((DRfee) => DRfee.tokenAddress); - const args_3 = [disputeResolver3.id, feeTokenAddressesToRemove]; - const arrayIndex_3 = 1; - - const disputeResolver1 = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address); - const args_1 = [disputeResolver1, disputeResolverFees, []]; - const arrayIndex_1 = 1; - - return { - createDisputeResolver: { account: dr1, args: args_1, arrayIndex: arrayIndex_1 }, - addFeesToDisputeResolver: { account: dr2, args: args_2, arrayIndex: arrayIndex_2 }, - removeFeesFromDisputeResolver: { account: dr3, args: args_3, arrayIndex: arrayIndex_3 }, - }; -}; - -/* -Setup the environment for "maxOffersPerBatch". The following functions depend on it: -- createOfferBatch -- voidOfferBatch -- extendOfferBatch -*/ -setupEnvironment["maxOffersPerBatch"] = async function (offerCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - // Seller 2 - used in "voidOfferBatch" - const seller2 = mockSeller( - sellerWallet2.address, - sellerWallet2.address, - sellerWallet2.address, - sellerWallet2.address - ); - await accountHandler.connect(sellerWallet2).createSeller(seller2, emptyAuthToken, voucherInitValues); - - // Seller 3 - used in "extendOfferBatch" - const seller3 = mockSeller( - sellerWallet3.address, - sellerWallet3.address, - sellerWallet3.address, - sellerWallet3.address - ); - await accountHandler.connect(sellerWallet3).createSeller(seller3, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - const { offer, offerDates, offerDurations } = await mockOffer(); - const offers = new Array(offerCount).fill(offer); - const offerDatesList = new Array(offerCount).fill(offerDates); - const offerDurationsList = new Array(offerCount).fill(offerDurations); - const disputeResolverIds = new Array(offerCount).fill(disputeResolver.id); - const agentIds = new Array(offerCount).fill(agentId); - - for (let i = 0; i < offerCount; i++) { - // Create the offers for voiding/extending - await offerHandler - .connect(sellerWallet2) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - await offerHandler - .connect(sellerWallet3) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - } - - const offerIds = [...Array(offerCount + 1).keys()].slice(1); - - const args_1 = [offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds]; - const arrayIndex_1 = [0, 1, 2, 3, 4]; // adjusting length of all arguments simultaneously - - // voidOfferBatch inputs - const args_2 = [offerIds.map((offerId) => 2 * offerId - 1)]; - const arrayIndex_2 = 0; - - // extendOfferBatch - const newValidUntilDate = ethers.BigNumber.from(offerDates.validUntil).add("10000").toString(); - const args_3 = [offerIds.map((offerId) => 2 * offerId), newValidUntilDate]; - const arrayIndex_3 = 0; - - return { - createOfferBatch: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, - voidOfferBatch: { account: sellerWallet2, args: args_2, arrayIndex: arrayIndex_2 }, - extendOfferBatch: { account: sellerWallet3, args: args_3, arrayIndex: arrayIndex_3 }, - }; -}; - -/* -Setup the environment for "maxOffersPerGroup". The following functions depend on it: -- createGroup -- addOffersToGroup -- removeOffersFromGroup -*/ -setupEnvironment["maxOffersPerGroup"] = async function (offerCount = 10) { - // Create a seller - // Required constructor params - const groupId = "1"; // argument sent to contract for createSeller will be ignored - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - // Seller 2 - used in "addOffersToGroup" - const seller2 = mockSeller( - sellerWallet2.address, - sellerWallet2.address, - sellerWallet2.address, - sellerWallet2.address - ); - await accountHandler.connect(sellerWallet2).createSeller(seller2, emptyAuthToken, voucherInitValues); - - // Seller 3 - used in "removeOffersFromGroup" - const seller3 = mockSeller( - sellerWallet3.address, - sellerWallet3.address, - sellerWallet3.address, - sellerWallet3.address - ); - await accountHandler.connect(sellerWallet3).createSeller(seller3, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - // Mock offer, offerDates and offerDurations - const { offer, offerDates, offerDurations } = await mockOffer(); - - for (let i = 0; i < offerCount; i++) { - // Create the offer - await offerHandler - .connect(sellerWallet1) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - await offerHandler - .connect(sellerWallet2) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - await offerHandler - .connect(sellerWallet3) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - } - - const offerIds = [...Array(offerCount + 1).keys()].slice(1); - const condition = mockCondition({ method: EvaluationMethod.None, threshold: "0", maxCommits: "0" }); - - const group = new Group(groupId, seller1.id, offerIds); - - let group1 = group.clone(); - group1.offerIds = offerIds.map((offerId) => 3 * offerId - 2); - const args_1 = [group1, condition]; - const arrayIndex_1 = 0; - const structField_1 = "offerIds"; - - let group2 = group.clone(); - group2.offerIds = []; - await groupHandler.connect(sellerWallet2).createGroup(group2, condition); - const args_2 = ["1", offerIds.map((offerId) => 3 * offerId - 1)]; - const arrayIndex_2 = 1; - - let group3 = group.clone(); - group3.offerIds = offerIds.map((offerId) => 3 * offerId); - await groupHandler.connect(sellerWallet3).createGroup(group3, condition, { gasLimit }); - const args_3 = ["2", group3.offerIds]; - const arrayIndex_3 = 1; - - return { - createGroup: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, - addOffersToGroup: { account: sellerWallet2, args: args_2, arrayIndex: arrayIndex_2 }, - removeOffersFromGroup: { account: sellerWallet3, args: args_3, arrayIndex: arrayIndex_3 }, - }; -}; - -/* -Setup the environment for "maxOffersPerBundle". The following functions depend on it: -- createBundle -*/ -setupEnvironment["maxOffersPerBundle"] = async function (offerCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - // Mock offer, offerDates and offerDurations - const { offer, offerDates, offerDurations } = await mockOffer(); - - for (let i = 0; i < offerCount; i++) { - // Create the offer - await offerHandler - .connect(sellerWallet1) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - } - - // Create a valid twin. - const [bosonToken] = await deployMockTokens(); - const twin = mockTwin(bosonToken.address); - twin.supplyAvailable = ethers.BigNumber.from(twin.amount).mul(offerCount); - - // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(sellerWallet1).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler - - // Create a twin. - await twinHandler.connect(sellerWallet1).createTwin(twin); - const twinIds = ["1"]; - - const offerIds = [...Array(offerCount + 1).keys()].slice(1); - - const bundle = new Bundle("1", seller1.id, offerIds, twinIds); - - const args_1 = [bundle]; - const arrayIndex_1 = 0; - const structField_1 = "offerIds"; - - return { - createBundle: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, - }; -}; - -/* -Setup the environment for "maxTwinsPerBundle". The following functions depend on it: -- createBundle -*/ -setupEnvironment["maxTwinsPerBundle"] = async function (twinCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - for (let i = 0; i < twinCount; i++) { - const [twinContract] = await deployMockTokens(["Foreign20"]); - const twin = mockTwin(twinContract.address); - - // Approving the twinHandler contract to transfer seller's tokens - await twinContract.connect(sellerWallet1).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler - - // Create a twin. - await twinHandler.connect(sellerWallet1).createTwin(twin); - } - - // Create a valid offer. - // Mock offer, offerDates and offerDurations - const { offer, offerDates, offerDurations } = await mockOffer(); - - // Create the offer - await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - - const offerIds = ["1"]; - const twinIds = [...Array(twinCount + 1).keys()].slice(1); - - const bundle = new Bundle("1", seller1.id, offerIds, twinIds); - - const args_1 = [bundle]; - const arrayIndex_1 = 0; - const structField_1 = "twinIds"; - - return { - createBundle: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, - }; -}; - -/* -Setup the environment for "maxExchangesPerBatch". The following functions depend on it: -- completeExchangeBatch -*/ -setupEnvironment["maxExchangesPerBatch"] = async function (exchangesCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - // Create an offer with big enough quantity - const { offer, offerDates, offerDurations } = await mockOffer(); - offer.quantityAvailable = exchangesCount; - // Create the offer - await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - - // Deposit seller funds so the commit will succeed - const sellerPool = ethers.BigNumber.from(offer.price).mul(exchangesCount); - await fundsHandler - .connect(sellerWallet1) - .depositFunds(seller1.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); - - await setNextBlockTimestamp(Number(offerDates.voucherRedeemableFrom)); - for (let i = 1; i < exchangesCount + 1; i++) { - // Commit to offer, creating a new exchange - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id, { value: offer.price }); - - // Redeem voucher - await exchangeHandler.connect(buyer).redeemVoucher(i); - } - - // Set time forward to run out the dispute period - const blockNumber = await ethers.provider.getBlockNumber(); - const block = await ethers.provider.getBlock(blockNumber); - const newTime = ethers.BigNumber.from(block.timestamp).add(offerDurations.disputePeriod).add(1).toNumber(); - await setNextBlockTimestamp(newTime); - - const exchangeIds = [...Array(exchangesCount + 1).keys()].slice(1); - - const args_1 = [exchangeIds]; - const arrayIndex_1 = 0; - - return { - completeExchangeBatch: { account: rando, args: args_1, arrayIndex: arrayIndex_1 }, - }; -}; - -/* -Setup the environment for "maxDisputesPerBatch". The following functions depend on it: -- expireDisputeBatch -*/ -setupEnvironment["maxDisputesPerBatch"] = async function (exchangesCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - // Create an offer with big enough quantity - const { offer, offerDates, offerDurations } = await mockOffer(); - offer.quantityAvailable = exchangesCount; - // Create the offer - await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - - // Deposit seller funds so the commit will succeed - const sellerPool = ethers.BigNumber.from(offer.price).mul(exchangesCount); - await fundsHandler - .connect(sellerWallet1) - .depositFunds(seller1.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); - - await setNextBlockTimestamp(Number(offerDates.voucherRedeemableFrom)); - for (let i = 1; i < exchangesCount + 1; i++) { - // Commit to offer, creating a new exchange - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id, { value: offer.price }); - - // Redeem voucher - await exchangeHandler.connect(buyer).redeemVoucher(i); - - // Raise dispute - await disputeHandler.connect(buyer).raiseDispute(i); - } - - // Set time forward to run out the dispute period - const blockNumber = await ethers.provider.getBlockNumber(); - const block = await ethers.provider.getBlock(blockNumber); - const newTime = ethers.BigNumber.from(block.timestamp).add(offerDurations.resolutionPeriod).add(1).toNumber(); - await setNextBlockTimestamp(newTime); - - const exchangeIds = [...Array(exchangesCount + 1).keys()].slice(1); - - const args_1 = [exchangeIds]; - const arrayIndex_1 = 0; - - return { - expireDisputeBatch: { account: rando, args: args_1, arrayIndex: arrayIndex_1 }, - }; -}; - -/* -Setup the environment for "maxTokensPerWithdrawal". The following functions depend on it: -- withdrawFunds -- withdrawProtocolFees -*/ -setupEnvironment["maxTokensPerWithdrawal"] = async function (tokenCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - const { offer, offerDates, offerDurations } = await mockOffer(); - offerDates.voucherRedeemableFrom = offerDates.validFrom; - let tokenAddresses = []; - for (let i = 1; i < tokenCount + 1; i++) { - // create a token - const [tokenContract] = await deployMockTokens(["Foreign20"]); - tokenAddresses.push(tokenContract.address); - - offer.exchangeToken = tokenContract.address; - await tokenContract.mint(sellerWallet1.address, offer.sellerDeposit); - await tokenContract.mint(buyer.address, offer.price); - await tokenContract.connect(sellerWallet1).approve(protocolDiamond.address, offer.sellerDeposit); - await tokenContract.connect(buyer).approve(protocolDiamond.address, offer.price); - await fundsHandler.connect(sellerWallet1).depositFunds(seller1.id, tokenContract.address, offer.sellerDeposit); - - // add token to DR accepted tokens - await accountHandler - .connect(dr1) - .addFeesToDisputeResolver(disputeResolver.id, [new DisputeResolverFee(tokenContract.address, `Token${i}`, "0")]); - - // create the offer - await offerHandler - .connect(sellerWallet1) - .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - - // Commit to offer, creating a new exchange - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, i); - - // Redeem voucher - await exchangeHandler.connect(buyer).redeemVoucher(i); - - // Raise dispute - await exchangeHandler.connect(buyer).completeExchange(i); - } - - // seller withdrawal - const tokenAmounts_1 = new Array(tokenCount).fill(offer.price); - const args_1 = [seller1.id, tokenAddresses, tokenAmounts_1]; - const arrayIndex_1 = [1, 2]; - - // protocol fee withdrawal - await accessController.grantRole(Role.FEE_COLLECTOR, feeCollector.address); - const protocolFee = ethers.BigNumber.from(offer.price).mul(protocolFeePercentage).div(10000); - const tokenAmounts_2 = new Array(tokenCount).fill(protocolFee); - const args_2 = [tokenAddresses, tokenAmounts_2]; - const arrayIndex_2 = [0, 1]; - - return { - withdrawFunds: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, - withdrawProtocolFees: { account: feeCollector, args: args_2, arrayIndex: arrayIndex_2 }, - }; -}; - -/* -Setup the environment for "maxPremintedVouchers". The following function depend on it: -- preMint -*/ -setupEnvironment["maxPremintedVouchers"] = async function (tokenCount = 10) { - // Create a seller - // Required constructor params - const agentId = "0"; // agent id is optional while creating an offer - - const seller1 = mockSeller( - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address, - sellerWallet1.address - ); - const voucherInitValues = mockVoucherInitValues(); - const emptyAuthToken = mockAuthToken(); - - await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); - - const disputeResolver = mockDisputeResolver(dr1.address, dr1.address, dr1.address, dr1.address, true); - await accountHandler - .connect(dr1) - .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")], []); - - // create the offer - const { offer, offerDates, offerDurations } = await mockOffer(); - offer.quantityAvailable = ethers.constants.MaxUint256; - await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); - - // reserve range - let length = ethers.BigNumber.from(2).pow(128).sub(1); - await offerHandler.connect(sellerWallet1).reserveRange(offer.id, length); - - // update bosonVoucher address - handlers.IBosonVoucher = bosonVoucher.attach(calculateContractAddress(accountHandler.address, seller1.id)); - - // make an empty array of length tokenCount - const amounts = new Array(tokenCount); - - const args_1 = [offer.id, amounts]; - const arrayIndex_1 = 1; - - return { - preMint: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, - }; -}; - -/* -Invoke the methods that setup the environment and iterate over all limits and pass them to estimation. -At the end it writes the results to json file. -*/ -async function estimateLimits() { - if (hre.network.name !== "hardhat") { - console.log("Unsupported network"); - process.exit(1); - } - - for (const limit of limitsToEstimate.limits) { - console.log(`## ${limit.name} ##`); - console.log(`Setting up the environment`); - await setupCommonEnvironment(); - const inputs = await setupEnvironment[limit.name](limitsToEstimate.maxArrayLength); - console.log(`Estimating the limit`); - await estimateLimit(limit, inputs, limitsToEstimate.safeGasLimitPercent); - accountId.next(true); - } - makeReport(result, limitsToEstimate.maxArrayLength); -} - -/* -Esitmates individual limit. It estimates gas for different lenghts of input array and forwards -the result to function that calculates the actual limit. - -It stores the list of point estimates and maximum and safe lenght of the array to results. -*/ -async function estimateLimit(limit, inputs, safeGasLimitPercent) { - result[limit.name] = {}; - for (const [method, handler] of Object.entries(limit.methods)) { - console.log(`=== ${method} ===`); - const methodInputs = inputs[method]; - if (methodInputs === undefined) { - console.log(`Missing setup for ${limit.name}:${method}`); - continue; - } - - const maxArrayLength = methodInputs.structField - ? methodInputs.args[methodInputs.arrayIndex][methodInputs.structField].length - : methodInputs.args[Array.isArray(methodInputs.arrayIndex) ? methodInputs.arrayIndex[0] : methodInputs.arrayIndex] - .length; - let gasEstimates = []; - for (let o = 0; Math.pow(10, o) <= maxArrayLength; o++) { - for (let i = 1; i < 10; i++) { - let arrayLength = i * Math.pow(10, o); - if (arrayLength > maxArrayLength) arrayLength = maxArrayLength; - - const args = methodInputs.args; - let adjustedArgs = [...args]; - - if (methodInputs.structField) { - adjustedArgs[methodInputs.arrayIndex] = { ...adjustedArgs[methodInputs.arrayIndex] }; - adjustedArgs[methodInputs.arrayIndex][methodInputs.structField] = args[methodInputs.arrayIndex][ - methodInputs.structField - ].slice(0, arrayLength); - } else { - if (Array.isArray(methodInputs.arrayIndex)) { - for (const ai of methodInputs.arrayIndex) { - adjustedArgs[ai] = args[ai].slice(0, arrayLength); - } - } else { - // if args contains null values, just use arrayLength instead - adjustedArgs[methodInputs.arrayIndex] = args[methodInputs.arrayIndex][0] - ? args[methodInputs.arrayIndex].slice(0, arrayLength) - : arrayLength; - } - } - - try { - const gasEstimate = await handlers[handler] - .connect(methodInputs.account) - .estimateGas[method](...adjustedArgs, { gasLimit }); - console.log("Length:", arrayLength, "Gas:", gasEstimate.toNumber()); - gasEstimates.push([gasEstimate.toNumber(), arrayLength]); - } catch (e) { - // console.log(e) - console.log("Block gas limit already hit"); - break; - } - if (arrayLength == maxArrayLength) break; - } - } - const { maxNumber, safeNumber } = calculateLimit(gasEstimates, safeGasLimitPercent); - result[limit.name][method] = { gasEstimates, maxNumber, safeNumber }; - console.log(`Estimation complete`); - } -} - -/* -Based on point gas estimates calculates the maximum and safe length of the array that can be passed in -Safe length is determined by safeGasLimitPercent which is the percentage amount of block that is considered -safe to be taken -*/ -function calculateLimit(gasEstimates, safeGasLimitPercent) { - const regCoef = simpleStatistic.linearRegression(gasEstimates); - const line = simpleStatistic.linearRegressionLine(regCoef); - - const maxNumber = Math.floor(line(gasLimit)); - const safeNumber = Math.floor(line((gasLimit * safeGasLimitPercent) / 100)); - return { maxNumber, safeNumber }; -} - -/* -Deploys protocol contracts, casts facets to interfaces and makes accounts available -*/ -async function setupCommonEnvironment() { - // Make accounts available - [ - deployer, - sellerWallet1, - sellerWallet2, - sellerWallet3, - dr1, - dr2, - dr3, - buyer, - rando, - other1, - other2, - other3, - protocolAdmin, - feeCollector, - other1, - ] = await ethers.getSigners(); - - // Deploy the Protocol Diamond - [protocolDiamond, , , , accessController] = await deployProtocolDiamond(); - - // Temporarily grant UPGRADER role to deployer account - await accessController.grantRole(Role.UPGRADER, deployer.address); - - // Grant PROTOCOL role to ProtocolDiamond address and renounces admin - await accessController.grantRole(Role.PROTOCOL, protocolDiamond.address); - - // Grant ADMIN role to and address that can call restricted functions. - // This ADMIN role is a protocol-level role. It is not the same an admin address for an account type - await accessController.grantRole(Role.ADMIN, protocolAdmin.address); - - // Deploy the Protocol client implementation/proxy pairs (currently just the Boson Voucher) - const protocolClientArgs = [protocolDiamond.address]; - const [, beacons, proxies, bv] = await deployProtocolClients(protocolClientArgs, gasLimit); - const [beacon] = beacons; - const [proxy] = proxies; - [bosonVoucher] = bv; - - // Set protocolFees - protocolFeePercentage = "200"; // 2 % - protocolFeeFlatBoson = ethers.utils.parseUnits("0.01", "ether").toString(); - buyerEscalationDepositPercentage = "1000"; // 10% - - // Add config Handler, so ids start at 1, and so voucher address can be found - const protocolConfig = [ - // Protocol addresses - { - treasury: rando.address, - token: rando.address, - voucherBeacon: beacon.address, - beaconProxy: proxy.address, - }, - // Protocol limits - { - maxExchangesPerBatch: 10000, - maxOffersPerGroup: 10000, - maxTwinsPerBundle: 10000, - maxOffersPerBundle: 10000, - maxOffersPerBatch: 10000, - maxTokensPerWithdrawal: 10000, - maxFeesPerDisputeResolver: 10000, - maxEscalationResponsePeriod: oneMonth, - maxDisputesPerBatch: 10000, - maxAllowedSellers: 10000, - maxTotalOfferFeePercentage: 4000, //40% - maxRoyaltyPecentage: 1000, //10% - maxResolutionPeriod: oneMonth, - minDisputePeriod: oneWeek, - maxPremintedVouchers: 100, - }, - // Protocol fees - { - percentage: protocolFeePercentage, - flatBoson: protocolFeeFlatBoson, - buyerEscalationDepositPercentage, - }, - ]; - - const facetNames = [ - "AccountHandlerFacet", - "BundleHandlerFacet", - "DisputeHandlerFacet", - "DisputeResolverHandlerFacet", - "ExchangeHandlerFacet", - "FundsHandlerFacet", - "GroupHandlerFacet", - "OfferHandlerFacet", - "SellerHandlerFacet", - "TwinHandlerFacet", - "ProtocolInitializationHandlerFacet", - "ConfigHandlerFacet", - ]; - - const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); - - // Cut the protocol handler facets into the Diamond - await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, gasLimit); - // Cast Diamond to handlers - accountHandler = await ethers.getContractAt("IBosonAccountHandler", protocolDiamond.address); - bundleHandler = await ethers.getContractAt("IBosonBundleHandler", protocolDiamond.address); - disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); - exchangeHandler = await ethers.getContractAt("IBosonExchangeHandler", protocolDiamond.address); - fundsHandler = await ethers.getContractAt("IBosonFundsHandler", protocolDiamond.address); - groupHandler = await ethers.getContractAt("IBosonGroupHandler", protocolDiamond.address); - offerHandler = await ethers.getContractAt("IBosonOfferHandler", protocolDiamond.address); - twinHandler = await ethers.getContractAt("IBosonTwinHandler", protocolDiamond.address); - - handlers = { - IBosonAccountHandler: accountHandler, - IBosonBundleHandler: bundleHandler, - IBosonDisputeHandler: disputeHandler, - IBosonExchangeHandler: exchangeHandler, - IBosonFundsHandler: fundsHandler, - IBosonGroupHandler: groupHandler, - IBosonOfferHandler: offerHandler, - IBosonVoucher: bosonVoucher, - }; -} - -function makeReport(res, maxArrayLength) { - // TABLE 1: suggested values - let header1 = `| limit | max value | safe value |`; - let alignment1 = `| :-- | --: | --: |`; - let rows1 = []; - - // TABLE 2: all estimates - let header2 = `| # |`; - let alignment2 = `|--| `; - let row0 = `| |`; - let rows2 = []; - let numberOfRows = 0; - - for (let o = 0; Math.pow(10, o) <= maxArrayLength; o++) { - for (let i = 1; i < 10; i++) { - let arrayLength = i * Math.pow(10, o); - if (arrayLength > maxArrayLength) arrayLength = maxArrayLength; - rows2.push(`| ${arrayLength} |`); - numberOfRows++; - if (arrayLength == maxArrayLength) break; - } - } - - let maxNumber = `| **max** |`; - let safeNumber = `| safe |`; - - for (const [limit, result] of Object.entries(res)) { - let mn = Number.MAX_SAFE_INTEGER; - let sn = Number.MAX_SAFE_INTEGER; - for (const [method, estimates] of Object.entries(result)) { - header2 = `${header2} ${limit} |`; - alignment2 = `${alignment2} ---:|`; - row0 = `${row0} ${method} |`; - for (let i = 0; i < numberOfRows; i++) { - rows2[i] = `${rows2[i]} ${estimates.gasEstimates[i] ? estimates.gasEstimates[i][0] : " "} |`; - } - maxNumber = `${maxNumber} **${estimates.maxNumber}** |`; - safeNumber = `${safeNumber} ${estimates.safeNumber} |`; - - mn = Math.min(mn, estimates.maxNumber); - sn = Math.min(sn, estimates.safeNumber); - } - - rows1.push(`| ${limit} | ${mn} | ${sn} |`); - } - - const table1 = [header1, alignment1, ...rows1].join(`\n`); - const table2 = [header2, alignment2, row0, ...rows2, maxNumber, safeNumber].join(`\n`); - - const output = `${table1}\n\n${table2}`; - - fs.writeFileSync(__dirname + "/../../logs/limit_estimates.md", output); - fs.writeFileSync(__dirname + "/../../logs/limit_estimates.json", JSON.stringify(result)); -} - -exports.estimateLimits = estimateLimits; diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index fe6705d91..5b4a44b0b 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -283,206 +283,6 @@ describe("IBosonConfigHandler", function () { // All supported methods context("📋 Setters", async function () { - context("👉 setMaxOffersPerGroup()", async function () { - let maxOffersPerGroup; - beforeEach(async function () { - // set new value for max offers per group - maxOffersPerGroup = 150; - }); - - it("should emit a MaxOffersPerGroupChanged event", async function () { - // Set new max offer per group, testing for the event - await expect(configHandler.connect(deployer).setMaxOffersPerGroup(maxOffersPerGroup)) - .to.emit(configHandler, "MaxOffersPerGroupChanged") - .withArgs(maxOffersPerGroup, deployer.address); - }); - - it("should update state", async function () { - // Set new max offer per group, - await configHandler.connect(deployer).setMaxOffersPerGroup(maxOffersPerGroup); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxOffersPerGroup()).to.equal(maxOffersPerGroup); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max offer per group, expecting revert - await expect(configHandler.connect(rando).setMaxOffersPerGroup(maxOffersPerGroup)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxOffersPerGroup is zero", async function () { - maxOffersPerGroup = 0; - - await expect(configHandler.connect(deployer).setMaxOffersPerGroup(maxOffersPerGroup)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - - context("👉 setMaxTwinsPerBundle()", async function () { - let maxTwinsPerBundle; - beforeEach(async function () { - // set new value for max twins per bundle - maxTwinsPerBundle = 150; - }); - - it("should emit a MaxTwinsPerBundleChanged event", async function () { - // Set new max twin per bundle, testing for the event - await expect(configHandler.connect(deployer).setMaxTwinsPerBundle(maxTwinsPerBundle)) - .to.emit(configHandler, "MaxTwinsPerBundleChanged") - .withArgs(maxTwinsPerBundle, deployer.address); - }); - - it("should update state", async function () { - // Set new max twin per bundle, - await configHandler.connect(deployer).setMaxTwinsPerBundle(maxTwinsPerBundle); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxTwinsPerBundle()).to.equal(maxTwinsPerBundle); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max twin per bundle, expecting revert - await expect(configHandler.connect(rando).setMaxTwinsPerBundle(maxTwinsPerBundle)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxTwinsPerBundle is zero", async function () { - maxTwinsPerBundle = 0; - - await expect(configHandler.connect(deployer).setMaxTwinsPerBundle(maxTwinsPerBundle)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - - context("👉 setMaxOffersPerBundle()", async function () { - let maxOffersPerBundle; - beforeEach(async function () { - // set new value for max offers per bundle - maxOffersPerBundle = 150; - }); - - it("should emit a MaxOffersPerBundleChanged event", async function () { - // Set new max offer per bundle, testing for the event - await expect(configHandler.connect(deployer).setMaxOffersPerBundle(maxOffersPerBundle)) - .to.emit(configHandler, "MaxOffersPerBundleChanged") - .withArgs(maxOffersPerBundle, deployer.address); - }); - - it("should update state", async function () { - // Set new max offer per bundle, - await configHandler.connect(deployer).setMaxOffersPerBundle(maxOffersPerBundle); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxOffersPerBundle()).to.equal(maxOffersPerBundle); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max offer per bundle, expecting revert - await expect(configHandler.connect(rando).setMaxOffersPerBundle(maxOffersPerBundle)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxOffersPerBundle is zero", async function () { - maxOffersPerBundle = 0; - - await expect(configHandler.connect(deployer).setMaxOffersPerBundle(maxOffersPerBundle)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - - context("👉 setMaxOffersPerBatch()", async function () { - let maxOffersPerBatch; - beforeEach(async function () { - // set new value for max offers per batch - maxOffersPerBatch = 135; - }); - - it("should emit a MaxOffersPerBatchChanged event", async function () { - // Set new max offer per batch, testing for the event - await expect(configHandler.connect(deployer).setMaxOffersPerBatch(maxOffersPerBatch)) - .to.emit(configHandler, "MaxOffersPerBatchChanged") - .withArgs(maxOffersPerBatch, deployer.address); - }); - - it("should update state", async function () { - // Set new max offer per batch, - await configHandler.connect(deployer).setMaxOffersPerBatch(maxOffersPerBatch); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxOffersPerBatch()).to.equal(maxOffersPerBatch); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max offer per batch, expecting revert - await expect(configHandler.connect(rando).setMaxOffersPerBatch(maxOffersPerBatch)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxOffersPerBatch is zero", async function () { - maxOffersPerBatch = 0; - - await expect(configHandler.connect(deployer).setMaxOffersPerBatch(maxOffersPerBatch)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - - context("👉 setMaxTokensPerWithdrawal()", async function () { - let maxTokensPerWithdrawal; - beforeEach(async function () { - // set new value for max tokens per withdrawal - maxTokensPerWithdrawal = 598; - }); - - it("should emit a MaxTokensPerWithdrawalChanged event", async function () { - // Set new max tokens per withdrawal, testing for the event - await expect(configHandler.connect(deployer).setMaxTokensPerWithdrawal(maxTokensPerWithdrawal)) - .to.emit(configHandler, "MaxTokensPerWithdrawalChanged") - .withArgs(maxTokensPerWithdrawal, deployer.address); - }); - - it("should update state", async function () { - // Set new max offer tokens per withdrawal - await configHandler.connect(deployer).setMaxTokensPerWithdrawal(maxTokensPerWithdrawal); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxTokensPerWithdrawal()).to.equal(maxTokensPerWithdrawal); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new tokens per withdrawal, expecting revert - await expect( - configHandler.connect(rando).setMaxTokensPerWithdrawal(maxTokensPerWithdrawal) - ).to.revertedWith(RevertReasons.ACCESS_DENIED); - }); - - it("maxTokensPerWithdrawal is zero", async function () { - maxTokensPerWithdrawal = 0; - - await expect( - configHandler.connect(deployer).setMaxTokensPerWithdrawal(maxTokensPerWithdrawal) - ).to.revertedWith(RevertReasons.VALUE_ZERO_NOT_ALLOWED); - }); - }); - }); - context("👉 setTokenAddress()", async function () { let token; beforeEach(async function () { @@ -711,85 +511,6 @@ describe("IBosonConfigHandler", function () { }); }); - context("👉 setMaxDisputesPerBatch()", async function () { - let maxDisputesPerBatch; - beforeEach(async function () { - // set new value for max disputes per batch - maxDisputesPerBatch = 135; - }); - - it("should emit a MaxDisputesPerBatchChanged event", async function () { - // Set new max disputes per batch, testing for the event - await expect(configHandler.connect(deployer).setMaxDisputesPerBatch(maxDisputesPerBatch)) - .to.emit(configHandler, "MaxDisputesPerBatchChanged") - .withArgs(maxDisputesPerBatch, deployer.address); - }); - - it("should update state", async function () { - // Set new max disputes per batch, - await configHandler.connect(deployer).setMaxDisputesPerBatch(maxDisputesPerBatch); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxDisputesPerBatch()).to.equal(maxDisputesPerBatch); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max disputes per batch, expecting revert - await expect(configHandler.connect(rando).setMaxDisputesPerBatch(maxDisputesPerBatch)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxDisputesPerBatch is zero", async function () { - maxDisputesPerBatch = 0; - - await expect(configHandler.connect(deployer).setMaxDisputesPerBatch(maxDisputesPerBatch)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - - context("👉 setMaxFeesPerDisputeResolver()", async function () { - let maxFeesPerDisputeResolver; - beforeEach(async function () { - // set new value - maxFeesPerDisputeResolver = 200; - }); - - it("should emit a MaxFeesPerDisputeResolverChanged event", async function () { - // Set max fees per dispute resolver - await expect(configHandler.connect(deployer).setMaxFeesPerDisputeResolver(maxFeesPerDisputeResolver)) - .to.emit(configHandler, "MaxFeesPerDisputeResolverChanged") - .withArgs(maxFeesPerDisputeResolver, deployer.address); - }); - - it("should update state", async function () { - // Set max fees per dispute resolver - await configHandler.connect(deployer).setMaxFeesPerDisputeResolver(maxFeesPerDisputeResolver); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxFeesPerDisputeResolver()).to.equal(maxFeesPerDisputeResolver); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new value, expecting revert - await expect( - configHandler.connect(rando).setMaxFeesPerDisputeResolver(maxFeesPerDisputeResolver) - ).to.revertedWith(RevertReasons.ACCESS_DENIED); - }); - - it("maxFeesPerDisputeResolver is zero", async function () { - maxFeesPerDisputeResolver = 0; - await expect( - configHandler.connect(deployer).setMaxFeesPerDisputeResolver(maxFeesPerDisputeResolver) - ).to.revertedWith(RevertReasons.VALUE_ZERO_NOT_ALLOWED); - }); - }); - }); - context("👉 setMaxEscalationResponsePeriod()", async function () { let maxEscalationResponsePeriod; beforeEach(async function () { @@ -875,45 +596,6 @@ describe("IBosonConfigHandler", function () { }); }); - context("👉 setMaxAllowedSellers()", async function () { - let maxAllowedSellers; - beforeEach(async function () { - // set new value for max allowed sellers - maxAllowedSellers = 222; - }); - - it("should emit a MaxAllowedSellersChanged event", async function () { - // Set new max allowed sellers, testing for the event - await expect(configHandler.connect(deployer).setMaxAllowedSellers(maxAllowedSellers)) - .to.emit(configHandler, "MaxAllowedSellersChanged") - .withArgs(maxAllowedSellers, deployer.address); - }); - - it("should update state", async function () { - // Set new max allowed sellers, - await configHandler.connect(deployer).setMaxAllowedSellers(maxAllowedSellers); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxAllowedSellers()).to.equal(maxAllowedSellers); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max allowed sellers, expecting revert - await expect(configHandler.connect(rando).setMaxAllowedSellers(maxAllowedSellers)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxAllowedSellers is zero", async function () { - maxAllowedSellers = 0; - await expect(configHandler.connect(deployer).setMaxAllowedSellers(maxAllowedSellers)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - context("👉 setMaxTotalOfferFeePercentage()", async function () { let maxTotalOfferFeePercentage; beforeEach(async function () { @@ -1060,45 +742,6 @@ describe("IBosonConfigHandler", function () { }); }); - context("👉 setMaxExchangesPerBatch()", async function () { - let maxExchangesPerBatch; - beforeEach(async function () { - // set new value for max exchanges per batch - maxExchangesPerBatch = 135; - }); - - it("should emit a MaxExchangesPerBatchChanged event", async function () { - // Set new max exchange per batch, testing for the event - await expect(configHandler.connect(deployer).setMaxExchangesPerBatch(maxExchangesPerBatch)) - .to.emit(configHandler, "MaxExchangesPerBatchChanged") - .withArgs(maxExchangesPerBatch, deployer.address); - }); - - it("should update state", async function () { - // Set new max exchange per batch, - await configHandler.connect(deployer).setMaxExchangesPerBatch(maxExchangesPerBatch); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxExchangesPerBatch()).to.equal(maxExchangesPerBatch); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new max exchange per batch, expecting revert - await expect(configHandler.connect(rando).setMaxExchangesPerBatch(maxExchangesPerBatch)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxExchangesPerBatch is zero", async function () { - maxExchangesPerBatch = 0; - await expect(configHandler.connect(deployer).setMaxExchangesPerBatch(maxExchangesPerBatch)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - context("👉 setMaxResolutionPeriod()", async function () { let maxResolutionPeriod; beforeEach(async function () { @@ -1177,45 +820,6 @@ describe("IBosonConfigHandler", function () { }); }); - context("👉 setMaxPremintedVouchers()", async function () { - let maxPremintedVouchers; - beforeEach(async function () { - // set new value - maxPremintedVouchers = 50000; - }); - - it("should emit a MaxPremintedVouchersChanged event", async function () { - // Set new minumum dispute period - await expect(configHandler.connect(deployer).setMaxPremintedVouchers(maxPremintedVouchers)) - .to.emit(configHandler, "MaxPremintedVouchersChanged") - .withArgs(maxPremintedVouchers, deployer.address); - }); - - it("should update state", async function () { - // Set new minumum dispute period - await configHandler.connect(deployer).setMaxPremintedVouchers(maxPremintedVouchers); - - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxPremintedVouchers()).to.equal(maxPremintedVouchers); - }); - - context("💔 Revert Reasons", async function () { - it("caller is not the admin", async function () { - // Attempt to set new value, expecting revert - await expect(configHandler.connect(rando).setMaxPremintedVouchers(maxPremintedVouchers)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("maxPremintedVouchers is zero", async function () { - maxPremintedVouchers = 0; - await expect(configHandler.connect(deployer).setMaxPremintedVouchers(maxPremintedVouchers)).to.revertedWith( - RevertReasons.VALUE_ZERO_NOT_ALLOWED - ); - }); - }); - }); - context("👉 setAccessControllerAddress()", async function () { let newAccessController; beforeEach(async function () { @@ -1283,38 +887,10 @@ describe("IBosonConfigHandler", function () { protocolFeeFlatBoson, "Invalid flat boson fee" ); - expect(await configHandler.connect(rando).getMaxOffersPerGroup()).to.equal( - maxOffersPerGroup, - "Invalid max offers per group" - ); - expect(await configHandler.connect(rando).getMaxTwinsPerBundle()).to.equal( - maxTwinsPerBundle, - "Invalid max twins per bundle" - ); - expect(await configHandler.connect(rando).getMaxOffersPerBundle()).to.equal( - maxOffersPerBundle, - "Invalid max offers per bundle" - ); - expect(await configHandler.connect(rando).getMaxOffersPerBatch()).to.equal( - maxOffersPerBatch, - "Invalid max offers per batch" - ); - expect(await configHandler.connect(rando).getMaxTokensPerWithdrawal()).to.equal( - maxTokensPerWithdrawal, - "Invalid max tokens per withdrawal" - ); - expect(await configHandler.connect(rando).getMaxFeesPerDisputeResolver()).to.equal( - maxFeesPerDisputeResolver, - "Invalid max fees per dispute resolver" - ); expect(await configHandler.connect(rando).getMaxEscalationResponsePeriod()).to.equal( maxEscalationResponsePeriod, "Invalid max escalatio response period" ); - expect(await configHandler.connect(rando).getMaxDisputesPerBatch()).to.equal( - maxDisputesPerBatch, - "Invalid max disputes per batch" - ); expect(await configHandler.connect(rando).getMaxAllowedSellers()).to.equal( maxAllowedSellers, "Invalid max allowed sellers" @@ -1344,10 +920,6 @@ describe("IBosonConfigHandler", function () { ethers.constants.AddressZero, "Invalid auth token contract address" ); - expect(await configHandler.connect(rando).getMaxExchangesPerBatch()).to.equal( - maxExchangesPerBatch, - "Invalid max exchanges per batch" - ); expect(await configHandler.connect(rando).getMaxResolutionPeriod()).to.equal( maxResolutionPeriod, "Invalid max resolution period" @@ -1356,10 +928,6 @@ describe("IBosonConfigHandler", function () { minDisputePeriod, "Invalid min dispute period" ); - expect(await configHandler.connect(rando).getMaxPremintedVouchers()).to.equal( - maxPremintedVouchers, - "Invalid max preminted vouchers" - ); }); }); }); From bb09e94cac9529734820d652ac6f2f3a0b5b05fe Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Fri, 9 Jun 2023 10:52:59 -0300 Subject: [PATCH 02/22] Fix upgrade test setup --- hardhat.config.js | 12 ++++++++++++ test/upgrade/00_config.js | 3 ++- test/util/upgrade.js | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/hardhat.config.js b/hardhat.config.js index b3b0c7400..7ff1ee740 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -156,6 +156,18 @@ module.exports = { }, }, }, + { + version: "0.8.9", + settings: { + optimizer: { + enabled: true, + runs: 200, + details: { + yul: true, + }, + }, + }, + }, { version: "0.8.17", }, diff --git a/test/upgrade/00_config.js b/test/upgrade/00_config.js index fbd6deaaa..52583ccb8 100644 --- a/test/upgrade/00_config.js +++ b/test/upgrade/00_config.js @@ -210,7 +210,8 @@ const tagsByVersion = { }, "2.2.1": { oldVersion: "v2.2.0", - newVersion: "v2.2.1-rc.1", + newVersion: "v2.2.1", + deployScript: "v2.2.0", }, }; diff --git a/test/util/upgrade.js b/test/util/upgrade.js index 9979ef44c..e7d603abb 100644 --- a/test/util/upgrade.js +++ b/test/util/upgrade.js @@ -81,9 +81,11 @@ async function deploySuite(deployer, newVersion) { console.log(`Fetching tags`); shell.exec(`git fetch --force --tags origin`); + console.log(`Checking out version ${tag}`); shell.exec(`rm -rf contracts/*`); shell.exec(`git checkout ${tag} contracts`); + if (scriptsTag) { console.log(`Checking out scripts on version ${scriptsTag}`); shell.exec(`rm -rf scripts/*`); From 8cba2d44a8901d5982e123f3629ff63db40e1d5d Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Mon, 12 Jun 2023 10:18:21 -0300 Subject: [PATCH 03/22] Change approach --- test/util/upgrade.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/util/upgrade.js b/test/util/upgrade.js index e7d603abb..8577e1d7c 100644 --- a/test/util/upgrade.js +++ b/test/util/upgrade.js @@ -92,6 +92,14 @@ async function deploySuite(deployer, newVersion) { shell.exec(`git checkout ${scriptsTag} scripts`); } + shell.exec(`npx hardhat compile`); + + const isOldOZVersion = ["v2.0", "v2.1", "v2.2"].some((v) => tag.startsWith(v)); + if (isOldOZVersion) { + // Temporary install old OZ contracts + shell.exec("npm i @openzeppelin/contracts-upgradeable@4.7.1"); + } + const deployConfig = facets.deploy[tag]; if (!deployConfig) { @@ -150,6 +158,12 @@ async function deploySuite(deployer, newVersion) { await deployMockTokens(["Foreign20", "Foreign20", "Foreign721", "Foreign721", "Foreign20", "Foreign1155"]); const mockTwinTokens = [mockTwin721_1, mockTwin721_2]; + if (isOldOZVersion) { + // If reference commit is old version, we need to revert to target version + shell.exec(`git checkout ${versionTags.newVersion} package.json package-lock.json`); + shell.exec("npm i"); + } + return { protocolDiamondAddress, protocolContracts: { @@ -1812,9 +1826,8 @@ async function getVoucherContractState({ bosonVouchers, exchanges, sellers, buye } function revertState() { - shell.exec(`rm -rf contracts/* scripts/*`); - shell.exec(`git checkout HEAD contracts scripts`); - shell.exec(`git reset HEAD contracts scripts`); + shell.exec(`git checkout HEAD`); + shell.exec(`git reset HEAD`); } async function getDisputeResolver(accountHandler, value, { getBy }) { From f09ed28b84d88108d428157c0d831cbf77cdcc75 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Mon, 12 Jun 2023 10:32:20 -0300 Subject: [PATCH 04/22] Fixing upgrate test --- .../handlers/IBosonConfigHandler.sol | 2 +- hardhat.config.js | 4 +-- scripts/migrations/migrate_2.2.1.js | 31 +++++++++---------- test/upgrade/00_config.js | 1 - test/util/upgrade.js | 10 +++--- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonConfigHandler.sol b/contracts/interfaces/handlers/IBosonConfigHandler.sol index 7130fc714..e8ee32f9b 100644 --- a/contracts/interfaces/handlers/IBosonConfigHandler.sol +++ b/contracts/interfaces/handlers/IBosonConfigHandler.sol @@ -9,7 +9,7 @@ import { IBosonConfigEvents } from "../events/IBosonConfigEvents.sol"; * * @notice Handles management of configuration within the protocol. * - * The ERC-165 identifier for this interface is: 0xe393ad01 + * The ERC-165 identifier for this interface is: 0x7ada1012 */ interface IBosonConfigHandler is IBosonConfigEvents { /** diff --git a/hardhat.config.js b/hardhat.config.js index 7ff1ee740..60e78d91c 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -145,7 +145,7 @@ module.exports = { solidity: { compilers: [ { - version: "0.8.18", + version: "0.8.9", settings: { optimizer: { enabled: true, @@ -157,7 +157,7 @@ module.exports = { }, }, { - version: "0.8.9", + version: "0.8.18", settings: { optimizer: { enabled: true, diff --git a/scripts/migrations/migrate_2.2.1.js b/scripts/migrations/migrate_2.2.1.js index 0ebbce5e8..091943127 100644 --- a/scripts/migrations/migrate_2.2.1.js +++ b/scripts/migrations/migrate_2.2.1.js @@ -5,7 +5,6 @@ const ethers = hre.ethers; const network = hre.network.name; const { getStateModifyingFunctionsHashes } = require("../../scripts/util/diamond-utils.js"); const tag = "v2.2.1"; -const version = "2.2.1"; const config = { addOrUpgrade: [ @@ -28,17 +27,21 @@ async function migrate(env) { console.log(`Migration ${tag} started`); try { console.log("Removing any local changes before upgrading"); - shell.exec(`git reset @{u}`); + shell.exec(`git reset`); const statusOutput = shell.exec("git status -s -uno scripts"); if (statusOutput.stdout) { throw new Error("Local changes found. Please stash them before upgrading"); } - if (env != "upgrade-test") { - console.log("Installing dependencies"); - shell.exec(`npm install`); - } + // Checking old version contracts to get selectors to remove + console.log("Checking out contracts on version 2.2.0"); + shell.exec(`rm -rf contracts/*`); + shell.exec(`git checkout v2.2.0 contracts`); + + console.log("Compiling old contracts"); + await hre.run("clean"); + await hre.run("compile"); const { chainId } = await ethers.provider.getNetwork(); const contractsFile = readContracts(chainId, network, env); @@ -52,15 +55,6 @@ async function migrate(env) { // Get addresses of currently deployed contracts const protocolAddress = contracts.find((c) => c.name === "ProtocolDiamond")?.address; - // Checking old version contracts to get selectors to remove - console.log("Checking out contracts on version 2.2.0"); - shell.exec(`rm -rf contracts/*`); - shell.exec(`git checkout v2.2.0 contracts`); - - console.log("Compiling old contracts"); - await hre.run("clean"); - await hre.run("compile"); - const getFunctionHashesClosure = getStateModifyingFunctionsHashes( ["SellerHandlerFacet", "OrchestrationHandlerFacet1"], undefined, @@ -71,7 +65,10 @@ async function migrate(env) { console.log(`Checking out contracts on version ${tag}`); shell.exec(`rm -rf contracts/*`); - shell.exec(`git checkout ${tag} contracts`); + shell.exec(`git checkout ${tag} contracts package.json package-lock.json`); + + console.log("Installing dependencies"); + shell.exec(`npm install`); console.log("Compiling contracts"); await hre.run("clean"); @@ -81,7 +78,7 @@ async function migrate(env) { await hre.run("upgrade-facets", { env, facetConfig: JSON.stringify(config), - newVersion: version, + newVersion: tag, }); const selectorsToAdd = await getFunctionHashesClosure(); diff --git a/test/upgrade/00_config.js b/test/upgrade/00_config.js index 52583ccb8..edbcce0e5 100644 --- a/test/upgrade/00_config.js +++ b/test/upgrade/00_config.js @@ -211,7 +211,6 @@ const tagsByVersion = { "2.2.1": { oldVersion: "v2.2.0", newVersion: "v2.2.1", - deployScript: "v2.2.0", }, }; diff --git a/test/util/upgrade.js b/test/util/upgrade.js index 8577e1d7c..337fe7e6c 100644 --- a/test/util/upgrade.js +++ b/test/util/upgrade.js @@ -92,8 +92,6 @@ async function deploySuite(deployer, newVersion) { shell.exec(`git checkout ${scriptsTag} scripts`); } - shell.exec(`npx hardhat compile`); - const isOldOZVersion = ["v2.0", "v2.1", "v2.2"].some((v) => tag.startsWith(v)); if (isOldOZVersion) { // Temporary install old OZ contracts @@ -159,8 +157,7 @@ async function deploySuite(deployer, newVersion) { const mockTwinTokens = [mockTwin721_1, mockTwin721_2]; if (isOldOZVersion) { - // If reference commit is old version, we need to revert to target version - shell.exec(`git checkout ${versionTags.newVersion} package.json package-lock.json`); + shell.exec(`git checkout ${tag} package.json package-lock.json`); shell.exec("npm i"); } @@ -1826,8 +1823,9 @@ async function getVoucherContractState({ bosonVouchers, exchanges, sellers, buye } function revertState() { - shell.exec(`git checkout HEAD`); - shell.exec(`git reset HEAD`); + shell.exec(`rm -rf contracts/* scripts/*`); + shell.exec(`git checkout HEAD contracts scripts`); + shell.exec(`git reset HEAD contracts scripts`); } async function getDisputeResolver(accountHandler, value, { getBy }) { From 42cb91d11072ac232773f1434a047cb9a19cae91 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Mon, 12 Jun 2023 16:41:42 -0300 Subject: [PATCH 05/22] Remove gas limits --- contracts/domain/BosonConstants.sol | 10 +- contracts/mock/MockExchangeHandlerFacet.sol | 4 - contracts/protocol/bases/GroupBase.sol | 10 -- .../protocol/facets/ConfigHandlerFacet.sol | 1 + .../protocol/facets/DisputeHandlerFacet.sol | 4 - .../facets/DisputeResolverHandlerFacet.sol | 39 +---- .../protocol/facets/FundsHandlerFacet.sol | 5 - .../protocol/facets/OfferHandlerFacet.sol | 9 - docs/limit-estimation.md | 138 --------------- scripts/config/revert-reasons.js | 12 +- test/protocol/BundleHandlerTest.js | 20 --- test/protocol/ConfigHandlerTest.js | 4 - test/protocol/DisputeHandlerTest.js | 10 -- test/protocol/DisputeResolverHandlerTest.js | 62 +------ test/protocol/ExchangeHandlerTest.js | 10 -- test/protocol/FundsHandlerTest.js | 162 ------------------ test/protocol/GroupHandlerTest.js | 40 ----- test/protocol/OfferHandlerTest.js | 34 ---- .../ProtocolInitializationHandlerTest.js | 14 -- test/protocol/clients/BosonVoucherTest.js | 96 +---------- 20 files changed, 24 insertions(+), 660 deletions(-) delete mode 100644 docs/limit-estimation.md diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 083e75f3b..537fed017 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -58,13 +58,13 @@ string constant NO_SUCH_AGENT = "No such agent"; string constant WALLET_OWNS_VOUCHERS = "Wallet address owns vouchers"; string constant NO_SUCH_DISPUTE_RESOLVER = "No such dispute resolver"; string constant INVALID_ESCALATION_PERIOD = "Invalid escalation period"; -string constant INVALID_AMOUNT_DISPUTE_RESOLVER_FEES = "Dispute resolver fees are not present or exceed maximum dispute resolver fees in a single transaction"; +string constant INEXISTENT_DISPUTE_RESOLVER_FEES = "Dispute resolver fees are not present"; string constant DUPLICATE_DISPUTE_RESOLVER_FEES = "Duplicate dispute resolver fee"; string constant FEE_AMOUNT_NOT_YET_SUPPORTED = "Non-zero dispute resolver fees not yet supported"; string constant DISPUTE_RESOLVER_FEE_NOT_FOUND = "Dispute resolver fee not found"; string constant SELLER_ALREADY_APPROVED = "Seller id is approved already"; string constant SELLER_NOT_APPROVED = "Seller id is not approved"; -string constant INVALID_AMOUNT_ALLOWED_SELLERS = "Allowed sellers are not present or exceed maximum allowed sellers in a single transaction"; +string constant INEXISTENT_ALLOWED_SELLERS_LIST = "Allowed sellers are not present"; string constant INVALID_AUTH_TOKEN_TYPE = "Invalid AuthTokenType"; string constant ADMIN_OR_AUTH_TOKEN = "An admin address or an auth token is required"; string constant AUTH_TOKEN_MUST_BE_UNIQUE = "Auth token cannot be assigned to another entity of the same type"; @@ -97,7 +97,6 @@ string constant AGENT_FEE_AMOUNT_TOO_HIGH = "Sum of agent fee amount and protoco // Revert Reasons: Group related string constant NO_SUCH_GROUP = "No such group"; string constant OFFER_NOT_IN_GROUP = "Offer not part of the group"; -string constant TOO_MANY_OFFERS = "Exceeded maximum offers in a single transaction"; string constant NOTHING_UPDATED = "Nothing updated"; string constant INVALID_CONDITION_PARAMETERS = "Invalid condition parameters"; @@ -108,7 +107,6 @@ string constant VOUCHER_NOT_REDEEMABLE = "Voucher not yet valid or already expir string constant VOUCHER_EXTENSION_NOT_VALID = "Proposed date is not later than the current one"; string constant VOUCHER_STILL_VALID = "Voucher still valid"; string constant VOUCHER_HAS_EXPIRED = "Voucher has expired"; -string constant TOO_MANY_EXCHANGES = "Exceeded maximum exchanges in a single transaction"; string constant EXCHANGE_IS_NOT_IN_A_FINAL_STATE = "Exchange is not in a final state"; string constant EXCHANGE_ALREADY_EXISTS = "Exchange already exists"; string constant INVALID_RANGE_LENGTH = "Range length is too large or zero"; @@ -129,7 +127,6 @@ string constant INVALID_TOKEN_ADDRESS = "Token address is a contract that doesn' string constant NO_SUCH_BUNDLE = "No such bundle"; string constant TWIN_NOT_IN_BUNDLE = "Twin not part of the bundle"; string constant OFFER_NOT_IN_BUNDLE = "Offer not part of the bundle"; -string constant TOO_MANY_TWINS = "Exceeded maximum twins in a single transaction"; string constant BUNDLE_OFFER_MUST_BE_UNIQUE = "Offer must be unique to a bundle"; string constant BUNDLE_TWIN_MUST_BE_UNIQUE = "Twin must be unique to a bundle"; string constant EXCHANGE_FOR_BUNDLED_OFFERS_EXISTS = "Exchange for the bundled offers exists"; @@ -141,7 +138,6 @@ string constant NATIVE_WRONG_ADDRESS = "Native token address must be 0"; string constant NATIVE_WRONG_AMOUNT = "Transferred value must match amount"; string constant TOKEN_NAME_UNSPECIFIED = "Token name unspecified"; string constant NATIVE_CURRENCY = "Native currency"; -string constant TOO_MANY_TOKENS = "Too many tokens"; string constant TOKEN_AMOUNT_MISMATCH = "Number of amounts should match number of tokens"; string constant NOTHING_TO_WITHDRAW = "Nothing to withdraw"; string constant NOT_AUTHORIZED = "Not authorized to withdraw"; @@ -164,7 +160,6 @@ string constant DISPUTE_HAS_EXPIRED = "Dispute has expired"; string constant INVALID_BUYER_PERCENT = "Invalid buyer percent"; string constant DISPUTE_STILL_VALID = "Dispute still valid"; string constant INVALID_DISPUTE_TIMEOUT = "Invalid dispute timeout"; -string constant TOO_MANY_DISPUTES = "Exceeded maximum disputes in a single transaction"; string constant ESCALATION_NOT_ALLOWED = "Disputes without dispute resolver cannot be escalated"; // Revert Reasons: Config related @@ -187,7 +182,6 @@ string constant OFFER_RANGE_ALREADY_RESERVED = "Offer id already associated with string constant INVALID_RANGE_START = "Range start too low"; string constant INVALID_AMOUNT_TO_MINT = "Amount to mint is greater than remaining un-minted in range"; string constant NO_SILENT_MINT_ALLOWED = "Only owner's mappings can be updated without event"; -string constant TOO_MANY_TO_MINT = "Exceeded maximum amount to mint in a single transaction"; string constant OFFER_EXPIRED_OR_VOIDED = "Offer expired or voided"; string constant OFFER_STILL_VALID = "Offer still valid"; string constant NOTHING_TO_BURN = "Nothing to burn"; diff --git a/contracts/mock/MockExchangeHandlerFacet.sol b/contracts/mock/MockExchangeHandlerFacet.sol index be077dca9..bdfc48fe3 100644 --- a/contracts/mock/MockExchangeHandlerFacet.sol +++ b/contracts/mock/MockExchangeHandlerFacet.sol @@ -100,7 +100,6 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { * * Reverts if: * - The exchanges region of protocol is paused - * - Number of exchanges exceeds maximum allowed number per batch * - For any exchange: * - Exchange does not exist * - Exchange is not in Redeemed state @@ -109,9 +108,6 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { * @param _exchangeIds - the array of exchanges ids */ function completeExchangeBatch(uint256[] calldata _exchangeIds) external exchangesNotPaused { - // limit maximum number of exchanges to avoid running into block gas limit in a loop - require(_exchangeIds.length <= protocolLimits().maxExchangesPerBatch, TOO_MANY_EXCHANGES); - for (uint256 i = 0; i < _exchangeIds.length; i++) { // complete the exchange completeExchange(_exchangeIds[i]); diff --git a/contracts/protocol/bases/GroupBase.sol b/contracts/protocol/bases/GroupBase.sol index a3834e5c2..c1c86ed53 100644 --- a/contracts/protocol/bases/GroupBase.sol +++ b/contracts/protocol/bases/GroupBase.sol @@ -22,7 +22,6 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group - * - Number of offers exceeds maximum allowed number per group * * @param _group - the fully populated struct with group id set to 0x0 * @param _condition - the fully populated condition struct @@ -38,9 +37,6 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { (bool exists, uint256 sellerId) = getSellerIdByAssistant(sender); require(exists, NOT_ASSISTANT); - // limit maximum number of offers to avoid running into block gas limit in a loop - require(_group.offerIds.length <= protocolLimits().maxOffersPerGroup, TOO_MANY_OFFERS); - // condition must be valid require(validateCondition(_condition), INVALID_CONDITION_PARAMETERS); @@ -129,7 +125,6 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { * Reverts if: * - Caller is not the seller * - Offer ids param is an empty list - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - Group does not exist * - Any of offers belongs to different seller * - Any of offers does not exist @@ -146,10 +141,6 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { // check if group can be updated (uint256 sellerId, Group storage group) = preUpdateChecks(_groupId, _offerIds); - // limit maximum number of total offers to avoid running into block gas limit in a loop - // and make sure total number of offers in group does not exceed max - require(group.offerIds.length + _offerIds.length <= protocolLimits().maxOffersPerGroup, TOO_MANY_OFFERS); - for (uint256 i = 0; i < _offerIds.length; i++) { uint256 offerId = _offerIds[i]; // make sure offer exist and belong to the seller @@ -183,7 +174,6 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { * Reverts if: * - Caller is not the seller * - Offer ids param is an empty list - * - Number of offers exceeds maximum allowed number per group * - Group does not exist * * @param _groupId - the id of the group to be updated diff --git a/contracts/protocol/facets/ConfigHandlerFacet.sol b/contracts/protocol/facets/ConfigHandlerFacet.sol index 92630491b..5fafc1991 100644 --- a/contracts/protocol/facets/ConfigHandlerFacet.sol +++ b/contracts/protocol/facets/ConfigHandlerFacet.sol @@ -38,6 +38,7 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { setBeaconProxyAddress(_addresses.beaconProxy); setProtocolFeePercentage(_fees.percentage); setProtocolFeeFlatBoson(_fees.flatBoson); + setMaxEscalationResponsePeriod(_limits.maxEscalationResponsePeriod); setBuyerEscalationDepositPercentage(_fees.buyerEscalationDepositPercentage); setMaxTotalOfferFeePercentage(_limits.maxTotalOfferFeePercentage); setMaxRoyaltyPecentage(_limits.maxRoyaltyPecentage); diff --git a/contracts/protocol/facets/DisputeHandlerFacet.sol b/contracts/protocol/facets/DisputeHandlerFacet.sol index 74d7c8076..65759c65c 100644 --- a/contracts/protocol/facets/DisputeHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeHandlerFacet.sol @@ -188,7 +188,6 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { * * Reverts if: * - The disputes region of protocol is paused - * - Number of disputes exceeds maximum allowed number per batch * - For any dispute: * - Exchange does not exist * - Exchange is not in a Disputed state @@ -198,9 +197,6 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { * @param _exchangeIds - the array of ids of the associated exchanges */ function expireDisputeBatch(uint256[] calldata _exchangeIds) external override { - // limit maximum number of disputes to avoid running into block gas limit in a loop - require(_exchangeIds.length <= protocolLimits().maxDisputesPerBatch, TOO_MANY_DISPUTES); - for (uint256 i = 0; i < _exchangeIds.length; i++) { // create offer and update structs values to represent true state expireDispute(_exchangeIds[i]); diff --git a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol index d8ecb2c87..574ba2208 100644 --- a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol @@ -34,7 +34,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * - Any address is zero address * - Any address is not unique to this dispute resolver * - EscalationResponsePeriod is invalid - * - Number of seller ids in _sellerAllowList array exceeds max * - Some seller does not exist * - Some seller id is duplicated * - DisputeResolver is not active (if active == false) @@ -93,15 +92,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Cache protocol limits for reference ProtocolLib.ProtocolLimits storage limits = protocolLimits(); - // Make sure the gas block limit is not hit - require(_sellerAllowList.length <= limits.maxAllowedSellers, INVALID_AMOUNT_ALLOWED_SELLERS); - - // The number of fees cannot exceed the maximum number of dispute resolver fees to avoid running into block gas limit in a loop - require( - _disputeResolverFees.length <= limits.maxFeesPerDisputeResolver, - INVALID_AMOUNT_DISPUTE_RESOLVER_FEES - ); - // Escalation period must be greater than zero and less than or equal to the max allowed require( _disputeResolver.escalationResponsePeriod > 0 && @@ -412,12 +402,8 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Check that msg.sender is the admin address for this dispute resolver require(disputeResolver.admin == sender, NOT_ADMIN); - // At least one fee must be specified and the number of fees cannot exceed the maximum number of dispute resolver fees to avoid running into block gas limit in a loop - require( - _disputeResolverFees.length > 0 && - _disputeResolverFees.length <= protocolLimits().maxFeesPerDisputeResolver, - INVALID_AMOUNT_DISPUTE_RESOLVER_FEES - ); + // At least one fee must be specified + require(_disputeResolverFees.length > 0, INEXISTENT_DISPUTE_RESOLVER_FEES); // Set dispute resolver fees. Must loop because calldata structs cannot be converted to storage structs for (uint256 i = 0; i < _disputeResolverFees.length; i++) { @@ -476,11 +462,8 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Check that msg.sender is the admin address for this dispute resolver require(disputeResolver.admin == sender, NOT_ADMIN); - // At least one fee must be specified and the number of fees cannot exceed the maximum number of dispute resolver fees to avoid running into block gas limit in a loop - require( - _feeTokenAddresses.length > 0 && _feeTokenAddresses.length <= protocolLimits().maxFeesPerDisputeResolver, - INVALID_AMOUNT_DISPUTE_RESOLVER_FEES - ); + // At least one fee must be specified and + require(_feeTokenAddresses.length > 0, INEXISTENT_DISPUTE_RESOLVER_FEES); // Set dispute resolver fees. Must loop because calldata structs cannot be converted to storage structs for (uint256 i = 0; i < _feeTokenAddresses.length; i++) { @@ -530,11 +513,8 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { uint256 _disputeResolverId, uint256[] calldata _sellerAllowList ) external disputeResolversNotPaused nonReentrant { - // At least one seller id must be specified and the number of ids cannot exceed the maximum number of seller ids to avoid running into block gas limit in a loop - require( - _sellerAllowList.length > 0 && _sellerAllowList.length <= protocolLimits().maxAllowedSellers, - INVALID_AMOUNT_ALLOWED_SELLERS - ); + // At least one seller id must be specified + require(_sellerAllowList.length > 0, INEXISTENT_ALLOWED_SELLERS_LIST); bool exists; DisputeResolver storage disputeResolver; @@ -578,11 +558,8 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Cache protocol lookups for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); - // At least one seller id must be specified and the number of ids cannot exceed the maximum number of seller ids to avoid running into block gas limit in a loop - require( - _sellerAllowList.length > 0 && _sellerAllowList.length <= protocolLimits().maxAllowedSellers, - INVALID_AMOUNT_ALLOWED_SELLERS - ); + // At least one seller id must be specified + require(_sellerAllowList.length > 0, INEXISTENT_ALLOWED_SELLERS_LIST); bool exists; DisputeResolver storage disputeResolver; diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 99e972497..0a39f427f 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -199,7 +199,6 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * Reverts if: * - Caller is not associated with the entity id * - Token list length does not match amount list length - * - Token list length exceeds the maximum allowed number of tokens * - Caller tries to withdraw more that they have in available funds * - There is nothing to withdraw * - Transfer of funds is not successful @@ -221,10 +220,6 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { // Make sure that the data is complete require(_tokenList.length == _tokenAmounts.length, TOKEN_AMOUNT_MISMATCH); - // Limit maximum number of tokens to avoid running into block gas limit in a loop - uint256 maxTokensPerWithdrawal = protocolLimits().maxTokensPerWithdrawal; - require(_tokenList.length <= maxTokensPerWithdrawal, TOO_MANY_TOKENS); - // Two possible options: withdraw all, or withdraw only specified tokens and amounts if (_tokenList.length == 0) { // Withdraw everything diff --git a/contracts/protocol/facets/OfferHandlerFacet.sol b/contracts/protocol/facets/OfferHandlerFacet.sol index 17699c11a..b42fda838 100644 --- a/contracts/protocol/facets/OfferHandlerFacet.sol +++ b/contracts/protocol/facets/OfferHandlerFacet.sol @@ -70,7 +70,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * Reverts if: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - Number of elements in offers, offerDates and offerDurations do not match * - For any offer: * - Caller is not an assistant @@ -106,8 +105,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { uint256[] calldata _disputeResolverIds, uint256[] calldata _agentIds ) external override offersNotPaused nonReentrant { - // Limit maximum number of offers to avoid running into block gas limit in a loop - require(_offers.length <= protocolLimits().maxOffersPerBatch, TOO_MANY_OFFERS); // Number of offer dates structs, offer durations structs and _disputeResolverIds must match the number of offers require( _offers.length == _offerDates.length && @@ -182,7 +179,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * Reverts if, for any offer: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - Offer id is invalid * - Caller is not the assistant of the offer * - Offer has already been voided @@ -190,8 +186,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @param _offerIds - list of ids of offers to void */ function voidOfferBatch(uint256[] calldata _offerIds) external override offersNotPaused { - // limit maximum number of offers to avoid running into block gas limit in a loop - require(_offerIds.length <= protocolLimits().maxOffersPerBatch, TOO_MANY_OFFERS); for (uint256 i = 0; i < _offerIds.length; i++) { voidOffer(_offerIds[i]); } @@ -241,7 +235,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * Reverts if: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - For any of the offers: * - Offer does not exist * - Caller is not the assistant of the offer @@ -252,8 +245,6 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @param _validUntilDate - new valid until date */ function extendOfferBatch(uint256[] calldata _offerIds, uint256 _validUntilDate) external override offersNotPaused { - // Limit maximum number of offers to avoid running into block gas limit in a loop - require(_offerIds.length <= protocolLimits().maxOffersPerBatch, TOO_MANY_OFFERS); for (uint256 i = 0; i < _offerIds.length; i++) { extendOffer(_offerIds[i], _validUntilDate); } diff --git a/docs/limit-estimation.md b/docs/limit-estimation.md deleted file mode 100644 index 7099167bf..000000000 --- a/docs/limit-estimation.md +++ /dev/null @@ -1,138 +0,0 @@ -[![banner](images/banner.png)](https://bosonprotocol.io) - -

Boson Protocol V2

- -### [Intro](../README.md) | [Audits](audits.md) | [Setup](setup.md) | [Tasks](tasks.md) | [Architecture](architecture.md) | [Domain Model](domain.md) | [State Machines](state-machines.md) | [Sequences](sequences.md) - -# Protocol limit estimation - -Certain actions in the protocol require looping over dynamic size arrays. To avoid hitting the block gas limit, special protocol limits were introduced which revert the transaction before the loop even starts. Values for these limits are then determined through estimation process, described here. - -## Identifying the limits - -First we identify which limits we use at the moment and which functions (or chain of functions) use them. The goal is to identify the external functions which can be called during the estimation. - -### List of limits - -| limit | used in | remarks | -| :---- | :------ | :------ | -| maxExchangesPerBatch | **completeExchangeBatch** | | -| maxOffersPerGroup | createGroupInternal -> **createGroup** | | -| maxOffersPerGroup | createGroupInternal -> **createOfferWithCondition** | not a problem, always length 1 | -| maxOffersPerGroup | preUpdateChecks -> addOffersToGroupInternal -> **addOffersToGroup** | | -| maxOffersPerGroup | preUpdateChecks -> addOffersToGroupInternal -> **createOfferAddToGroup** | not a problem, always length 1 | -| maxOffersPerGroup | preUpdateChecks -> **removeOffersFromGroup** | | -| maxOffersPerBundle | createBundleInternal -> **createBundle** | | -| maxOffersPerBundle | createBundleInternal -> **createTwinAndBundleAfterOffer** | not a problem, always length 1 | -| maxTwinsPerBundle | createBundleInternal -> **createBundle** | | -| maxTwinsPerBundle | createBundleInternal -> **createTwinAndBundleAfterOffer** | not a problem, always length 1 | -| maxOffersPerBatch | **createOfferBatch** | | -| maxOffersPerBatch | **voidOfferBatch** | | -| maxOffersPerBatch | **extendOfferBatch** | | -| maxTokensPerWithdrawal | withdrawFundsInternal -> **withdrawFunds** | | -| maxTokensPerWithdrawal | withdrawFundsInternal -> **withdrawProtocolFees** | | -| maxFeesPerDisputeResolver | **createDisputeResolver** | | -| maxFeesPerDisputeResolver | **addFeesToDisputeResolver** | | -| maxFeesPerDisputeResolver | **removeFeesFromDisputeResolver** | | -| maxDisputesPerBatch | **expireDisputeBatch** | | -| maxAllowedSellers | **createDisputeResolver** | | -| maxAllowedSellers | **addSellersToAllowList** | | -| maxAllowedSellers | **removeSellersFromAllowList** | | - -## Estimation config - -Config file is placed in `scripts/config/limit-estimation.js`. It has the following fields: -- `blockGasLimit`: block gas limit against which you want to make the estimate -- `safeGasLimitPercent`: percent of total gas block limit that you consider safe for a transaction to actually be included in the block. For example if `blockGasLimit` is `30M` and you don't want your transaction to exceed `15M`, set `safeGasLimitPercent` to `50`. -- `maxArrayLength`: maximum length of the array used during the estimation. This value is typically smaller than actual limits calculated at the end. Increasing this value makes estimation more precise, however it also takes more time. Improvement in the estimate is increasing slower than run time, so setting this to `100` should be more than enough. If you want to speed up the process, setting this to `10` will still give you very good results. -- `limits`: list of limits you want to estimate. Each limit is an object with fields: - - `name`: name of the limit - - `methods`: object of pairs `"methodName":"handlerName"` where `methodName` is the name of the external function that uses the limit and `handlerName` is the name of the handler where this function is implemented. Example for limit `maxOffersPerGroup`: - ``` - { - name: "maxOffersPerGroup", - methods: { - createGroup: "IBosonGroupHandler", - addOffersToGroup: "IBosonGroupHandler", - removeOffersFromGroup: "IBosonGroupHandler", - }, - }, - ``` - -## Setting up the environment - -For each of the limits you must prepare an evironment, before it can be tested. For example, before `maxOffersPerGroup` can be tested, protocol contracts must be deployed and enough offers must be created so the limit can actually be tested. A similar setup is needed for all other methods. - -This is done in file `scripts/util/estimate-limits.js`. Each of the limits must have a setup function which accepts `maxArrayLength`, prepares the environment and returns the invocation details that can be then used when invoking the `methods` during the estimation. - -Invocation details contain -- `account`: account that calls the method (important if access is restricted) -- `args`: array of arguments that needs to be passed into method -- `arrayIndex`: index that tells which parameter's length should be varied during the estimation -- `structField`: if array is part of a struct, specify the field name - -The returned object must be in form `{ methodName_1: invocationDetails_1, methodName_2: invocationDetails_2, ..., methodName_n: invocationDetails_2}` with details for all methods specified in estimation config. - -## Running the script - -Scrip is run by calling - -```npm run estimate-limits``` - -During the estimation it outputs the information about the method it is estimating. At the end it stores the estimation details into two files: -- `logs/limit_estimates.json` Data in JSON format -- `logs/limit_estimates.md` Data in MD table - -## Results - -The results for parameters -- `blockGasLimit`: `30,000,000` (current ethereum mainnet block gas limit) -- `safeGasLimitPercent`: `60` - -| limit | max value | safe value | -| :-- | --: | --: | -|maxExchangesPerBatch | 557 | 333| -|maxOffersPerGroup | 388 | 232| -|maxOffersPerBundle | 508 | 303| -|maxTwinsPerBundle | 510 | 304| -|maxOffersPerBatch | 51 | 31| -|maxTokensPerWithdrawal | 491 | 293| -|maxFeesPerDisputeResolver | 305 | 181| -|maxDisputesPerBatch | 302 | 181| -|maxAllowedSellers | 597 | 352| - -`max value` is determined based on `blockGasLimit`, while safe value also applies `safeGasLimitPercent`. - -### Gas spent for different sizes of arrays -| # | maxExchangesPerBatch | maxOffersPerGroup | maxOffersPerGroup | maxOffersPerGroup | maxOffersPerBundle | maxTwinsPerBundle | maxOffersPerBatch | maxOffersPerBatch | maxOffersPerBatch | maxTokensPerWithdrawal | maxTokensPerWithdrawal | maxFeesPerDisputeResolver | maxFeesPerDisputeResolver | maxFeesPerDisputeResolver | maxDisputesPerBatch | maxAllowedSellers | maxAllowedSellers | maxAllowedSellers | -|--| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| ---:| -| | completeExchangeBatch | createGroup | addOffersToGroup | removeOffersFromGroup | createBundle | createBundle | createOfferBatch | voidOfferBatch | extendOfferBatch | withdrawFunds | withdrawProtocolFees | createDisputeResolver | addFeesToDisputeResolver | removeFeesFromDisputeResolver | expireDisputeBatch | createDisputeResolver | addSellersToAllowList | removeSellersFromAllowList | -| 1 | 184357 | 216179 | 166813 | 339950 | 266339 | 266339 | 645410 | 76369 | 63841 | 98246 | 124263 | 456727 | 168014 | 99325 | 223643 | 720355 | 120611 | 76720 | -| 2 | 237945 | 292824 | 243899 | 362917 | 324373 | 324237 | 1220883 | 111203 | 85650 | 144880 | 193424 | 552740 | 264455 | 145349 | 322061 | 769463 | 169117 | 99602 | -| 3 | 290692 | 369287 | 319811 | 385956 | 382685 | 382414 | 1796358 | 145208 | 107442 | 191521 | 262643 | 649449 | 360736 | 191089 | 420849 | 817790 | 217760 | 122522 | -| 4 | 344271 | 446010 | 396802 | 409687 | 440789 | 440369 | 2371848 | 179853 | 129223 | 238345 | 330958 | 745985 | 457657 | 236552 | 519034 | 866852 | 266659 | 145744 | -| 5 | 397506 | 522438 | 473499 | 432743 | 498656 | 498102 | 2947330 | 213982 | 150777 | 284285 | 399900 | 842313 | 553655 | 282583 | 617217 | 915916 | 315181 | 168453 | -| 6 | 450933 | 599145 | 549901 | 455724 | 556830 | 556142 | 3522863 | 248993 | 172476 | 330931 | 468759 | 939354 | 650340 | 327624 | 716072 | 964978 | 364020 | 191603 | -| 7 | 504557 | 675705 | 626011 | 478855 | 615481 | 614658 | 4098400 | 282740 | 194322 | 377398 | 538021 | 1038389 | 746851 | 373526 | 814350 | 1015980 | 412672 | 214102 | -| 8 | 557350 | 752116 | 703168 | 502121 | 673490 | 672532 | 4673951 | 317493 | 216313 | 423687 | 606770 | 1135642 | 843190 | 418787 | 912629 | 1065136 | 462015 | 237182 | -| 9 | 610704 | 829174 | 779581 | 525383 | 731386 | 730295 | 5289900 | 351745 | 237643 | 470697 | 675733 | 1232882 | 940232 | 464621 | 1013746 | 1114293 | 510385 | 260262 | -| 10 | 664157 | 905366 | 855847 | 548648 | 789928 | 788700 | 5869922 | 386681 | 259853 | 516720 | 744561 | 1325047 | 1039257 | 510454 | 1112302 | 1163448 | 559101 | 283342 | -| 20 | 1199674 | 1672031 | 1622868 | 780097 | 1371424 | 1368854 | 11585194 | 730484 | 476329 | 981216 | 1435505 | 2293894 | 2003996 | 966566 | 2097899 | 1648704 | 1050125 | 511799 | -| 30 | 1731595 | 2440464 | 2391564 | 1012387 | 1955241 | 1951326 | 17350904 | 1078743 | 693892 | 1446838 | 2126474 | 3256561 | 2967075 | 1424971 | 3083565 | 2138411 | 1535807 | 740419 | -| 40 | 2271836 | 3202792 | 3154246 | 1244680 | 2539116 | 2533856 | 23112733 | 1423827 | 911454 | 1913450 | 2817507 | 4248050 | 3934100 | 1882668 | 4069301 | 2628126 | 2025507 | 969085 | -| 50 | 2800719 | 3969835 | 3921550 | 1476974 | 3117065 | 3110472 | 28911628 | 1768963 | 1135500 | 2380130 | 3503226 | 5220856 | 4929416 | 2340381 | 5055106 | 3111881 | 2515218 | 1199241 | -| 60 | 3333793 | 4764194 | 4715895 | 1542105 | 3699938 | 3692002 | | 2114152 | 1353506 | 2841377 | 3983943 | 6192222 | 5902160 | 2409313 | 6040982 | 3600680 | 2999184 | 1220202 | -| 70 | 3866927 | 5535734 | 5487697 | 1607235 | 4307529 | 4298195 | | 2459395 | 1566348 | 3307259 | 4471569 | 7137316 | 6853941 | 2478233 | 7030675 | 4113038 | 3487977 | 1241164 | -| 80 | 4424168 | 6302543 | 6256137 | 1672369 | 4893873 | 4883188 | | 2804690 | 1783781 | 3773180 | 4953126 | 8125585 | 7799011 | 2547183 | 8046950 | 4604672 | 3976781 | 1262132 | -| 90 | 4960338 | 7052123 | 7005985 | 1737566 | 5480275 | 5468239 | | 3144491 | 2001293 | 4263556 | 5434728 | 9068993 | 8785898 | 2616114 | 9014128 | 5096317 | 4491310 | 1283105 | -| 100 | 5496569 | 7801814 | 7755932 | 1802826 | 6066735 | 6053349 | | 3489284 | 2218882 | 4732249 | 5916387 | 10051836 | 9729258 | 2685059 | 9977691 | 5587972 | 4982950 | 1304081 | -| **max** | **557** | **388** | **389** | **1733** | **508** | **510** | **51** | **868** | **1375** | **641** | **491** | **305** | **309** | **932** | **302** | **597** | **610** | **1906** | -| safe | 333 | 232 | 232 | 1031 | 303 | 304 | 31 | 520 | 824 | 384 | 293 | 181 | 185 | 557 | 181 | 352 | 365 | 1141 | - -## Methodology - -As seen from the results above, some limits are relatively high and to actually hit the limit, already the setup would take a lot of time (e.g. for making `>1700` offers to hit limit in `removeOffersFromGroup`). To get the estimates we therefore use the following approach: -- get the actual estimates for relatively small number of different array lengths -- given how gas is determined, there exist approximate linear relation, which can be written as `gasSpent = intrinsicGas + arrayLength*costPerLoop`. Intrinsic costs here contains all costs that are fixed regardless of the array size. -- use linear regression to estimate `intrinsicGas` and `costPerLoop` -- use these estimates to calculate the biggest `arrayLength` where `gasSpent <= blockGasLimit` which gives the maximum value. To get the safe value we find the biggest `arrayLength` where `gasSpent <= safeGasLimitPercent*blockGasLimit` diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index 4e3b70eda..496ba14f3 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -54,7 +54,6 @@ exports.RevertReasons = { // Group related NO_SUCH_GROUP: "No such group", OFFER_NOT_IN_GROUP: "Offer not part of the group", - TOO_MANY_OFFERS: "Exceeded maximum offers in a single transaction", NOTHING_UPDATED: "Nothing updated", INVALID_CONDITION_PARAMETERS: "Invalid condition parameters", @@ -76,8 +75,7 @@ exports.RevertReasons = { NOT_DISPUTE_RESOLVER_ASSISTANT: "Not dispute resolver's assistant address", NO_SUCH_DISPUTE_RESOLVER: "No such dispute resolver", INVALID_ESCALATION_PERIOD: "Invalid escalation period", - INVALID_AMOUNT_DISPUTE_RESOLVER_FEES: - "Dispute resolver fees are not present or exceed maximum dispute resolver fees in a single transaction", + INEXISTENT_DISPUTE_RESOLVER_FEES: "Dispute resolver fees are not present", DUPLICATE_DISPUTE_RESOLVER_FEES: "Duplicate dispute resolver fee", DISPUTE_RESOLVER_FEE_NOT_FOUND: "Dispute resolver fee not found", FEE_AMOUNT_NOT_YET_SUPPORTED: "Non-zero dispute resolver fees not yet supported", @@ -86,8 +84,7 @@ exports.RevertReasons = { AUTH_TOKEN_MUST_BE_UNIQUE: "Auth token cannot be assigned to another entity of the same type", SELLER_ALREADY_APPROVED: "Seller id is approved already", SELLER_NOT_APPROVED: "Seller id is not approved", - INVALID_AMOUNT_ALLOWED_SELLERS: - "Allowed sellers are not present or exceed maximum allowed sellers in a single transaction", + INEXISTENT_ALLOWED_SELLERS_LIST: "Allowed sellers are not present", INVALID_AGENT_FEE_PERCENTAGE: "Sum of agent fee percentage and protocol fee percentage should be <= max fee percentage limit", NO_PENDING_UPDATE_FOR_ACCOUNT: "No pending updates for the given account", @@ -109,7 +106,6 @@ exports.RevertReasons = { NO_SUCH_BUNDLE: "No such bundle", TWIN_NOT_IN_BUNDLE: "Twin not part of the bundle", OFFER_NOT_IN_BUNDLE: "Offer not part of the bundle", - TOO_MANY_TWINS: "Exceeded maximum twins in a single transaction", BUNDLE_OFFER_MUST_BE_UNIQUE: "Offer must be unique to a bundle", BUNDLE_TWIN_MUST_BE_UNIQUE: "Twin must be unique to a bundle", INSUFFICIENT_TWIN_SUPPLY_TO_COVER_BUNDLE_OFFERS: @@ -124,7 +120,6 @@ exports.RevertReasons = { VOUCHER_EXTENSION_NOT_VALID: "Proposed date is not later than the current one", VOUCHER_STILL_VALID: "Voucher still valid", VOUCHER_HAS_EXPIRED: "Voucher has expired", - TOO_MANY_EXCHANGES: "Exceeded maximum exchanges in a single transaction", EXCHANGE_IS_NOT_IN_A_FINAL_STATE: "Exchange is not in a final state", INVALID_RANGE_LENGTH: "Range length is too large or zero", EXCHANGE_ALREADY_EXISTS: "Exchange already exists", @@ -136,7 +131,6 @@ exports.RevertReasons = { INVALID_RANGE_START: "Range start too low", INVALID_AMOUNT_TO_MINT: "Amount to mint is greater than remaining un-minted in range", NO_SILENT_MINT_ALLOWED: "Only owner's mappings can be updated without event", - TOO_MANY_TO_MINT: "Exceeded maximum amount to mint in a single transaction", OFFER_EXPIRED_OR_VOIDED: "Offer expired or voided", OFFER_STILL_VALID: "Offer still valid", NOTHING_TO_BURN: "Nothing to burn", @@ -151,7 +145,6 @@ exports.RevertReasons = { INSUFFICIENT_VALUE_RECEIVED: "Insufficient value received", INSUFFICIENT_AVAILABLE_FUNDS: "Insufficient available funds", NATIVE_NOT_ALLOWED: "Transfer of native currency not allowed", - TOO_MANY_TOKENS: "Too many tokens", TOKEN_AMOUNT_MISMATCH: "Number of amounts should match number of tokens", NOTHING_TO_WITHDRAW: "Nothing to withdraw", NOT_AUTHORIZED: "Not authorized to withdraw", @@ -184,7 +177,6 @@ exports.RevertReasons = { INVALID_BUYER_PERCENT: "Invalid buyer percent", DISPUTE_STILL_VALID: "Dispute still valid", INVALID_DISPUTE_TIMEOUT: "Invalid dispute timeout", - TOO_MANY_DISPUTES: "Exceeded maximum disputes in a single transaction", ESCALATION_NOT_ALLOWED: "Disputes without dispute resolver cannot be escalated", // Config related diff --git a/test/protocol/BundleHandlerTest.js b/test/protocol/BundleHandlerTest.js index 5fab960e5..4e60fae92 100644 --- a/test/protocol/BundleHandlerTest.js +++ b/test/protocol/BundleHandlerTest.js @@ -485,16 +485,6 @@ describe("IBosonBundleHandler", function () { ); }); - it("Adding too many offers", async function () { - // Try to add the more than 100 offers - bundle.offerIds = [...Array(101).keys()]; - - // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( - RevertReasons.TOO_MANY_OFFERS - ); - }); - it("Twin is duplicated", async function () { // Try to add the same twin twice bundle.twinIds = ["1", "1", "4"]; @@ -505,16 +495,6 @@ describe("IBosonBundleHandler", function () { ); }); - it("Adding too many twins", async function () { - // Try to add the more than 100 twins - bundle.twinIds = [...Array(101).keys()]; - - // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( - RevertReasons.TOO_MANY_TWINS - ); - }); - it("Exchange already exists for the offerId in bundle", async function () { // Deposit seller funds so the commit will succeed await fundsHandler diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index 5b4a44b0b..4d3db493f 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -891,10 +891,6 @@ describe("IBosonConfigHandler", function () { maxEscalationResponsePeriod, "Invalid max escalatio response period" ); - expect(await configHandler.connect(rando).getMaxAllowedSellers()).to.equal( - maxAllowedSellers, - "Invalid max allowed sellers" - ); expect(await configHandler.connect(rando).getBuyerEscalationDepositPercentage()).to.equal( buyerEscalationDepositPercentage, "Invalid buyer escalation deposit" diff --git a/test/protocol/DisputeHandlerTest.js b/test/protocol/DisputeHandlerTest.js index 4e6dac311..999f8ba96 100644 --- a/test/protocol/DisputeHandlerTest.js +++ b/test/protocol/DisputeHandlerTest.js @@ -2508,16 +2508,6 @@ describe("IBosonDisputeHandler", function () { RevertReasons.INVALID_STATE ); }); - - it("Expiring too many disputes", async function () { - // Try to expire the more than 100 disputes - disputesToExpire = [...Array(101).keys()]; - - // Attempt to expire the disputes, expecting revert - await expect(disputeHandler.connect(rando).expireDisputeBatch(disputesToExpire)).to.revertedWith( - RevertReasons.TOO_MANY_DISPUTES - ); - }); }); }); }); diff --git a/test/protocol/DisputeResolverHandlerTest.js b/test/protocol/DisputeResolverHandlerTest.js index 40e25a6f5..ce99cf0f8 100644 --- a/test/protocol/DisputeResolverHandlerTest.js +++ b/test/protocol/DisputeResolverHandlerTest.js @@ -501,15 +501,6 @@ describe("DisputeResolverHandler", function () { ).to.revertedWith(RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE); }); - it("DisputeResolverFees above max", async function () { - await configHandler.setMaxFeesPerDisputeResolver(2); - - // Attempt to Create a DisputeResolver, expecting revert - await expect( - accountHandler.connect(admin).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_DISPUTE_RESOLVER_FEES); - }); - it("EscalationResponsePeriod is invalid", async function () { await configHandler.setMaxEscalationResponsePeriod(oneWeek); @@ -541,15 +532,6 @@ describe("DisputeResolverHandler", function () { ).to.revertedWith(RevertReasons.DUPLICATE_DISPUTE_RESOLVER_FEES); }); - it("Number of seller ids above max", async function () { - sellerAllowList = new Array(101).fill("1"); - - // Attempt to Create a DisputeResolver, expecting revert - await expect( - accountHandler.connect(admin).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_ALLOWED_SELLERS); - }); - it("Duplicate dispute resolver fees", async function () { //Create new sellerAllowList array sellerAllowList = ["3", "2", "8"]; @@ -1481,16 +1463,7 @@ describe("DisputeResolverHandler", function () { // Attempt to add fees to the dispute resolver, expecting revert await expect( accountHandler.connect(admin).addFeesToDisputeResolver(disputeResolver.id, disputeResolverFees) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_DISPUTE_RESOLVER_FEES); - }); - - it("DisputeResolverFees above max", async function () { - await configHandler.setMaxFeesPerDisputeResolver(2); - - // Attempt to add fees to the dispute resolver, expecting revert - await expect( - accountHandler.connect(admin).addFeesToDisputeResolver(disputeResolver.id, disputeResolverFees) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_DISPUTE_RESOLVER_FEES); + ).to.revertedWith(RevertReasons.INEXISTENT_DISPUTE_RESOLVER_FEES); }); it("Duplicate dispute resolver fees", async function () { @@ -1731,16 +1704,7 @@ describe("DisputeResolverHandler", function () { // Attempt to remove fees from the dispute resolver, expecting revert await expect( accountHandler.connect(admin).removeFeesFromDisputeResolver(disputeResolver.id, feeTokenAddressesToRemove) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_DISPUTE_RESOLVER_FEES); - }); - - it("DisputeResolverFees above max", async function () { - await configHandler.setMaxFeesPerDisputeResolver(2); - - // Attempt to remove fees from the dispute resolver, expecting revert - await expect( - accountHandler.connect(admin).removeFeesFromDisputeResolver(disputeResolver.id, feeTokenAddressesToRemove) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_DISPUTE_RESOLVER_FEES); + ).to.revertedWith(RevertReasons.INEXISTENT_DISPUTE_RESOLVER_FEES); }); it("DisputeResolverFee in array does not exist for Dispute Resolver", async function () { @@ -1866,16 +1830,7 @@ describe("DisputeResolverHandler", function () { // Attempt to add sellers to the allow list, expecting revert await expect( accountHandler.connect(admin).addSellersToAllowList(disputeResolver.id, allowedSellersToAdd) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_ALLOWED_SELLERS); - }); - - it("SellerAllowList above max", async function () { - allowedSellersToAdd = new Array(101).fill("1"); - - // Attempt to add sellers to the allow list, expecting revert - await expect( - accountHandler.connect(admin).addSellersToAllowList(disputeResolver.id, allowedSellersToAdd) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_ALLOWED_SELLERS); + ).to.revertedWith(RevertReasons.INEXISTENT_ALLOWED_SELLERS_LIST); }); it("Some seller does not exist", async function () { @@ -2070,16 +2025,7 @@ describe("DisputeResolverHandler", function () { // Attempt to remove sellers from the allowed list, expecting revert await expect( accountHandler.connect(admin).removeSellersFromAllowList(disputeResolver.id, allowedSellersToRemove) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_ALLOWED_SELLERS); - }); - - it("SellerAllowList above max", async function () { - allowedSellersToRemove = new Array(101).fill("1"); - - // Attempt to remove sellers from the allowed list, expecting revert - await expect( - accountHandler.connect(admin).removeSellersFromAllowList(disputeResolver.id, allowedSellersToRemove) - ).to.revertedWith(RevertReasons.INVALID_AMOUNT_ALLOWED_SELLERS); + ).to.revertedWith(RevertReasons.INEXISTENT_ALLOWED_SELLERS_LIST); }); it("Seller id is not approved", async function () { diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index ccf8e0b8d..177083b35 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -1624,16 +1624,6 @@ describe("IBosonExchangeHandler", function () { ); }); - it("Completing too many exchanges", async function () { - // Try to complete more than 100 exchanges - exchangesToComplete = [...Array(101).keys()]; - - // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(rando).completeExchangeBatch(exchangesToComplete)).to.revertedWith( - RevertReasons.TOO_MANY_EXCHANGES - ); - }); - it("exchange id is invalid", async function () { // An invalid exchange id exchangeId = "666"; diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 9212ed4cf..aa32011b6 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -635,73 +635,6 @@ describe("IBosonFundsHandler", function () { ); }); - it("if user has more different tokens than maximum number allowed to withdraw, only part of it is withdrawn", async function () { - // set maximum tokens per withdraw to 1 - await configHandler.connect(deployer).setMaxTokensPerWithdrawal("1"); - - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - const treasuryNativeBalanceBefore = await ethers.provider.getBalance(treasury.address); - const treasuryTokenBalanceBefore = await mockToken.balanceOf(treasury.address); - - // Chain state should match the expected available funds before the withdrawal - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", sellerPayoff), - ]); - expect(sellersAvailableFunds).to.eql( - expectedSellerAvailableFunds, - "Seller available funds mismatch before withdrawal" - ); - - // withdraw all funds - await fundsHandler.connect(assistant).withdrawFunds(seller.id, [], []); - - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - let treasuryNativeBalanceAfter = await ethers.provider.getBalance(treasury.address); - const treasuryTokenBalanceAfter = await mockToken.balanceOf(treasury.address); - - // Chain state should match the expected available funds after the withdrawal - // Funds available should still have the entries from above the threshold - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", sellerPayoff), - ]); - expect(sellersAvailableFunds).to.eql( - expectedSellerAvailableFunds, - "Seller available funds mismatch after first withdrawal" - ); - // Token balance is increased for sellerPayoff, while native currency balance remains the same - expect(treasuryNativeBalanceAfter).to.eql( - treasuryNativeBalanceBefore, - "Treasury native currency balance mismatch after first withdrawal" - ); - expect(treasuryTokenBalanceAfter).to.eql( - treasuryTokenBalanceBefore.add(sellerPayoff), - "Treasury token balance mismatch after first withdrawal" - ); - - // withdraw all funds again - await fundsHandler.connect(assistant).withdrawFunds(seller.id, [], []); - - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - treasuryNativeBalanceAfter = await ethers.provider.getBalance(treasury.address); - - // Chain state should match the expected available funds after the withdrawal - // Funds available should now be an empty list - expectedSellerAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql( - expectedSellerAvailableFunds, - "Seller available funds mismatch after second withdrawal" - ); - // Native currency balance is increased for the withdrawAmount - expect(treasuryNativeBalanceAfter).to.eql( - treasuryNativeBalanceBefore.add(sellerPayoff), - "Treasury native currency balance mismatch after second withdrawal" - ); - }); - it("It's possible to withdraw same toke twice if in total enough available funds", async function () { let reduction = ethers.utils.parseUnits("0.1", "ether").toString(); // Withdraw token @@ -881,16 +814,6 @@ describe("IBosonFundsHandler", function () { ).to.revertedWith(RevertReasons.TOKEN_AMOUNT_MISMATCH); }); - it("Caller wants to withdraw more different tokens than allowed", async function () { - tokenList = new Array(101).fill(ethers.constants.AddressZero); - tokenAmounts = new Array(101).fill("1"); - - // Attempt to withdraw the funds, expecting revert - await expect( - fundsHandler.connect(assistant).withdrawFunds(seller.id, tokenList, tokenAmounts) - ).to.revertedWith(RevertReasons.TOO_MANY_TOKENS); - }); - it("Caller tries to withdraw more than they have in the available funds", async function () { // Withdraw token tokenList = [mockToken.address]; @@ -1180,81 +1103,6 @@ describe("IBosonFundsHandler", function () { ); }); - it("if protocol has more different tokens than maximum number allowed to withdraw, only part of it is withdrawn", async function () { - // set maximum tokens per withdraw to 1 - await configHandler.connect(deployer).setMaxTokensPerWithdrawal("1"); - - // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - let protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); - - // Chain state should match the expected available funds before the withdrawal - expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", protocolPayoff), - ]); - expect(protocolAvailableFunds).to.eql( - expectedProtocolAvailableFunds, - "Protocol available funds mismatch before withdrawal" - ); - - // withdraw all funds - let tx = await fundsHandler.connect(feeCollector).withdrawProtocolFees([], []); - - // calcualte tx costs - txReceipt = await tx.wait(); - txCost = tx.gasPrice.mul(txReceipt.gasUsed); - - // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - let protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); - - // Chain state should match the expected available funds after the withdrawal - // Funds available should still have the entries from above the threshold - expectedProtocolAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", protocolPayoff), - ]); - expect(protocolAvailableFunds).to.eql( - expectedProtocolAvailableFunds, - "Protocol available funds mismatch after first withdrawal" - ); - // Token balance is increased for protocolFee, while native currency balance is reduced only for tx costs - expect(protocolTreasuryNativeBalanceAfter).to.eql( - protocolTreasuryNativeBalanceBefore, - "Fee collector native currency balance mismatch after first withdrawal" - ); - expect(protocolTreasuryTokenBalanceAfter).to.eql( - protocolTreasuryTokenBalanceBefore.add(protocolPayoff), - "Fee collector token balance mismatch after first withdrawal" - ); - - // withdraw all funds again - tx = await fundsHandler.connect(feeCollector).withdrawProtocolFees([], []); - - // calcualte tx costs - txReceipt = await tx.wait(); - txCost = tx.gasPrice.mul(txReceipt.gasUsed); - - // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); - - // Chain state should match the expected available funds after the withdrawal - // Funds available should now be an empty list - expectedProtocolAvailableFunds = new FundsList([]); - expect(protocolAvailableFunds).to.eql( - expectedProtocolAvailableFunds, - "Protocol available funds mismatch after second withdrawal" - ); - // Native currency balance is increased for the protocol fee - expect(protocolTreasuryNativeBalanceAfter).to.eql( - protocolTreasuryNativeBalanceBefore.add(offerTokenProtocolFee), - "Fee collector native currency balance mismatch after second withdrawal" - ); - }); - it("It's possible to withdraw same token twice if in total enough available funds", async function () { let reduction = ethers.utils.parseUnits("0.01", "ether").toString(); // Withdraw token @@ -1311,16 +1159,6 @@ describe("IBosonFundsHandler", function () { ).to.revertedWith(RevertReasons.TOKEN_AMOUNT_MISMATCH); }); - it("Caller wants to withdraw more different tokens than allowed", async function () { - tokenList = new Array(101).fill(ethers.constants.AddressZero); - tokenAmounts = new Array(101).fill("1"); - - // Attempt to withdraw the funds, expecting revert - await expect( - fundsHandler.connect(feeCollector).withdrawProtocolFees(tokenList, tokenAmounts) - ).to.revertedWith(RevertReasons.TOO_MANY_TOKENS); - }); - it("Caller tries to withdraw more than they have in the available funds", async function () { // Withdraw token tokenList = [mockToken.address]; diff --git a/test/protocol/GroupHandlerTest.js b/test/protocol/GroupHandlerTest.js index 466c6f017..2a2f50327 100644 --- a/test/protocol/GroupHandlerTest.js +++ b/test/protocol/GroupHandlerTest.js @@ -359,16 +359,6 @@ describe("IBosonGroupHandler", function () { ); }); - it("Adding too many offers", async function () { - // Try to add the more than 100 offers - group.offerIds = [...Array(101).keys()]; - - // Attempt to create a group, expecting revert - await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( - RevertReasons.TOO_MANY_OFFERS - ); - }); - context("Condition 'None' has some values in other fields", async function () { beforeEach(async function () { condition = mockCondition({ method: EvaluationMethod.None, threshold: "0", maxCommits: "0" }); @@ -607,26 +597,6 @@ describe("IBosonGroupHandler", function () { ); }); - it("Number of offers to add exceeds max", async function () { - // Try to add the more than 100 offers - offerIdsToAdd = [...Array(101).keys()]; - - // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( - RevertReasons.TOO_MANY_OFFERS - ); - }); - - it("Current number of offers plus number of offers to add exceeds max", async function () { - // Try to add offers to that total is more than 100. Group currently has 3. - offerIdsToAdd = [...Array(98).keys()]; - - // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( - RevertReasons.TOO_MANY_OFFERS - ); - }); - it("Adding nothing", async function () { // Try to add nothing offerIdsToAdd = []; @@ -801,16 +771,6 @@ describe("IBosonGroupHandler", function () { ).to.revertedWith(RevertReasons.OFFER_NOT_IN_GROUP); }); - it("Removing too many offers", async function () { - // Try to remove the more than 100 offers - offerIdsToRemove = [...Array(101).keys()]; - - // Attempt to remove offers from the group, expecting revert - await expect( - groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) - ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); - }); - it("Removing nothing", async function () { // Try to remove nothing offerIdsToRemove = []; diff --git a/test/protocol/OfferHandlerTest.js b/test/protocol/OfferHandlerTest.js index f182e0441..f5891ea10 100644 --- a/test/protocol/OfferHandlerTest.js +++ b/test/protocol/OfferHandlerTest.js @@ -2219,20 +2219,6 @@ describe("IBosonOfferHandler", function () { ).to.revertedWith(RevertReasons.OFFER_MUST_BE_ACTIVE); }); - it("Creating too many offers", async function () { - const gasLimit = 10000000; - - // Try to create the more than 100 offers - offers = new Array(101).fill(offer); - - // Attempt to create the offers, expecting revert - await expect( - offerHandler - .connect(assistant) - .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds, { gasLimit }) - ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); - }); - it("Dispute valid duration is 0 for some offer", async function () { // Set dispute valid duration to 0 offerDurationsList[2].resolutionPeriod = "0"; @@ -2821,16 +2807,6 @@ describe("IBosonOfferHandler", function () { RevertReasons.OFFER_HAS_BEEN_VOIDED ); }); - - it("Voiding too many offers", async function () { - // Try to void the more than 100 offers - offersToVoid = [...Array(101).keys()]; - - // Attempt to void the offers, expecting revert - await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( - RevertReasons.TOO_MANY_OFFERS - ); - }); }); }); @@ -2989,16 +2965,6 @@ describe("IBosonOfferHandler", function () { offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); - - it("Extending too many offers", async function () { - // Try to extend the more than 100 offers - offersToExtend = [...Array(101).keys()]; - - // Attempt to extend the offers, expecting revert - await expect( - offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) - ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); - }); }); }); }); diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 16ba4cb19..1b224ea58 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -457,20 +457,6 @@ describe("ProtocolInitializationHandler", async function () { ); }); - it("Should emit MaxPremintedVouchersChanged event", async function () { - // Make the cut, check the event - await expect( - diamondCutFacet.diamondCut( - [facetCut], - deployedProtocolInitializationHandlerFacet.address, - calldataProtocolInitialization, - await getFees(maxPriorityFeePerGas) - ) - ) - .to.emit(configHandler, "MaxPremintedVouchersChanged") - .withArgs(maxPremintedVouchers, deployer.address); - }); - it("Should update state", async function () { // Make the cut, check the event await diamondCutFacet.diamondCut( diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index b4b9e9894..08abb0717 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -1,4 +1,5 @@ const { ethers } = require("hardhat"); +const { assert, expect } = require("chai"); const DisputeResolutionTerms = require("../../../scripts/domain/DisputeResolutionTerms"); const { getInterfaceIds } = require("../../../scripts/config/supported-interfaces.js"); @@ -7,9 +8,6 @@ const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverF const Range = require("../../../scripts/domain/Range"); const VoucherInitValues = require("../../../scripts/domain/VoucherInitValues"); const { Funds, FundsList } = require("../../../scripts/domain/Funds"); - -const { mockOffer, mockExchange, mockVoucher } = require("../../util/mock.js"); -const { assert, expect } = require("chai"); const { RevertReasons } = require("../../../scripts/config/revert-reasons"); const { mockDisputeResolver, @@ -18,6 +16,9 @@ const { mockAuthToken, mockBuyer, accountId, + mockVoucher, + mockExchange, + mockOffer, } = require("../../util/mock"); const { applyPercentage, @@ -275,7 +276,6 @@ describe("IBosonVoucher", function () { const mockProtocol = await deployMockProtocol(); const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); await mockProtocol.mock.getOffer.returns( true, offer, @@ -314,7 +314,7 @@ describe("IBosonVoucher", function () { const mockProtocol = await deployMockProtocol(); const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); + await mockProtocol.mock.getOffer.returns( true, offer, @@ -398,7 +398,6 @@ describe("IBosonVoucher", function () { mockProtocol = await deployMockProtocol(); ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); await mockProtocol.mock.getOffer.returns( true, offer, @@ -601,18 +600,6 @@ describe("IBosonVoucher", function () { ); }); - it("Too many to mint in a single transaction", async function () { - await mockProtocol.mock.getMaxPremintedVouchers.returns("100"); - - // Set invalid amount - amount = "101"; - - // Try to premint, it should fail - await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.TOO_MANY_TO_MINT - ); - }); - it("Offer already expired", async function () { // Skip to after offer expiration await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); @@ -647,16 +634,13 @@ describe("IBosonVoucher", function () { let offerId, start, length, amount; let mockProtocol; let offer, offerDates, offerDurations, offerFees, disputeResolutionTerms; - let maxPremintedVouchers; beforeEach(async function () { offerId = "5"; - maxPremintedVouchers = "10"; mockProtocol = await deployMockProtocol(); ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns(maxPremintedVouchers); await mockProtocol.mock.getOffer .withArgs(offerId) .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); @@ -790,7 +774,7 @@ describe("IBosonVoucher", function () { }); }); - it("Should burn all vouchers if there is less than MaxPremintedVouchers to burn", async function () { + it("Should burn all vouchers", async function () { // Burn tokens, test for event let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); @@ -810,64 +794,6 @@ describe("IBosonVoucher", function () { ); }); - it("Should burn only first MaxPremintedVouchers vouchers if there is more than MaxPremintedVouchers to burn", async function () { - // make offer not voided so premint is possible - offer.voided = false; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // Mint another 10 vouchers, so that there are 15 in total - await bosonVoucher.connect(assistant).preMint(offerId, 10); - amount = `${Number(amount) + 10}`; - - // "void" the offer - offer.voided = true; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // Burn tokens, test for event - let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); - - // Number of events emitted should be equal to maxPremintedVouchers - assert.equal((await tx.wait()).events.length, Number(maxPremintedVouchers), "Wrong number of events emitted"); - - // Last burned id should be updated - const tokenIdStart = deriveTokenId(offerId, start); - let lastBurnedId = tokenIdStart.add(maxPremintedVouchers - 1); - let range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); - let returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // Second call should burn the difference - tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); - - // Number of events emitted should be equal to amount - assert.equal( - (await tx.wait()).events.length, - Number(amount) - maxPremintedVouchers, - "Wrong number of events emitted" - ); - - // Last burned id should be updated - lastBurnedId = tokenIdStart.add(amount - 1); - range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); - returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // All burned tokens should not have an owner - for (let i = 0; i < Number(amount); i++) { - let tokenId = tokenIdStart.add(i); - await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - - // Second call should revert since there's nothing to burn - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( - RevertReasons.NOTHING_TO_BURN - ); - }); - it("Should skip all vouchers were already committed", async function () { let committedVouchers = [11, 14].map((tokenId) => deriveTokenId(offerId, tokenId).toString()); @@ -999,7 +925,6 @@ describe("IBosonVoucher", function () { mockProtocol = await deployMockProtocol(); ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); await mockProtocol.mock.getOffer.returns( true, offer, @@ -1033,9 +958,6 @@ describe("IBosonVoucher", function () { }); it("Range is fully minted", async function () { - // Adjust config value - await configHandler.connect(deployer).setMaxPremintedVouchers(length); - // Premint tokens await bosonVoucher.connect(assistant).preMint(offerId, length); @@ -1098,7 +1020,6 @@ describe("IBosonVoucher", function () { const mockProtocol = await deployMockProtocol(); const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); await mockProtocol.mock.getOffer.returns( true, offer, @@ -1168,7 +1089,6 @@ describe("IBosonVoucher", function () { mockProtocol = await deployMockProtocol(); ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getMaxPremintedVouchers.returns("1000"); await mockProtocol.mock.getOffer.returns( true, offer, @@ -1228,8 +1148,6 @@ describe("IBosonVoucher", function () { // Add five more ranges // This tests more getPreMintStatus than ownerOf // Might even be put into integration tests - // Adjust config value - await configHandler.connect(deployer).setMaxPremintedVouchers("10000"); let previousOfferId = Number(offerId); let previousStartId = Number(start); let ranges = [new Range(Number(start), length, amount, "0")]; @@ -1895,7 +1813,7 @@ describe("IBosonVoucher", function () { ); expect(disputeResolver.isValid()).is.true; - //Create DisputeResolverFee array so offer creation will succeed + // Create DisputeResolverFee array so offer creation will succeed disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; // Make empty seller list, so every seller is allowed From 939e256ec10156c6034ef8cc9a0a2d7ac4154ec9 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Tue, 13 Jun 2023 08:38:59 -0300 Subject: [PATCH 06/22] Fix init v2.2.0 test --- scripts/migrations/migrate_2.2.1.js | 4 +- test/protocol/ConfigHandlerTest.js | 40 ------------------- .../ProtocolInitializationHandlerTest.js | 10 +++-- 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/scripts/migrations/migrate_2.2.1.js b/scripts/migrations/migrate_2.2.1.js index 091943127..fa515511f 100644 --- a/scripts/migrations/migrate_2.2.1.js +++ b/scripts/migrations/migrate_2.2.1.js @@ -27,7 +27,7 @@ async function migrate(env) { console.log(`Migration ${tag} started`); try { console.log("Removing any local changes before upgrading"); - shell.exec(`git reset`); + shell.exec(`git reset @{u}`); const statusOutput = shell.exec("git status -s -uno scripts"); if (statusOutput.stdout) { @@ -78,7 +78,7 @@ async function migrate(env) { await hre.run("upgrade-facets", { env, facetConfig: JSON.stringify(config), - newVersion: tag, + newVersion: tag.replace("v", ""), }); const selectorsToAdd = await getFunctionHashesClosure(); diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index 4d3db493f..655e50b15 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -158,46 +158,10 @@ describe("IBosonConfigHandler", function () { .to.emit(configHandler, "ProtocolFeeFlatBosonChanged") .withArgs(protocolFeeFlatBoson, deployer.address); - await expect(cutTransaction) - .to.emit(configHandler, "MaxExchangesPerBatchChanged") - .withArgs(maxExchangesPerBatch, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxOffersPerGroupChanged") - .withArgs(maxOffersPerGroup, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxTwinsPerBundleChanged") - .withArgs(maxTwinsPerBundle, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxOffersPerBundleChanged") - .withArgs(maxOffersPerBundle, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxOffersPerBatchChanged") - .withArgs(maxOffersPerBatch, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxTokensPerWithdrawalChanged") - .withArgs(maxTokensPerWithdrawal, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxFeesPerDisputeResolverChanged") - .withArgs(maxFeesPerDisputeResolver, deployer.address); - await expect(cutTransaction) .to.emit(configHandler, "MaxEscalationResponsePeriodChanged") .withArgs(maxEscalationResponsePeriod, deployer.address); - await expect(cutTransaction) - .to.emit(configHandler, "MaxDisputesPerBatchChanged") - .withArgs(maxDisputesPerBatch, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxAllowedSellersChanged") - .withArgs(maxAllowedSellers, deployer.address); - await expect(cutTransaction) .to.emit(configHandler, "BuyerEscalationFeePercentageChanged") .withArgs(buyerEscalationDepositPercentage, deployer.address); @@ -213,10 +177,6 @@ describe("IBosonConfigHandler", function () { await expect(cutTransaction) .to.emit(configHandler, "MinDisputePeriodChanged") .withArgs(minDisputePeriod, deployer.address); - - await expect(cutTransaction) - .to.emit(configHandler, "MaxPremintedVouchersChanged") - .withArgs(maxPremintedVouchers, deployer.address); }); }); }); diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 1b224ea58..7da3d1712 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -1,6 +1,7 @@ const { expect } = require("chai"); const hre = require("hardhat"); const ethers = hre.ethers; +const { keccak256, toUtf8Bytes } = ethers.utils; const Role = require("../../scripts/domain/Role"); const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); @@ -12,6 +13,7 @@ const { getFacetAddCut, getFacetReplaceCut } = require("../../scripts/util/diamo const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); const { getFacetsWithArgs } = require("../util/utils.js"); const { getV2_2_0DeployConfig } = require("../upgrade/00_config.js"); +const { getStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); describe("ProtocolInitializationHandler", async function () { // Common vars @@ -56,7 +58,7 @@ describe("ProtocolInitializationHandler", async function () { version = "2.2.0"; // initialization data for v2.2.0 - maxPremintedVouchers = "1000"; + maxPremintedVouchers = "10000"; initializationData = ethers.utils.defaultAbiCoder.encode(["uint256"], [maxPremintedVouchers]); }); @@ -466,8 +468,10 @@ describe("ProtocolInitializationHandler", async function () { await getFees(maxPriorityFeePerGas) ); - // Verify that new value is stored - expect(await configHandler.connect(rando).getMaxPremintedVouchers()).to.equal(maxPremintedVouchers); + const protocolLimitsSlot = ethers.BigNumber.from(keccak256(toUtf8Bytes("boson.protocol.limits"))); + const maxPremintedVoucherStorage = await getStorageAt(diamondCutFacet.address, protocolLimitsSlot.add(4)); + + expect(ethers.BigNumber.from(maxPremintedVoucherStorage).toString()).to.equal(maxPremintedVouchers); }); context("💔 Revert Reasons", async function () { From f5067ba835a910265a74b70607741b6e91f0526d Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Tue, 27 Jun 2023 15:20:07 -0300 Subject: [PATCH 07/22] Add _amount parameter to burnPremintedVouchers --- .../interfaces/clients/IBosonVoucher.sol | 3 +- .../protocol/clients/voucher/BosonVoucher.sol | 5 ++-- test/protocol/clients/BosonVoucherTest.js | 30 +++++++++---------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/contracts/interfaces/clients/IBosonVoucher.sol b/contracts/interfaces/clients/IBosonVoucher.sol index 98060f2cb..d2cfc6844 100644 --- a/contracts/interfaces/clients/IBosonVoucher.sol +++ b/contracts/interfaces/clients/IBosonVoucher.sol @@ -189,8 +189,9 @@ interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable, IERC7 * - There is nothing to burn * * @param _offerId - the id of the offer + * @param _amount - amount to burn */ - function burnPremintedVouchers(uint256 _offerId) external; + function burnPremintedVouchers(uint256 _offerId, uint256 _amount) external; /** * @notice Gets the number of vouchers available to be pre-minted for an offer. diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 7e2e14461..257b9e5aa 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -263,8 +263,9 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * - There is nothing to burn * * @param _offerId - the id of the offer + * @param _amount - amount to burn */ - function burnPremintedVouchers(uint256 _offerId) external override onlyOwner { + function burnPremintedVouchers(uint256 _offerId, uint256 _amount) external override onlyOwner { // Get the offer's range Range storage range = _rangeByOfferId[_offerId]; @@ -279,7 +280,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable uint256 start = (range.lastBurnedTokenId == 0) ? range.start : (range.lastBurnedTokenId + 1); // Get the last token to burn - uint256 end = range.start + range.minted; + uint256 end = range.start + _amount; // End should be greater than start require(end > start, NOTHING_TO_BURN); diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index 08abb0717..3f5889327 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -663,7 +663,7 @@ describe("IBosonVoucher", function () { it("Should emit Transfer events", async function () { // Burn tokens, test for event - const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Number of events emitted should be equal to amount assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); @@ -681,7 +681,7 @@ describe("IBosonVoucher", function () { let sellerBalanceBefore = await bosonVoucher.balanceOf(assistant.address); // Burn tokens - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // All burned tokens should not have an owner const startId = deriveTokenId(offerId, start); @@ -714,7 +714,7 @@ describe("IBosonVoucher", function () { // Burn tokens, test for event let tx; await expect(() => { - tx = bosonVoucher.connect(rando).burnPremintedVouchers(offerId); + tx = bosonVoucher.connect(rando).burnPremintedVouchers(offerId, amount); return tx; }).to.changeTokenBalance(bosonVoucher, assistant, Number(amount) * -1); @@ -756,7 +756,7 @@ describe("IBosonVoucher", function () { // Burn tokens, test for event let tx; await expect(() => { - tx = bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + tx = bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); return tx; }).to.changeTokenBalance(bosonVoucher, bosonVoucher, Number(amount) * -1); @@ -776,7 +776,7 @@ describe("IBosonVoucher", function () { it("Should burn all vouchers", async function () { // Burn tokens, test for event - let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Number of events emitted should be equal to amount assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); @@ -789,7 +789,7 @@ describe("IBosonVoucher", function () { assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); // Second call should revert since there's nothing to burn - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.NOTHING_TO_BURN ); }); @@ -806,7 +806,7 @@ describe("IBosonVoucher", function () { ); // Burn tokens, test for event - let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Number of events emitted should be equal to amount of preminted vouchers decreased by length of committed vouchers // We test this to indirectly verify that no events were emitted for committed vouchers @@ -850,7 +850,7 @@ describe("IBosonVoucher", function () { await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); // Burn tokens, test for event - const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Number of events emitted should be equal to amount assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); @@ -866,7 +866,7 @@ describe("IBosonVoucher", function () { context("💔 Revert Reasons", async function () { it("Caller is not the owner", async function () { - await expect(bosonVoucher.connect(rando).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(rando).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); }); @@ -876,7 +876,7 @@ describe("IBosonVoucher", function () { offerId = 15; // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.NO_RESERVED_RANGE_FOR_OFFER ); }); @@ -889,17 +889,17 @@ describe("IBosonVoucher", function () { .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_STILL_VALID ); }); it("Nothing to burn", async function () { // Burn tokens - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.NOTHING_TO_BURN ); }); @@ -1324,7 +1324,7 @@ describe("IBosonVoucher", function () { ); // Burn preminted voucher - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Token should have no owner await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( @@ -1645,7 +1645,7 @@ describe("IBosonVoucher", function () { await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted vouchers - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, "1"); // None of reserved but not preminted tokens should have an owner await expect( From 1844a9c992a5c6eae00bc6086801c43ed40e3a2f Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 28 Jun 2023 20:09:52 -0300 Subject: [PATCH 08/22] Addings tests to Boson Voucher --- contracts/domain/BosonConstants.sol | 2 +- .../interfaces/clients/IBosonVoucher.sol | 2 +- .../protocol/clients/voucher/BosonVoucher.sol | 7 +++- scripts/config/revert-reasons.js | 2 +- test/protocol/clients/BosonVoucherTest.js | 37 +++++++++++++++++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 537fed017..4a46fc61f 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -184,7 +184,7 @@ string constant INVALID_AMOUNT_TO_MINT = "Amount to mint is greater than remaini string constant NO_SILENT_MINT_ALLOWED = "Only owner's mappings can be updated without event"; string constant OFFER_EXPIRED_OR_VOIDED = "Offer expired or voided"; string constant OFFER_STILL_VALID = "Offer still valid"; -string constant NOTHING_TO_BURN = "Nothing to burn"; +string constant AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN = "Amount exceeds the range or there is nothing to burn"; string constant OWNABLE_ZERO_ADDRESS = "Ownable: new owner is the zero address"; string constant ROYALTY_FEE_INVALID = "ERC2981: royalty fee exceeds protocol limit"; string constant NOT_COMMITTABLE = "Token not committable"; diff --git a/contracts/interfaces/clients/IBosonVoucher.sol b/contracts/interfaces/clients/IBosonVoucher.sol index d2cfc6844..62adad281 100644 --- a/contracts/interfaces/clients/IBosonVoucher.sol +++ b/contracts/interfaces/clients/IBosonVoucher.sol @@ -10,7 +10,7 @@ import { IERC721ReceiverUpgradeable } from "@openzeppelin/contracts-upgradeable/ * * @notice This is the interface for the Boson Protocol ERC-721 Voucher contract. * - * The ERC-165 identifier for this interface is: 0xaf16da6e + * The ERC-165 identifier for this interface is: 0x5235dd2b */ interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable, IERC721ReceiverUpgradeable { event ContractURIChanged(string contractURI); diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 257b9e5aa..8927f384c 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.18; + +import "hardhat/console.sol"; import "../../../domain/BosonConstants.sol"; import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; @@ -280,10 +282,10 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable uint256 start = (range.lastBurnedTokenId == 0) ? range.start : (range.lastBurnedTokenId + 1); // Get the last token to burn - uint256 end = range.start + _amount; + uint256 end = start + _amount; // End should be greater than start - require(end > start, NOTHING_TO_BURN); + require(end > start && end <= range.start + range.minted, AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN); // Burn the range address rangeOwner = range.owner; @@ -291,6 +293,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable for (uint256 tokenId = start; tokenId < end; tokenId++) { // Burn only if not already _committed if (!_committed[tokenId]) { + console.log('burning', tokenId); emit Transfer(rangeOwner, address(0), tokenId); burned++; } diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index 496ba14f3..a4aa6de44 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -133,7 +133,7 @@ exports.RevertReasons = { NO_SILENT_MINT_ALLOWED: "Only owner's mappings can be updated without event", OFFER_EXPIRED_OR_VOIDED: "Offer expired or voided", OFFER_STILL_VALID: "Offer still valid", - NOTHING_TO_BURN: "Nothing to burn", + AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN: "Amount exceeds the range or there is nothing to burn", NOT_COMMITTABLE: "Token not committable", INVALID_TO_ADDRESS: "Tokens can only be pre-mined to the contract or contract owner address", EXTERNAL_CALL_FAILED: "External call failed", diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index 3f5889327..732809c88 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -790,10 +790,34 @@ describe("IBosonVoucher", function () { // Second call should revert since there's nothing to burn await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.NOTHING_TO_BURN + RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN ); }); + it("Should respect amount parameter", async function () { + // amout minted = 5 + // burn = 3 + const amountMinted = amount; + amount = 3; + + // Burn tokens, test for event + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); + + // Last burned id should be updated + const tokenIdStart = deriveTokenId(offerId, start); + const lastBurnedId = tokenIdStart.add(amount - 1); + let range = new Range(tokenIdStart.toString(), length, amountMinted, lastBurnedId.toString(), assistant.address); + let returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); + assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); + + // burn remaining vouchers + amount = 2; + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); + range.lastBurnedTokenId = tokenIdStart.add(amountMinted - 1).toString(); + returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); + assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); + }); + it("Should skip all vouchers were already committed", async function () { let committedVouchers = [11, 14].map((tokenId) => deriveTokenId(offerId, tokenId).toString()); @@ -846,7 +870,6 @@ describe("IBosonVoucher", function () { await mockProtocol.mock.getOffer .withArgs(offerId) .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - // skip to after offer expiration await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); // Burn tokens, test for event @@ -900,7 +923,15 @@ describe("IBosonVoucher", function () { // Try to burn, it should fail await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.NOTHING_TO_BURN + RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN + ); + }); + + it("Amounts overflows minted range", async function () { + const amount = 6; + + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( + RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN ); }); }); From 93640a499d11492ce38474e6686a06e23f0ae3d5 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 28 Jun 2023 20:19:06 -0300 Subject: [PATCH 09/22] Tidy --- contracts/protocol/clients/voucher/BosonVoucher.sol | 2 -- scripts/util/estimate-configurations.js | 0 test/protocol/clients/BosonVoucherTest.js | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 scripts/util/estimate-configurations.js diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 8927f384c..daec8930f 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.18; -import "hardhat/console.sol"; import "../../../domain/BosonConstants.sol"; import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; @@ -293,7 +292,6 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable for (uint256 tokenId = start; tokenId < end; tokenId++) { // Burn only if not already _committed if (!_committed[tokenId]) { - console.log('burning', tokenId); emit Transfer(rangeOwner, address(0), tokenId); burned++; } diff --git a/scripts/util/estimate-configurations.js b/scripts/util/estimate-configurations.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index 732809c88..27e63194b 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -801,7 +801,7 @@ describe("IBosonVoucher", function () { amount = 3; // Burn tokens, test for event - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Last burned id should be updated const tokenIdStart = deriveTokenId(offerId, start); @@ -931,7 +931,7 @@ describe("IBosonVoucher", function () { const amount = 6; await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN + RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN ); }); }); From 7a12bb1f8944c091225ce79b60295defe9edbacc Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Fri, 30 Jun 2023 17:21:11 -0300 Subject: [PATCH 10/22] Adding _tokenList parameter to getAvailableFunds --- .../handlers/IBosonFundsHandler.sol | 3 +- .../protocol/facets/FundsHandlerFacet.sol | 11 +- test/protocol/FundsHandlerTest.js | 381 +++++++++--------- 3 files changed, 200 insertions(+), 195 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonFundsHandler.sol b/contracts/interfaces/handlers/IBosonFundsHandler.sol index 4b1003c2c..21881d173 100644 --- a/contracts/interfaces/handlers/IBosonFundsHandler.sol +++ b/contracts/interfaces/handlers/IBosonFundsHandler.sol @@ -78,7 +78,8 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { * @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol. * * @param _entityId - id of entity for which availability of funds should be checked + * @param _tokenList - list of tokens addresses to get available funds * @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn */ - function getAvailableFunds(uint256 _entityId) external view returns (BosonTypes.Funds[] memory availableFunds); + function getAvailableFunds(uint256 _entityId, address[] calldata _tokenList) external view returns (BosonTypes.Funds[] memory availableFunds); } diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 0a39f427f..301c19947 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -155,21 +155,20 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol. * * @param _entityId - id of entity for which availability of funds should be checked + * @param _tokenList - list of tokens addresses to get available funds * @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn */ - function getAvailableFunds(uint256 _entityId) external view override returns (Funds[] memory availableFunds) { + function getAvailableFunds(uint256 _entityId, address[] calldata _tokenList) external view override returns (Funds[] memory availableFunds) { // Cache protocol lookups for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); - // get list of token addresses for the entity - address[] storage tokenList = lookups.tokenList[_entityId]; - availableFunds = new Funds[](tokenList.length); + availableFunds = new Funds[](_tokenList.length); // Get entity's availableFunds storage pointer mapping(address => uint256) storage entityFunds = lookups.availableFunds[_entityId]; - for (uint256 i = 0; i < tokenList.length; i++) { - address tokenAddress = tokenList[i]; + for (uint256 i = 0; i < _tokenList.length; i++) { + address tokenAddress = _tokenList[i]; string memory tokenName; if (tokenAddress == address(0)) { diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index aa32011b6..f148c4b0f 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -96,6 +96,7 @@ describe("IBosonFundsHandler", function () { let DRFee, buyerEscalationDeposit; let protocolDiamondAddress; let snapshotId; + let availableFundsAddresses; before(async function () { accountId.next(true); @@ -123,7 +124,7 @@ describe("IBosonFundsHandler", function () { offerHandler, exchangeHandler, fundsHandler, - configHandler, + pauseHandler, disputeHandler, }, @@ -142,6 +143,8 @@ describe("IBosonFundsHandler", function () { // Deploy the mock token [mockToken] = await deployMockTokens(["Foreign20"]); + availableFundsAddresses = [mockToken.address]; + // Get snapshot id snapshotId = await getSnapshot(); }); @@ -217,12 +220,12 @@ describe("IBosonFundsHandler", function () { .withArgs(seller.id, rando.address, ethers.constants.AddressZero, depositAmount); }); - it("should update state", async function () { + it.only("should update state", async function () { // Deposit token await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); @@ -233,8 +236,10 @@ describe("IBosonFundsHandler", function () { .connect(rando) .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount, { value: depositAmount }); + availableFundsAddresses.push(ethers.constants.AddressZero) + // Get new on chain state - returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Chain state should match the expected available funds expectedAvailableFunds.funds.push(new Funds(ethers.constants.AddressZero, "Native currency", depositAmount)); @@ -246,7 +251,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); @@ -256,7 +261,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, 2 * depositAmount); // Get new on chain state - returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Chain state should match the expected available funds expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", `${3 * depositAmount}`)]); @@ -515,7 +520,7 @@ describe("IBosonFundsHandler", function () { // WITHDRAW ONE TOKEN PARTIALLY // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryBalanceBefore = await ethers.provider.getBalance(treasury.address); // Chain state should match the expected available funds before the withdrawal @@ -537,7 +542,7 @@ describe("IBosonFundsHandler", function () { .withdrawFunds(seller.id, [ethers.constants.AddressZero], [withdrawAmount]); // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryBalanceAfter = await ethers.provider.getBalance(treasury.address); // Chain state should match the expected available funds after the withdrawal @@ -560,7 +565,7 @@ describe("IBosonFundsHandler", function () { // WITHDRAW ONE TOKEN FULLY // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); const buyerBalanceBefore = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds before the withdrawal @@ -577,7 +582,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(buyer).withdrawFunds(buyerId, [mockToken.address], [buyerPayoff]); // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); const buyerBalanceAfter = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds after the withdrawal @@ -595,7 +600,7 @@ describe("IBosonFundsHandler", function () { it("should allow to withdraw all funds at once", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryNativeBalanceBefore = await ethers.provider.getBalance(treasury.address); const treasuryTokenBalanceBefore = await mockToken.balanceOf(treasury.address); @@ -613,7 +618,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).withdrawFunds(seller.id, [], []); // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryNativeBalanceAfter = await ethers.provider.getBalance(treasury.address); const treasuryTokenBalanceAfter = await mockToken.balanceOf(treasury.address); @@ -994,7 +999,7 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); @@ -1025,7 +1030,7 @@ describe("IBosonFundsHandler", function () { txCost = tx.gasPrice.mul(txReceipt.gasUsed); // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); @@ -1058,7 +1063,7 @@ describe("IBosonFundsHandler", function () { it("should allow to withdraw all funds at once", async function () { // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); @@ -1080,7 +1085,7 @@ describe("IBosonFundsHandler", function () { txCost = tx.gasPrice.mul(txReceipt.gasUsed); // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); @@ -1269,7 +1274,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([ @@ -1427,7 +1432,7 @@ describe("IBosonFundsHandler", function () { // contract native token balance const contractNativeBalanceBefore = await ethers.provider.getBalance(protocolDiamondAddress); // seller's available funds - const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Commit to an offer with erc20 token await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); @@ -1442,7 +1447,7 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) @@ -1463,7 +1468,7 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // native currency is the second on the list of the available funds and the amount should be decreased for the sellerDeposit expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) @@ -1475,7 +1480,7 @@ describe("IBosonFundsHandler", function () { context("seller's available funds drop to 0", async function () { it("token should be removed from the tokenList", async function () { // seller's available funds - let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellersAvailableFunds.funds.length).to.eql(2, "Funds length mismatch"); expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( mockToken.address, @@ -1491,7 +1496,7 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); // Token address should be removed and have only native currency in the list - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellersAvailableFunds.funds.length).to.eql(1, "Funds length mismatch"); expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( ethers.constants.AddressZero, @@ -1503,7 +1508,7 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); // Seller available funds must be empty - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellersAvailableFunds.funds.length).to.eql(0, "Funds length mismatch"); }); @@ -1531,7 +1536,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, otherToken.address, sellerDeposit); // seller's available funds - let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellersAvailableFunds.funds.length).to.eql(3, "Funds length mismatch"); expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( mockToken.address, @@ -1551,7 +1556,7 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); // Native currency address should be removed and have only mock token and other token in the list - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellersAvailableFunds.funds.length).to.eql(2, "Funds length mismatch"); expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( mockToken.address, @@ -1635,7 +1640,7 @@ describe("IBosonFundsHandler", function () { // get token balance before the commit const buyerTokenBalanceBefore = await mockToken.balanceOf(buyer.address); - const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // reserve a range and premint vouchers await offerHandler @@ -1657,7 +1662,7 @@ describe("IBosonFundsHandler", function () { .withArgs(seller.id, mockToken.address, encumberedFunds, bosonVoucher.address); // Check that seller's pool balance was reduced - let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) @@ -1705,7 +1710,7 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // native currency the second on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) @@ -1944,10 +1949,10 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -1971,10 +1976,10 @@ describe("IBosonFundsHandler", function () { // agent: 0 expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); expectedProtocolAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -1984,10 +1989,10 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(++exchangeId); await exchangeHandler.connect(buyer).completeExchange(exchangeId); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expectedSellerAvailableFunds.funds[1] = new Funds( mockToken.address, "Foreign20", @@ -2057,10 +2062,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2085,10 +2090,10 @@ describe("IBosonFundsHandler", function () { expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); expectedProtocolAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee)); expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2119,10 +2124,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2146,10 +2151,10 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2175,10 +2180,10 @@ describe("IBosonFundsHandler", function () { expectedSellerAvailableFunds = new FundsList([ new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2224,10 +2229,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2251,10 +2256,10 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2281,10 +2286,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", `${sellerDeposit}`), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2322,10 +2327,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2354,10 +2359,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2405,10 +2410,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2437,10 +2442,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2509,10 +2514,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2541,10 +2546,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2604,10 +2609,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2634,10 +2639,10 @@ describe("IBosonFundsHandler", function () { ); expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2689,10 +2694,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2721,10 +2726,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2797,10 +2802,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2829,10 +2834,10 @@ describe("IBosonFundsHandler", function () { expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); expectedAgentAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", agentPayoff); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2905,10 +2910,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2937,10 +2942,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -3012,10 +3017,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3041,10 +3046,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) ); expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -3100,10 +3105,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3132,10 +3137,10 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3188,10 +3193,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3218,10 +3223,10 @@ describe("IBosonFundsHandler", function () { ); expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3299,10 +3304,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3331,10 +3336,10 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3416,10 +3421,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3445,10 +3450,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) ); expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -3501,10 +3506,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3533,10 +3538,10 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3600,10 +3605,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3629,10 +3634,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) ); expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3681,10 +3686,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ From be085293c5d5e21b884d14df10732d543ce6b01b Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Tue, 4 Jul 2023 10:54:49 -0300 Subject: [PATCH 11/22] Add new parameter to getAvailableFunds calls --- test/protocol/FundsHandlerTest.js | 71 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index f148c4b0f..d9eafdb7b 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -220,7 +220,7 @@ describe("IBosonFundsHandler", function () { .withArgs(seller.id, rando.address, ethers.constants.AddressZero, depositAmount); }); - it.only("should update state", async function () { + it("should update state", async function () { // Deposit token await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); @@ -236,10 +236,8 @@ describe("IBosonFundsHandler", function () { .connect(rando) .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount, { value: depositAmount }); - availableFundsAddresses.push(ethers.constants.AddressZero) - // Get new on chain state - returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, [...availableFundsAddresses, ethers.constants.AddressZero])); // Chain state should match the expected available funds expectedAvailableFunds.funds.push(new Funds(ethers.constants.AddressZero, "Native currency", depositAmount)); @@ -519,6 +517,8 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // WITHDRAW ONE TOKEN PARTIALLY + availableFundsAddresses = [...availableFundsAddresses, ethers.constants.AddressZero]; + // Read on chain state sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryBalanceBefore = await ethers.provider.getBalance(treasury.address); @@ -586,8 +586,9 @@ describe("IBosonFundsHandler", function () { const buyerBalanceAfter = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds after the withdrawal - // Since all tokens are withdrawn, token should be removed from the list + // Since all tokens are withdrawn, getAvailableFunds should return 0 for token expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", '0'), new Funds(ethers.constants.AddressZero, "Native currency", buyerPayoff), ]); expect(buyerAvailableFunds).to.eql( @@ -623,8 +624,12 @@ describe("IBosonFundsHandler", function () { const treasuryTokenBalanceAfter = await mockToken.balanceOf(treasury.address); // Chain state should match the expected available funds after the withdrawal - // Funds available should be an empty list - expectedSellerAvailableFunds = new FundsList([]); + // Funds available should be zero + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expect(sellersAvailableFunds).to.eql( expectedSellerAvailableFunds, "Seller available funds mismatch after withdrawal" @@ -3863,10 +3868,10 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -3895,10 +3900,10 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3944,19 +3949,23 @@ describe("IBosonFundsHandler", function () { }); it("should update state", async function () { + availableFundsAddresses.push(ethers.constants.AddressZero) // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = emptyFundsList; + expectedProtocolAvailableFunds = emptyFundsList; + expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3970,14 +3979,16 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit; // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); From a8fa3cbddcd5cd5896faeec725d56e2c02422da7 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Tue, 4 Jul 2023 19:15:15 -0300 Subject: [PATCH 12/22] Fix rest of tests on FundsHandler --- test/protocol/FundsHandlerTest.js | 377 ++++++++++++------------------ 1 file changed, 151 insertions(+), 226 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index d9eafdb7b..c67cf1e7a 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -124,7 +124,7 @@ describe("IBosonFundsHandler", function () { offerHandler, exchangeHandler, fundsHandler, - + configHandler, pauseHandler, disputeHandler, }, @@ -143,7 +143,6 @@ describe("IBosonFundsHandler", function () { // Deploy the mock token [mockToken] = await deployMockTokens(["Foreign20"]); - availableFundsAddresses = [mockToken.address]; // Get snapshot id snapshotId = await getSnapshot(); @@ -195,6 +194,8 @@ describe("IBosonFundsHandler", function () { // Set agent id as zero as it is optional for createOffer(). agentId = "0"; + + availableFundsAddresses = [mockToken.address, ethers.constants.AddressZero]; }); afterEach(async function () { @@ -224,6 +225,8 @@ describe("IBosonFundsHandler", function () { // Deposit token await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); + availableFundsAddresses = [mockToken.address] + // Read on chain state let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); @@ -245,6 +248,8 @@ describe("IBosonFundsHandler", function () { }); it("should be possible to top up the account", async function () { + availableFundsAddresses = [mockToken.address]; + // Deposit token await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); @@ -517,8 +522,6 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // WITHDRAW ONE TOKEN PARTIALLY - availableFundsAddresses = [...availableFundsAddresses, ethers.constants.AddressZero]; - // Read on chain state sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const treasuryBalanceBefore = await ethers.provider.getBalance(treasury.address); @@ -1043,6 +1046,7 @@ describe("IBosonFundsHandler", function () { // Native currency available funds are reduced for the withdrawal amount // Mock token is fully withdrawn expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds( ethers.constants.AddressZero, "Native currency", @@ -1096,7 +1100,7 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds after the withdrawal // Funds available should be an empty list - expectedProtocolAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); expect(protocolAvailableFunds).to.eql( expectedProtocolAvailableFunds, "Protocol available funds mismatch after withdrawal" @@ -1270,6 +1274,7 @@ describe("IBosonFundsHandler", function () { it("Returns info also for ERC20 tokens without the name", async function () { // Deploy the mock token with no name const [mockToken] = await deployMockTokens(["Foreign20NoName"]); + // top up assistants account await mockToken.mint(assistant.address, "1000000"); // approve protocol to transfer the tokens @@ -1279,7 +1284,7 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, [mockToken.address])); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([ @@ -1399,6 +1404,9 @@ describe("IBosonFundsHandler", function () { agentOfferProtocolFee = mo.offerFees.protocolFee; randoBuyerId = "4"; // 1: seller, 2: disputeResolver, 3: agent, 4: rando + + + availableFundsAddresses = [mockToken.address, ethers.constants.AddressZero]; }); afterEach(async function () { @@ -1482,98 +1490,6 @@ describe("IBosonFundsHandler", function () { ).to.eql(sellerDeposit, "Native currency seller available funds mismatch"); }); - context("seller's available funds drop to 0", async function () { - it("token should be removed from the tokenList", async function () { - // seller's available funds - let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - expect(sellersAvailableFunds.funds.length).to.eql(2, "Funds length mismatch"); - expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( - mockToken.address, - "Token contract address mismatch" - ); - expect(sellersAvailableFunds.funds[1].tokenAddress).to.eql( - ethers.constants.AddressZero, - "Native currency address mismatch" - ); - - // Commit to offer with token twice to empty the seller's pool - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - - // Token address should be removed and have only native currency in the list - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - expect(sellersAvailableFunds.funds.length).to.eql(1, "Funds length mismatch"); - expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( - ethers.constants.AddressZero, - "Native currency address mismatch" - ); - - // Commit to offer with token twice to empty the seller's pool - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); - - // Seller available funds must be empty - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - expect(sellersAvailableFunds.funds.length).to.eql(0, "Funds length mismatch"); - }); - - it("token should be removed from the token list even when list length - 1 is different from index", async function () { - // length - 1 is different from index when index isn't the first or last element in the list - // Deploy a new mock token - let TokenContractFactory = await ethers.getContractFactory("Foreign20"); - const otherToken = await TokenContractFactory.deploy(); - await otherToken.deployed(); - - // Add otherToken to DR fees - await accountHandler - .connect(adminDR) - .addFeesToDisputeResolver(disputeResolver.id, [ - new DisputeResolverFee(otherToken.address, "Other Token", "0"), - ]); - - // top up seller's and buyer's account - await otherToken.mint(assistant.address, sellerDeposit); - - // approve protocol to transfer the tokens - await otherToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); - - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, otherToken.address, sellerDeposit); - - // seller's available funds - let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - expect(sellersAvailableFunds.funds.length).to.eql(3, "Funds length mismatch"); - expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( - mockToken.address, - "Token contract address mismatch" - ); - expect(sellersAvailableFunds.funds[1].tokenAddress).to.eql( - ethers.constants.AddressZero, - "Native currency address mismatch" - ); - expect(sellersAvailableFunds.funds[2].tokenAddress).to.eql( - otherToken.address, - "Boson token address mismatch" - ); - - // Commit to offer with token twice to empty the seller's pool - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); - - // Native currency address should be removed and have only mock token and other token in the list - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - expect(sellersAvailableFunds.funds.length).to.eql(2, "Funds length mismatch"); - expect(sellersAvailableFunds.funds[0].tokenAddress).to.eql( - mockToken.address, - "Token contract address mismatch" - ); - expect(sellersAvailableFunds.funds[1].tokenAddress).to.eql( - otherToken.address, - "Other token address mismatch" - ); - }); - }); - it("when someone else deposits on buyer's behalf, callers funds are transferred", async function () { // buyer will commit to an offer on rando's behalf // get token balance before the commit @@ -1901,7 +1817,6 @@ describe("IBosonFundsHandler", function () { ); }); }); - }); context("👉 releaseFunds()", async function () { beforeEach(async function () { @@ -1961,11 +1876,13 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -1979,8 +1896,8 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocolFee - agentFee // protocol: protocolFee // agent: 0 - expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); - expectedProtocolAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee)); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -1998,7 +1915,7 @@ describe("IBosonFundsHandler", function () { buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expectedSellerAvailableFunds.funds[1] = new Funds( + expectedSellerAvailableFunds.funds[0] = new Funds( mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).mul(2).toString() @@ -2074,11 +1991,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2092,9 +2010,9 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocolFee - agentFee // protocol: protocolFee // agent: agentFee - expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); - expectedProtocolAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee)); - expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedAgentAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2139,9 +2057,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2155,7 +2073,7 @@ describe("IBosonFundsHandler", function () { // seller: 0 // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2183,6 +2101,7 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(buyerPayoff).mul(2).toString() ); expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); @@ -2244,9 +2163,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", `${2 * sellerDeposit}`), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2260,7 +2179,7 @@ describe("IBosonFundsHandler", function () { // seller: 0 // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2342,9 +2261,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2363,7 +2282,7 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2425,9 +2344,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2446,7 +2365,8 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0") + ]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2529,9 +2449,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2550,7 +2470,7 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); + expectedProtocolAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2621,11 +2541,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2639,11 +2560,9 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocol fee - agentFee; // protocol: protocolFee // agent: agentFee - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2709,9 +2628,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2730,7 +2649,7 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2814,11 +2733,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2833,12 +2753,12 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: agent fee expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), new Funds(mockToken.address, "Foreign20", sellerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - expectedAgentAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", agentPayoff); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -2925,9 +2845,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2946,7 +2866,7 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -3029,11 +2949,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3047,10 +2968,9 @@ describe("IBosonFundsHandler", function () { // seller: (price + sellerDeposit)*(1-buyerPercentage); // protocol: 0 // agent: 0 - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -3120,9 +3040,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3141,11 +3062,13 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3205,11 +3128,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3223,15 +3147,15 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocol fee - agentFee + buyerEscalationDeposit; // protocol: protocolFee // agent: agentFee - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - expectedProtocolAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", protocolPayoff); - expectedAgentAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", agentPayoff)); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) + expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3319,9 +3243,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3335,7 +3259,7 @@ describe("IBosonFundsHandler", function () { // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); expectedSellerAvailableFunds.funds[0] = new Funds( mockToken.address, "Foreign20", @@ -3433,11 +3357,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3451,10 +3376,12 @@ describe("IBosonFundsHandler", function () { // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); // protocol: 0 // agent: 0 - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`)] ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0")]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -3521,9 +3448,9 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3537,7 +3464,7 @@ describe("IBosonFundsHandler", function () { // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); expectedSellerAvailableFunds.funds[0] = new Funds( mockToken.address, "Foreign20", @@ -3617,11 +3544,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3635,14 +3563,15 @@ describe("IBosonFundsHandler", function () { // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); // protocol: 0 // agent: 0 - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff)]); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3701,9 +3630,8 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedAgentAvailableFunds = expectedProtocolAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3717,16 +3645,16 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); expectedSellerAvailableFunds.funds[0] = new Funds( mockToken.address, "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3780,18 +3708,18 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3805,14 +3733,12 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit; // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - expectedSellerAvailableFunds.funds.push( - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + expectedBuyerAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3878,9 +3804,10 @@ describe("IBosonFundsHandler", function () { new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + expectedBuyerAvailableFunds = emptyFundsList; + expectedProtocolAvailableFunds = emptyFundsList; + expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3894,12 +3821,10 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); + expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedSellerAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString()), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`),]); sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); @@ -3949,7 +3874,6 @@ describe("IBosonFundsHandler", function () { }); it("should update state", async function () { - availableFundsAddresses.push(ethers.constants.AddressZero) // Read on chain state sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); @@ -4175,3 +4099,4 @@ describe("IBosonFundsHandler", function () { }); }); }); +}); From 137d59b49c21fc261634e57ad6681d9da7408a75 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 08:40:42 -0300 Subject: [PATCH 13/22] Fixing interface id --- .../handlers/IBosonFundsHandler.sol | 2 +- test/protocol/FundsHandlerTest.js | 3666 ++++++++++------- 2 files changed, 2153 insertions(+), 1515 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonFundsHandler.sol b/contracts/interfaces/handlers/IBosonFundsHandler.sol index 21881d173..c4591c901 100644 --- a/contracts/interfaces/handlers/IBosonFundsHandler.sol +++ b/contracts/interfaces/handlers/IBosonFundsHandler.sol @@ -10,7 +10,7 @@ import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; * * @notice Handles custody and withdrawal of buyer and seller funds within the protocol. * - * The ERC-165 identifier for this interface is: 0x18834247 + * The ERC-165 identifier for this interface is: 0xb5850c2a */ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { /** diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index c67cf1e7a..2e2efaeac 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -143,7 +143,6 @@ describe("IBosonFundsHandler", function () { // Deploy the mock token [mockToken] = await deployMockTokens(["Foreign20"]); - // Get snapshot id snapshotId = await getSnapshot(); }); @@ -225,10 +224,12 @@ describe("IBosonFundsHandler", function () { // Deposit token await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); - availableFundsAddresses = [mockToken.address] + availableFundsAddresses = [mockToken.address]; // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + let returnedAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); @@ -240,7 +241,9 @@ describe("IBosonFundsHandler", function () { .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount, { value: depositAmount }); // Get new on chain state - returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, [...availableFundsAddresses, ethers.constants.AddressZero])); + returnedAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [...availableFundsAddresses, ethers.constants.AddressZero]) + ); // Chain state should match the expected available funds expectedAvailableFunds.funds.push(new Funds(ethers.constants.AddressZero, "Native currency", depositAmount)); @@ -254,7 +257,9 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + let returnedAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); @@ -264,7 +269,9 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, 2 * depositAmount); // Get new on chain state - returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + returnedAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", `${3 * depositAmount}`)]); @@ -523,7 +530,9 @@ describe("IBosonFundsHandler", function () { // WITHDRAW ONE TOKEN PARTIALLY // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); const treasuryBalanceBefore = await ethers.provider.getBalance(treasury.address); // Chain state should match the expected available funds before the withdrawal @@ -545,7 +554,9 @@ describe("IBosonFundsHandler", function () { .withdrawFunds(seller.id, [ethers.constants.AddressZero], [withdrawAmount]); // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); const treasuryBalanceAfter = await ethers.provider.getBalance(treasury.address); // Chain state should match the expected available funds after the withdrawal @@ -568,7 +579,9 @@ describe("IBosonFundsHandler", function () { // WITHDRAW ONE TOKEN FULLY // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); const buyerBalanceBefore = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds before the withdrawal @@ -585,13 +598,15 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(buyer).withdrawFunds(buyerId, [mockToken.address], [buyerPayoff]); // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); const buyerBalanceAfter = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds after the withdrawal // Since all tokens are withdrawn, getAvailableFunds should return 0 for token expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", '0'), + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", buyerPayoff), ]); expect(buyerAvailableFunds).to.eql( @@ -604,7 +619,9 @@ describe("IBosonFundsHandler", function () { it("should allow to withdraw all funds at once", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); const treasuryNativeBalanceBefore = await ethers.provider.getBalance(treasury.address); const treasuryTokenBalanceBefore = await mockToken.balanceOf(treasury.address); @@ -622,15 +639,17 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).withdrawFunds(seller.id, [], []); // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); const treasuryNativeBalanceAfter = await ethers.provider.getBalance(treasury.address); const treasuryTokenBalanceAfter = await mockToken.balanceOf(treasury.address); // Chain state should match the expected available funds after the withdrawal // Funds available should be zero expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), ]); expect(sellersAvailableFunds).to.eql( @@ -1007,7 +1026,9 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); @@ -1038,7 +1059,9 @@ describe("IBosonFundsHandler", function () { txCost = tx.gasPrice.mul(txReceipt.gasUsed); // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); @@ -1072,7 +1095,9 @@ describe("IBosonFundsHandler", function () { it("should allow to withdraw all funds at once", async function () { // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); @@ -1094,13 +1119,18 @@ describe("IBosonFundsHandler", function () { txCost = tx.gasPrice.mul(txReceipt.gasUsed); // Read on chain state - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); // Chain state should match the expected available funds after the withdrawal // Funds available should be an empty list - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expect(protocolAvailableFunds).to.eql( expectedProtocolAvailableFunds, "Protocol available funds mismatch after withdrawal" @@ -1284,7 +1314,9 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state - let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, [mockToken.address])); + let returnedAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [mockToken.address]) + ); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([ @@ -1405,7 +1437,6 @@ describe("IBosonFundsHandler", function () { randoBuyerId = "4"; // 1: seller, 2: disputeResolver, 3: agent, 4: rando - availableFundsAddresses = [mockToken.address, ethers.constants.AddressZero]; }); @@ -1445,7 +1476,9 @@ describe("IBosonFundsHandler", function () { // contract native token balance const contractNativeBalanceBefore = await ethers.provider.getBalance(protocolDiamondAddress); // seller's available funds - const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + const sellersAvailableFundsBefore = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // Commit to an offer with erc20 token await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); @@ -1460,7 +1493,9 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + let sellersAvailableFundsAfter = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) @@ -1481,7 +1516,9 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFundsAfter = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // native currency is the second on the list of the available funds and the amount should be decreased for the sellerDeposit expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) @@ -1561,7 +1598,9 @@ describe("IBosonFundsHandler", function () { // get token balance before the commit const buyerTokenBalanceBefore = await mockToken.balanceOf(buyer.address); - const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + const sellersAvailableFundsBefore = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // reserve a range and premint vouchers await offerHandler @@ -1583,7 +1622,9 @@ describe("IBosonFundsHandler", function () { .withArgs(seller.id, mockToken.address, encumberedFunds, bosonVoucher.address); // Check that seller's pool balance was reduced - let sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + let sellersAvailableFundsAfter = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) @@ -1631,7 +1672,9 @@ describe("IBosonFundsHandler", function () { ); // Check that seller's pool balance was reduced - sellersAvailableFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + sellersAvailableFundsAfter = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); // native currency the second on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) @@ -1818,183 +1861,80 @@ describe("IBosonFundsHandler", function () { }); }); - context("👉 releaseFunds()", async function () { - beforeEach(async function () { - // ids - protocolId = "0"; - buyerId = "4"; - exchangeId = "1"; - - // commit to offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - }); - - context("Final state COMPLETED", async function () { + context("👉 releaseFunds()", async function () { beforeEach(async function () { - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); - - // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; - }); - - it("should emit a FundsReleased event", async function () { - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); - }); + // ids + protocolId = "0"; + buyerId = "4"; + exchangeId = "1"; - it("should update state", async function () { - // commit again, so seller has nothing in available funds + // commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Complete the exchange so the funds are released - await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocolFee - agentFee - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // complete another exchange so we test funds are only updated, no new entry is created - await exchangeHandler.connect(buyer).redeemVoucher(++exchangeId); - await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerPayoff).mul(2).toString() - ); - expectedProtocolAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(protocolPayoff).mul(2).toString() - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - context("Offer has an agent", async function () { + context("Final state COMPLETED", async function () { beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); // succesfully redeem exchange - exchangeId = "2"; await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); // expected payoffs // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) .toString(); // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; + protocolPayoff = offerTokenProtocolFee; }); it("should emit a FundsReleased event", async function () { // Complete the exchange, expecting event const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - // Complete the exchange, expecting event await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); }); it("should update state", async function () { + // commit again, so seller has nothing in available funds + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); @@ -2009,161 +1949,227 @@ describe("IBosonFundsHandler", function () { // buyer: 0 // seller: sellerDeposit + price - protocolFee - agentFee // protocol: protocolFee - // agent: agentFee + // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedAgentAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // complete another exchange so we test funds are only updated, no new entry is created + await exchangeHandler.connect(buyer).redeemVoucher(++exchangeId); + await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).mul(2).toString() + ); + expectedProtocolAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(protocolPayoff).mul(2).toString() + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state REVOKED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: sellerDeposit + price - buyerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.price).toString(); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // seller: 0 - sellerPayoff = 0; + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - // protocol: 0 - protocolPayoff = 0; - }); + // succesfully redeem exchange + exchangeId = "2"; + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - it("should emit a FundsReleased event", async function () { - // Revoke the voucher, expecting event - await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); - }); + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // agentPayoff: agentFee + agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); + agentPayoff = agentFee; - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Revoke the voucher so the funds are released - await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); - - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0 - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Test that if buyer has some funds available, and gets more, the funds are only updated - // Commit again - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.price) + .sub(agentOfferProtocolFee) + .sub(agentFee) + .toString(); - // Revoke another voucher - await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); - - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0; but during the commitToOffer, sellerDeposit is encumbered - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(buyerPayoff).mul(2).toString() - ); - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + it("should emit a FundsReleased event", async function () { + // Complete the exchange, expecting event + const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); + // Complete the exchange, expecting event + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); - await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + }); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Complete the exchange so the funds are released + await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocolFee - agentFee + // protocol: protocolFee + // agent: agentFee + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", agentPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + context("Final state REVOKED", async function () { + beforeEach(async function () { // expected payoffs // buyer: sellerDeposit + price - buyerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit).add(agentOffer.price).toString(); + buyerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.price).toString(); // seller: 0 sellerPayoff = 0; // protocol: 0 protocolPayoff = 0; + }); - // agent: 0 - agentPayoff = 0; - - exchangeId = "2"; + it("should emit a FundsReleased event", async function () { + // Revoke the voucher, expecting event + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", `${2 * sellerDeposit}`), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); @@ -2179,11 +2185,22 @@ describe("IBosonFundsHandler", function () { // seller: 0 // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2191,7 +2208,7 @@ describe("IBosonFundsHandler", function () { // Test that if buyer has some funds available, and gets more, the funds are only updated // Commit again - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); // Revoke another voucher await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); @@ -2207,144 +2224,221 @@ describe("IBosonFundsHandler", function () { ethers.BigNumber.from(buyerPayoff).mul(2).toString() ); expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", `${sellerDeposit}`), + new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state CANCELED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(offerToken.price).sub(offerToken.buyerCancelPenalty).toString(); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.buyerCancelPenalty).toString(); + // top up seller's and buyer's account + await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); + await mockToken.mint(buyer.address, `${2 * price}`); - // protocol: 0 - protocolPayoff = 0; - }); + // approve protocol to transfer the tokens + await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); + await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); - it("should emit a FundsReleased event", async function () { - // Cancel the voucher, expecting event - const tx = await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, buyer.address); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); - }); + // expected payoffs + // buyer: sellerDeposit + price + buyerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit).add(agentOffer.price).toString(); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // seller: 0 + sellerPayoff = 0; - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + // protocol: 0 + protocolPayoff = 0; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // agent: 0 + agentPayoff = 0; - // Cancel the voucher, so the funds are released - await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); + exchangeId = "2"; + }); - // Available funds should be increased for - // buyer: price - buyerCancelPenalty - // seller: sellerDeposit + buyerCancelPenalty; note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", `${2 * sellerDeposit}`), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); - await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); + // Revoke the voucher so the funds are released + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); + // Available funds should be increased for + // buyer: sellerDeposit + price + // seller: 0 + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Test that if buyer has some funds available, and gets more, the funds are only updated + // Commit again + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + + // Revoke another voucher + await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); + + // Available funds should be increased for + // buyer: sellerDeposit + price + // seller: 0; but during the commitToOffer, sellerDeposit is encumbered + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(buyerPayoff).mul(2).toString() + ); + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", `${sellerDeposit}`), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + context("Final state CANCELED", async function () { + beforeEach(async function () { // expected payoffs // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(agentOffer.price).sub(agentOffer.buyerCancelPenalty).toString(); + buyerPayoff = ethers.BigNumber.from(offerToken.price).sub(offerToken.buyerCancelPenalty).toString(); // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.buyerCancelPenalty) + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.buyerCancelPenalty) .toString(); // protocol: 0 protocolPayoff = 0; + }); - // agent: 0 - agentPayoff = 0; + it("should emit a FundsReleased event", async function () { + // Cancel the voucher, expecting event + const tx = await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, buyer.address); - exchangeId = "2"; + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); @@ -2365,154 +2459,170 @@ describe("IBosonFundsHandler", function () { "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0") - ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state DISPUTED", async function () { - beforeEach(async function () { - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // top up seller's and buyer's account + await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); + await mockToken.mint(buyer.address, `${2 * price}`); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // approve protocol to transfer the tokens + await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); + await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); - }); + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); - context("Final state DISPUTED - RETRACTED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // expected payoffs + // buyer: price - buyerCancelPenalty + buyerPayoff = ethers.BigNumber.from(agentOffer.price).sub(agentOffer.buyerCancelPenalty).toString(); - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; - }); + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.buyerCancelPenalty) + .toString(); - it("should emit a FundsReleased event", async function () { - // Retract from the dispute, expecting event - const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); + // protocol: 0 + protocolPayoff = 0; - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + // agent: 0 + agentPayoff = 0; - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + exchangeId = "2"; + }); - //check that FundsReleased event was NOT emitted with buyer Id - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - buyerId, - offerToken.exchangeToken, - buyerPayoff, - buyer.address, - ]); - expect(match).to.be.false; - }); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", sellerDeposit), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Cancel the voucher, so the funds are released + await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); + // Available funds should be increased for + // buyer: price - buyerCancelPenalty + // seller: sellerDeposit + buyerCancelPenalty; note that seller has sellerDeposit in availableFunds from before + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - expectedProtocolAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + context("Final state DISPUTED", async function () { + beforeEach(async function () { + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); }); - context("Offer has an agent", async function () { + context("Final state DISPUTED - RETRACTED", async function () { beforeEach(async function () { // expected payoffs // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) .toString(); // protocol: 0 - protocolPayoff = agentOfferProtocolFee; - - // Exchange id - exchangeId = "2"; - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + protocolPayoff = offerTokenProtocolFee; }); it("should emit a FundsReleased event", async function () { @@ -2527,25 +2637,46 @@ describe("IBosonFundsHandler", function () { .to.emit(disputeHandler, "FundsReleased") .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + //check that FundsReleased event was NOT emitted with buyer Id + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + buyerId, + offerToken.exchangeToken, + buyerPayoff, + buyer.address, + ]); + expect(match).to.be.false; }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -2557,151 +2688,176 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agentFee; + // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before // protocol: protocolFee - // agent: agentFee - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state DISPUTED - RETRACTED via expireDispute", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + context("Offer has an agent", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // agentPayoff: agentFee + agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); + agentPayoff = agentFee; - // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.price) + .sub(agentOfferProtocolFee) + .sub(agentFee) + .toString(); - await setNextBlockTimestamp(Number(timeout)); - }); + // protocol: 0 + protocolPayoff = agentOfferProtocolFee; - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, rando.address); + // Exchange id + exchangeId = "2"; + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - //check that FundsReleased event was NOT emitted with buyer Id - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - buyerId, - offerToken.exchangeToken, - buyerPayoff, - rando.address, - ]); - expect(match).to.be.false; - }); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); + }); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + it("should emit a FundsReleased event", async function () { + // Retract from the dispute, expecting event + const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + await expect(tx) + .to.emit(disputeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - // Expire the dispute, so the funds are released - await disputeHandler.connect(rando).expireDispute(exchangeId); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + }); - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Retract from the dispute, so the funds are released + await disputeHandler.connect(buyer).retractDispute(exchangeId); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee - agentFee; + // protocol: protocolFee + // agent: agentFee + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", agentPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + + context("Final state DISPUTED - RETRACTED via expireDispute", async function () { + beforeEach(async function () { // expected payoffs // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agent fee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) .toString(); // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; - - // Exchange id - exchangeId = "2"; - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); + protocolPayoff = offerTokenProtocolFee; await setNextBlockTimestamp(Number(timeout)); }); @@ -2709,35 +2865,54 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Expire the dispute, expecting event const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); - - // Complete the exchange, expecting event await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, rando.address); + .to.emit(disputeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, rando.address); await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, rando.address); + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, rando.address); + //check that FundsReleased event was NOT emitted with buyer Id + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + buyerId, + offerToken.exchangeToken, + buyerPayoff, + rando.address, + ]); + expect(match).to.be.false; }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -2749,166 +2924,190 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agent fee; + // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before // protocol: protocolFee - // agent: agent fee - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), ]); - - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state DISPUTED - RESOLVED", async function () { - beforeEach(async function () { - buyerPercentBasisPoints = "5566"; // 55.66% + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // expected payoffs - // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // protocol: 0 - protocolPayoff = 0; + // agentPayoff: agentFee + agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); + agentPayoff = agentFee; - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); - }); + // seller: sellerDeposit + price - protocolFee - agent fee + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.price) + .sub(agentOfferProtocolFee) + .sub(agentFee) + .toString(); - it("should emit a FundsReleased event", async function () { - // Resolve the dispute, expecting event - const tx = await disputeHandler - .connect(assistant) - .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + // Exchange id + exchangeId = "2"; - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + await setNextBlockTimestamp(Number(timeout)); + }); - // Resolve the dispute, so the funds are released - await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); - // Available funds should be increased for - // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // Complete the exchange, expecting event + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, rando.address); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, rando.address); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, rando.address); + }); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - exchangeId = "2"; + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Expire the dispute, so the funds are released + await disputeHandler.connect(rando).expireDispute(exchangeId); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee - agent fee; + // protocol: protocolFee + // agent: agent fee + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", sellerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", agentPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + + context("Final state DISPUTED - RESOLVED", async function () { + beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + buyerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .mul(buyerPercentBasisPoints) .div("10000") .toString(); // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + sellerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .sub(buyerPayoff) .toString(); @@ -2940,20 +3139,50 @@ describe("IBosonFundsHandler", function () { )); }); + it("should emit a FundsReleased event", async function () { + // Resolve the dispute, expecting event + const tx = await disputeHandler + .connect(assistant) + .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); + it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); @@ -2965,175 +3194,246 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage); + // seller: (price + sellerDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .add(buyerEscalationDeposit) - .toString(); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; + exchangeId = "2"; - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - it("should emit a FundsReleased event", async function () { - // Retract from the dispute, expecting event - const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + buyerPercentBasisPoints = "5566"; // 55.66% - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + // expected payoffs + // buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .mul(buyerPercentBasisPoints) + .div("10000") + .toString(); + + // seller: (price + sellerDeposit)*(1-buyerPercentage) + sellerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .sub(buyerPayoff) + .toString(); - //check that FundsReleased event was NOT emitted with buyer Id - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - buyerId, - offerToken.exchangeToken, - buyerPayoff, - buyer.address, - ]); - expect(match).to.be.false; - }); + // protocol: 0 + protocolPayoff = 0; - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + disputeHandler.address + )); + }); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); + // Resolve the dispute, so the funds are released + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee + buyerEscalationDeposit; note that seller has sellerDeposit in availableFunds from before - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + // Available funds should be increased for + // buyer: (price + sellerDeposit)*buyerPercentage + // seller: (price + sellerDeposit)*(1-buyerPercentage); + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).toString() + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); }); - context("Offer has an agent", async function () { + context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { beforeEach(async function () { // expected payoffs // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) + // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) .add(buyerEscalationDeposit) .toString(); // protocol: 0 - protocolPayoff = agentOfferProtocolFee; + protocolPayoff = offerTokenProtocolFee; - // Exchange id - exchangeId = "2"; - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + it("should emit a FundsReleased event", async function () { + // Retract from the dispute, expecting event + const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + await expect(tx) + .to.emit(disputeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + //check that FundsReleased event was NOT emitted with buyer Id + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + buyerId, + offerToken.exchangeToken, + buyerPayoff, + buyer.address, + ]); + expect(match).to.be.false; }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3144,173 +3444,175 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agentFee + buyerEscalationDeposit; + // seller: sellerDeposit + price - protocol fee + buyerEscalationDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: protocolFee - // agent: agentFee - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - expectedProtocolAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", protocolPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedAgentAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", agentPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - - context("Final state DISPUTED - ESCALATED - RESOLVED", async function () { - beforeEach(async function () { - buyerPercentBasisPoints = "5566"; // 55.66% - - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); - - // protocol: 0 - protocolPayoff = 0; - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); - - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + context("Offer has an agent", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - it("should emit a FundsReleased event", async function () { - // Resolve the dispute, expecting event - const tx = await disputeHandler - .connect(assistant) - .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + // agentPayoff: agentFee + agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); + agentPayoff = agentFee; - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.price) + .sub(agentOfferProtocolFee) + .sub(agentFee) + .add(buyerEscalationDeposit) + .toString(); - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); + // protocol: 0 + protocolPayoff = agentOfferProtocolFee; - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // Exchange id + exchangeId = "2"; + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(buyer.address, agentOffer.price); + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // Resolve the dispute, so the funds are released - await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - exchangeId = "2"; + // Retract from the dispute, so the funds are released + await disputeHandler.connect(buyer).retractDispute(exchangeId); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee - agentFee + buyerEscalationDeposit; + // protocol: protocolFee + // agent: agentFee + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", protocolPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", agentPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + context("Final state DISPUTED - ESCALATED - RESOLVED", async function () { + beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + buyerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .add(buyerEscalationDeposit) .mul(buyerPercentBasisPoints) .div("10000") .toString(); // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + sellerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .add(buyerEscalationDeposit) .sub(buyerPayoff) .toString(); @@ -3342,27 +3644,55 @@ describe("IBosonFundsHandler", function () { disputeHandler.address )); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + // Escalate the dispute await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); + it("should emit a FundsReleased event", async function () { + // Resolve the dispute, expecting event + const tx = await disputeHandler + .connect(assistant) + .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); + it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3373,155 +3703,196 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`)] + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - }); - - context("Final state DISPUTED - ESCALATED - DECIDED", async function () { - beforeEach(async function () { - buyerPercentBasisPoints = "5566"; // 55.66% - - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); - // protocol: 0 - protocolPayoff = 0; - - // escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - it("should emit a FundsReleased event", async function () { - // Decide the dispute, expecting event - const tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(buyer.address, agentOffer.price); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); + exchangeId = "2"; - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + buyerPercentBasisPoints = "5566"; // 55.66% - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .add(buyerEscalationDeposit) + .mul(buyerPercentBasisPoints) + .div("10000") + .toString(); + + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .add(buyerEscalationDeposit) + .sub(buyerPayoff) + .toString(); - // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() - ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // protocol: 0 + protocolPayoff = 0; - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + disputeHandler.address + )); + + // escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; - exchangeId = "2"; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // Resolve the dispute, so the funds are released + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // Available funds should be increased for + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + context("Final state DISPUTED - ESCALATED - DECIDED", async function () { + beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + buyerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .add(buyerEscalationDeposit) .mul(buyerPercentBasisPoints) .div("10000") .toString(); // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) + sellerPayoff = ethers.BigNumber.from(offerToken.price) + .add(offerToken.sellerDeposit) .add(buyerEscalationDeposit) .sub(buyerPayoff) .toString(); @@ -3530,131 +3901,86 @@ describe("IBosonFundsHandler", function () { protocolPayoff = 0; // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); - - // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); - // protocol: 0 - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()) - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - }); - - context( - "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", - async function () { - beforeEach(async function () { - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); - - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; - - // protocol: 0 - protocolPayoff = 0; - - // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); - - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); - }); - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + // Decide the dispute, expecting event + const tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, rando.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); + await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedAgentAvailableFunds = expectedProtocolAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + // Decide the dispute, so the funds are released + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedSellerAvailableFunds.funds[0] = new Funds( mockToken.address, "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3683,6 +4009,116 @@ describe("IBosonFundsHandler", function () { // raise the dispute tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); + + buyerPercentBasisPoints = "5566"; // 55.66% + + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .add(buyerEscalationDeposit) + .mul(buyerPercentBasisPoints) + .div("10000") + .toString(); + + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ethers.BigNumber.from(agentOffer.price) + .add(agentOffer.sellerDeposit) + .add(buyerEscalationDeposit) + .sub(buyerPayoff) + .toString(); + + // protocol: 0 + protocolPayoff = 0; + + // escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Decide the dispute, so the funds are released + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + + // Available funds should be increased for + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).toString() + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + + context( + "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", + async function () { + beforeEach(async function () { // expected payoffs // buyer: price + buyerEscalationDeposit buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); @@ -3694,8 +4130,6 @@ describe("IBosonFundsHandler", function () { protocolPayoff = 0; // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); // Get the block timestamp of the confirmed tx and set escalatedDate @@ -3706,20 +4140,47 @@ describe("IBosonFundsHandler", function () { await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); }); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, rando.address); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); + + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); + it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedAgentAvailableFunds = + expectedProtocolAvailableFunds = + emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3730,133 +4191,156 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; + // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - } - ); - - context( - "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", - async function () { - beforeEach(async function () { - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); - - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; - - // protocol: 0 - protocolPayoff = 0; - - // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); - - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); - - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - - //check that FundsReleased event was NOT emitted with rando address - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - seller.id, - offerToken.exchangeToken, - sellerPayoff, - rando.address, - ]); - expect(match).to.be.false; - }); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) - expectedBuyerAvailableFunds = emptyFundsList; - expectedProtocolAvailableFunds = emptyFundsList; - expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0")]); - expectedSellerAvailableFunds= new FundsList([new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString()), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`),]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(buyer.address, agentOffer.price); + + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; + + // protocol: 0 + protocolPayoff = 0; + + // Escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); + + await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Expire the escalated dispute, so the funds are released + await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + + // Available funds should be increased for + // buyer: price + buyerEscalationDeposit + // seller: sellerDeposit; + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedSellerAvailableFunds.funds[0] = new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerPayoff).toString() + ); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + } + ); - context("Offer has an agent", async function () { + context( + "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", + async function () { beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - - exchangeId = "2"; - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // expected payoffs // buyer: price + buyerEscalationDeposit buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); @@ -3868,25 +4352,59 @@ describe("IBosonFundsHandler", function () { protocolPayoff = 0; // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); + + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); + + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + + //check that FundsReleased event was NOT emitted with rando address + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + seller.id, + offerToken.exchangeToken, + sellerPayoff, + rando.address, + ]); + expect(match).to.be.false; }); it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), + new Funds(mockToken.address, "Foreign20", sellerDeposit), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - - const emptyFundsList = new FundsList([new Funds(mockToken.address, "Foreign20", "0"), new Funds(ethers.constants.AddressZero, "Native currency", "0")]) + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedBuyerAvailableFunds = emptyFundsList; expectedProtocolAvailableFunds = emptyFundsList; expectedAgentAvailableFunds = emptyFundsList; @@ -3900,101 +4418,151 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; + // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 - expectedBuyerAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", buyerPayoff), new Funds(ethers.constants.AddressZero, "Native currency", "0")]); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), + new Funds( + mockToken.address, + "Foreign20", + ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + ), new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), ]); - - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - }); - } - ); - }); - - context("Changing the protocol fee", async function () { - beforeEach(async function () { - // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamondAddress); - - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); - }); - - it("Protocol fee for existing exchanges should be the same as at the offer creation", async function () { - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); - }); - - it("Protocol fee for new exchanges should be the same as at the offer creation", async function () { - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - - // similar as teste before, excpet the commit to offer is done after the procol fee change - - // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - txReceipt = await tx.wait(); - event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); - exchangeId = event.exchangeId.toString(); - - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // Complete the exchange, expecting event - tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); - - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(buyer.address, agentOffer.price); + + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); + + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; + + // protocol: 0 + protocolPayoff = 0; + + // Escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + + const emptyFundsList = new FundsList([ + new Funds(mockToken.address, "Foreign20", "0"), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = emptyFundsList; + expectedProtocolAvailableFunds = emptyFundsList; + expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Expire the escalated dispute, so the funds are released + await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); + + // Available funds should be increased for + // buyer: price + buyerEscalationDeposit + // seller: sellerDeposit; + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + new Funds(ethers.constants.AddressZero, "Native currency", "0"), + ]); + expectedSellerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + } + ); }); - context("Offer has an agent", async function () { + context("Changing the protocol fee", async function () { beforeEach(async function () { - exchangeId = "2"; - // Cast Diamond to IBosonConfigHandler configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamondAddress); @@ -4002,34 +4570,18 @@ describe("IBosonFundsHandler", function () { // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) .toString(); + }); - // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; - - // Create Agent Offer before setting new protocol fee as 3% - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // Commit to Agent Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - + it("Protocol fee for existing exchanges should be the same as at the offer creation", async function () { // set the new procol fee protocolFeePercentage = "300"; // 3% await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - }); - it("Protocol fee for existing exchanges should be the same as at the agent offer creation", async function () { // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -4038,36 +4590,24 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); }); - it("Protocol fee for new exchanges should be the same as at the agent offer creation", async function () { - // similar as tests before, excpet the commit to offer is done after the protocol fee change - - // top up seller's and buyer's account - await mockToken.mint(assistant.address, sellerDeposit); - await mockToken.mint(buyer.address, price); - - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, price); + it("Protocol fee for new exchanges should be the same as at the offer creation", async function () { + // set the new procol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + // similar as teste before, excpet the commit to offer is done after the procol fee change // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId.toString(); @@ -4080,23 +4620,121 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - // Complete the exchange, expecting event await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); + }); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + context("Offer has an agent", async function () { + beforeEach(async function () { + exchangeId = "2"; + + // Cast Diamond to IBosonConfigHandler + configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamondAddress); + + // expected payoffs + // buyer: 0 + buyerPayoff = 0; + + // agentPayoff: agentFee + agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); + agentPayoff = agentFee; + + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.price) + .sub(agentOfferProtocolFee) + .sub(agentFee) + .toString(); + + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; + + // Create Agent Offer before setting new protocol fee as 3% + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // Commit to Agent Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + + // set the new procol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); + }); + + it("Protocol fee for existing exchanges should be the same as at the agent offer creation", async function () { + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // Complete the exchange, expecting event + const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + }); + + it("Protocol fee for new exchanges should be the same as at the agent offer creation", async function () { + // similar as tests before, excpet the commit to offer is done after the protocol fee change + + // top up seller's and buyer's account + await mockToken.mint(assistant.address, sellerDeposit); + await mockToken.mint(buyer.address, price); + + // approve protocol to transfer the tokens + await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, price); + + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + + // commit to offer and get the correct exchangeId + tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + txReceipt = await tx.wait(); + event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); + exchangeId = event.exchangeId.toString(); + + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // Complete the exchange, expecting event + tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + // Complete the exchange, expecting event + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + }); }); }); }); }); }); }); -}); From 6029aab697c60108f9730984ca26bc550e2c50c9 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 19:32:46 -0300 Subject: [PATCH 14/22] Resolve conflicts on FundsHandler --- scripts/util/estimate-limits.js | 1086 ++++++++++++ test/protocol/FundsHandlerTest.js | 2660 ++++++++++++++--------------- 2 files changed, 2358 insertions(+), 1388 deletions(-) create mode 100644 scripts/util/estimate-limits.js diff --git a/scripts/util/estimate-limits.js b/scripts/util/estimate-limits.js new file mode 100644 index 000000000..5ec712ad7 --- /dev/null +++ b/scripts/util/estimate-limits.js @@ -0,0 +1,1086 @@ +const hre = require("hardhat"); +const { Wallet, provider, ZeroAddress, parseEther, MaxUint256, getContractAt, getSigners, parseUnits } = hre.ethers; +const simpleStatistic = require("simple-statistics"); +const fs = require("fs"); + +const { limitsToEstimate } = require("../config/limit-estimation"); +const gasLimit = limitsToEstimate.blockGasLimit; +hre.network.config.blockGasLimit = gasLimit; + +const Role = require("../domain/Role"); +const Bundle = require("../domain/Bundle"); +const Group = require("../domain/Group"); +const EvaluationMethod = require("../domain/EvaluationMethod"); +const { DisputeResolverFee } = require("../domain/DisputeResolverFee"); +const { deployProtocolDiamond } = require("../util/deploy-protocol-diamond.js"); +const { deployAndCutFacets } = require("../util/deploy-protocol-handler-facets.js"); +const { deployProtocolClients } = require("../util/deploy-protocol-clients"); +const { deployMockTokens } = require("../util/deploy-mock-tokens"); +const { oneWeek, oneMonth } = require("../../test/util/constants"); +const { + mockSeller, + mockDisputeResolver, + mockVoucherInitValues, + mockAuthToken, + mockCondition, + mockOffer, + mockTwin, + accountId, +} = require("../../test/util/mock"); +const { setNextBlockTimestamp, getFacetsWithArgs, calculateContractAddress } = require("../../test/util/utils.js"); + +// Common vars +let deployer, + sellerWallet1, + sellerWallet2, + sellerWallet3, + dr1, + dr2, + dr3, + buyer, + rando, + other1, + other2, + other3, + protocolAdmin, + feeCollector; +let protocolDiamond, + accessController, + accountHandler, + bundleHandler, + disputeHandler, + exchangeHandler, + fundsHandler, + groupHandler, + offerHandler, + twinHandler; +let bosonVoucher; +let protocolFeePercentage, protocolFeeFlatBoson, buyerEscalationDepositPercentage; +let handlers = {}; +let result = {}; + +let setupEnvironment = {}; + +/* +For each limit from limitsToEstimate, a full setup is needed before a function that depends on a limit can be estimated. +The function that prepares an environment must return the object with invocation details for all methods that depend on a limit. +{ method_1: invocationDetails_1, method_2: invocationDetails_2, ..., method_n: invocationDetails_2} + +Invocation details contain +- account: account that calls the method (important if access is restiricted) +- args: array of arguments that needs to be passed into method +- arrayIndex: index that tells which parameter's length should be varied during the estimation +- structField: if array is part of a struct, specify the field name +*/ + +/* +Setup the environment for "maxAllowedSellers". The following functions depend on it: +- createDisputeResolver +- addSellersToAllowList +- removeSellersFromAllowList +*/ +setupEnvironment["maxAllowedSellers"] = async function (sellerCount = 10) { + // AuthToken + const emptyAuthToken = mockAuthToken(); + const voucherInitValues = mockVoucherInitValues(); + + for (let i = 0; i < sellerCount; i++) { + const wallet = Wallet.createRandom(); + + //Random wallet has no provider. Connect wallet to ethers provider. The connected wallet will have no ETH + const connectedWallet = wallet.connect(provider); + + //Fund the new wallet + let tx = { + to: await connectedWallet.getAddress(), + // Convert currency unit from ether to wei + value: parseEther("1"), + }; + + await other1.sendTransaction(tx); + const seller = mockSeller( + await wallet.getAddress(), + await wallet.getAddress(), + await wallet.getAddress(), + await wallet.getAddress() + ); + await accountHandler.connect(connectedWallet).createSeller(seller, emptyAuthToken, voucherInitValues); + } + + //Create DisputeResolverFee array + const disputeResolverFees = [ + new DisputeResolverFee(await other1.getAddress(), "MockToken1", "0"), + new DisputeResolverFee(await other2.getAddress(), "MockToken2", "0"), + new DisputeResolverFee(await other3.getAddress(), "MockToken3", "0"), + ]; + + const sellerAllowList = [...Array(sellerCount + 1).keys()].slice(1); + + // Dispute resolver 2 - used in "addSellersToAllowList" + const disputeResolver2 = mockDisputeResolver( + await dr2.getAddress(), + await dr2.getAddress(), + await dr2.getAddress(), + await dr2.getAddress() + ); + await accountHandler.connect(dr2).createDisputeResolver(disputeResolver2, disputeResolverFees, []); + const args_2 = [disputeResolver2.id, sellerAllowList]; + const arrayIndex_2 = 1; + + // Dispute resolver 3 - used in "removeSellersFromAllowList" + const disputeResolver3 = mockDisputeResolver( + await dr3.getAddress(), + await dr3.getAddress(), + await dr3.getAddress(), + await dr3.getAddress() + ); + await accountHandler.connect(dr3).createDisputeResolver(disputeResolver3, disputeResolverFees, sellerAllowList); + const args_3 = [disputeResolver3.id, sellerAllowList]; + const arrayIndex_3 = 1; + + const disputeResolver1 = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress() + ); + const args_1 = [disputeResolver1, disputeResolverFees, sellerAllowList]; + const arrayIndex_1 = 2; + + return { + createDisputeResolver: { account: dr1, args: args_1, arrayIndex: arrayIndex_1 }, + addSellersToAllowList: { account: dr2, args: args_2, arrayIndex: arrayIndex_2 }, + removeSellersFromAllowList: { account: dr3, args: args_3, arrayIndex: arrayIndex_3 }, + }; +}; + +/* +Setup the environment for "maxFeesPerDisputeResolver". The following functions depend on it: +- createDisputeResolver +- addFeesToDisputeResolver +- removeFeesFromDisputeResolver +*/ +setupEnvironment["maxFeesPerDisputeResolver"] = async function (feesCount = 10) { + //Create DisputeResolverFee array + let disputeResolverFees = []; + for (let i = 0; i < feesCount; i++) { + const wallet = Wallet.createRandom(); + disputeResolverFees.push(new DisputeResolverFee(await wallet.getAddress(), `MockToken${i}`, "0")); + } + + // Dispute resolver 2 - used in "addFeesToDisputeResolver" + const disputeResolver2 = mockDisputeResolver( + await dr2.getAddress(), + await dr2.getAddress(), + await dr2.getAddress(), + await dr2.getAddress() + ); + await accountHandler.connect(dr2).createDisputeResolver(disputeResolver2, [], []); + const args_2 = [disputeResolver2.id, disputeResolverFees]; + const arrayIndex_2 = 1; + + // Dispute resolver 3 - used in "removeFeesFromDisputeResolver" + const disputeResolver3 = mockDisputeResolver( + await dr3.getAddress(), + await dr3.getAddress(), + await dr3.getAddress(), + await dr3.getAddress() + ); + await accountHandler.connect(dr3).createDisputeResolver(disputeResolver3, disputeResolverFees, [], { gasLimit }); + const feeTokenAddressesToRemove = disputeResolverFees.map((DRfee) => DRfee.tokenAddress); + const args_3 = [disputeResolver3.id, feeTokenAddressesToRemove]; + const arrayIndex_3 = 1; + + const disputeResolver1 = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress() + ); + const args_1 = [disputeResolver1, disputeResolverFees, []]; + const arrayIndex_1 = 1; + + return { + createDisputeResolver: { account: dr1, args: args_1, arrayIndex: arrayIndex_1 }, + addFeesToDisputeResolver: { account: dr2, args: args_2, arrayIndex: arrayIndex_2 }, + removeFeesFromDisputeResolver: { account: dr3, args: args_3, arrayIndex: arrayIndex_3 }, + }; +}; + +/* +Setup the environment for "maxOffersPerBatch". The following functions depend on it: +- createOfferBatch +- voidOfferBatch +- extendOfferBatch +*/ +setupEnvironment["maxOffersPerBatch"] = async function (offerCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + // Seller 2 - used in "voidOfferBatch" + const seller2 = mockSeller( + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress() + ); + await accountHandler.connect(sellerWallet2).createSeller(seller2, emptyAuthToken, voucherInitValues); + + // Seller 3 - used in "extendOfferBatch" + const seller3 = mockSeller( + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress() + ); + await accountHandler.connect(sellerWallet3).createSeller(seller3, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + const { offer, offerDates, offerDurations } = await mockOffer(); + const offers = new Array(offerCount).fill(offer); + const offerDatesList = new Array(offerCount).fill(offerDates); + const offerDurationsList = new Array(offerCount).fill(offerDurations); + const disputeResolverIds = new Array(offerCount).fill(disputeResolver.id); + const agentIds = new Array(offerCount).fill(agentId); + + for (let i = 0; i < offerCount; i++) { + // Create the offers for voiding/extending + await offerHandler + .connect(sellerWallet2) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + await offerHandler + .connect(sellerWallet3) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + } + + const offerIds = [...Array(offerCount + 1).keys()].slice(1); + + const args_1 = [offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds]; + const arrayIndex_1 = [0, 1, 2, 3, 4]; // adjusting length of all arguments simultaneously + + // voidOfferBatch inputs + const args_2 = [offerIds.map((offerId) => 2 * offerId - 1)]; + const arrayIndex_2 = 0; + + // extendOfferBatch + const newValidUntilDate = BigInt(offerDates.validUntil).add("10000").toString(); + const args_3 = [offerIds.map((offerId) => 2 * offerId), newValidUntilDate]; + const arrayIndex_3 = 0; + + return { + createOfferBatch: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, + voidOfferBatch: { account: sellerWallet2, args: args_2, arrayIndex: arrayIndex_2 }, + extendOfferBatch: { account: sellerWallet3, args: args_3, arrayIndex: arrayIndex_3 }, + }; +}; + +/* +Setup the environment for "maxOffersPerGroup". The following functions depend on it: +- createGroup +- addOffersToGroup +- removeOffersFromGroup +*/ +setupEnvironment["maxOffersPerGroup"] = async function (offerCount = 10) { + // Create a seller + // Required constructor params + const groupId = "1"; // argument sent to contract for createSeller will be ignored + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + // Seller 2 - used in "addOffersToGroup" + const seller2 = mockSeller( + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress(), + await sellerWallet2.getAddress() + ); + await accountHandler.connect(sellerWallet2).createSeller(seller2, emptyAuthToken, voucherInitValues); + + // Seller 3 - used in "removeOffersFromGroup" + const seller3 = mockSeller( + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress(), + await sellerWallet3.getAddress() + ); + await accountHandler.connect(sellerWallet3).createSeller(seller3, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + // Mock offer, offerDates and offerDurations + const { offer, offerDates, offerDurations } = await mockOffer(); + + for (let i = 0; i < offerCount; i++) { + // Create the offer + await offerHandler + .connect(sellerWallet1) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + await offerHandler + .connect(sellerWallet2) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + await offerHandler + .connect(sellerWallet3) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + } + + const offerIds = [...Array(offerCount + 1).keys()].slice(1); + const condition = mockCondition({ method: EvaluationMethod.None, threshold: "0", maxCommits: "0" }); + + const group = new Group(groupId, seller1.id, offerIds); + + let group1 = group.clone(); + group1.offerIds = offerIds.map((offerId) => 3 * offerId - 2); + const args_1 = [group1, condition]; + const arrayIndex_1 = 0; + const structField_1 = "offerIds"; + + let group2 = group.clone(); + group2.offerIds = []; + await groupHandler.connect(sellerWallet2).createGroup(group2, condition); + const args_2 = ["1", offerIds.map((offerId) => 3 * offerId - 1)]; + const arrayIndex_2 = 1; + + let group3 = group.clone(); + group3.offerIds = offerIds.map((offerId) => 3 * offerId); + await groupHandler.connect(sellerWallet3).createGroup(group3, condition, { gasLimit }); + const args_3 = ["2", group3.offerIds]; + const arrayIndex_3 = 1; + + return { + createGroup: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, + addOffersToGroup: { account: sellerWallet2, args: args_2, arrayIndex: arrayIndex_2 }, + removeOffersFromGroup: { account: sellerWallet3, args: args_3, arrayIndex: arrayIndex_3 }, + }; +}; + +/* +Setup the environment for "maxOffersPerBundle". The following functions depend on it: +- createBundle +*/ +setupEnvironment["maxOffersPerBundle"] = async function (offerCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + // Mock offer, offerDates and offerDurations + const { offer, offerDates, offerDurations } = await mockOffer(); + + for (let i = 0; i < offerCount; i++) { + // Create the offer + await offerHandler + .connect(sellerWallet1) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + } + + // Create a valid twin. + const [bosonToken] = await deployMockTokens(); + const twin = mockTwin(await bosonToken.getAddress()); + twin.supplyAvailable = BigInt(twin.amount).mul(offerCount); + + // Approving the twinHandler contract to transfer seller's tokens + await bosonToken.connect(sellerWallet1).approve(await twinHandler.getAddress(), twin.supplyAvailable); // approving the twin handler + + // Create a twin. + await twinHandler.connect(sellerWallet1).createTwin(twin); + const twinIds = ["1"]; + + const offerIds = [...Array(offerCount + 1).keys()].slice(1); + + const bundle = new Bundle("1", seller1.id, offerIds, twinIds); + + const args_1 = [bundle]; + const arrayIndex_1 = 0; + const structField_1 = "offerIds"; + + return { + createBundle: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, + }; +}; + +/* +Setup the environment for "maxTwinsPerBundle". The following functions depend on it: +- createBundle +*/ +setupEnvironment["maxTwinsPerBundle"] = async function (twinCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + for (let i = 0; i < twinCount; i++) { + const [twinContract] = await deployMockTokens(["Foreign20"]); + const twin = mockTwin(await twinContract.getAddress()); + + // Approving the twinHandler contract to transfer seller's tokens + await twinContract.connect(sellerWallet1).approve(await twinHandler.getAddress(), twin.supplyAvailable); // approving the twin handler + + // Create a twin. + await twinHandler.connect(sellerWallet1).createTwin(twin); + } + + // Create a valid offer. + // Mock offer, offerDates and offerDurations + const { offer, offerDates, offerDurations } = await mockOffer(); + + // Create the offer + await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + + const offerIds = ["1"]; + const twinIds = [...Array(twinCount + 1).keys()].slice(1); + + const bundle = new Bundle("1", seller1.id, offerIds, twinIds); + + const args_1 = [bundle]; + const arrayIndex_1 = 0; + const structField_1 = "twinIds"; + + return { + createBundle: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1, structField: structField_1 }, + }; +}; + +/* +Setup the environment for "maxExchangesPerBatch". The following functions depend on it: +- completeExchangeBatch +*/ +setupEnvironment["maxExchangesPerBatch"] = async function (exchangesCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + // Create an offer with big enough quantity + const { offer, offerDates, offerDurations } = await mockOffer(); + offer.quantityAvailable = exchangesCount; + // Create the offer + await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + + // Deposit seller funds so the commit will succeed + const sellerPool = BigInt(offer.price).mul(exchangesCount); + await fundsHandler.connect(sellerWallet1).depositFunds(seller1.id, ZeroAddress, sellerPool, { value: sellerPool }); + + await setNextBlockTimestamp(Number(offerDates.voucherRedeemableFrom)); + for (let i = 1; i < exchangesCount + 1; i++) { + // Commit to offer, creating a new exchange + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offer.id, { value: offer.price }); + + // Redeem voucher + await exchangeHandler.connect(buyer).redeemVoucher(i); + } + + // Set time forward to run out the dispute period + const blockNumber = await provider.getBlockNumber(); + const block = await provider.getBlock(blockNumber); + const newTime = Number(BigInt(block.timestamp) + BigInt(offerDurations.disputePeriod) + 1n); + await setNextBlockTimestamp(newTime); + + const exchangeIds = [...Array(exchangesCount + 1).keys()].slice(1); + + const args_1 = [exchangeIds]; + const arrayIndex_1 = 0; + + return { + completeExchangeBatch: { account: rando, args: args_1, arrayIndex: arrayIndex_1 }, + }; +}; + +/* +Setup the environment for "maxDisputesPerBatch". The following functions depend on it: +- expireDisputeBatch +*/ +setupEnvironment["maxDisputesPerBatch"] = async function (exchangesCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + // Create an offer with big enough quantity + const { offer, offerDates, offerDurations } = await mockOffer(); + offer.quantityAvailable = exchangesCount; + // Create the offer + await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + + // Deposit seller funds so the commit will succeed + const sellerPool = BigInt(offer.price).mul(exchangesCount); + await fundsHandler.connect(sellerWallet1).depositFunds(seller1.id, ZeroAddress, sellerPool, { value: sellerPool }); + + await setNextBlockTimestamp(Number(offerDates.voucherRedeemableFrom)); + for (let i = 1; i < exchangesCount + 1; i++) { + // Commit to offer, creating a new exchange + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offer.id, { value: offer.price }); + + // Redeem voucher + await exchangeHandler.connect(buyer).redeemVoucher(i); + + // Raise dispute + await disputeHandler.connect(buyer).raiseDispute(i); + } + + // Set time forward to run out the dispute period + const blockNumber = await provider.getBlockNumber(); + const block = await provider.getBlock(blockNumber); + const newTime = Number(BigInt(block.timestamp) + BigInt(offerDurations.resolutionPeriod) + 1n); + await setNextBlockTimestamp(newTime); + + const exchangeIds = [...Array(exchangesCount + 1).keys()].slice(1); + + const args_1 = [exchangeIds]; + const arrayIndex_1 = 0; + + return { + expireDisputeBatch: { account: rando, args: args_1, arrayIndex: arrayIndex_1 }, + }; +}; + +/* +Setup the environment for "maxTokensPerWithdrawal". The following functions depend on it: +- withdrawFunds +- withdrawProtocolFees +*/ +setupEnvironment["maxTokensPerWithdrawal"] = async function (tokenCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + const { offer, offerDates, offerDurations } = await mockOffer(); + offerDates.voucherRedeemableFrom = offerDates.validFrom; + let tokenAddresses = []; + for (let i = 1; i < tokenCount + 1; i++) { + // create a token + const [tokenContract] = await deployMockTokens(["Foreign20"]); + tokenAddresses.push(await tokenContract.getAddress()); + + offer.exchangeToken = await tokenContract.getAddress(); + await tokenContract.mint(await sellerWallet1.getAddress(), offer.sellerDeposit); + await tokenContract.mint(await buyer.getAddress(), offer.price); + await tokenContract.connect(sellerWallet1).approve(await protocolDiamond.getAddress(), offer.sellerDeposit); + await tokenContract.connect(buyer).approve(await protocolDiamond.getAddress(), offer.price); + await fundsHandler + .connect(sellerWallet1) + .depositFunds(seller1.id, await tokenContract.getAddress(), offer.sellerDeposit); + + // add token to DR accepted tokens + await accountHandler + .connect(dr1) + .addFeesToDisputeResolver(disputeResolver.id, [ + new DisputeResolverFee(await tokenContract.getAddress(), `Token${i}`, "0"), + ]); + + // create the offer + await offerHandler + .connect(sellerWallet1) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + + // Commit to offer, creating a new exchange + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), i); + + // Redeem voucher + await exchangeHandler.connect(buyer).redeemVoucher(i); + + // Raise dispute + await exchangeHandler.connect(buyer).completeExchange(i); + } + + // seller withdrawal + const tokenAmounts_1 = new Array(tokenCount).fill(offer.price); + const args_1 = [seller1.id, tokenAddresses, tokenAmounts_1]; + const arrayIndex_1 = [1, 2]; + + // protocol fee withdrawal + await accessController.grantRole(Role.FEE_COLLECTOR, await feeCollector.getAddress()); + const protocolFee = BigInt(offer.price).mul(protocolFeePercentage).div(10000); + const tokenAmounts_2 = new Array(tokenCount).fill(protocolFee); + const args_2 = [tokenAddresses, tokenAmounts_2]; + const arrayIndex_2 = [0, 1]; + + return { + withdrawFunds: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, + withdrawProtocolFees: { account: feeCollector, args: args_2, arrayIndex: arrayIndex_2 }, + }; +}; + +/* +Setup the environment for "maxPremintedVouchers". The following function depend on it: +- preMint +*/ +setupEnvironment["maxPremintedVouchers"] = async function (tokenCount = 10) { + // Create a seller + // Required constructor params + const agentId = "0"; // agent id is optional while creating an offer + + const seller1 = mockSeller( + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress(), + await sellerWallet1.getAddress() + ); + const voucherInitValues = mockVoucherInitValues(); + const emptyAuthToken = mockAuthToken(); + + await accountHandler.connect(sellerWallet1).createSeller(seller1, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver( + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + await dr1.getAddress(), + true + ); + await accountHandler + .connect(dr1) + .createDisputeResolver(disputeResolver, [new DisputeResolverFee(ZeroAddress, "Native", "0")], []); + + // create the offer + const { offer, offerDates, offerDurations } = await mockOffer(); + offer.quantityAvailable = MaxUint256; + await offerHandler.connect(sellerWallet1).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); + + // reserve range + let length = BigInt(2).pow(128).sub(1); + await offerHandler.connect(sellerWallet1).reserveRange(offer.id, length); + + // update bosonVoucher address + handlers.IBosonVoucher = bosonVoucher.attach(calculateContractAddress(await accountHandler.getAddress(), seller1.id)); + + // make an empty array of length tokenCount + const amounts = new Array(tokenCount); + + const args_1 = [offer.id, amounts]; + const arrayIndex_1 = 1; + + return { + preMint: { account: sellerWallet1, args: args_1, arrayIndex: arrayIndex_1 }, + }; +}; + +/* +Invoke the methods that setup the environment and iterate over all limits and pass them to estimation. +At the end it writes the results to json file. +*/ +async function estimateLimits() { + if (hre.network.name !== "hardhat") { + console.log("Unsupported network"); + process.exit(1); + } + + for (const limit of limitsToEstimate.limits) { + console.log(`## ${limit.name} ##`); + console.log(`Setting up the environment`); + await setupCommonEnvironment(); + const inputs = await setupEnvironment[limit.name](limitsToEstimate.maxArrayLength); + console.log(`Estimating the limit`); + await estimateLimit(limit, inputs, limitsToEstimate.safeGasLimitPercent); + accountId.next(true); + } + makeReport(result, limitsToEstimate.maxArrayLength); +} + +/* +Esitmates individual limit. It estimates gas for different lenghts of input array and forwards +the result to function that calculates the actual limit. + +It stores the list of point estimates and maximum and safe lenght of the array to results. +*/ +async function estimateLimit(limit, inputs, safeGasLimitPercent) { + result[limit.name] = {}; + for (const [method, handler] of Object.entries(limit.methods)) { + console.log(`=== ${method} ===`); + const methodInputs = inputs[method]; + if (methodInputs === undefined) { + console.log(`Missing setup for ${limit.name}:${method}`); + continue; + } + + const maxArrayLength = methodInputs.structField + ? methodInputs.args[methodInputs.arrayIndex][methodInputs.structField].length + : methodInputs.args[Array.isArray(methodInputs.arrayIndex) ? methodInputs.arrayIndex[0] : methodInputs.arrayIndex] + .length; + let gasEstimates = []; + for (let o = 0; Math.pow(10, o) <= maxArrayLength; o++) { + for (let i = 1; i < 10; i++) { + let arrayLength = i * Math.pow(10, o); + if (arrayLength > maxArrayLength) arrayLength = maxArrayLength; + + const args = methodInputs.args; + let adjustedArgs = [...args]; + + if (methodInputs.structField) { + adjustedArgs[methodInputs.arrayIndex] = { ...adjustedArgs[methodInputs.arrayIndex] }; + adjustedArgs[methodInputs.arrayIndex][methodInputs.structField] = args[methodInputs.arrayIndex][ + methodInputs.structField + ].slice(0, arrayLength); + } else { + if (Array.isArray(methodInputs.arrayIndex)) { + for (const ai of methodInputs.arrayIndex) { + adjustedArgs[ai] = args[ai].slice(0, arrayLength); + } + } else { + // if args contains null values, just use arrayLength instead + adjustedArgs[methodInputs.arrayIndex] = args[methodInputs.arrayIndex][0] + ? args[methodInputs.arrayIndex].slice(0, arrayLength) + : arrayLength; + } + } + + try { + const gasEstimate = await handlers[handler] + .connect(methodInputs.account) + .estimateGas[method](...adjustedArgs, { gasLimit }); + console.log("Length:", arrayLength, "Gas:", Number(gasEstimate)); + gasEstimates.push([Number(gasEstimate), arrayLength]); + } catch (e) { + // console.log(e) + console.log("Block gas limit already hit"); + break; + } + if (arrayLength == maxArrayLength) break; + } + } + const { maxNumber, safeNumber } = calculateLimit(gasEstimates, safeGasLimitPercent); + result[limit.name][method] = { gasEstimates, maxNumber, safeNumber }; + console.log(`Estimation complete`); + } +} + +/* +Based on point gas estimates calculates the maximum and safe length of the array that can be passed in +Safe length is determined by safeGasLimitPercent which is the percentage amount of block that is considered +safe to be taken +*/ +function calculateLimit(gasEstimates, safeGasLimitPercent) { + const regCoef = simpleStatistic.linearRegression(gasEstimates); + const line = simpleStatistic.linearRegressionLine(regCoef); + + const maxNumber = Math.floor(line(gasLimit)); + const safeNumber = Math.floor(line((gasLimit * safeGasLimitPercent) / 100)); + return { maxNumber, safeNumber }; +} + +/* +Deploys protocol contracts, casts facets to interfaces and makes accounts available +*/ +async function setupCommonEnvironment() { + // Make accounts available + [ + deployer, + sellerWallet1, + sellerWallet2, + sellerWallet3, + dr1, + dr2, + dr3, + buyer, + rando, + other1, + other2, + other3, + protocolAdmin, + feeCollector, + other1, + ] = await getSigners(); + + // Deploy the Protocol Diamond + [protocolDiamond, , , , accessController] = await deployProtocolDiamond(); + + // Temporarily grant UPGRADER role to deployer account + await accessController.grantRole(Role.UPGRADER, await deployer.getAddress()); + + // Grant PROTOCOL role to ProtocolDiamond address and renounces admin + await accessController.grantRole(Role.PROTOCOL, await protocolDiamond.getAddress()); + + // Grant ADMIN role to and address that can call restricted functions. + // This ADMIN role is a protocol-level role. It is not the same an admin address for an account type + await accessController.grantRole(Role.ADMIN, await protocolAdmin.getAddress()); + + // Deploy the Protocol client implementation/proxy pairs (currently just the Boson Voucher) + const protocolClientArgs = [await protocolDiamond.getAddress()]; + const [, beacons, proxies, bv] = await deployProtocolClients(protocolClientArgs, gasLimit); + const [beacon] = beacons; + const [proxy] = proxies; + [bosonVoucher] = bv; + + // Set protocolFees + protocolFeePercentage = "200"; // 2 % + protocolFeeFlatBoson = parseUnits("0.01", "ether").toString(); + buyerEscalationDepositPercentage = "1000"; // 10% + + // Add config Handler, so ids start at 1, and so voucher address can be found + const protocolConfig = [ + // Protocol addresses + { + treasury: await rando.getAddress(), + token: await rando.getAddress(), + voucherBeacon: await beacon.getAddress(), + beaconProxy: await proxy.getAddress(), + }, + // Protocol limits + { + maxExchangesPerBatch: 10000, + maxOffersPerGroup: 10000, + maxTwinsPerBundle: 10000, + maxOffersPerBundle: 10000, + maxOffersPerBatch: 10000, + maxTokensPerWithdrawal: 10000, + maxFeesPerDisputeResolver: 10000, + maxEscalationResponsePeriod: oneMonth, + maxDisputesPerBatch: 10000, + maxAllowedSellers: 10000, + maxTotalOfferFeePercentage: 4000, //40% + maxRoyaltyPecentage: 1000, //10% + maxResolutionPeriod: oneMonth, + minDisputePeriod: oneWeek, + maxPremintedVouchers: 100, + }, + // Protocol fees + { + percentage: protocolFeePercentage, + flatBoson: protocolFeeFlatBoson, + buyerEscalationDepositPercentage, + }, + ]; + + const facetNames = [ + "AccountHandlerFacet", + "BundleHandlerFacet", + "DisputeHandlerFacet", + "DisputeResolverHandlerFacet", + "ExchangeHandlerFacet", + "FundsHandlerFacet", + "GroupHandlerFacet", + "OfferHandlerFacet", + "SellerHandlerFacet", + "TwinHandlerFacet", + "ProtocolInitializationHandlerFacet", + "ConfigHandlerFacet", + ]; + + const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); + + // Cut the protocol handler facets into the Diamond + await deployAndCutFacets(await protocolDiamond.getAddress(), facetsToDeploy, gasLimit); + // Cast Diamond to handlers + accountHandler = await getContractAt("IBosonAccountHandler", await protocolDiamond.getAddress()); + bundleHandler = await getContractAt("IBosonBundleHandler", await protocolDiamond.getAddress()); + disputeHandler = await getContractAt("IBosonDisputeHandler", await protocolDiamond.getAddress()); + exchangeHandler = await getContractAt("IBosonExchangeHandler", await protocolDiamond.getAddress()); + fundsHandler = await getContractAt("IBosonFundsHandler", await protocolDiamond.getAddress()); + groupHandler = await getContractAt("IBosonGroupHandler", await protocolDiamond.getAddress()); + offerHandler = await getContractAt("IBosonOfferHandler", await protocolDiamond.getAddress()); + twinHandler = await getContractAt("IBosonTwinHandler", await protocolDiamond.getAddress()); + + handlers = { + IBosonAccountHandler: accountHandler, + IBosonBundleHandler: bundleHandler, + IBosonDisputeHandler: disputeHandler, + IBosonExchangeHandler: exchangeHandler, + IBosonFundsHandler: fundsHandler, + IBosonGroupHandler: groupHandler, + IBosonOfferHandler: offerHandler, + IBosonVoucher: bosonVoucher, + }; +} + +function makeReport(res, maxArrayLength) { + // TABLE 1: suggested values + let header1 = `| limit | max value | safe value |`; + let alignment1 = `| :-- | --: | --: |`; + let rows1 = []; + + // TABLE 2: all estimates + let header2 = `| # |`; + let alignment2 = `|--| `; + let row0 = `| |`; + let rows2 = []; + let numberOfRows = 0; + + for (let o = 0; Math.pow(10, o) <= maxArrayLength; o++) { + for (let i = 1; i < 10; i++) { + let arrayLength = i * Math.pow(10, o); + if (arrayLength > maxArrayLength) arrayLength = maxArrayLength; + rows2.push(`| ${arrayLength} |`); + numberOfRows++; + if (arrayLength == maxArrayLength) break; + } + } + + let maxNumber = `| **max** |`; + let safeNumber = `| safe |`; + + for (const [limit, result] of Object.entries(res)) { + let mn = Number.MAX_SAFE_INTEGER; + let sn = Number.MAX_SAFE_INTEGER; + for (const [method, estimates] of Object.entries(result)) { + header2 = `${header2} ${limit} |`; + alignment2 = `${alignment2} ---:|`; + row0 = `${row0} ${method} |`; + for (let i = 0; i < numberOfRows; i++) { + rows2[i] = `${rows2[i]} ${estimates.gasEstimates[i] ? estimates.gasEstimates[i][0] : " "} |`; + } + maxNumber = `${maxNumber} **${estimates.maxNumber}** |`; + safeNumber = `${safeNumber} ${estimates.safeNumber} |`; + + mn = Math.min(mn, estimates.maxNumber); + sn = Math.min(sn, estimates.safeNumber); + } + + rows1.push(`| ${limit} | ${mn} | ${sn} |`); + } + + const table1 = [header1, alignment1, ...rows1].join(`\n`); + const table2 = [header2, alignment2, row0, ...rows2, maxNumber, safeNumber].join(`\n`); + + const output = `${table1}\n\n${table2}`; + + fs.writeFileSync(__dirname + "/../../logs/limit_estimates.md", output); + fs.writeFileSync(__dirname + "/../../logs/limit_estimates.json", JSON.stringify(result)); +} + +exports.estimateLimits = estimateLimits; diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 2e2efaeac..4ab556203 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -1,4 +1,5 @@ const { ethers } = require("hardhat"); +const { ZeroAddress, getSigners, provider, parseUnits, getContractAt, getContractFactory } = ethers; const { expect, assert } = require("chai"); const Role = require("../../scripts/domain/Role"); const { Funds, FundsList } = require("../../scripts/domain/Funds"); @@ -136,9 +137,9 @@ describe("IBosonFundsHandler", function () { // make all account the same assistant = admin; assistantDR = adminDR; - clerk = clerkDR = { address: ethers.constants.AddressZero }; + clerk = clerkDR = { address: ZeroAddress }; - [deployer, protocolTreasury] = await ethers.getSigners(); + [deployer, protocolTreasury] = await getSigners(); // Deploy the mock token [mockToken] = await deployMockTokens(["Foreign20"]); @@ -169,7 +170,12 @@ describe("IBosonFundsHandler", function () { context("📋 Funds Handler Methods", async function () { beforeEach(async function () { // Create a valid seller, then set fields in tests directly - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); + seller = mockSeller( + await assistant.getAddress(), + await admin.getAddress(), + clerk.address, + await treasury.getAddress() + ); expect(seller.isValid()).is.true; // VoucherInitValues @@ -183,18 +189,18 @@ describe("IBosonFundsHandler", function () { await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); // top up assistants account - await mockToken.mint(assistant.address, "1000000"); + await mockToken.mint(await assistant.getAddress(), "1000000"); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, "1000000"); // set the deposit amount - depositAmount = "100"; + depositAmount = 100n; // Set agent id as zero as it is optional for createOffer(). agentId = "0"; - availableFundsAddresses = [mockToken.address, ethers.constants.AddressZero]; + availableFundsAddresses = [await mockToken.getAddress(), ZeroAddress]; }); afterEach(async function () { @@ -206,25 +212,25 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsDeposited event", async function () { // Deposit funds, testing for the event // Deposit token - await expect(fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount)) + await expect( + fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount) + ) .to.emit(fundsHandler, "FundsDeposited") - .withArgs(seller.id, assistant.address, mockToken.address, depositAmount); + .withArgs(seller.id, await assistant.getAddress(), await mockToken.getAddress(), depositAmount); // Deposit native currency await expect( - fundsHandler - .connect(rando) - .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount, { value: depositAmount }) + fundsHandler.connect(rando).depositFunds(seller.id, ZeroAddress, depositAmount, { value: depositAmount }) ) .to.emit(fundsHandler, "FundsDeposited") - .withArgs(seller.id, rando.address, ethers.constants.AddressZero, depositAmount); + .withArgs(seller.id, await rando.getAddress(), ZeroAddress, depositAmount); }); it("should update state", async function () { // Deposit token - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount); - availableFundsAddresses = [mockToken.address]; + availableFundsAddresses = [await mockToken.getAddress()]; // Read on chain state let returnedAvailableFunds = FundsList.fromStruct( @@ -232,29 +238,29 @@ describe("IBosonFundsHandler", function () { ); // Chain state should match the expected available funds - let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); + let expectedAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", depositAmount.toString()), + ]); expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); // Deposit native currency to the same seller id - await fundsHandler - .connect(rando) - .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount, { value: depositAmount }); + await fundsHandler.connect(rando).depositFunds(seller.id, ZeroAddress, depositAmount, { value: depositAmount }); // Get new on chain state returnedAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, [...availableFundsAddresses, ethers.constants.AddressZero]) + await fundsHandler.getAvailableFunds(seller.id, [...availableFundsAddresses, ZeroAddress]) ); // Chain state should match the expected available funds - expectedAvailableFunds.funds.push(new Funds(ethers.constants.AddressZero, "Native currency", depositAmount)); + expectedAvailableFunds.funds.push(new Funds(ZeroAddress, "Native currency", depositAmount.toString())); expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); }); it("should be possible to top up the account", async function () { - availableFundsAddresses = [mockToken.address]; + availableFundsAddresses = [await mockToken.getAddress()]; // Deposit token - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount); // Read on chain state let returnedAvailableFunds = FundsList.fromStruct( @@ -262,11 +268,13 @@ describe("IBosonFundsHandler", function () { ); // Chain state should match the expected available funds - let expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", depositAmount)]); + let expectedAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", depositAmount.toString()), + ]); expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); // Deposit the same token again - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, 2 * depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), 2n * depositAmount); // Get new on chain state returnedAvailableFunds = FundsList.fromStruct( @@ -274,7 +282,9 @@ describe("IBosonFundsHandler", function () { ); // Chain state should match the expected available funds - expectedAvailableFunds = new FundsList([new Funds(mockToken.address, "Foreign20", `${3 * depositAmount}`)]); + expectedAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", (3n * depositAmount).toString()), + ]); expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); }); @@ -285,7 +295,7 @@ describe("IBosonFundsHandler", function () { // Attempt to deposit funds, expecting revert await expect( - fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -293,7 +303,7 @@ describe("IBosonFundsHandler", function () { // Attempt to deposit the funds, expecting revert seller.id = "555"; await expect( - fundsHandler.connect(rando).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(rando).depositFunds(seller.id, await mockToken.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.NO_SUCH_SELLER); }); @@ -302,7 +312,7 @@ describe("IBosonFundsHandler", function () { await expect( fundsHandler .connect(rando) - .depositFunds(seller.id, mockToken.address, depositAmount, { value: depositAmount }) + .depositFunds(seller.id, await mockToken.getAddress(), depositAmount, { value: depositAmount }) ).to.revertedWith(RevertReasons.NATIVE_WRONG_ADDRESS); }); @@ -311,7 +321,7 @@ describe("IBosonFundsHandler", function () { await expect( fundsHandler .connect(rando) - .depositFunds(seller.id, ethers.constants.AddressZero, depositAmount * 2, { value: depositAmount }) + .depositFunds(seller.id, ZeroAddress, depositAmount * 2n, { value: depositAmount }) ).to.revertedWith(RevertReasons.NATIVE_WRONG_AMOUNT); }); @@ -321,14 +331,14 @@ describe("IBosonFundsHandler", function () { // Attempt to deposit the funds, expecting revert await expect( - fundsHandler.connect(rando).depositFunds(seller.id, bosonToken.address, depositAmount) + fundsHandler.connect(rando).depositFunds(seller.id, await bosonToken.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.SAFE_ERC20_LOW_LEVEL_CALL); }); it("Token address is not a contract", async function () { // Attempt to deposit the funds, expecting revert await expect( - fundsHandler.connect(rando).depositFunds(seller.id, admin.address, depositAmount) + fundsHandler.connect(rando).depositFunds(seller.id, await admin.getAddress(), depositAmount) ).to.revertedWithoutReason(); }); @@ -338,13 +348,13 @@ describe("IBosonFundsHandler", function () { await mockToken.connect(rando).approve(protocolDiamondAddress, depositAmount); // Attempt to deposit the funds, expecting revert await expect( - fundsHandler.connect(rando).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(rando).depositFunds(seller.id, await mockToken.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.ERC20_EXCEEDS_BALANCE); // not approved - depositAmount = "10000000"; + depositAmount = 10000000n; await expect( - fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.ERC20_INSUFFICIENT_ALLOWANCE); }); @@ -353,23 +363,25 @@ describe("IBosonFundsHandler", function () { const [Foreign20WithFee] = await deployMockTokens(["Foreign20WithFee"]); // mint tokens and approve - await Foreign20WithFee.mint(assistant.address, depositAmount); + await Foreign20WithFee.mint(await assistant.getAddress(), depositAmount); await Foreign20WithFee.connect(assistant).approve(protocolDiamondAddress, depositAmount); // Attempt to deposit funds, expecting revert await expect( - fundsHandler.connect(assistant).depositFunds(seller.id, Foreign20WithFee.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, await Foreign20WithFee.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); it("ERC20 transferFrom returns false", async function () { const [foreign20ReturnFalse] = await deployMockTokens(["Foreign20TransferFromReturnFalse"]); - await foreign20ReturnFalse.connect(assistant).mint(assistant.address, depositAmount); + await foreign20ReturnFalse.connect(assistant).mint(await assistant.getAddress(), depositAmount); await foreign20ReturnFalse.connect(assistant).approve(protocolDiamondAddress, depositAmount); await expect( - fundsHandler.connect(assistant).depositFunds(seller.id, foreign20ReturnFalse.address, depositAmount) + fundsHandler + .connect(assistant) + .depositFunds(seller.id, await foreign20ReturnFalse.getAddress(), depositAmount) ).to.revertedWith(RevertReasons.SAFE_ERC20_NOT_SUCCEEDED); }); }); @@ -382,18 +394,18 @@ describe("IBosonFundsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - assistantDR.address, - adminDR.address, + await assistantDR.getAddress(), + await adminDR.getAddress(), clerkDR.address, - treasuryDR.address, + await treasuryDR.getAddress(), true ); expect(disputeResolver.isValid()).is.true; //Create DisputeResolverFee array so offer creation will succeed disputeResolverFees = [ - new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0"), - new DisputeResolverFee(mockToken.address, "mockToken", "0"), + new DisputeResolverFee(ZeroAddress, "Native", "0"), + new DisputeResolverFee(await mockToken.getAddress(), "mockToken", "0"), ]; // Make empty seller list, so every seller is allowed @@ -412,7 +424,7 @@ describe("IBosonFundsHandler", function () { offerToken = offer.clone(); offerToken.id = "2"; - offerToken.exchangeToken = mockToken.address; + offerToken.exchangeToken = await mockToken.getAddress(); // Check if domais are valid expect(offerNative.isValid()).is.true; @@ -439,7 +451,10 @@ describe("IBosonFundsHandler", function () { offerTokenProtocolFee = offerNativeProtocolFee = offerFees.protocolFee; // top up seller's and buyer's account - await Promise.all([mockToken.mint(assistant.address, sellerDeposit), mockToken.mint(buyer.address, price)]); + await Promise.all([ + mockToken.mint(await assistant.getAddress(), sellerDeposit), + mockToken.mint(await buyer.getAddress(), price), + ]); // approve protocol to transfer the tokens await Promise.all([ @@ -449,15 +464,15 @@ describe("IBosonFundsHandler", function () { // deposit to seller's pool await Promise.all([ - fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit), - fundsHandler - .connect(assistant) - .depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit }), + fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), sellerDeposit), + fundsHandler.connect(assistant).depositFunds(seller.id, ZeroAddress, sellerDeposit, { value: sellerDeposit }), ]); // commit to both offers - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: offerNative.price }); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); + await exchangeHandler + .connect(buyer) + .commitToOffer(await buyer.getAddress(), offerNative.id, { value: offerNative.price }); buyerId = accountId.next().value; }); @@ -475,21 +490,21 @@ describe("IBosonFundsHandler", function () { // expected payoffs - they are the same for token and native currency // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(offerToken.price).sub(offerToken.buyerCancelPenalty).toString(); + buyerPayoff = BigInt(offerToken.price) - BigInt(offerToken.buyerCancelPenalty); // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.buyerCancelPenalty).toString(); + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.buyerCancelPenalty); }); it("should emit a FundsWithdrawn event", async function () { // Withdraw funds, testing for the event // Withdraw tokens - tokenListSeller = [mockToken.address, ethers.constants.AddressZero]; - tokenListBuyer = [ethers.constants.AddressZero, mockToken.address]; + tokenListSeller = [await mockToken.getAddress(), ZeroAddress]; + tokenListBuyer = [ZeroAddress, await mockToken.getAddress()]; // Withdraw amounts - tokenAmountsSeller = [sellerPayoff, ethers.BigNumber.from(sellerPayoff).div("2").toString()]; - tokenAmountsBuyer = [buyerPayoff, ethers.BigNumber.from(buyerPayoff).div("5").toString()]; + tokenAmountsSeller = [sellerPayoff, (BigInt(sellerPayoff) / 2n).toString()]; + tokenAmountsBuyer = [buyerPayoff, (BigInt(buyerPayoff) / 5n).toString()]; // seller withdrawal const tx = await fundsHandler @@ -497,33 +512,39 @@ describe("IBosonFundsHandler", function () { .withdrawFunds(seller.id, tokenListSeller, tokenAmountsSeller); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(seller.id, treasury.address, mockToken.address, sellerPayoff, assistant.address); + .withArgs( + seller.id, + await treasury.getAddress(), + await mockToken.getAddress(), + sellerPayoff, + await assistant.getAddress() + ); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") .withArgs( seller.id, - treasury.address, - ethers.constants.Zero, - ethers.BigNumber.from(sellerPayoff).div("2"), - assistant.address + await treasury.getAddress(), + 0n, + BigInt(sellerPayoff) / 2n, + await assistant.getAddress() ); // buyer withdrawal const tx2 = await fundsHandler.connect(buyer).withdrawFunds(buyerId, tokenListBuyer, tokenAmountsBuyer); await expect(tx2) - .to.emit(fundsHandler, "FundsWithdrawn", buyer.address) + .to.emit(fundsHandler, "FundsWithdrawn", await buyer.getAddress()) .withArgs( buyerId, - buyer.address, - mockToken.address, - ethers.BigNumber.from(buyerPayoff).div("5"), - buyer.address + await buyer.getAddress(), + await mockToken.getAddress(), + BigInt(buyerPayoff) / 5n, + await buyer.getAddress() ); await expect(tx2) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(buyerId, buyer.address, ethers.constants.Zero, buyerPayoff, buyer.address); + .withArgs(buyerId, await buyer.getAddress(), 0n, buyerPayoff, await buyer.getAddress()); }); it("should update state", async function () { @@ -533,12 +554,12 @@ describe("IBosonFundsHandler", function () { sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); - const treasuryBalanceBefore = await ethers.provider.getBalance(treasury.address); + const treasuryBalanceBefore = await provider.getBalance(await treasury.getAddress()); // Chain state should match the expected available funds before the withdrawal expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", sellerPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", sellerPayoff), ]); expect(sellersAvailableFunds).to.eql( expectedSellerAvailableFunds, @@ -546,33 +567,30 @@ describe("IBosonFundsHandler", function () { ); // withdraw funds - const withdrawAmount = ethers.BigNumber.from(sellerPayoff) - .sub(ethers.utils.parseUnits("0.1", "ether")) - .toString(); - await fundsHandler - .connect(assistant) - .withdrawFunds(seller.id, [ethers.constants.AddressZero], [withdrawAmount]); + const withdrawAmount = BigInt(sellerPayoff) - parseUnits("0.1", "ether"); + await fundsHandler.connect(assistant).withdrawFunds(seller.id, [ZeroAddress], [withdrawAmount]); // Read on chain state sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); - const treasuryBalanceAfter = await ethers.provider.getBalance(treasury.address); + const treasuryBalanceAfter = await provider.getBalance(await treasury.getAddress()); // Chain state should match the expected available funds after the withdrawal // Native currency available funds are reduced for the withdrawal amount expectedSellerAvailableFunds.funds[1] = new Funds( - ethers.constants.AddressZero, + ZeroAddress, "Native currency", - ethers.BigNumber.from(sellerPayoff).sub(withdrawAmount).toString() + BigInt(sellerPayoff) - BigInt(withdrawAmount) ); expect(sellersAvailableFunds).to.eql( expectedSellerAvailableFunds, "Seller available funds mismatch after withdrawal" ); + // Native currency balance is increased for the withdrawAmount expect(treasuryBalanceAfter).to.eql( - treasuryBalanceBefore.add(withdrawAmount), + treasuryBalanceBefore + withdrawAmount, "Treasury token balance mismatch" ); @@ -582,12 +600,12 @@ describe("IBosonFundsHandler", function () { buyerAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) ); - const buyerBalanceBefore = await mockToken.balanceOf(buyer.address); + const buyerBalanceBefore = await mockToken.balanceOf(await buyer.getAddress()); // Chain state should match the expected available funds before the withdrawal expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", buyerPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", buyerPayoff), ]); expect(buyerAvailableFunds).to.eql( expectedBuyerAvailableFunds, @@ -595,26 +613,27 @@ describe("IBosonFundsHandler", function () { ); // withdraw funds - await fundsHandler.connect(buyer).withdrawFunds(buyerId, [mockToken.address], [buyerPayoff]); + await fundsHandler.connect(buyer).withdrawFunds(buyerId, [await mockToken.getAddress()], [buyerPayoff]); // Read on chain state buyerAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) ); - const buyerBalanceAfter = await mockToken.balanceOf(buyer.address); // Chain state should match the expected available funds after the withdrawal // Since all tokens are withdrawn, getAvailableFunds should return 0 for token expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", buyerPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", buyerPayoff) ]); + const buyerBalanceAfter = await mockToken.balanceOf(await buyer.getAddress()); + expect(buyerAvailableFunds).to.eql( expectedBuyerAvailableFunds, "Buyer available funds mismatch after withdrawal" ); // Token balance is increased for the buyer payoff - expect(buyerBalanceAfter).to.eql(buyerBalanceBefore.add(buyerPayoff), "Buyer token balance mismatch"); + expect(buyerBalanceAfter).to.eql(buyerBalanceBefore + buyerPayoff, "Buyer token balance mismatch"); }); it("should allow to withdraw all funds at once", async function () { @@ -622,13 +641,13 @@ describe("IBosonFundsHandler", function () { sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); - const treasuryNativeBalanceBefore = await ethers.provider.getBalance(treasury.address); - const treasuryTokenBalanceBefore = await mockToken.balanceOf(treasury.address); + const treasuryNativeBalanceBefore = await provider.getBalance(await treasury.getAddress()); + const treasuryTokenBalanceBefore = await mockToken.balanceOf(await treasury.getAddress()); // Chain state should match the expected available funds before the withdrawal expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", sellerPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", sellerPayoff), ]); expect(sellersAvailableFunds).to.eql( expectedSellerAvailableFunds, @@ -642,14 +661,14 @@ describe("IBosonFundsHandler", function () { sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); - const treasuryNativeBalanceAfter = await ethers.provider.getBalance(treasury.address); - const treasuryTokenBalanceAfter = await mockToken.balanceOf(treasury.address); + const treasuryNativeBalanceAfter = await provider.getBalance(await treasury.getAddress()); + const treasuryTokenBalanceAfter = await mockToken.balanceOf(await treasury.getAddress()); // Chain state should match the expected available funds after the withdrawal // Funds available should be zero expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expect(sellersAvailableFunds).to.eql( @@ -658,20 +677,22 @@ describe("IBosonFundsHandler", function () { ); // Native currency balance is increased for the withdrawAmount expect(treasuryNativeBalanceAfter).to.eql( - treasuryNativeBalanceBefore.add(sellerPayoff), + treasuryNativeBalanceBefore + sellerPayoff, "Treasury native currency balance mismatch" ); expect(treasuryTokenBalanceAfter).to.eql( - treasuryTokenBalanceBefore.add(sellerPayoff), + treasuryTokenBalanceBefore + sellerPayoff, "Treasury token balance mismatch" ); }); + + it("It's possible to withdraw same toke twice if in total enough available funds", async function () { - let reduction = ethers.utils.parseUnits("0.1", "ether").toString(); + let reduction = parseUnits("0.1", "ether"); // Withdraw token - tokenListSeller = [mockToken.address, mockToken.address]; - tokenAmountsSeller = [ethers.BigNumber.from(sellerPayoff).sub(reduction).toString(), reduction]; + tokenListSeller = [await mockToken.getAddress(), await mockToken.getAddress()]; + tokenAmountsSeller = [BigInt(sellerPayoff) - BigInt(reduction), reduction]; // seller withdrawal const tx = await fundsHandler @@ -681,22 +702,28 @@ describe("IBosonFundsHandler", function () { .to.emit(fundsHandler, "FundsWithdrawn") .withArgs( seller.id, - treasury.address, - mockToken.address, - ethers.BigNumber.from(sellerPayoff).sub(reduction).toString(), - assistant.address + await treasury.getAddress(), + await mockToken.getAddress(), + BigInt(sellerPayoff) - BigInt(reduction), + await assistant.getAddress() ); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(seller.id, treasury.address, mockToken.address, reduction, assistant.address); + .withArgs( + seller.id, + await treasury.getAddress(), + await mockToken.getAddress(), + reduction, + await assistant.getAddress() + ); }); context("Agent Withdraws funds", async function () { beforeEach(async function () { // Create a valid agent, agentId = "4"; - agent = mockAgent(other.address); + agent = mockAgent(await other.getAddress()); agent.id = agentId; expect(agent.isValid()).is.true; @@ -708,7 +735,7 @@ describe("IBosonFundsHandler", function () { agentOffer = offer.clone(); agentOffer.id = "3"; exchangeId = "3"; - agentOffer.exchangeToken = mockToken.address; + agentOffer.exchangeToken = await mockToken.getAddress(); // Create offer with agent await offerHandler @@ -721,18 +748,18 @@ describe("IBosonFundsHandler", function () { voucherRedeemableFrom = offerDates.voucherRedeemableFrom; // top up seller's and buyer's account - await mockToken.mint(assistant.address, sellerDeposit); - await mockToken.mint(buyer.address, price); + await mockToken.mint(await assistant.getAddress(), sellerDeposit); + await mockToken.mint(await buyer.getAddress(), price); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamondAddress, price); // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), sellerDeposit); // commit to agent offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -750,17 +777,18 @@ describe("IBosonFundsHandler", function () { // Check the balance BEFORE withdrawFunds() const feeCollectorNativeBalanceBefore = await mockToken.balanceOf(agent.wallet); - await expect(fundsHandler.connect(other).withdrawFunds(agentId, [mockToken.address], [agentPayoff])) + await expect( + fundsHandler.connect(other).withdrawFunds(agentId, [await mockToken.getAddress()], [agentPayoff]) + ) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(agentId, agent.wallet, mockToken.address, agentPayoff, agent.wallet); + .withArgs(agentId, agent.wallet, await mockToken.getAddress(), agentPayoff, agent.wallet); // Check the balance AFTER withdrawFunds() const feeCollectorNativeBalanceAfter = await mockToken.balanceOf(agent.wallet); // Expected balance - const expectedFeeCollectorNativeBalanceAfter = ethers.BigNumber.from(feeCollectorNativeBalanceBefore).add( - agentPayoff - ); + const expectedFeeCollectorNativeBalanceAfter = + BigInt(feeCollectorNativeBalanceBefore) + BigInt(agentPayoff); // Check agent wallet balance and verify the transfer really happened. expect(feeCollectorNativeBalanceAfter).to.eql( @@ -776,22 +804,23 @@ describe("IBosonFundsHandler", function () { // retract from the dispute await disputeHandler.connect(buyer).retractDispute(exchangeId); - agentPayoff = ethers.BigNumber.from(agentOffer.price).mul(agent.feePercentage).div("10000").toString(); + agentPayoff = ((BigInt(agentOffer.price) * BigInt(agent.feePercentage)) / 10000n).toString(); // Check the balance BEFORE withdrawFunds() const feeCollectorNativeBalanceBefore = await mockToken.balanceOf(agent.wallet); - await expect(fundsHandler.connect(other).withdrawFunds(agentId, [mockToken.address], [agentPayoff])) + await expect( + fundsHandler.connect(other).withdrawFunds(agentId, [await mockToken.getAddress()], [agentPayoff]) + ) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(agentId, agent.wallet, mockToken.address, agentPayoff, agent.wallet); + .withArgs(agentId, agent.wallet, await mockToken.getAddress(), agentPayoff, agent.wallet); // Check the balance AFTER withdrawFunds() const feeCollectorNativeBalanceAfter = await mockToken.balanceOf(agent.wallet); // Expected balance - const expectedFeeCollectorNativeBalanceAfter = ethers.BigNumber.from(feeCollectorNativeBalanceBefore).add( - agentPayoff - ); + const expectedFeeCollectorNativeBalanceAfter = + BigInt(feeCollectorNativeBalanceBefore) + BigInt(agentPayoff); // Check agent wallet balance and verify the transfer really happened. expect(feeCollectorNativeBalanceAfter).to.eql( @@ -804,10 +833,10 @@ describe("IBosonFundsHandler", function () { context("💔 Revert Reasons", async function () { it("The funds region of protocol is paused", async function () { // Withdraw tokens - tokenListBuyer = [ethers.constants.AddressZero, mockToken.address]; + tokenListBuyer = [ZeroAddress, await mockToken.getAddress()]; // Withdraw amounts - tokenAmountsBuyer = [buyerPayoff, ethers.BigNumber.from(buyerPayoff).div("5").toString()]; + tokenAmountsBuyer = [BigInt(buyerPayoff), BigInt(buyerPayoff) / 5n]; // Pause the funds region of the protocol await pauseHandler.connect(pauser).pause([PausableRegion.Funds]); @@ -837,7 +866,7 @@ describe("IBosonFundsHandler", function () { it("Token list address does not match token amount address", async function () { // Withdraw token - tokenList = [mockToken.address, ethers.constants.AddressZero]; + tokenList = [await mockToken.getAddress(), ZeroAddress]; tokenAmounts = [sellerPayoff]; // Attempt to withdraw the funds, expecting revert @@ -846,10 +875,11 @@ describe("IBosonFundsHandler", function () { ).to.revertedWith(RevertReasons.TOKEN_AMOUNT_MISMATCH); }); + it("Caller tries to withdraw more than they have in the available funds", async function () { // Withdraw token - tokenList = [mockToken.address]; - tokenAmounts = [ethers.BigNumber.from(sellerPayoff).mul("2")]; + tokenList = [await mockToken.getAddress()]; + tokenAmounts = [BigInt(sellerPayoff) * 2n]; // Attempt to withdraw the funds, expecting revert await expect( @@ -859,7 +889,7 @@ describe("IBosonFundsHandler", function () { it("Caller tries to withdraw the same token twice", async function () { // Withdraw token - tokenList = [mockToken.address, mockToken.address]; + tokenList = [await mockToken.getAddress(), await mockToken.getAddress()]; tokenAmounts = [sellerPayoff, sellerPayoff]; // Attempt to withdraw the funds, expecting revert @@ -870,7 +900,7 @@ describe("IBosonFundsHandler", function () { it("Nothing to withdraw", async function () { // Withdraw token - tokenList = [mockToken.address]; + tokenList = [await mockToken.getAddress()]; tokenAmounts = ["0"]; await expect( @@ -893,7 +923,7 @@ describe("IBosonFundsHandler", function () { // commit to offer on behalf of some contract tx = await exchangeHandler .connect(buyer) - .commitToOffer(fallbackErrorContract.address, offerNative.id, { value: price }); + .commitToOffer(await fallbackErrorContract.getAddress(), offerNative.id, { value: price }); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId; @@ -905,9 +935,9 @@ describe("IBosonFundsHandler", function () { // we call a fallbackContract which calls fundsHandler.withdraw, which should revert await expect( fallbackErrorContract.withdrawFunds( - fundsHandler.address, + await fundsHandler.getAddress(), fallbackContractBuyerId, - [ethers.constants.AddressZero], + [ZeroAddress], [offerNative.price] ) ).to.revertedWith(RevertReasons.TOKEN_TRANSFER_FAILED); @@ -920,7 +950,7 @@ describe("IBosonFundsHandler", function () { // commit to offer on behalf of some contract tx = await exchangeHandler .connect(buyer) - .commitToOffer(fallbackErrorContract.address, offerNative.id, { value: price }); + .commitToOffer(await fallbackErrorContract.getAddress(), offerNative.id, { value: price }); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId; @@ -932,9 +962,9 @@ describe("IBosonFundsHandler", function () { // we call a fallbackContract which calls fundsHandler.withdraw, which should revert await expect( fallbackErrorContract.withdrawFunds( - fundsHandler.address, + await fundsHandler.getAddress(), fallbackContractBuyerId, - [ethers.constants.AddressZero], + [ZeroAddress], [offerNative.price] ) ).to.revertedWith(RevertReasons.TOKEN_TRANSFER_FAILED); @@ -961,13 +991,17 @@ describe("IBosonFundsHandler", function () { it("Transfer of funds failed - ERC20 transfer returns false", async function () { const [foreign20ReturnFalse] = await deployMockTokens(["Foreign20TransferReturnFalse"]); - await foreign20ReturnFalse.connect(assistant).mint(assistant.address, sellerDeposit); + await foreign20ReturnFalse.connect(assistant).mint(await assistant.getAddress(), sellerDeposit); await foreign20ReturnFalse.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); - await fundsHandler.connect(assistant).depositFunds(seller.id, foreign20ReturnFalse.address, sellerDeposit); + await fundsHandler + .connect(assistant) + .depositFunds(seller.id, await foreign20ReturnFalse.getAddress(), sellerDeposit); await expect( - fundsHandler.connect(assistant).withdrawFunds(seller.id, [foreign20ReturnFalse.address], [sellerDeposit]) + fundsHandler + .connect(assistant) + .withdrawFunds(seller.id, [await foreign20ReturnFalse.getAddress()], [sellerDeposit]) ).to.revertedWith(RevertReasons.SAFE_ERC20_NOT_SUCCEEDED); }); }); @@ -990,13 +1024,13 @@ describe("IBosonFundsHandler", function () { buyerPayoff = 0; // seller: sellerDeposit + offerToken.price - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.price).toString(); + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price); // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; + protocolPayoff = BigInt(offerTokenProtocolFee); // grant fee collecor role - await accessController.grantRole(Role.FEE_COLLECTOR, feeCollector.address); + await accessController.grantRole(Role.FEE_COLLECTOR, await feeCollector.getAddress()); // set the protocol id protocolId = "0"; @@ -1004,23 +1038,29 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsWithdrawn event", async function () { // Withdraw funds, testing for the event - tokenList = [mockToken.address, ethers.constants.AddressZero]; + tokenList = [await mockToken.getAddress(), ZeroAddress]; tokenAmounts = [protocolPayoff, protocolPayoff]; // protocol fee withdrawal const tx = await fundsHandler.connect(feeCollector).withdrawProtocolFees(tokenList, tokenAmounts); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(protocolId, protocolTreasury.address, mockToken.address, protocolPayoff, feeCollector.address); + .withArgs( + protocolId, + await protocolTreasury.getAddress(), + await mockToken.getAddress(), + protocolPayoff, + await feeCollector.getAddress() + ); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") .withArgs( protocolId, - protocolTreasury.address, - ethers.constants.Zero, + await protocolTreasury.getAddress(), + 0n, protocolPayoff, - feeCollector.address + await feeCollector.getAddress() ); }); @@ -1029,52 +1069,48 @@ describe("IBosonFundsHandler", function () { protocolAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) ); - const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); + + + const protocolTreasuryNativeBalanceBefore = await provider.getBalance(await protocolTreasury.getAddress()); + const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(await protocolTreasury.getAddress()); // Chain state should match the expected available funds before the withdrawal expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", protocolPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff.toString()), + new Funds(ZeroAddress, "Native currency", protocolPayoff.toString()), ]); + expect(protocolAvailableFunds).to.eql( expectedProtocolAvailableFunds, "Protocol available funds mismatch before withdrawal" ); // withdraw funds - const partialFeeWithdrawAmount = ethers.BigNumber.from(protocolPayoff) - .sub(ethers.utils.parseUnits("0.01", "ether")) - .toString(); - + const partialFeeWithdrawAmount = BigInt(protocolPayoff) - parseUnits("0.01", "ether"); tx = await fundsHandler .connect(feeCollector) .withdrawProtocolFees( - [mockToken.address, ethers.constants.AddressZero], + [await mockToken.getAddress(), ZeroAddress], [protocolPayoff, partialFeeWithdrawAmount] ); // calcualte tx costs txReceipt = await tx.wait(); - txCost = tx.gasPrice.mul(txReceipt.gasUsed); + txCost = tx.gasPrice * txReceipt.gasUsed; // Read on chain state protocolAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) ); - const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); + const protocolTreasuryNativeBalanceAfter = await provider.getBalance(await protocolTreasury.getAddress()); + const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(await protocolTreasury.getAddress()); // Chain state should match the expected available funds after the withdrawal // Native currency available funds are reduced for the withdrawal amount // Mock token is fully withdrawn expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds( - ethers.constants.AddressZero, - "Native currency", - ethers.BigNumber.from(protocolPayoff).sub(partialFeeWithdrawAmount).toString() - ), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", (BigInt(protocolPayoff) - partialFeeWithdrawAmount).toString()), ]); expect(protocolAvailableFunds).to.eql( @@ -1083,12 +1119,12 @@ describe("IBosonFundsHandler", function () { ); // Native currency balance is increased for the partialFeeWithdrawAmount expect(protocolTreasuryNativeBalanceAfter).to.eql( - protocolTreasuryNativeBalanceBefore.add(partialFeeWithdrawAmount), + protocolTreasuryNativeBalanceBefore + partialFeeWithdrawAmount, "Fee collector token balance mismatch" ); // Token balance is increased for the protocol fee expect(protocolTreasuryTokenBalanceAfter).to.eql( - protocolTreasuryTokenBalanceBefore.add(protocolPayoff), + protocolTreasuryTokenBalanceBefore + BigInt(protocolPayoff), "Fee collector token balance mismatch" ); }); @@ -1098,14 +1134,15 @@ describe("IBosonFundsHandler", function () { protocolAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) ); - const protocolTreasuryNativeBalanceBefore = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(protocolTreasury.address); + const protocolTreasuryNativeBalanceBefore = await provider.getBalance(await protocolTreasury.getAddress()); + const protocolTreasuryTokenBalanceBefore = await mockToken.balanceOf(await protocolTreasury.getAddress()); // Chain state should match the expected available funds before the withdrawal expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", protocolPayoff), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff.toString()), + new Funds(ZeroAddress, "Native currency", protocolPayoff.toString()), ]); + expect(protocolAvailableFunds).to.eql( expectedProtocolAvailableFunds, "Protocol available funds mismatch before withdrawal" @@ -1116,20 +1153,20 @@ describe("IBosonFundsHandler", function () { // calcualte tx costs txReceipt = await tx.wait(); - txCost = tx.gasPrice.mul(txReceipt.gasUsed); + txCost = tx.gasPrice * txReceipt.gasUsed; // Read on chain state protocolAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) ); - const protocolTreasuryNativeBalanceAfter = await ethers.provider.getBalance(protocolTreasury.address); - const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(protocolTreasury.address); + const protocolTreasuryNativeBalanceAfter = await provider.getBalance(await protocolTreasury.getAddress()); + const protocolTreasuryTokenBalanceAfter = await mockToken.balanceOf(await protocolTreasury.getAddress()); // Chain state should match the expected available funds after the withdrawal // Funds available should be an empty list expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expect(protocolAvailableFunds).to.eql( expectedProtocolAvailableFunds, @@ -1137,21 +1174,22 @@ describe("IBosonFundsHandler", function () { ); // Native currency balance is increased for the partialFeeWithdrawAmount expect(protocolTreasuryNativeBalanceAfter).to.eql( - protocolTreasuryNativeBalanceBefore.add(protocolPayoff), + protocolTreasuryNativeBalanceBefore + protocolPayoff, "Fee collector native currency balance mismatch" ); // Token balance is increased for the protocol fee expect(protocolTreasuryTokenBalanceAfter).to.eql( - protocolTreasuryTokenBalanceBefore.add(protocolPayoff), + protocolTreasuryTokenBalanceBefore + protocolPayoff, "Fee collector token balance mismatch" ); }); + it("It's possible to withdraw same token twice if in total enough available funds", async function () { - let reduction = ethers.utils.parseUnits("0.01", "ether").toString(); + let reduction = parseUnits("0.01", "ether"); // Withdraw token - tokenList = [mockToken.address, mockToken.address]; - tokenAmounts = [ethers.BigNumber.from(protocolPayoff).sub(reduction).toString(), reduction]; + tokenList = [await mockToken.getAddress(), await mockToken.getAddress()]; + tokenAmounts = [BigInt(protocolPayoff) - reduction, reduction]; // protocol fee withdrawal const tx = await fundsHandler.connect(feeCollector).withdrawProtocolFees(tokenList, tokenAmounts); @@ -1159,21 +1197,27 @@ describe("IBosonFundsHandler", function () { .to.emit(fundsHandler, "FundsWithdrawn") .withArgs( protocolId, - protocolTreasury.address, - mockToken.address, - ethers.BigNumber.from(protocolPayoff).sub(reduction).toString(), - feeCollector.address + await protocolTreasury.getAddress(), + await mockToken.getAddress(), + BigInt(protocolPayoff) - reduction, + await feeCollector.getAddress() ); await expect(tx) .to.emit(fundsHandler, "FundsWithdrawn") - .withArgs(protocolId, protocolTreasury.address, mockToken.address, reduction, feeCollector.address); + .withArgs( + protocolId, + await protocolTreasury.getAddress(), + await mockToken.getAddress(), + reduction, + await feeCollector.getAddress() + ); }); context("💔 Revert Reasons", async function () { it("The funds region of protocol is paused", async function () { // Withdraw funds, testing for the event - tokenList = [mockToken.address, ethers.constants.AddressZero]; + tokenList = [await mockToken.getAddress(), ZeroAddress]; tokenAmounts = [protocolPayoff, protocolPayoff]; // Pause the funds region of the protocol @@ -1194,7 +1238,7 @@ describe("IBosonFundsHandler", function () { it("Token list address does not match token amount address", async function () { // Withdraw token - tokenList = [mockToken.address, ethers.constants.AddressZero]; + tokenList = [await mockToken.getAddress(), ZeroAddress]; tokenAmounts = [sellerPayoff]; // Attempt to withdraw the funds, expecting revert @@ -1205,8 +1249,8 @@ describe("IBosonFundsHandler", function () { it("Caller tries to withdraw more than they have in the available funds", async function () { // Withdraw token - tokenList = [mockToken.address]; - tokenAmounts = [ethers.BigNumber.from(offerTokenProtocolFee).mul("2")]; + tokenList = [await mockToken.getAddress()]; + tokenAmounts = [BigInt(offerTokenProtocolFee) * 2n]; // Attempt to withdraw the funds, expecting revert await expect( @@ -1216,7 +1260,7 @@ describe("IBosonFundsHandler", function () { it("Caller tries to withdraw the same token twice", async function () { // Withdraw token - tokenList = [mockToken.address, mockToken.address]; + tokenList = [await mockToken.getAddress(), await mockToken.getAddress()]; tokenAmounts = [offerTokenProtocolFee, offerTokenProtocolFee]; // Attempt to withdraw the funds, expecting revert @@ -1227,7 +1271,7 @@ describe("IBosonFundsHandler", function () { it("Nothing to withdraw", async function () { // Withdraw token - tokenList = [mockToken.address]; + tokenList = [await mockToken.getAddress()]; tokenAmounts = ["0"]; await expect( @@ -1248,16 +1292,14 @@ describe("IBosonFundsHandler", function () { const [fallbackErrorContract] = await deployMockTokens(["FallbackError"]); // temporarily grant ADMIN role to deployer account - await accessController.grantRole(Role.ADMIN, deployer.address); + await accessController.grantRole(Role.ADMIN, await deployer.getAddress()); // set treasury to this contract - await configHandler.connect(deployer).setTreasuryAddress(fallbackErrorContract.address); + await configHandler.connect(deployer).setTreasuryAddress(await fallbackErrorContract.getAddress()); // attempt to withdraw the funds, expecting revert await expect( - fundsHandler - .connect(feeCollector) - .withdrawProtocolFees([ethers.constants.AddressZero], [offerNativeProtocolFee]) + fundsHandler.connect(feeCollector).withdrawProtocolFees([ZeroAddress], [offerNativeProtocolFee]) ).to.revertedWith(RevertReasons.TOKEN_TRANSFER_FAILED); }); @@ -1266,16 +1308,14 @@ describe("IBosonFundsHandler", function () { const [fallbackErrorContract] = await deployMockTokens(["WithoutFallbackError"]); // temporarily grant ADMIN role to deployer account - await accessController.grantRole(Role.ADMIN, deployer.address); + await accessController.grantRole(Role.ADMIN, await deployer.getAddress()); // set treasury to this contract - await configHandler.connect(deployer).setTreasuryAddress(fallbackErrorContract.address); + await configHandler.connect(deployer).setTreasuryAddress(await fallbackErrorContract.getAddress()); // attempt to withdraw the funds, expecting revert await expect( - fundsHandler - .connect(feeCollector) - .withdrawProtocolFees([ethers.constants.AddressZero], [offerNativeProtocolFee]) + fundsHandler.connect(feeCollector).withdrawProtocolFees([ZeroAddress], [offerNativeProtocolFee]) ).to.revertedWith(RevertReasons.TOKEN_TRANSFER_FAILED); }); @@ -1306,21 +1346,21 @@ describe("IBosonFundsHandler", function () { const [mockToken] = await deployMockTokens(["Foreign20NoName"]); // top up assistants account - await mockToken.mint(assistant.address, "1000000"); + await mockToken.mint(await assistant.getAddress(), "1000000"); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, "1000000"); // Deposit token - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), depositAmount); // Read on chain state let returnedAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, [mockToken.address]) + await fundsHandler.getAvailableFunds(seller.id, [await mockToken.getAddress()]) ); // Chain state should match the expected available funds let expectedAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Token name unspecified", depositAmount), + new Funds(await mockToken.getAddress(), "Token name unspecified", depositAmount.toString()), ]); expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); }); @@ -1332,7 +1372,12 @@ describe("IBosonFundsHandler", function () { context("📋 FundsLib Methods", async function () { beforeEach(async function () { // Create a valid seller - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); + seller = mockSeller( + await assistant.getAddress(), + await admin.getAddress(), + clerk.address, + await treasury.getAddress() + ); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1347,19 +1392,19 @@ describe("IBosonFundsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - assistantDR.address, - adminDR.address, + await assistantDR.getAddress(), + await adminDR.getAddress(), clerkDR.address, - treasuryDR.address, + await treasuryDR.getAddress(), true ); expect(disputeResolver.isValid()).is.true; //Create DisputeResolverFee array so offer creation will succeed - DRFee = ethers.utils.parseUnits("0", "ether").toString(); + DRFee = parseUnits("0", "ether").toString(); disputeResolverFees = [ - new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0"), - new DisputeResolverFee(mockToken.address, "mockToken", DRFee), + new DisputeResolverFee(ZeroAddress, "Native", "0"), + new DisputeResolverFee(await mockToken.getAddress(), "mockToken", DRFee), ]; // Make empty seller list, so every seller is allowed @@ -1378,7 +1423,7 @@ describe("IBosonFundsHandler", function () { offerToken = offerNative.clone(); offerToken.id = "2"; - offerToken.exchangeToken = mockToken.address; + offerToken.exchangeToken = await mockToken.getAddress(); offerDates = mo.offerDates; expect(offerDates.isValid()).is.true; @@ -1405,26 +1450,26 @@ describe("IBosonFundsHandler", function () { resolutionPeriod = offerDurations.resolutionPeriod; // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); + await mockToken.mint(await assistant.getAddress(), `${2 * sellerDeposit}`); + await mockToken.mint(await buyer.getAddress(), `${2 * price}`); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); await fundsHandler .connect(assistant) - .depositFunds(seller.id, ethers.constants.AddressZero, `${2 * sellerDeposit}`, { - value: `${2 * sellerDeposit}`, - }); + .depositFunds(seller.id, await mockToken.getAddress(), `${2 * sellerDeposit}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, ZeroAddress, `${2 * sellerDeposit}`, { + value: `${2 * sellerDeposit}`, + }); // Agents // Create a valid agent, agentId = "3"; agentFeePercentage = "500"; //5% - agent = mockAgent(other.address); + agent = mockAgent(await other.getAddress()); expect(agent.isValid()).is.true; @@ -1437,7 +1482,7 @@ describe("IBosonFundsHandler", function () { randoBuyerId = "4"; // 1: seller, 2: disputeResolver, 3: agent, 4: rando - availableFundsAddresses = [mockToken.address, ethers.constants.AddressZero]; + availableFundsAddresses = [await mockToken.getAddress(), ZeroAddress]; }); afterEach(async function () { @@ -1450,45 +1495,47 @@ describe("IBosonFundsHandler", function () { let buyerId = "4"; // 1: seller, 2: disputeResolver, 3: agent, 4: buyer // Commit to an offer with erc20 token, test for FundsEncumbered event - const tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + const tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); await expect(tx) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(buyerId, mockToken.address, price, buyer.address); + .withArgs(buyerId, await mockToken.getAddress(), price, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(seller.id, mockToken.address, sellerDeposit, buyer.address); + .withArgs(seller.id, await mockToken.getAddress(), sellerDeposit, await buyer.getAddress()); // Commit to an offer with native currency, test for FundsEncumbered event - const tx2 = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); + const tx2 = await exchangeHandler + .connect(buyer) + .commitToOffer(await buyer.getAddress(), offerNative.id, { value: price }); await expect(tx2) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(buyerId, ethers.constants.AddressZero, price, buyer.address); + .withArgs(buyerId, ZeroAddress, price, await buyer.getAddress()); await expect(tx2) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(seller.id, ethers.constants.AddressZero, sellerDeposit, buyer.address); + .withArgs(seller.id, ZeroAddress, sellerDeposit, await buyer.getAddress()); }); it("should update state", async function () { // contract token value const contractTokenBalanceBefore = await mockToken.balanceOf(protocolDiamondAddress); // contract native token balance - const contractNativeBalanceBefore = await ethers.provider.getBalance(protocolDiamondAddress); + const contractNativeBalanceBefore = await provider.getBalance(protocolDiamondAddress); // seller's available funds const sellersAvailableFundsBefore = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); // Commit to an offer with erc20 token - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); // Check that token balance increased const contractTokenBalanceAfter = await mockToken.balanceOf(protocolDiamondAddress); // contract token balance should increase for the incoming price // seller's deposit was already held in the contract's pool before - expect(contractTokenBalanceAfter.sub(contractTokenBalanceBefore).toString()).to.eql( - price, + expect(contractTokenBalanceAfter - contractTokenBalanceBefore).to.eql( + BigInt(price), "Token wrong balance increase" ); @@ -1498,20 +1545,19 @@ describe("IBosonFundsHandler", function () { ); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit expect( - ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) - .sub(ethers.BigNumber.from(sellersAvailableFundsAfter.funds[0].availableAmount)) - .toString() - ).to.eql(sellerDeposit, "Token seller available funds mismatch"); + BigInt(sellersAvailableFundsBefore.funds[0].availableAmount) - + BigInt(sellersAvailableFundsAfter.funds[0].availableAmount) + ).to.eql(BigInt(sellerDeposit), "Token seller available funds mismatch"); // Commit to an offer with native currency - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerNative.id, { value: price }); // check that native currency balance increased - const contractNativeBalanceAfter = await ethers.provider.getBalance(protocolDiamondAddress); + const contractNativeBalanceAfter = await provider.getBalance(protocolDiamondAddress); // contract token balance should increase for the incoming price // seller's deposit was already held in the contract's pool before - expect(contractNativeBalanceAfter.sub(contractNativeBalanceBefore).toString()).to.eql( - price, + expect(contractNativeBalanceAfter - contractNativeBalanceBefore).to.eql( + BigInt(price), "Native currency wrong balance increase" ); @@ -1521,55 +1567,53 @@ describe("IBosonFundsHandler", function () { ); // native currency is the second on the list of the available funds and the amount should be decreased for the sellerDeposit expect( - ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) - .sub(ethers.BigNumber.from(sellersAvailableFundsAfter.funds[1].availableAmount)) - .toString() - ).to.eql(sellerDeposit, "Native currency seller available funds mismatch"); + BigInt(sellersAvailableFundsBefore.funds[1].availableAmount) - + BigInt(sellersAvailableFundsAfter.funds[1].availableAmount) + ).to.eql(BigInt(sellerDeposit), "Native currency seller available funds mismatch"); }); it("when someone else deposits on buyer's behalf, callers funds are transferred", async function () { // buyer will commit to an offer on rando's behalf // get token balance before the commit - const buyerTokenBalanceBefore = await mockToken.balanceOf(buyer.address); - const randoTokenBalanceBefore = await mockToken.balanceOf(rando.address); + const buyerTokenBalanceBefore = await mockToken.balanceOf(await buyer.getAddress()); + const randoTokenBalanceBefore = await mockToken.balanceOf(await rando.getAddress()); // commit to an offer with token on rando's behalf - await exchangeHandler.connect(buyer).commitToOffer(rando.address, offerToken.id); + await exchangeHandler.connect(buyer).commitToOffer(await rando.getAddress(), offerToken.id); // get token balance after the commit - const buyerTokenBalanceAfter = await mockToken.balanceOf(buyer.address); - const randoTokenBalanceAfter = await mockToken.balanceOf(rando.address); + const buyerTokenBalanceAfter = await mockToken.balanceOf(await buyer.getAddress()); + const randoTokenBalanceAfter = await mockToken.balanceOf(await rando.getAddress()); // buyer's balance should decrease, rando's should remain - expect(buyerTokenBalanceBefore.sub(buyerTokenBalanceAfter).toString()).to.eql( - price, + expect(buyerTokenBalanceBefore - buyerTokenBalanceAfter).to.eql( + BigInt(price), "Buyer's token balance should decrease for a price" ); - expect(randoTokenBalanceAfter.toString()).to.eql( - randoTokenBalanceBefore.toString(), - "Rando's token balance should remain the same" - ); + expect(randoTokenBalanceAfter).to.eql(randoTokenBalanceBefore, "Rando's token balance should remain the same"); // make sure that rando is actually the buyer of the exchange let exchange; [, exchange] = await exchangeHandler.getExchange("1"); expect(exchange.buyerId.toString()).to.eql(randoBuyerId, "Wrong buyer id"); // get native currency balance before the commit - const buyerNativeBalanceBefore = await ethers.provider.getBalance(buyer.address); - const randoNativeBalanceBefore = await ethers.provider.getBalance(rando.address); + const buyerNativeBalanceBefore = await provider.getBalance(await buyer.getAddress()); + const randoNativeBalanceBefore = await provider.getBalance(await rando.getAddress()); // commit to an offer with native currency on rando's behalf - tx = await exchangeHandler.connect(buyer).commitToOffer(rando.address, offerNative.id, { value: price }); + tx = await exchangeHandler + .connect(buyer) + .commitToOffer(await rando.getAddress(), offerNative.id, { value: price }); txReceipt = await tx.wait(); - txCost = tx.gasPrice.mul(txReceipt.gasUsed); + txCost = tx.gasPrice * txReceipt.gasUsed; // get token balance after the commit - const buyerNativeBalanceAfter = await ethers.provider.getBalance(buyer.address); - const randoNativeBalanceAfter = await ethers.provider.getBalance(rando.address); + const buyerNativeBalanceAfter = await provider.getBalance(await buyer.getAddress()); + const randoNativeBalanceAfter = await provider.getBalance(await rando.getAddress()); // buyer's balance should decrease, rando's should remain - expect(buyerNativeBalanceBefore.sub(buyerNativeBalanceAfter).sub(txCost).toString()).to.eql( - price, + expect(buyerNativeBalanceBefore - buyerNativeBalanceAfter - txCost).to.eql( + BigInt(price), "Buyer's native balance should decrease for a price" ); expect(randoNativeBalanceAfter.toString()).to.eql( @@ -1582,21 +1626,21 @@ describe("IBosonFundsHandler", function () { // make sure that randoBuyerId actually belongs to rando address let [, buyerStruct] = await accountHandler.getBuyer(randoBuyerId); - expect(buyerStruct.wallet).to.eql(rando.address, "Wrong buyer address"); + expect(buyerStruct.wallet).to.eql(await rando.getAddress(), "Wrong await buyer.getAddress()"); }); it("if offer is preminted, only sellers funds are encumbered", async function () { // deposit to seller's pool to cover for the price const buyerId = mockBuyer().id; - await mockToken.mint(assistant.address, `${2 * price}`); + await mockToken.mint(await assistant.getAddress(), `${2 * price}`); await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * price}`); - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * price}`); - await fundsHandler.connect(assistant).depositFunds(seller.id, ethers.constants.AddressZero, `${2 * price}`, { + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), `${2 * price}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, ZeroAddress, `${2 * price}`, { value: `${2 * price}`, }); // get token balance before the commit - const buyerTokenBalanceBefore = await mockToken.balanceOf(buyer.address); + const buyerTokenBalanceBefore = await mockToken.balanceOf(await buyer.getAddress()); const sellersAvailableFundsBefore = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -1605,21 +1649,23 @@ describe("IBosonFundsHandler", function () { // reserve a range and premint vouchers await offerHandler .connect(assistant) - .reserveRange(offerToken.id, offerToken.quantityAvailable, assistant.address); - const voucherCloneAddress = calculateContractAddress(accountHandler.address, "1"); - const bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherCloneAddress); + .reserveRange(offerToken.id, offerToken.quantityAvailable, await assistant.getAddress()); + const voucherCloneAddress = calculateContractAddress(await accountHandler.getAddress(), "1"); + const bosonVoucher = await getContractAt("BosonVoucher", voucherCloneAddress); await bosonVoucher.connect(assistant).preMint(offerToken.id, offerToken.quantityAvailable); // commit to an offer via preminted voucher let exchangeId = "1"; let tokenId = deriveTokenId(offerToken.id, exchangeId); - tx = await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); + tx = await bosonVoucher + .connect(assistant) + .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId); // it should emit FundsEncumbered event with amount equal to sellerDeposit + price - let encumberedFunds = ethers.BigNumber.from(sellerDeposit).add(price); + let encumberedFunds = BigInt(sellerDeposit) + BigInt(price); await expect(tx) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(seller.id, mockToken.address, encumberedFunds, bosonVoucher.address); + .withArgs(seller.id, await mockToken.getAddress(), encumberedFunds, await bosonVoucher.getAddress()); // Check that seller's pool balance was reduced let sellersAvailableFundsAfter = FundsList.fromStruct( @@ -1627,13 +1673,12 @@ describe("IBosonFundsHandler", function () { ); // token is the first on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( - ethers.BigNumber.from(sellersAvailableFundsBefore.funds[0].availableAmount) - .sub(ethers.BigNumber.from(sellersAvailableFundsAfter.funds[0].availableAmount)) - .toString() - ).to.eql(encumberedFunds.toString(), "Token seller available funds mismatch"); + BigInt(sellersAvailableFundsBefore.funds[0].availableAmount) - + BigInt(sellersAvailableFundsAfter.funds[0].availableAmount) + ).to.eql(encumberedFunds, "Token seller available funds mismatch"); // buyer's token balance should stay the same - const buyerTokenBalanceAfter = await mockToken.balanceOf(buyer.address); + const buyerTokenBalanceAfter = await mockToken.balanceOf(await buyer.getAddress()); expect(buyerTokenBalanceBefore.toString()).to.eql( buyerTokenBalanceAfter.toString(), "Buyer's token balance should remain the same" @@ -1645,27 +1690,29 @@ describe("IBosonFundsHandler", function () { expect(exchange.buyerId.toString()).to.eql(buyerId, "Wrong buyer id"); // get native currency balance before the commit - const buyerNativeBalanceBefore = await ethers.provider.getBalance(buyer.address); + const buyerNativeBalanceBefore = await provider.getBalance(await buyer.getAddress()); // reserve a range and premint vouchers exchangeId = await exchangeHandler.getNextExchangeId(); tokenId = deriveTokenId(offerNative.id, exchangeId); await offerHandler .connect(assistant) - .reserveRange(offerNative.id, offerNative.quantityAvailable, assistant.address); + .reserveRange(offerNative.id, offerNative.quantityAvailable, await assistant.getAddress()); await bosonVoucher.connect(assistant).preMint(offerNative.id, offerNative.quantityAvailable); // commit to an offer via preminted voucher - tx = await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); + tx = await bosonVoucher + .connect(assistant) + .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId); // it should emit FundsEncumbered event with amount equal to sellerDeposit + price - encumberedFunds = ethers.BigNumber.from(sellerDeposit).add(price); + encumberedFunds = BigInt(sellerDeposit) + BigInt(price); await expect(tx) .to.emit(exchangeHandler, "FundsEncumbered") - .withArgs(seller.id, ethers.constants.AddressZero, encumberedFunds, bosonVoucher.address); + .withArgs(seller.id, ZeroAddress, encumberedFunds, await bosonVoucher.getAddress()); // buyer's balance should remain the same - const buyerNativeBalanceAfter = await ethers.provider.getBalance(buyer.address); + const buyerNativeBalanceAfter = await provider.getBalance(await buyer.getAddress()); expect(buyerNativeBalanceBefore.toString()).to.eql( buyerNativeBalanceAfter.toString(), "Buyer's native balance should remain the same" @@ -1677,10 +1724,9 @@ describe("IBosonFundsHandler", function () { ); // native currency the second on the list of the available funds and the amount should be decreased for the sellerDeposit and price expect( - ethers.BigNumber.from(sellersAvailableFundsBefore.funds[1].availableAmount) - .sub(ethers.BigNumber.from(sellersAvailableFundsAfter.funds[1].availableAmount)) - .toString() - ).to.eql(encumberedFunds.toString(), "Native currency seller available funds mismatch"); + BigInt(sellersAvailableFundsBefore.funds[1].availableAmount) - + BigInt(sellersAvailableFundsAfter.funds[1].availableAmount) + ).to.eql(encumberedFunds, "Native currency seller available funds mismatch"); // make sure that buyer is actually the buyer of the exchange [, exchange] = await exchangeHandler.getExchange(exchangeId); @@ -1693,14 +1739,14 @@ describe("IBosonFundsHandler", function () { await expect( exchangeHandler .connect(buyer) - .commitToOffer(buyer.address, offerNative.id, { value: ethers.BigNumber.from(price).sub("1").toString() }) + .commitToOffer(await buyer.getAddress(), offerNative.id, { value: BigInt(price) - 1n }) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); it("Native currency sent together with ERC20 token transfer", async function () { // Attempt to commit to an offer, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id, { value: price }) + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id, { value: price }) ).to.revertedWith(RevertReasons.NATIVE_NOT_ALLOWED); }); @@ -1709,7 +1755,7 @@ describe("IBosonFundsHandler", function () { [bosonToken] = await deployMockTokens(["BosonToken"]); // create an offer with a bad token contrat - offerToken.exchangeToken = bosonToken.address; + offerToken.exchangeToken = await bosonToken.getAddress(); offerToken.id = "3"; // add to DR fees @@ -1723,14 +1769,14 @@ describe("IBosonFundsHandler", function () { .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert - await expect(exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id)).to.revertedWith( - RevertReasons.SAFE_ERC20_LOW_LEVEL_CALL - ); + await expect( + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id) + ).to.revertedWith(RevertReasons.SAFE_ERC20_LOW_LEVEL_CALL); }); it("Token address is not a contract", async function () { // create an offer with a bad token contrat - offerToken.exchangeToken = admin.address; + offerToken.exchangeToken = await admin.getAddress(); offerToken.id = "3"; // add to DR fees @@ -1746,7 +1792,7 @@ describe("IBosonFundsHandler", function () { // Attempt to commit to an offer, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id) + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id) ).to.revertedWithoutReason(); }); @@ -1755,35 +1801,33 @@ describe("IBosonFundsHandler", function () { // approve more than account actually have await mockToken.connect(rando).approve(protocolDiamondAddress, price); // Attempt to commit to an offer, expecting revert - await expect(exchangeHandler.connect(rando).commitToOffer(rando.address, offerToken.id)).to.revertedWith( - RevertReasons.ERC20_EXCEEDS_BALANCE - ); + await expect( + exchangeHandler.connect(rando).commitToOffer(await rando.getAddress(), offerToken.id) + ).to.revertedWith(RevertReasons.ERC20_EXCEEDS_BALANCE); // not approved - await mockToken - .connect(rando) - .approve(protocolDiamondAddress, ethers.BigNumber.from(price).sub("1").toString()); + await mockToken.connect(rando).approve(protocolDiamondAddress, BigInt(price) - 1n); // Attempt to commit to an offer, expecting revert - await expect(exchangeHandler.connect(rando).commitToOffer(rando.address, offerToken.id)).to.revertedWith( - RevertReasons.ERC20_INSUFFICIENT_ALLOWANCE - ); + await expect( + exchangeHandler.connect(rando).commitToOffer(await rando.getAddress(), offerToken.id) + ).to.revertedWith(RevertReasons.ERC20_INSUFFICIENT_ALLOWANCE); }); it("Seller'a availableFunds is less than the required sellerDeposit", async function () { // create an offer with token with higher seller deposit - offerToken.sellerDeposit = ethers.BigNumber.from(offerToken.sellerDeposit).mul("4"); + offerToken.sellerDeposit = BigInt(offerToken.sellerDeposit) * 4n; offerToken.id = "3"; await offerHandler .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert - await expect(exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id)).to.revertedWith( - RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS - ); + await expect( + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id) + ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); // create an offer with native currency with higher seller deposit - offerNative.sellerDeposit = ethers.BigNumber.from(offerNative.sellerDeposit).mul("4"); + offerNative.sellerDeposit = BigInt(offerNative.sellerDeposit) * 4n; offerNative.id = "4"; await offerHandler .connect(assistant) @@ -1791,7 +1835,7 @@ describe("IBosonFundsHandler", function () { // Attempt to commit to an offer, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerNative.id, { value: price }) + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerNative.id, { value: price }) ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); }); @@ -1799,9 +1843,9 @@ describe("IBosonFundsHandler", function () { // reserve a range and premint vouchers for offer in tokens await offerHandler .connect(assistant) - .reserveRange(offerToken.id, offerToken.quantityAvailable, assistant.address); - const voucherCloneAddress = calculateContractAddress(accountHandler.address, "1"); - const bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherCloneAddress); + .reserveRange(offerToken.id, offerToken.quantityAvailable, await assistant.getAddress()); + const voucherCloneAddress = calculateContractAddress(await accountHandler.getAddress(), "1"); + const bosonVoucher = await getContractAt("BosonVoucher", voucherCloneAddress); await bosonVoucher.connect(assistant).preMint(offerToken.id, offerToken.quantityAvailable); // Seller's availableFunds is 2*sellerDeposit which is less than sellerDeposit + price. @@ -1810,7 +1854,9 @@ describe("IBosonFundsHandler", function () { // Attempt to commit to an offer via preminted voucher, expecting revert let tokenId = deriveTokenId(offerToken.id, "1"); await expect( - bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) + bosonVoucher + .connect(assistant) + .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId) ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); // reserve a range and premint vouchers for offer in native currency @@ -1818,12 +1864,14 @@ describe("IBosonFundsHandler", function () { tokenId = deriveTokenId(offerNative.id, exchangeId); await offerHandler .connect(assistant) - .reserveRange(offerNative.id, offerNative.quantityAvailable, assistant.address); + .reserveRange(offerNative.id, offerNative.quantityAvailable, await assistant.getAddress()); await bosonVoucher.connect(assistant).preMint(offerNative.id, offerNative.quantityAvailable); // Attempt to commit to an offer, expecting revert await expect( - bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) + bosonVoucher + .connect(assistant) + .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId) ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); }); @@ -1832,16 +1880,16 @@ describe("IBosonFundsHandler", function () { const [Foreign20WithFee] = await deployMockTokens(["Foreign20WithFee"]); // add to DR fees - DRFee = ethers.utils.parseUnits("0", "ether").toString(); + DRFee = parseUnits("0", "ether").toString(); await accountHandler .connect(adminDR) .addFeesToDisputeResolver(disputeResolverId, [ - new DisputeResolverFee(Foreign20WithFee.address, "Foreign20WithFee", DRFee), + new DisputeResolverFee(await Foreign20WithFee.getAddress(), "Foreign20WithFee", DRFee), ]); // Create an offer with ERC20 with fees // Prepare an absolute zero offer - offerToken.exchangeToken = Foreign20WithFee.address; + offerToken.exchangeToken = await Foreign20WithFee.getAddress(); offerToken.sellerDeposit = "0"; offerToken.id++; @@ -1851,70 +1899,69 @@ describe("IBosonFundsHandler", function () { .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // mint tokens and approve - await Foreign20WithFee.mint(buyer.address, offerToken.price); + await Foreign20WithFee.mint(await buyer.getAddress(), offerToken.price); await Foreign20WithFee.connect(buyer).approve(protocolDiamondAddress, offerToken.price); // Attempt to commit to offer, expecting revert - await expect(exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id)).to.revertedWith( - RevertReasons.INSUFFICIENT_VALUE_RECEIVED - ); + await expect( + exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); }); + }); - context("👉 releaseFunds()", async function () { - beforeEach(async function () { - // ids - protocolId = "0"; - buyerId = "4"; - exchangeId = "1"; - - // commit to offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - }); + context("👉 releaseFunds()", async function () { + beforeEach(async function () { + // ids + protocolId = "0"; + buyerId = "4"; + exchangeId = "1"; - context("Final state COMPLETED", async function () { - beforeEach(async function () { - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + // commit to offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); + }); + context("Final state COMPLETED", async function () { + beforeEach(async function () { + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ( + BigInt(offerToken.sellerDeposit) + + BigInt(offerToken.price) - + BigInt(offerTokenProtocolFee) + ).toString(); - // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; - }); + // protocol: protocolFee + protocolPayoff = offerTokenProtocolFee; + }); - it("should emit a FundsReleased event", async function () { - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + it("should emit a FundsReleased event", async function () { + // Complete the exchange, expecting event + const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); - }); + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); + }); - it("should update state", async function () { - // commit again, so seller has nothing in available funds - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + it("should update state", async function () { + // commit again, so seller has nothing in available funds + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); buyerAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) ); @@ -1924,36 +1971,35 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - - const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Complete the exchange so the funds are released - await exchangeHandler.connect(buyer).completeExchange(exchangeId); + // Complete the exchange so the funds are released + await exchangeHandler.connect(buyer).completeExchange(exchangeId); - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocolFee - agentFee - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocolFee - agentFee + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", offerTokenProtocolFee), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", offerTokenProtocolFee), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -1967,16 +2013,55 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // complete another exchange so we test funds are only updated, no new entry is created + await exchangeHandler.connect(buyer).redeemVoucher(++exchangeId); + await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + BigInt(sellerPayoff) * 2n + ); + expectedProtocolAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + BigInt(protocolPayoff) * 2n + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); - // complete another exchange so we test funds are only updated, no new entry is created - await exchangeHandler.connect(buyer).redeemVoucher(++exchangeId); - await exchangeHandler.connect(buyer).completeExchange(exchangeId); + }); - sellersAvailableFunds = FundsList.fromStruct( + context("Final state REVOKED", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: sellerDeposit + price + buyerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price); + + // seller: 0 + sellerPayoff = 0; + + // protocol: 0 + protocolPayoff = 0; + }); + + it("should emit a FundsReleased event", async function () { + // Revoke the voucher, expecting event + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); buyerAvailableFunds = FundsList.fromStruct( @@ -1988,71 +2073,111 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); - expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(sellerPayoff).mul(2).toString() - ); - expectedProtocolAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(protocolPayoff).mul(2).toString() - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", (2n * BigInt(sellerDeposit)).toString()), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - context("Offer has an agent", async function () { + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Revoke the voucher so the funds are released + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); + + // Available funds should be increased for + // buyer: sellerDeposit + price + // seller: 0 + // protocol: 0 + // agent: 0 + // + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", BigInt(buyerPayoff)), + new Funds(ZeroAddress, "Native currency", "0") + ]); + + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Test that if buyer has some funds available, and gets more, the funds are only updated + // Commit again + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); + + // Revoke another voucher + await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); + + // Available funds should be increased for + // buyer: sellerDeposit + price + // seller: 0; but during the commitToOffer, sellerDeposit is encumbered + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + BigInt(buyerPayoff) * 2n + ); + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + + context("Offer has an agent", async function () { beforeEach(async function () { // Create Agent offer await offerHandler .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - - // succesfully redeem exchange - exchangeId = "2"; - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // top up seller's and buyer's account + await mockToken.mint(await assistant.getAddress(), `${2 * sellerDeposit}`); + await mockToken.mint(await buyer.getAddress(), `${2 * price}`); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // approve protocol to transfer the tokens + await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); + await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), `${2 * sellerDeposit}`); - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .toString(); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; - }); + // expected payoffs + // buyer: sellerDeposit + price + buyerPayoff = BigInt(agentOffer.sellerDeposit) + BigInt(agentOffer.price); - it("should emit a FundsReleased event", async function () { - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + // seller: 0 + sellerPayoff = 0; - // Complete the exchange, expecting event - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + // protocol: 0 + protocolPayoff = 0; - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + // agent: 0 + agentPayoff = 0; - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + exchangeId = "2"; }); it("should update state", async function () { @@ -2072,12 +2197,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", `${2 * sellerDeposit}`), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -2089,22 +2214,17 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Complete the exchange so the funds are released - await exchangeHandler.connect(buyer).completeExchange(exchangeId); + // Revoke the voucher so the funds are released + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocolFee - agentFee - // protocol: protocolFee - // agent: agentFee - expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); - expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", agentOfferProtocolFee), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - expectedAgentAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", agentPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + // buyer: sellerDeposit + price + // seller: 0 + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2122,235 +2242,13 @@ describe("IBosonFundsHandler", function () { expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - }); - - context("Final state REVOKED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: sellerDeposit + price - buyerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.price).toString(); - - // seller: 0 - sellerPayoff = 0; - // protocol: 0 - protocolPayoff = 0; - }); + // Test that if buyer has some funds available, and gets more, the funds are only updated + // Commit again + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - it("should emit a FundsReleased event", async function () { - // Revoke the voucher, expecting event - await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); - }); - - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Revoke the voucher so the funds are released - await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); - - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0 - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Test that if buyer has some funds available, and gets more, the funds are only updated - // Commit again - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); - - // Revoke another voucher - await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); - - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0; but during the commitToOffer, sellerDeposit is encumbered - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds.funds[0] = new Funds( - mockToken.address, - "Foreign20", - ethers.BigNumber.from(buyerPayoff).mul(2).toString() - ); - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); - - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); - await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); - - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - - // expected payoffs - // buyer: sellerDeposit + price - buyerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit).add(agentOffer.price).toString(); - - // seller: 0 - sellerPayoff = 0; - - // protocol: 0 - protocolPayoff = 0; - - // agent: 0 - agentPayoff = 0; - - exchangeId = "2"; - }); - - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", `${2 * sellerDeposit}`), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Revoke the voucher so the funds are released - await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); - - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0 - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Test that if buyer has some funds available, and gets more, the funds are only updated - // Commit again - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - - // Revoke another voucher - await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); + // Revoke another voucher + await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); // Available funds should be increased for // buyer: sellerDeposit + price @@ -2358,13 +2256,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(buyerPayoff).mul(2).toString() + BigInt(buyerPayoff) * 2n ); expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", `${sellerDeposit}`), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", `${sellerDeposit}`), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2384,33 +2282,29 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); }); - }); - - context("Final state CANCELED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(offerToken.price).sub(offerToken.buyerCancelPenalty).toString(); - // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.buyerCancelPenalty) - .toString(); + context("Final state CANCELED", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: price - buyerCancelPenalty + buyerPayoff = BigInt(offerToken.price) - BigInt(offerToken.buyerCancelPenalty); - // protocol: 0 - protocolPayoff = 0; - }); + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.buyerCancelPenalty); + protocol: 0 + protocolPayoff = 0; + }); - it("should emit a FundsReleased event", async function () { + it("should emit a FundsReleased event", async function () { // Cancel the voucher, expecting event const tx = await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, buyer.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await buyer.getAddress()); await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); }); @@ -2432,12 +2326,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; @@ -2455,13 +2349,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2489,27 +2383,25 @@ describe("IBosonFundsHandler", function () { .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); + await mockToken.mint(await assistant.getAddress(), `${2 * sellerDeposit}`); + await mockToken.mint(await buyer.getAddress(), `${2 * price}`); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, `${2 * sellerDeposit}`); await mockToken.connect(buyer).approve(protocolDiamondAddress, `${2 * price}`); // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), `${sellerDeposit}`); // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); // expected payoffs - // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(agentOffer.price).sub(agentOffer.buyerCancelPenalty).toString(); + // buyer: price - buyerCancelPenalty + buyerPayoff = BigInt(agentOffer.price) - BigInt(agentOffer.buyerCancelPenalty); - // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.buyerCancelPenalty) - .toString(); + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = BigInt(agentOffer.sellerDeposit) + BigInt(agentOffer.buyerCancelPenalty); // protocol: 0 protocolPayoff = 0; @@ -2537,12 +2429,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -2563,13 +2455,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2589,53 +2481,50 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); }); - }); + }); - context("Final state DISPUTED", async function () { - beforeEach(async function () { - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + context("Final state DISPUTED", async function () { + beforeEach(async function () { + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); - }); + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = BigInt(disputedDate) + BigInt(resolutionPeriod); + }); - context("Final state DISPUTED - RETRACTED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + context("Final state DISPUTED - RETRACTED", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // seller: sellerDeposit + price - protocolFee + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price) - BigInt(offerTokenProtocolFee); - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; - }); + // protocol: 0 + protocolPayoff = offerTokenProtocolFee; + }); - it("should emit a FundsReleased event", async function () { + it("should emit a FundsReleased event", async function () { // Retract from the dispute, expecting event const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); await expect(tx) .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); //check that FundsReleased event was NOT emitted with buyer Id const txReceipt = await tx.wait(); @@ -2644,7 +2533,7 @@ describe("IBosonFundsHandler", function () { buyerId, offerToken.exchangeToken, buyerPayoff, - buyer.address, + await buyer.getAddress(), ]); expect(match).to.be.false; }); @@ -2666,12 +2555,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -2692,13 +2581,13 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2723,17 +2612,17 @@ describe("IBosonFundsHandler", function () { // expected payoffs // buyer: 0 buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .toString(); + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = ( + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee) + ).toString(); // protocol: 0 protocolPayoff = agentOfferProtocolFee; @@ -2743,7 +2632,7 @@ describe("IBosonFundsHandler", function () { await offerHandler .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); // succesfully redeem exchange await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); @@ -2758,15 +2647,15 @@ describe("IBosonFundsHandler", function () { await expect(tx) .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); it("should update state", async function () { @@ -2786,12 +2675,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -2812,17 +2701,17 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: agentFee expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerPayoff).toString() + BigInt(sellerPayoff).toString() ); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedAgentAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", agentPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2846,18 +2735,19 @@ describe("IBosonFundsHandler", function () { context("Final state DISPUTED - RETRACTED via expireDispute", async function () { beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ( + BigInt(offerToken.sellerDeposit) + + BigInt(offerToken.price) - + BigInt(offerTokenProtocolFee) + ).toString(); - // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; + // protocol: protocolFee + protocolPayoff = offerTokenProtocolFee; await setNextBlockTimestamp(Number(timeout)); }); @@ -2902,12 +2792,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -2928,13 +2818,13 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2962,23 +2852,23 @@ describe("IBosonFundsHandler", function () { .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - agent fee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .toString(); + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; + // seller: sellerDeposit + price - protocolFee - agent fee + sellerPayoff = ( + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee) + ).toString(); // protocol: protocolFee protocolPayoff = agentOfferProtocolFee; @@ -2995,7 +2885,7 @@ describe("IBosonFundsHandler", function () { blockNumber = tx.blockNumber; block = await ethers.provider.getBlock(blockNumber); disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); + timeout = BigInt(disputedDate) + resolutionPeriod.toString(); await setNextBlockTimestamp(Number(timeout)); }); @@ -3035,12 +2925,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3060,18 +2950,19 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocol fee - agent fee; // protocol: protocolFee // agent: agent fee - expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedAgentAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", agentPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3093,51 +2984,46 @@ describe("IBosonFundsHandler", function () { }); }); - context("Final state DISPUTED - RESOLVED", async function () { + context("Final state DISPUTED - RESOLVED", async function () { beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / + 10000n; - // protocol: 0 - protocolPayoff = 0; + // seller: (price + sellerDeposit)*(1-buyerPercentage) + sellerPayoff = BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) - buyerPayoff; - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; + // protocol: 0 + protocolPayoff = 0; - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); - }); + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + }); it("should emit a FundsReleased event", async function () { // Resolve the dispute, expecting event @@ -3146,11 +3032,11 @@ describe("IBosonFundsHandler", function () { .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3172,12 +3058,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3198,13 +3084,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3228,64 +3114,64 @@ describe("IBosonFundsHandler", function () { context("Offer has an agent", async function () { beforeEach(async function () { // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - exchangeId = "2"; + exchangeId = "2"; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - buyerPercentBasisPoints = "5566"; // 55.66% + buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); - // protocol: 0 - protocolPayoff = 0; + // seller: (price + sellerDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) - + BigInt(buyerPayoff) + ).toString(); - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); - }); + // protocol: 0 + protocolPayoff = 0; + + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + }); it("should update state", async function () { // Read on chain state @@ -3304,12 +3190,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3330,13 +3216,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerPayoff).toString() + sellerPayoff ); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( @@ -3362,22 +3248,23 @@ describe("IBosonFundsHandler", function () { context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .add(buyerEscalationDeposit) - .toString(); + // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit + sellerPayoff = ( + BigInt(offerToken.sellerDeposit) + + BigInt(offerToken.price) - + BigInt(offerTokenProtocolFee) + + BigInt(buyerEscalationDeposit) + ).toString(); - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; + // protocol: 0 + protocolPayoff = offerTokenProtocolFee; - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should emit a FundsReleased event", async function () { @@ -3386,11 +3273,11 @@ describe("IBosonFundsHandler", function () { await expect(tx) .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); //check that FundsReleased event was NOT emitted with buyer Id const txReceipt = await tx.wait(); @@ -3399,7 +3286,7 @@ describe("IBosonFundsHandler", function () { buyerId, offerToken.exchangeToken, buyerPayoff, - buyer.address, + await buyer.getAddress(), ]); expect(match).to.be.false; }); @@ -3421,13 +3308,13 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3448,13 +3335,13 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( @@ -3478,46 +3365,47 @@ describe("IBosonFundsHandler", function () { context("Offer has an agent", async function () { beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; - // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .add(buyerEscalationDeposit) - .toString(); + // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit + sellerPayoff = ( + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee) + + BigInt(buyerEscalationDeposit) + ).toString(); - // protocol: 0 - protocolPayoff = agentOfferProtocolFee; + // protocol: 0 + protocolPayoff = agentOfferProtocolFee; - // Exchange id - exchangeId = "2"; - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Exchange id + exchangeId = "2"; + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -3537,13 +3425,14 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); + const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3563,17 +3452,17 @@ describe("IBosonFundsHandler", function () { // protocol: protocolFee // agent: agentFee expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerPayoff).toString() + sellerPayoff ); expectedProtocolAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", protocolPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedAgentAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", agentPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( @@ -3601,51 +3490,51 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); - - // protocol: 0 - protocolPayoff = 0; - - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); + + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(offerToken.price) + + BigInt(offerToken.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); + // protocol: 0 + protocolPayoff = 0; - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should emit a FundsReleased event", async function () { @@ -3655,11 +3544,11 @@ describe("IBosonFundsHandler", function () { .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3681,13 +3570,13 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3707,13 +3596,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3735,75 +3624,75 @@ describe("IBosonFundsHandler", function () { context("Offer has an agent", async function () { beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - exchangeId = "2"; + exchangeId = "2"; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - buyerPercentBasisPoints = "5566"; // 55.66% + buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); - // protocol: 0 - protocolPayoff = 0; + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); - - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // protocol: 0 + protocolPayoff = 0; + + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + + // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -3823,12 +3712,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3849,12 +3738,12 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3877,32 +3766,32 @@ describe("IBosonFundsHandler", function () { }); }); - context("Final state DISPUTED - ESCALATED - DECIDED", async function () { + context("Final state DISPUTED - ESCALATED - DECIDED", async function () { beforeEach(async function () { - buyerPercentBasisPoints = "5566"; // 55.66% - - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); + buyerPercentBasisPoints = "5566"; // 55.66% - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); + + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(offerToken.price) + + BigInt(offerToken.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - // protocol: 0 - protocolPayoff = 0; + // protocol: 0 + protocolPayoff = 0; - // escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); it("should emit a FundsReleased event", async function () { // Decide the dispute, expecting event @@ -3935,12 +3824,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -3961,13 +3850,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3990,56 +3879,56 @@ describe("IBosonFundsHandler", function () { context("Offer has an agent", async function () { beforeEach(async function () { // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - exchangeId = "2"; + exchangeId = "2"; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = (BigInt(disputedDate) + BigInt(resolutionPeriod)).toString(); - buyerPercentBasisPoints = "5566"; // 55.66% + buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); - // protocol: 0 - protocolPayoff = 0; + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // protocol: 0 + protocolPayoff = 0; + + // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -4059,12 +3948,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -4085,13 +3974,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerPayoff).toString() + sellerPayoff ); expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); sellersAvailableFunds = FundsList.fromStruct( @@ -4114,30 +4003,29 @@ describe("IBosonFundsHandler", function () { }); }); }); - context( "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", async function () { beforeEach(async function () { // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; - // protocol: 0 - protocolPayoff = 0; + // protocol: 0 + protocolPayoff = 0; - // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Escalate the dispute + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); + await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); }); it("should emit a FundsReleased event", async function () { @@ -4170,12 +4058,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedAgentAvailableFunds = @@ -4195,13 +4083,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -4223,47 +4111,47 @@ describe("IBosonFundsHandler", function () { context("Offer has an agent", async function () { beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - exchangeId = "2"; + exchangeId = "2"; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; - // protocol: 0 - protocolPayoff = 0; + // protocol: 0 + protocolPayoff = 0; - // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); + await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); }); it("should update state", async function () { @@ -4283,12 +4171,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = @@ -4308,13 +4196,13 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds.funds[0] = new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerPayoff).toString() + sellerPayoff ); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -4337,22 +4225,22 @@ describe("IBosonFundsHandler", function () { } ); - context( + context( "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", async function () { beforeEach(async function () { - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; - // protocol: 0 - protocolPayoff = 0; + // protocol: 0 + protocolPayoff = 0; - // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Escalate the dispute + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should emit a FundsReleased event", async function () { @@ -4398,12 +4286,12 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", sellerDeposit), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = emptyFundsList; expectedProtocolAvailableFunds = emptyFundsList; @@ -4422,16 +4310,16 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds = new FundsList([ new Funds( - mockToken.address, + await mockToken.getAddress(), "Foreign20", - ethers.BigNumber.from(sellerDeposit).add(sellerPayoff).toString() + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -4455,38 +4343,38 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - exchangeId = "2"; + exchangeId = "2"; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; - // protocol: 0 - protocolPayoff = 0; + // protocol: 0 + protocolPayoff = 0; - // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // Escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -4506,13 +4394,13 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ - new Funds(mockToken.address, "Foreign20", "0"), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = emptyFundsList; expectedProtocolAvailableFunds = emptyFundsList; @@ -4531,12 +4419,12 @@ describe("IBosonFundsHandler", function () { // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", buyerPayoff), - new Funds(ethers.constants.AddressZero, "Native currency", "0"), + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds = new FundsList([ - new Funds(mockToken.address, "Foreign20", ethers.BigNumber.from(sellerPayoff).toString()), - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); sellersAvailableFunds = FundsList.fromStruct( @@ -4559,22 +4447,19 @@ describe("IBosonFundsHandler", function () { }); } ); - }); + }); - context("Changing the protocol fee", async function () { + context("Changing the protocol fee", async function () { beforeEach(async function () { - // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamondAddress); + // Cast Diamond to IBosonConfigHandler + configHandler = await getContractAt("IBosonConfigHandler", protocolDiamondAddress); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .toString(); + // seller: sellerDeposit + price - protocolFee + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price) - BigInt(offerTokenProtocolFee); }); it("Protocol fee for existing exchanges should be the same as at the offer creation", async function () { @@ -4592,11 +4477,11 @@ describe("IBosonFundsHandler", function () { const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); }); it("Protocol fee for new exchanges should be the same as at the offer creation", async function () { @@ -4607,7 +4492,7 @@ describe("IBosonFundsHandler", function () { // similar as teste before, excpet the commit to offer is done after the procol fee change // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); + tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId.toString(); @@ -4622,49 +4507,49 @@ describe("IBosonFundsHandler", function () { tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); }); context("Offer has an agent", async function () { beforeEach(async function () { exchangeId = "2"; - // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamondAddress); + // Cast Diamond to IBosonConfigHandler + configHandler = await getContractAt("IBosonConfigHandler", protocolDiamondAddress); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .toString(); + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee); - // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; - // Create Agent Offer before setting new protocol fee as 3% - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // Create Agent Offer before setting new protocol fee as 3% + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // Commit to Agent Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // Commit to Agent Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); + // set the new procol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); }); it("Protocol fee for existing exchanges should be the same as at the agent offer creation", async function () { @@ -4679,33 +4564,33 @@ describe("IBosonFundsHandler", function () { await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); it("Protocol fee for new exchanges should be the same as at the agent offer creation", async function () { // similar as tests before, excpet the commit to offer is done after the protocol fee change // top up seller's and buyer's account - await mockToken.mint(assistant.address, sellerDeposit); - await mockToken.mint(buyer.address, price); + await mockToken.mint(await assistant.getAddress(), sellerDeposit); + await mockToken.mint(await buyer.getAddress(), price); // approve protocol to transfer the tokens await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamondAddress, price); // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), sellerDeposit); // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId.toString(); @@ -4722,19 +4607,18 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); }); }); }); }); }); -}); From 325f20ef54e7846a9ab34a469221fe7431818991 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 19:44:55 -0300 Subject: [PATCH 15/22] Resolve conflicts on ConfigHandlerTest.js --- test/protocol/ConfigHandlerTest.js | 204 ++++++++++++++++------------- 1 file changed, 111 insertions(+), 93 deletions(-) diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index 655e50b15..66bd9e52c 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -1,4 +1,5 @@ const { ethers } = require("hardhat"); +const { getSigners, getContractAt, ZeroAddress, parseUnits } = ethers; const { expect } = require("chai"); const Role = require("../../scripts/domain/Role"); @@ -42,18 +43,18 @@ describe("IBosonConfigHandler", function () { InterfaceIds = await getInterfaceIds(); // Make accounts available - accounts = await ethers.getSigners(); + accounts = await getSigners(); [deployer, rando, token, treasury, beacon, proxy] = accounts; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); // Temporarily grant UPGRADER role to deployer account - await accessController.grantRole(Role.UPGRADER, deployer.address); + await accessController.grantRole(Role.UPGRADER, await deployer.getAddress()); // Set protocol config protocolFeePercentage = 12; - protocolFeeFlatBoson = ethers.utils.parseUnits("0.01", "ether").toString(); + protocolFeeFlatBoson = parseUnits("0.01", "ether").toString(); maxExchangesPerBatch = 100; maxOffersPerGroup = 100; maxTwinsPerBundle = 100; @@ -72,10 +73,10 @@ describe("IBosonConfigHandler", function () { maxPremintedVouchers = 10000; // Cast Diamond to IERC165 - erc165 = await ethers.getContractAt("ERC165Facet", protocolDiamond.address); + erc165 = await getContractAt("ERC165Facet", await protocolDiamond.getAddress()); // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + configHandler = await getContractAt("IBosonConfigHandler", await protocolDiamond.getAddress()); // Get snapshot id snapshotId = await getSnapshot(); @@ -92,10 +93,10 @@ describe("IBosonConfigHandler", function () { const protocolConfig = [ // Protocol addresses { - token: token.address, - treasury: treasury.address, - voucherBeacon: beacon.address, - beaconProxy: proxy.address, + token: await token.getAddress(), + treasury: await treasury.getAddress(), + voucherBeacon: await beacon.getAddress(), + beaconProxy: await proxy.getAddress(), }, // Protocol limits { @@ -129,54 +130,55 @@ describe("IBosonConfigHandler", function () { // Cut the protocol handler facets into the Diamond const { cutTransaction } = await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas ); await expect(cutTransaction) .to.emit(configHandler, "TokenAddressChanged") - .withArgs(token.address, deployer.address); + .withArgs(await token.getAddress(), await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "TreasuryAddressChanged") - .withArgs(treasury.address, deployer.address); + .withArgs(await treasury.getAddress(), await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "VoucherBeaconAddressChanged") - .withArgs(beacon.address, deployer.address); + .withArgs(await beacon.getAddress(), await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "BeaconProxyAddressChanged") - .withArgs(proxy.address, deployer.address); + + .withArgs(await proxy.getAddress(), await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "ProtocolFeePercentageChanged") - .withArgs(protocolFeePercentage, deployer.address); + .withArgs(protocolFeePercentage, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "ProtocolFeeFlatBosonChanged") - .withArgs(protocolFeeFlatBoson, deployer.address); + .withArgs(protocolFeeFlatBoson, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "MaxEscalationResponsePeriodChanged") - .withArgs(maxEscalationResponsePeriod, deployer.address); + .withArgs(maxEscalationResponsePeriod, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "BuyerEscalationFeePercentageChanged") - .withArgs(buyerEscalationDepositPercentage, deployer.address); + .withArgs(buyerEscalationDepositPercentage, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "MaxRoyaltyPercentageChanged") - .withArgs(maxRoyaltyPecentage, deployer.address); + .withArgs(maxRoyaltyPecentage, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "MaxResolutionPeriodChanged") - .withArgs(maxResolutionPeriod, deployer.address); + .withArgs(maxResolutionPeriod, await deployer.getAddress()); await expect(cutTransaction) .to.emit(configHandler, "MinDisputePeriodChanged") - .withArgs(minDisputePeriod, deployer.address); + .withArgs(minDisputePeriod, await deployer.getAddress()); }); }); }); @@ -187,10 +189,10 @@ describe("IBosonConfigHandler", function () { const protocolConfig = [ // Protocol addresses { - treasury: treasury.address, - token: token.address, - voucherBeacon: beacon.address, - beaconProxy: proxy.address, + treasury: await treasury.getAddress(), + token: await token.getAddress(), + voucherBeacon: await beacon.getAddress(), + beaconProxy: await proxy.getAddress(), }, // Protocol limits { @@ -222,7 +224,7 @@ describe("IBosonConfigHandler", function () { const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); // Cut the protocol handler facets into the Diamond - await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); + await deployAndCutFacets(await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas); // Update id snapshotId = await getSnapshot(); @@ -252,30 +254,30 @@ describe("IBosonConfigHandler", function () { it("should emit a TokenAddressChanged event", async function () { // Set new token address, testing for the event - await expect(configHandler.connect(deployer).setTokenAddress(token.address)) + await expect(configHandler.connect(deployer).setTokenAddress(await token.getAddress())) .to.emit(configHandler, "TokenAddressChanged") - .withArgs(token.address, deployer.address); + .withArgs(await token.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new token address - await configHandler.connect(deployer).setTokenAddress(token.address); + await configHandler.connect(deployer).setTokenAddress(await token.getAddress()); // Verify that new value is stored - expect(await configHandler.connect(rando).getTokenAddress()).to.equal(token.address); + expect(await configHandler.connect(rando).getTokenAddress()).to.equal(await token.getAddress()); }); context("💔 Revert Reasons", async function () { it("caller is not the admin", async function () { // Attempt to set new token address, expecting revert - await expect(configHandler.connect(rando).setTokenAddress(token.address)).to.revertedWith( + await expect(configHandler.connect(rando).setTokenAddress(await token.getAddress())).to.revertedWith( RevertReasons.ACCESS_DENIED ); }); it("token address is the zero address", async function () { // Attempt to set new token address, expecting revert - await expect(configHandler.connect(deployer).setTokenAddress(ethers.constants.AddressZero)).to.revertedWith( + await expect(configHandler.connect(deployer).setTokenAddress(ZeroAddress)).to.revertedWith( RevertReasons.INVALID_ADDRESS ); }); @@ -291,32 +293,32 @@ describe("IBosonConfigHandler", function () { it("should emit a TreasuryAddressChanged event", async function () { // Set new treasury address, testing for the event - await expect(configHandler.connect(deployer).setTreasuryAddress(treasury.address)) + await expect(configHandler.connect(deployer).setTreasuryAddress(await treasury.getAddress())) .to.emit(configHandler, "TreasuryAddressChanged") - .withArgs(treasury.address, deployer.address); + .withArgs(await treasury.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new treasury address - await configHandler.connect(deployer).setTreasuryAddress(treasury.address); + await configHandler.connect(deployer).setTreasuryAddress(await treasury.getAddress()); // Verify that new value is stored - expect(await configHandler.connect(rando).getTreasuryAddress()).to.equal(treasury.address); + expect(await configHandler.connect(rando).getTreasuryAddress()).to.equal(await treasury.getAddress()); }); context("💔 Revert Reasons", async function () { it("caller is not the admin", async function () { // Attempt to set new treasury address, expecting revert - await expect(configHandler.connect(rando).setTreasuryAddress(treasury.address)).to.revertedWith( + await expect(configHandler.connect(rando).setTreasuryAddress(await treasury.getAddress())).to.revertedWith( RevertReasons.ACCESS_DENIED ); }); it("treasury address is the zero address", async function () { // Attempt to set new treasury address, expecting revert - await expect( - configHandler.connect(deployer).setTreasuryAddress(ethers.constants.AddressZero) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + await expect(configHandler.connect(deployer).setTreasuryAddress(ZeroAddress)).to.revertedWith( + RevertReasons.INVALID_ADDRESS + ); }); }); }); @@ -330,32 +332,32 @@ describe("IBosonConfigHandler", function () { it("should emit a VoucherAddressChanged event", async function () { // Set new beacon address, testing for the event - await expect(configHandler.connect(deployer).setVoucherBeaconAddress(beacon.address)) + await expect(configHandler.connect(deployer).setVoucherBeaconAddress(await beacon.getAddress())) .to.emit(configHandler, "VoucherBeaconAddressChanged") - .withArgs(beacon.address, deployer.address); + .withArgs(await beacon.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new beacon address - await configHandler.connect(deployer).setVoucherBeaconAddress(beacon.address); + await configHandler.connect(deployer).setVoucherBeaconAddress(await beacon.getAddress()); // Verify that new value is stored - expect(await configHandler.connect(rando).getVoucherBeaconAddress()).to.equal(beacon.address); + expect(await configHandler.connect(rando).getVoucherBeaconAddress()).to.equal(await beacon.getAddress()); }); context("💔 Revert Reasons", async function () { it("caller is not the admin", async function () { // Attempt to set new beacon address, expecting revert - await expect(configHandler.connect(rando).setVoucherBeaconAddress(beacon.address)).to.revertedWith( - RevertReasons.ACCESS_DENIED - ); + await expect( + configHandler.connect(rando).setVoucherBeaconAddress(await beacon.getAddress()) + ).to.revertedWith(RevertReasons.ACCESS_DENIED); }); it("voucher beacon address is the zero address", async function () { // Attempt to set new beacon address, expecting revert - await expect( - configHandler.connect(deployer).setVoucherBeaconAddress(ethers.constants.AddressZero) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + await expect(configHandler.connect(deployer).setVoucherBeaconAddress(ZeroAddress)).to.revertedWith( + RevertReasons.INVALID_ADDRESS + ); }); }); }); @@ -369,32 +371,32 @@ describe("IBosonConfigHandler", function () { it("should emit a VoucherAddressChanged event", async function () { // Set new proxy address, testing for the event - await expect(configHandler.connect(deployer).setBeaconProxyAddress(proxy.address)) + await expect(configHandler.connect(deployer).setBeaconProxyAddress(await proxy.getAddress())) .to.emit(configHandler, "BeaconProxyAddressChanged") - .withArgs(proxy.address, deployer.address); + .withArgs(await proxy.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new proxy address - await configHandler.connect(deployer).setBeaconProxyAddress(proxy.address); + await configHandler.connect(deployer).setBeaconProxyAddress(await proxy.getAddress()); // Verify that new value is stored - expect(await configHandler.connect(rando).getBeaconProxyAddress()).to.equal(proxy.address); + expect(await configHandler.connect(rando).getBeaconProxyAddress()).to.equal(await proxy.getAddress()); }); context("💔 Revert Reasons", async function () { it("caller is not the admin", async function () { // Attempt to set new proxy address, expecting revert - await expect(configHandler.connect(rando).setBeaconProxyAddress(proxy.address)).to.revertedWith( + await expect(configHandler.connect(rando).setBeaconProxyAddress(await proxy.getAddress())).to.revertedWith( RevertReasons.ACCESS_DENIED ); }); it("beacon proxy address is the zero address", async function () { // Attempt to set new proxy address, expecting revert - await expect( - configHandler.connect(deployer).setBeaconProxyAddress(ethers.constants.AddressZero) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + await expect(configHandler.connect(deployer).setBeaconProxyAddress(ZeroAddress)).to.revertedWith( + RevertReasons.INVALID_ADDRESS + ); }); }); }); @@ -410,7 +412,7 @@ describe("IBosonConfigHandler", function () { // Set new protocol fee precentage address, testing for the event await expect(configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage)) .to.emit(configHandler, "ProtocolFeePercentageChanged") - .withArgs(protocolFeePercentage, deployer.address); + .withArgs(protocolFeePercentage, await deployer.getAddress()); }); it("should update state", async function () { @@ -443,14 +445,14 @@ describe("IBosonConfigHandler", function () { let protocolFeeFlatBoson; beforeEach(async function () { // set new value for flat boson protocol fee - protocolFeeFlatBoson = ethers.utils.parseUnits("0.02", "ether").toString(); + protocolFeeFlatBoson = parseUnits("0.02", "ether").toString(); }); it("should emit a ProtocolFeeFlatBosonChanged event", async function () { // Set new flat boson protocol feel, testing for the event await expect(configHandler.connect(deployer).setProtocolFeeFlatBoson(protocolFeeFlatBoson)) .to.emit(configHandler, "ProtocolFeeFlatBosonChanged") - .withArgs(protocolFeeFlatBoson, deployer.address); + .withArgs(protocolFeeFlatBoson, await deployer.getAddress()); }); it("should update state", async function () { @@ -475,14 +477,14 @@ describe("IBosonConfigHandler", function () { let maxEscalationResponsePeriod; beforeEach(async function () { // set new value - maxEscalationResponsePeriod = ethers.BigNumber.from(oneMonth).add(oneWeek); + maxEscalationResponsePeriod = oneMonth + oneWeek; }); it("should emit a MaxEscalationResponsePeriodChanged event", async function () { // Set new escalation response period await expect(configHandler.connect(deployer).setMaxEscalationResponsePeriod(maxEscalationResponsePeriod)) .to.emit(configHandler, "MaxEscalationResponsePeriodChanged") - .withArgs(maxEscalationResponsePeriod, deployer.address); + .withArgs(maxEscalationResponsePeriod, await deployer.getAddress()); }); it("should update state", async function () { @@ -525,7 +527,7 @@ describe("IBosonConfigHandler", function () { configHandler.connect(deployer).setBuyerEscalationDepositPercentage(buyerEscalationDepositPercentage) ) .to.emit(configHandler, "BuyerEscalationFeePercentageChanged") - .withArgs(buyerEscalationDepositPercentage, deployer.address); + .withArgs(buyerEscalationDepositPercentage, await deployer.getAddress()); }); it("should update state", async function () { @@ -567,7 +569,7 @@ describe("IBosonConfigHandler", function () { // set new value for Max Total Offer Fee Percentage, testing for the event await expect(configHandler.connect(deployer).setMaxTotalOfferFeePercentage(maxTotalOfferFeePercentage)) .to.emit(configHandler, "MaxTotalOfferFeePercentageChanged") - .withArgs(maxTotalOfferFeePercentage, deployer.address); + .withArgs(maxTotalOfferFeePercentage, await deployer.getAddress()); }); it("should update state", async function () { @@ -609,7 +611,7 @@ describe("IBosonConfigHandler", function () { // set new value for Max Royalty Percentage, testing for the event await expect(configHandler.connect(deployer).setMaxRoyaltyPecentage(maxRoyaltyPecentage)) .to.emit(configHandler, "MaxRoyaltyPercentageChanged") - .withArgs(maxRoyaltyPecentage, deployer.address); + .withArgs(maxRoyaltyPecentage, await deployer.getAddress()); }); it("should update state", async function () { @@ -655,19 +657,23 @@ describe("IBosonConfigHandler", function () { it("should emit an AuthTokenContractChanged event", async function () { // Set new auth token contract, testing for the event await expect( - configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.Lens, authTokenContract.address) + configHandler + .connect(deployer) + .setAuthTokenContract(AuthTokenType.Lens, await authTokenContract.getAddress()) ) .to.emit(configHandler, "AuthTokenContractChanged") - .withArgs(AuthTokenType.Lens, authTokenContract.address, deployer.address); + .withArgs(AuthTokenType.Lens, await authTokenContract.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new auth token contract, - await configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.ENS, authTokenContract.address); + await configHandler + .connect(deployer) + .setAuthTokenContract(AuthTokenType.ENS, await authTokenContract.getAddress()); // Verify that new value is stored expect(await configHandler.connect(rando).getAuthTokenContract(AuthTokenType.ENS)).to.equal( - authTokenContract.address + await authTokenContract.getAddress() ); }); @@ -675,28 +681,32 @@ describe("IBosonConfigHandler", function () { it("caller is not the admin", async function () { // Attempt to set new auth token contract, expecting revert await expect( - configHandler.connect(rando).setAuthTokenContract(AuthTokenType.ENS, authTokenContract.address) + configHandler.connect(rando).setAuthTokenContract(AuthTokenType.ENS, await authTokenContract.getAddress()) ).to.revertedWith(RevertReasons.ACCESS_DENIED); }); it("_authTokenType is None", async function () { // Attempt to set new auth token contract, expecting revert await expect( - configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.None, authTokenContract.address) + configHandler + .connect(deployer) + .setAuthTokenContract(AuthTokenType.None, await authTokenContract.getAddress()) ).to.revertedWith(RevertReasons.INVALID_AUTH_TOKEN_TYPE); }); it("_authTokenType is Custom", async function () { // Attempt to set new auth token contract, expecting revert await expect( - configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.Custom, authTokenContract.address) + configHandler + .connect(deployer) + .setAuthTokenContract(AuthTokenType.Custom, await authTokenContract.getAddress()) ).to.revertedWith(RevertReasons.INVALID_AUTH_TOKEN_TYPE); }); it("_authTokenContract is the zero address", async function () { // Attempt to set new auth token contract, expecting revert await expect( - configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.ENS, ethers.constants.AddressZero) + configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.ENS, ZeroAddress) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); }); @@ -706,14 +716,14 @@ describe("IBosonConfigHandler", function () { let maxResolutionPeriod; beforeEach(async function () { // set new value - maxResolutionPeriod = ethers.BigNumber.from(oneMonth).add(oneWeek); + maxResolutionPeriod = oneMonth + oneWeek; }); it("should emit a MaxResolutionPeriodChanged event", async function () { // Set new resolution period await expect(configHandler.connect(deployer).setMaxResolutionPeriod(maxResolutionPeriod)) .to.emit(configHandler, "MaxResolutionPeriodChanged") - .withArgs(maxResolutionPeriod, deployer.address); + .withArgs(maxResolutionPeriod, await deployer.getAddress()); }); it("should update state", async function () { @@ -745,14 +755,14 @@ describe("IBosonConfigHandler", function () { let minDisputePeriod; beforeEach(async function () { // set new value - minDisputePeriod = ethers.BigNumber.from(oneMonth).sub(oneWeek); + minDisputePeriod = oneMonth - oneWeek; }); it("should emit a MinDisputePeriodChanged event", async function () { // Set new minumum dispute period await expect(configHandler.connect(deployer).setMinDisputePeriod(minDisputePeriod)) .to.emit(configHandler, "MinDisputePeriodChanged") - .withArgs(minDisputePeriod, deployer.address); + .withArgs(minDisputePeriod, await deployer.getAddress()); }); it("should update state", async function () { @@ -782,6 +792,7 @@ describe("IBosonConfigHandler", function () { context("👉 setAccessControllerAddress()", async function () { let newAccessController; + beforeEach(async function () { // set new value newAccessController = accounts[9]; @@ -789,32 +800,36 @@ describe("IBosonConfigHandler", function () { it("should emit an AccessControllerAddressChanged event", async function () { // Set new access controller address - await expect(configHandler.connect(deployer).setAccessControllerAddress(newAccessController.address)) + await expect( + configHandler.connect(deployer).setAccessControllerAddress(await newAccessController.getAddress()) + ) .to.emit(configHandler, "AccessControllerAddressChanged") - .withArgs(newAccessController.address, deployer.address); + .withArgs(await newAccessController.getAddress(), await deployer.getAddress()); }); it("should update state", async function () { // Set new access controller address - await configHandler.connect(deployer).setAccessControllerAddress(newAccessController.address); + await configHandler.connect(deployer).setAccessControllerAddress(await newAccessController.getAddress()); // Verify that new value is stored - expect(await configHandler.connect(rando).getAccessControllerAddress()).to.equal(newAccessController.address); + expect(await configHandler.connect(rando).getAccessControllerAddress()).to.equal( + await newAccessController.getAddress() + ); }); context("💔 Revert Reasons", async function () { it("caller is not the admin", async function () { // Attempt to set new value, expecting revert await expect( - configHandler.connect(rando).setAccessControllerAddress(newAccessController.address) + configHandler.connect(rando).setAccessControllerAddress(await newAccessController.getAddress()) ).to.revertedWith(RevertReasons.ACCESS_DENIED); }); it("_accessControllerAddress is the zero address", async function () { // Attempt to set new value, expecting revert - await expect( - configHandler.connect(deployer).setAccessControllerAddress(ethers.constants.AddressZero) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + await expect(configHandler.connect(deployer).setAccessControllerAddress(ZeroAddress)).to.revertedWith( + RevertReasons.INVALID_ADDRESS + ); }); }); }); @@ -827,16 +842,19 @@ describe("IBosonConfigHandler", function () { it("Initial values are correct", async function () { // Verify that initial values matches those in constructor expect(await configHandler.connect(rando).getTreasuryAddress()).to.equal( - treasury.address, + await treasury.getAddress(), "Invalid treasury address" ); - expect(await configHandler.connect(rando).getTokenAddress()).to.equal(token.address, "Invalid token address"); + expect(await configHandler.connect(rando).getTokenAddress()).to.equal( + await token.getAddress(), + "Invalid token address" + ); expect(await configHandler.connect(rando).getVoucherBeaconAddress()).to.equal( - beacon.address, + await beacon.getAddress(), "Invalid voucher address" ); expect(await configHandler.connect(rando).getBeaconProxyAddress()).to.equal( - proxy.address, + await proxy.getAddress(), "Invalid voucher address" ); expect(await configHandler.connect(rando).getProtocolFeePercentage()).to.equal( @@ -865,15 +883,15 @@ describe("IBosonConfigHandler", function () { ); //setAuthTokenContract is not called in the initialize function expect(await configHandler.connect(rando).getAuthTokenContract(AuthTokenType.Lens)).to.equal( - ethers.constants.AddressZero, + ZeroAddress, "Invalid auth token contract address" ); expect(await configHandler.connect(rando).getAuthTokenContract(AuthTokenType.ENS)).to.equal( - ethers.constants.AddressZero, + ZeroAddress, "Invalid auth token contract address" ); expect(await configHandler.connect(rando).getAuthTokenContract(AuthTokenType.Custom)).to.equal( - ethers.constants.AddressZero, + ZeroAddress, "Invalid auth token contract address" ); expect(await configHandler.connect(rando).getMaxResolutionPeriod()).to.equal( From 8d28bf6342609e2d7a25f38254dd2668d232c6cc Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 20:28:37 -0300 Subject: [PATCH 16/22] Resolve conflicts on BV and ProtocolInitialization --- .../ProtocolInitializationHandlerTest.js | 184 +- test/protocol/clients/BosonVoucherTest.js | 2471 +---------------- 2 files changed, 95 insertions(+), 2560 deletions(-) diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 7da3d1712..205014e96 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -1,8 +1,6 @@ const { expect } = require("chai"); const hre = require("hardhat"); -const ethers = hre.ethers; -const { keccak256, toUtf8Bytes } = ethers.utils; - +const { getContractAt, getContractFactory, getSigners, encodeBytes32String, AbiCoder, ZeroHash, keccak256, toUtf8Bytes } = hre.ethers; const Role = require("../../scripts/domain/Role"); const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); const { deployAndCutFacets, deployProtocolFacets } = require("../../scripts/util/deploy-protocol-handler-facets"); @@ -24,6 +22,7 @@ describe("ProtocolInitializationHandler", async function () { let erc165; let version; let maxPremintedVouchers, initializationData; + let abiCoder; before(async function () { // get interface Ids @@ -32,41 +31,44 @@ describe("ProtocolInitializationHandler", async function () { beforeEach(async function () { // Make accounts available - [deployer, rando] = await ethers.getSigners(); + [deployer, rando] = await getSigners(); // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); // Temporarily grant UPGRADER role to deployer account - await accessController.grantRole(Role.UPGRADER, deployer.address); + await accessController.grantRole(Role.UPGRADER, await deployer.getAddress()); // Temporarily grant UPGRADER role to deployer 1ccount - await accessController.grantRole(Role.UPGRADER, deployer.address); + await accessController.grantRole(Role.UPGRADER, await deployer.getAddress()); // Cast Diamond to IERC165 - erc165 = await ethers.getContractAt("ERC165Facet", protocolDiamond.address); + erc165 = await getContractAt("ERC165Facet", await protocolDiamond.getAddress()); // Cast Diamond to DiamondCutFacet - diamondCutFacet = await ethers.getContractAt("DiamondCutFacet", protocolDiamond.address); + diamondCutFacet = await getContractAt("DiamondCutFacet", await protocolDiamond.getAddress()); // Cast Diamond to ProtocolInitializationHandlerFacet - protocolInitializationFacet = await ethers.getContractAt( + protocolInitializationFacet = await getContractAt( "ProtocolInitializationHandlerFacet", - protocolDiamond.address + await protocolDiamond.getAddress() ); version = "2.2.0"; + abiCoder = AbiCoder.defaultAbiCoder(); + // initialization data for v2.2.0 - maxPremintedVouchers = "10000"; - initializationData = ethers.utils.defaultAbiCoder.encode(["uint256"], [maxPremintedVouchers]); + maxPremintedVouchers = "1000"; + + initializationData = abiCoder.encode(["uint256"], [maxPremintedVouchers]); }); describe("Deploy tests", async function () { context("📋 Initializer", async function () { it("Should initialize version 2.2.0 and emit ProtocolInitialized", async function () { const { cutTransaction } = await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), { ProtocolInitializationHandlerFacet: [] }, maxPriorityFeePerGas ); @@ -78,22 +80,20 @@ describe("ProtocolInitializationHandler", async function () { let protocolInitializationFacetDeployed; beforeEach(async function () { - const ProtocolInitilizationContractFactory = await ethers.getContractFactory( - "ProtocolInitializationHandlerFacet" - ); + const ProtocolInitilizationContractFactory = await getContractFactory("ProtocolInitializationHandlerFacet"); protocolInitializationFacetDeployed = await ProtocolInitilizationContractFactory.deploy( await getFees(maxPriorityFeePerGas) ); - await protocolInitializationFacetDeployed.deployTransaction.wait(); + await protocolInitializationFacetDeployed.waitForDeployment(); }); it("Addresses and calldata length mismatch", async function () { - version = ethers.utils.formatBytes32String("2.2.0"); + version = encodeBytes32String("2.2.0"); const callData = protocolInitializationFacetDeployed.interface.encodeFunctionData("initialize", [ version, - [rando.address], + [await rando.getAddress()], [], true, initializationData, @@ -101,11 +101,11 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - let facetCut = getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); + let facetCut = await getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); const cutArgs = [ [facetCut], - protocolInitializationFacetDeployed.address, + await protocolInitializationFacetDeployed.getAddress(), callData, await getFees(maxPriorityFeePerGas), ]; @@ -117,7 +117,7 @@ describe("ProtocolInitializationHandler", async function () { it("Version is empty", async function () { const callData = protocolInitializationFacetDeployed.interface.encodeFunctionData("initialize", [ - ethers.constants.HashZero, + ZeroHash, [], [], true, @@ -126,11 +126,11 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - let facetCut = getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); + let facetCut = await getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); const cutArgs = [ [facetCut], - protocolInitializationFacetDeployed.address, + await protocolInitializationFacetDeployed.getAddress(), callData, await getFees(maxPriorityFeePerGas), ]; @@ -141,7 +141,7 @@ describe("ProtocolInitializationHandler", async function () { }); it("Initialize same version twice", async function () { - version = ethers.utils.formatBytes32String("2.2.0"); + version = encodeBytes32String("2.2.0"); const callData = protocolInitializationFacetDeployed.interface.encodeFunctionData("initialize", [ version, @@ -153,32 +153,32 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - let facetCut = getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); + let facetCut = await getFacetAddCut(protocolInitializationFacetDeployed, [callData.slice(0, 10)]); await diamondCutFacet.diamondCut( [facetCut], - protocolInitializationFacetDeployed.address, + await protocolInitializationFacetDeployed.getAddress(), callData, await getFees(maxPriorityFeePerGas) ); // Mock a new facet to add to diamond so we can call initialize again - let FacetTestFactory = await ethers.getContractFactory("Test3Facet"); + let FacetTestFactory = await getContractFactory("Test3Facet"); const testFacet = await FacetTestFactory.deploy(await getFees(maxPriorityFeePerGas)); - await testFacet.deployTransaction.wait(); + await testFacet.waitForDeployment(); - const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [rando.address]); + const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [await rando.getAddress()]); - facetCut = getFacetAddCut(testFacet, [calldataTestFacet.slice(0, 10)]); + facetCut = await getFacetAddCut(testFacet, [calldataTestFacet.slice(0, 10)]); const calldataProtocolInitialization = protocolInitializationFacetDeployed.interface.encodeFunctionData( "initialize", - [version, [testFacet.address], [calldataTestFacet], true, initializationData, [], []] + [version, [await testFacet.getAddress()], [calldataTestFacet], true, initializationData, [], []] ); const cutTransaction = diamondCutFacet.diamondCut( [facetCut], - protocolInitializationFacetDeployed.address, + await protocolInitializationFacetDeployed.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ); @@ -192,30 +192,32 @@ describe("ProtocolInitializationHandler", async function () { // Add protocolInitializationFacet to diamond await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), { ProtocolInitializationHandlerFacet: [] }, maxPriorityFeePerGas ); // Get actual deployed protocolInitializationFacet - const diamondLoupe = await ethers.getContractAt("DiamondLoupeFacet", protocolDiamond.address); - const signature = protocolInitializationFacet.interface.getSighash("getVersion()"); + const diamondLoupe = await getContractAt("DiamondLoupeFacet", await protocolDiamond.getAddress()); + const signature = protocolInitializationFacet.interface.fragments.find( + (f) => f.name == "getVersion" + ).selector; const existingFacetAddress = await diamondLoupe.facetAddress(signature); - const protocolInitializationFacet2 = await ethers.getContractAt( + const protocolInitializationFacet2 = await getContractAt( "ProtocolInitializationHandlerFacet", existingFacetAddress ); // Deploy selfDestruct contract that will be called during initialize - const SelfDestructorFactory = await ethers.getContractFactory("SelfDestructor"); + const SelfDestructorFactory = await getContractFactory("SelfDestructor"); const selfDestructor = await SelfDestructorFactory.deploy(); const selfDestructorInitData = selfDestructor.interface.encodeFunctionData("destruct"); // call initialize await expect( protocolInitializationFacet2.initialize( - ethers.utils.formatBytes32String("haha"), - [selfDestructor.address], + encodeBytes32String("haha"), + [await selfDestructor.getAddress()], [selfDestructorInitData], false, "0x", @@ -236,7 +238,7 @@ describe("ProtocolInitializationHandler", async function () { const interfaceId = InterfaceIds[interfaceImplementers["ProtocolInitializationHandlerFacet"]]; const { deployedFacets } = await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), { ProtocolInitializationHandlerFacet: [version, [], [], true] }, maxPriorityFeePerGas, version, @@ -262,7 +264,7 @@ describe("ProtocolInitializationHandler", async function () { const configHandlerInterface = InterfaceIds[interfaceImplementers["ConfigHandlerFacet"]]; const accountInterface = InterfaceIds[interfaceImplementers["AccountHandlerFacet"]]; - version = ethers.utils.formatBytes32String("2.3.0"); + version = encodeBytes32String("2.3.0"); const calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.contract.interface.encodeFunctionData("initialize", [ version, @@ -276,7 +278,7 @@ describe("ProtocolInitializationHandler", async function () { await diamondCutFacet.diamondCut( [], - deployedProtocolInitializationHandlerFacet.contract.address, + await deployedProtocolInitializationHandlerFacet.contract.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ); @@ -298,17 +300,17 @@ describe("ProtocolInitializationHandler", async function () { }); it("Should call facet initializer internally when _addresses and _calldata are supplied", async function () { - let FacetTestFactory = await ethers.getContractFactory("Test3Facet"); + let FacetTestFactory = await getContractFactory("Test3Facet"); const testFacet = await FacetTestFactory.deploy(await getFees(maxPriorityFeePerGas)); - await testFacet.deployTransaction.wait(); + await testFacet.waitForDeployment(); - const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [rando.address]); + const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [await rando.getAddress()]); - version = ethers.utils.formatBytes32String("2.3.0"); + version = encodeBytes32String("2.3.0"); const calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.contract.interface.encodeFunctionData("initialize", [ version, - [testFacet.address], + [await testFacet.getAddress()], [calldataTestFacet], true, "0x", @@ -316,38 +318,38 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - const facetCuts = [getFacetAddCut(testFacet)]; + const facetCuts = [await getFacetAddCut(testFacet)]; await diamondCutFacet.diamondCut( facetCuts, - deployedProtocolInitializationHandlerFacet.contract.address, + await deployedProtocolInitializationHandlerFacet.contract.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ); - const testFacetContract = await ethers.getContractAt("Test3Facet", protocolDiamond.address); + const testFacetContract = await getContractAt("Test3Facet", await protocolDiamond.getAddress()); - expect(await testFacetContract.getTestAddress()).to.equal(rando.address); + expect(await testFacetContract.getTestAddress()).to.equal(await rando.getAddress()); }); context("💔 Revert Reasons", async function () { let testFacet, version; beforeEach(async function () { - let FacetTestFactory = await ethers.getContractFactory("Test3Facet"); + let FacetTestFactory = await getContractFactory("Test3Facet"); testFacet = await FacetTestFactory.deploy(await getFees(maxPriorityFeePerGas)); - await testFacet.deployTransaction.wait(); + await testFacet.waitForDeployment(); - version = ethers.utils.formatBytes32String("2.3.0"); + version = encodeBytes32String("2.3.0"); }); it("Delegate call to initialize fails", async function () { - const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [testFacet.address]); + const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [await testFacet.getAddress()]); const calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.contract.interface.encodeFunctionData("initialize", [ version, - [testFacet.address], + [await testFacet.getAddress()], [calldataTestFacet], true, initializationData, @@ -355,12 +357,12 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - const facetCuts = [getFacetAddCut(testFacet)]; + const facetCuts = [await getFacetAddCut(testFacet)]; await expect( diamondCutFacet.diamondCut( facetCuts, - deployedProtocolInitializationHandlerFacet.contract.address, + await deployedProtocolInitializationHandlerFacet.contract.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ) @@ -370,12 +372,12 @@ describe("ProtocolInitializationHandler", async function () { it("Default reason if not supplied by implementation", async () => { // If the caller's address is supplied Test3Facet's initializer will revert with no reason // and so the diamondCut function will supply it's own reason - const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [deployer.address]); + const calldataTestFacet = testFacet.interface.encodeFunctionData("initialize", [await deployer.getAddress()]); const calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.contract.interface.encodeFunctionData("initialize", [ version, - [testFacet.address], + [await testFacet.getAddress()], [calldataTestFacet], true, initializationData, @@ -383,12 +385,12 @@ describe("ProtocolInitializationHandler", async function () { [], ]); - const facetCuts = [getFacetAddCut(testFacet)]; + const facetCuts = [await getFacetAddCut(testFacet)]; await expect( diamondCutFacet.diamondCut( facetCuts, - deployedProtocolInitializationHandlerFacet.contract.address, + await deployedProtocolInitializationHandlerFacet.contract.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ) @@ -407,14 +409,12 @@ describe("ProtocolInitializationHandler", async function () { version = "2.1.0"; // Deploy mock protocol initialization facet which simulates state before v2.2.0 - const ProtocolInitilizationContractFactory = await ethers.getContractFactory( - "MockProtocolInitializationHandlerFacet" - ); + const ProtocolInitilizationContractFactory = await getContractFactory("MockProtocolInitializationHandlerFacet"); const mockInitializationFacetDeployed = await ProtocolInitilizationContractFactory.deploy( await getFees(maxPriorityFeePerGas) ); - await mockInitializationFacetDeployed.deployTransaction.wait(); + await mockInitializationFacetDeployed.waitForDeployment(); const facetNames = [ "SellerHandlerFacet", @@ -430,7 +430,7 @@ describe("ProtocolInitializationHandler", async function () { // Make initial deployment (simulate v2.1.0) await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas, version, @@ -446,12 +446,11 @@ describe("ProtocolInitializationHandler", async function () { await getFees(maxPriorityFeePerGas) ); - version = ethers.utils.formatBytes32String("2.2.0"); - + version = encodeBytes32String("2.2.0"); // Prepare cut data - facetCut = getFacetAddCut(configHandler); + facetCut = await getFacetAddCut(configHandler); // Attach correct address to configHandler - configHandler = configHandler.attach(protocolDiamond.address); + configHandler = configHandler.attach(await protocolDiamond.getAddress()); // Prepare calldata calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.interface.encodeFunctionData( "initialize", @@ -463,22 +462,22 @@ describe("ProtocolInitializationHandler", async function () { // Make the cut, check the event await diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ); - const protocolLimitsSlot = ethers.BigNumber.from(keccak256(toUtf8Bytes("boson.protocol.limits"))); - const maxPremintedVoucherStorage = await getStorageAt(diamondCutFacet.address, protocolLimitsSlot.add(4)); + const protocolLimitsSlot = BigInt(keccak256(toUtf8Bytes("boson.protocol.limits"))); + const maxPremintedVoucherStorage = await getStorageAt(await diamondCutFacet.getAddress(), protocolLimitsSlot + 4n); - expect(ethers.BigNumber.from(maxPremintedVoucherStorage).toString()).to.equal(maxPremintedVouchers); + expect(BigInt(maxPremintedVoucherStorage).toString()).to.equal(maxPremintedVouchers); }); context("💔 Revert Reasons", async function () { it("Max preminted vouchers is zero", async function () { // set invalid maxPremintedVouchers maxPremintedVouchers = "0"; - initializationData = ethers.utils.defaultAbiCoder.encode(["uint256"], [maxPremintedVouchers]); + initializationData = abiCoder.encode(["uint256"], [maxPremintedVouchers]); calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.interface.encodeFunctionData( "initialize", @@ -489,7 +488,7 @@ describe("ProtocolInitializationHandler", async function () { await expect( diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ) @@ -503,7 +502,7 @@ describe("ProtocolInitializationHandler", async function () { const { deployedFacets: [{ contract: deployedProtocolInitializationHandlerFacet }], } = await deployAndCutFacets( - protocolDiamond.address, + await protocolDiamond.getAddress(), { ProtocolInitializationHandlerFacet: [version, [], [], true] }, maxPriorityFeePerGas, version, @@ -512,13 +511,13 @@ describe("ProtocolInitializationHandler", async function () { ); // Prepare 2.2.0 deployment - version = ethers.utils.formatBytes32String("2.2.0"); + version = encodeBytes32String("2.2.0"); // make diamond cut, expect revert await expect( diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ) @@ -526,6 +525,7 @@ describe("ProtocolInitializationHandler", async function () { }); }); }); + describe("initV2_2_1", async function () { let deployedProtocolInitializationHandlerFacet; let facetCut; @@ -537,7 +537,7 @@ describe("ProtocolInitializationHandler", async function () { const facetsToDeploy = await getV2_2_0DeployConfig(); // Make initial deployment (simulate v2.2.0) - await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas, version); + await deployAndCutFacets(await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas, version); version = "2.2.1"; @@ -549,12 +549,14 @@ describe("ProtocolInitializationHandler", async function () { ); // Prepare cut data - facetCut = getFacetReplaceCut(deployedProtocolInitializationHandlerFacet, ["initialize"]); + facetCut = await getFacetReplaceCut(deployedProtocolInitializationHandlerFacet, [ + deployedProtocolInitializationHandlerFacet.interface.fragments.find((f) => f.name == "initialize").selector, + ]); // Prepare calldata calldataProtocolInitialization = deployedProtocolInitializationHandlerFacet.interface.encodeFunctionData( "initialize", - [ethers.utils.formatBytes32String(version), [], [], true, [], [], []] + [encodeBytes32String(version), [], [], true, "0x", [], []] ); }); @@ -562,7 +564,7 @@ describe("ProtocolInitializationHandler", async function () { // Make the cut, check the event const tx = await diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ); @@ -577,18 +579,18 @@ describe("ProtocolInitializationHandler", async function () { // Prepare calldata const calldataProtocolInitializationWrong = deployedProtocolInitializationHandlerFacet.interface.encodeFunctionData("initialize", [ - ethers.utils.formatBytes32String(wrongVersion), + encodeBytes32String(wrongVersion), [], [], true, - [], + "0x", [], [], ]); await diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitializationWrong, await getFees(maxPriorityFeePerGas) ); @@ -600,13 +602,15 @@ describe("ProtocolInitializationHandler", async function () { ); // Prepare cut data - facetCut = getFacetReplaceCut(accountHandler, ["initialize"]); + facetCut = await getFacetReplaceCut(accountHandler, [ + accountHandler.interface.fragments.find((f) => f.name == "initialize").selector, + ]); // Make diamond cut, expect revert await expect( diamondCutFacet.diamondCut( [facetCut], - deployedProtocolInitializationHandlerFacet.address, + await deployedProtocolInitializationHandlerFacet.getAddress(), calldataProtocolInitialization, await getFees(maxPriorityFeePerGas) ) diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index 27e63194b..78c847719 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -1,2470 +1 @@ -const { ethers } = require("hardhat"); -const { assert, expect } = require("chai"); - -const DisputeResolutionTerms = require("../../../scripts/domain/DisputeResolutionTerms"); -const { getInterfaceIds } = require("../../../scripts/config/supported-interfaces.js"); -const Role = require("../../../scripts/domain/Role"); -const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); -const Range = require("../../../scripts/domain/Range"); -const VoucherInitValues = require("../../../scripts/domain/VoucherInitValues"); -const { Funds, FundsList } = require("../../../scripts/domain/Funds"); -const { RevertReasons } = require("../../../scripts/config/revert-reasons"); -const { - mockDisputeResolver, - mockSeller, - mockVoucherInitValues, - mockAuthToken, - mockBuyer, - accountId, - mockVoucher, - mockExchange, - mockOffer, -} = require("../../util/mock"); -const { - applyPercentage, - calculateContractAddress, - calculateVoucherExpiry, - setNextBlockTimestamp, - setupTestEnvironment, - getSnapshot, - revertToSnapshot, - prepareDataSignatureParameters, - getEvent, - deriveTokenId, -} = require("../../util/utils.js"); -const { deployMockTokens } = require("../../../scripts/util/deploy-mock-tokens"); -const { deployMockContract } = require("@ethereum-waffle/mock-contract"); -const FormatTypes = ethers.utils.FormatTypes; - -describe("IBosonVoucher", function () { - let interfaceIds; - let accessController; - let bosonVoucher, offerHandler, accountHandler, exchangeHandler, fundsHandler, configHandler; - let deployer, - protocol, - buyer, - rando, - rando2, - assistant, - admin, - clerk, - treasury, - assistantDR, - adminDR, - clerkDR, - treasuryDR, - seller, - foreign20; - let beacon; - let disputeResolver, disputeResolverFees; - let emptyAuthToken; - let agentId; - let voucherInitValues, contractURI, royaltyPercentage, exchangeId, offerPrice; - let forwarder; - let snapshotId; - - before(async function () { - accountId.next(true); - - // Get interface id - const { IBosonVoucher, IERC721, IERC2981 } = await getInterfaceIds(); - interfaceIds = { IBosonVoucher, IERC721, IERC2981 }; - - // Mock forwarder to test metatx - const MockForwarder = await ethers.getContractFactory("MockForwarder"); - - forwarder = await MockForwarder.deploy(); - - // Specify contracts needed for this test - const contracts = { - accountHandler: "IBosonAccountHandler", - offerHandler: "IBosonOfferHandler", - exchangeHandler: "IBosonExchangeHandler", - fundsHandler: "IBosonFundsHandler", - configHandler: "IBosonConfigHandler", - }; - - ({ - signers: [protocol, buyer, rando, rando2, admin, treasury, adminDR, treasuryDR], - contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, configHandler }, - extraReturnValues: { bosonVoucher, beacon, accessController }, - } = await setupTestEnvironment(contracts, { - forwarderAddress: [forwarder.address], - })); - - // make all account the same - assistant = admin; - assistantDR = adminDR; - clerk = clerkDR = { address: ethers.constants.AddressZero }; - [deployer] = await ethers.getSigners(); - - // Grant protocol role to eoa so it's easier to test - await accessController.grantRole(Role.PROTOCOL, protocol.address); - - // Initialize voucher contract - const sellerId = 1; - - // prepare the VoucherInitValues - voucherInitValues = mockVoucherInitValues(); - const bosonVoucherInit = await ethers.getContractAt("BosonVoucher", bosonVoucher.address); - - await bosonVoucherInit.initializeVoucher(sellerId, assistant.address, voucherInitValues); - - [foreign20] = await deployMockTokens(["Foreign20", "BosonToken"]); - - // Get snapshot id - snapshotId = await getSnapshot(); - }); - - afterEach(async function () { - await revertToSnapshot(snapshotId); - snapshotId = await getSnapshot(); - }); - - // Interface support - context("📋 Interfaces", async function () { - context("👉 supportsInterface()", async function () { - it("should indicate support for IBosonVoucher, IERC721 and IERC2981 interface", async function () { - // IBosonVoucher interface - let support = await bosonVoucher.supportsInterface(interfaceIds["IBosonVoucher"]); - expect(support, "IBosonVoucher interface not supported").is.true; - - // IERC721 interface - support = await bosonVoucher.supportsInterface(interfaceIds["IERC721"]); - expect(support, "IERC721 interface not supported").is.true; - - // IERC2981 interface - support = await bosonVoucher.supportsInterface(interfaceIds["IERC2981"]); - expect(support, "IERC2981 interface not supported").is.true; - }); - }); - }); - - context("General", async function () { - it("Contract can receive native token", async function () { - const balanceBefore = await ethers.provider.getBalance(bosonVoucher.address); - - const amount = ethers.utils.parseUnits("1", "ether"); - - await admin.sendTransaction({ to: bosonVoucher.address, value: amount }); - - const balanceAfter = await ethers.provider.getBalance(bosonVoucher.address); - expect(balanceAfter.sub(balanceBefore)).to.eq(amount); - }); - - it("Cannot initialize voucher twice", async function () { - const initalizableClone = await ethers.getContractAt("IInitializableVoucherClone", bosonVoucher.address); - await expect(initalizableClone.initializeVoucher(2, assistant.address, voucherInitValues)).to.be.revertedWith( - RevertReasons.INITIALIZABLE_ALREADY_INITIALIZED - ); - }); - }); - - context("issueVoucher()", function () { - let buyerStruct; - let buyerWallet; - - beforeEach(function () { - buyerStruct = mockBuyer(buyer.address).toStruct(); - buyerWallet = buyerStruct[1]; - }); - - after(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - it("should issue a voucher with success", async function () { - const balanceBefore = await bosonVoucher.balanceOf(buyer.address); - await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); - - const balanceAfter = await bosonVoucher.balanceOf(buyer.address); - - expect(balanceAfter.sub(balanceBefore)).eq(1); - }); - - it("should issue a voucher if it does not overlap with range", async function () { - const offerId = "5"; - const start = "10"; - const length = "123"; - const tokenId = deriveTokenId(offerId, start); // token within reserved range - - // Deploy mock protocol - await deployMockProtocol(); - - // Reserve a range - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // Token id just below the range - await expect(() => - bosonVoucher.connect(protocol).issueVoucher(tokenId.sub(1), buyerWallet) - ).to.changeTokenBalance(bosonVoucher, buyer, 1); - - // Token id just above the range - await expect(() => - bosonVoucher.connect(protocol).issueVoucher(tokenId.add(length), buyerWallet) - ).to.changeTokenBalance(bosonVoucher, buyer, 1); - }); - - context("💔 Revert Reasons", async function () { - it("should revert if caller does not have PROTOCOL role", async function () { - // Expect revert if random user attempts to issue voucher - await expect(bosonVoucher.connect(rando).issueVoucher(0, buyerWallet)).to.be.revertedWith( - RevertReasons.ACCESS_DENIED - ); - - // Grant PROTOCOL role to random user address - await accessController.grantRole(Role.PROTOCOL, rando.address); - - // Attempt to issue voucher again as a random user - const balanceBefore = await bosonVoucher.balanceOf(buyer.address); - await bosonVoucher.connect(rando).issueVoucher(0, buyerWallet); - const balanceAfter = await bosonVoucher.balanceOf(buyer.address); - - expect(balanceAfter.sub(balanceBefore)).eq(1); - }); - - it("issueVoucher should revert if exchange id falls within a pre-minted offer's range", async function () { - const offerId = "5"; - const start = "10"; - const length = "123"; - const tokenId = deriveTokenId(offerId, "15"); // token within reserved range - - // Deploy mock protocol - await deployMockProtocol(); - - // Reserve a range - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // Expect revert if random user attempts to issue voucher - await expect(bosonVoucher.connect(protocol).issueVoucher(tokenId, buyerWallet)).to.be.revertedWith( - RevertReasons.EXCHANGE_ID_IN_RESERVED_RANGE - ); - }); - }); - }); - - context("reserveRange()", function () { - let offerId, start, length; - let range; - - beforeEach(async function () { - offerId = "5"; - start = "10"; - length = "123"; - const tokenStartId = deriveTokenId(offerId, start); - - range = new Range(tokenStartId.toString(), length, "0", "0", assistant.address); - }); - - it("Should emit event RangeReserved", async function () { - // Reserve range, test for event - await expect(bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address)) - .to.emit(bosonVoucher, "RangeReserved") - .withArgs(offerId, range.toStruct()); - }); - - it("Should update state", async function () { - // Reserve range - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // Get range object from contract - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // Mock getOffer call, otherwise getAvailablePreMints will return 0 - const mockProtocol = await deployMockProtocol(); - const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); - const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); - }); - - context("Owner range is contract", async function () { - beforeEach(async function () { - range.owner = bosonVoucher.address; - }); - - it("Should emit event RangeReserved", async function () { - // Reserve range, test for event - await expect(bosonVoucher.connect(protocol).reserveRange(offerId, start, length, bosonVoucher.address)) - .to.emit(bosonVoucher, "RangeReserved") - .withArgs(offerId, range.toStruct()); - }); - - it("Should update state", async function () { - // Reserve range - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, bosonVoucher.address); - - // Get range object from contract - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // Mock getOffer call, otherwise getAvailablePreMints will return 0 - const mockProtocol = await deployMockProtocol(); - const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); - const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); - }); - }); - - context("💔 Revert Reasons", async function () { - it("caller does not have PROTOCOL role", async function () { - await expect( - bosonVoucher.connect(rando).reserveRange(offerId, start, length, assistant.address) - ).to.be.revertedWith(RevertReasons.ACCESS_DENIED); - }); - - it("Start id is not greater than zero for the first range", async function () { - // Set start id to 0 - start = 0; - - // Try to reserve range, it should fail - await expect( - bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address) - ).to.be.revertedWith(RevertReasons.INVALID_RANGE_START); - }); - - it("Range length is zero", async function () { - // Set length to 0 - length = "0"; - - // Try to reserve range, it should fail - await expect( - bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address) - ).to.be.revertedWith(RevertReasons.INVALID_RANGE_LENGTH); - }); - - it("Range length is too large, i.e., would cause an overflow", async function () { - // Set such numbers that would cause an overflow - start = ethers.constants.MaxUint256.div(2).add(2); - length = ethers.constants.MaxUint256.div(2); - - // Try to reserve range, it should fail - await expect( - bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address) - ).to.be.revertedWith(RevertReasons.INVALID_RANGE_LENGTH); - }); - - it("Offer id is already associated with a range", async function () { - // Reserve range for an offer - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - start = Number(start) + Number(length) + 1; - - // Try to reserve range for the same offer, it should fail - await expect( - bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address) - ).to.be.revertedWith(RevertReasons.OFFER_RANGE_ALREADY_RESERVED); - }); - - it("_to address isn't contract address or contract owner address", async function () { - // Try to reserve range for rando address, it should fail - await expect( - bosonVoucher.connect(protocol).reserveRange(offerId, start, length, rando.address) - ).to.be.revertedWith(RevertReasons.INVALID_TO_ADDRESS); - }); - }); - }); - - context("preMint()", function () { - let offerId, start, length, amount; - let mockProtocol; - let offer, offerDates, offerDurations, offerFees, disputeResolutionTerms; - - beforeEach(async function () { - mockProtocol = await deployMockProtocol(); - ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); - disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // reserve a range - offerId = "5"; - start = 10; - length = "1000"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // amount to mint - amount = 50; - }); - - it("Should emit Transfer events", async function () { - // Premint tokens, test for event - const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Expect an event for every mint - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(ethers.constants.AddressZero, assistant.address, start.add(i)); - } - }); - - it("Should emit VouchersPreMinted event", async function () { - // Premint tokens, test for event - const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); - - start = deriveTokenId(offerId, start); - - await expect(tx).to.emit(bosonVoucher, "VouchersPreMinted").withArgs(offerId, start, start.add(amount).sub(1)); - }); - - context("Owner range is contract", async function () { - beforeEach(async function () { - offer.id = offerId = ++offerId; - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // reserve a range - start = "1010"; - length = "1000"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, bosonVoucher.address); - }); - - it("Transfer event should emit contract address", async function () { - // Premint tokens, test for event - const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Expect an event for every mint - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(ethers.constants.AddressZero, bosonVoucher.address, start.add(i)); - } - }); - - it("Should update state", async function () { - let contractBalanceBefore = await bosonVoucher.balanceOf(bosonVoucher.address); - - // Premint tokens - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Expect a correct owner for all preminted tokens - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - let tokenId = start.add(i); - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, bosonVoucher.address, `Wrong token owner for token ${tokenId}`); - } - - // Token that is inside a range, but wasn't preminted yet should not have an owner - await expect(bosonVoucher.ownerOf(start.add(amount).add(1))).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - - // Contract's balance should be updated for the total mint amount - let contractBalanceAfter = await bosonVoucher.balanceOf(bosonVoucher.address); - assert.equal(contractBalanceAfter.toNumber(), contractBalanceBefore.add(amount).toNumber(), "Balance mismatch"); - - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), Number(length) - Number(amount), "Available Premints mismatch"); - }); - }); - - it("Should update state", async function () { - let sellerBalanceBefore = await bosonVoucher.balanceOf(assistant.address); - - // Premint tokens - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Expect a correct owner for all preminted tokens - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - let tokenId = start.add(i); - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, assistant.address, `Wrong token owner for token ${tokenId}`); - } - - // Token that is inside a range, but wasn't preminted yet should not have an owner - await expect(bosonVoucher.ownerOf(start.add(amount).add(1))).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - - // Seller's balance should be updated for the total mint amount - let sellerBalanceAfter = await bosonVoucher.balanceOf(assistant.address); - assert.equal(sellerBalanceAfter.toNumber(), sellerBalanceBefore.add(amount).toNumber(), "Balance mismatch"); - - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), Number(length) - Number(amount), "Available Premints mismatch"); - }); - - it("MetaTx: forwarder can execute preMint on behalf of seller", async function () { - const nonce = Number(await forwarder.getNonce(assistant.address)); - - const types = { - ForwardRequest: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "nonce", type: "uint256" }, - { name: "data", type: "bytes" }, - ], - }; - - const functionSignature = bosonVoucher.interface.encodeFunctionData("preMint", [offerId, amount]); - - const message = { - from: assistant.address, - to: bosonVoucher.address, - nonce: nonce, - data: functionSignature, - }; - - const { signature } = await prepareDataSignatureParameters( - assistant, - types, - "ForwardRequest", - message, - forwarder.address, - "MockForwarder", - "0.0.1", - "0Z" - ); - - const tx = await forwarder.execute(message, signature); - - // Expect an event for every mint - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(ethers.constants.AddressZero, assistant.address, start.add(i)); - } - }); - - context("💔 Revert Reasons", async function () { - it("Caller is not the owner", async function () { - await expect(bosonVoucher.connect(rando).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - }); - - it("Offer id is not associated with a range", async function () { - // Set invalid offer id - offerId = 15; - - // Try to premint, it should fail - await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.NO_RESERVED_RANGE_FOR_OFFER - ); - }); - - it("Amount to mint is more than remaining un-minted in range", async function () { - // Mint 50 tokens - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Set invalid amount - amount = "990"; // length is 1000, already minted 50 - - // Try to premint, it should fail - await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.INVALID_AMOUNT_TO_MINT - ); - }); - - it("Offer already expired", async function () { - // Skip to after offer expiration - await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); - - // Try to premint, it should fail - await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.OFFER_EXPIRED_OR_VOIDED - ); - }); - - it("Offer is voided", async function () { - // Make offer voided - offer.voided = true; - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // Try to premint, it should fail - await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( - RevertReasons.OFFER_EXPIRED_OR_VOIDED - ); - }); - }); - }); - - context("burnPremintedVouchers()", function () { - let offerId, start, length, amount; - let mockProtocol; - let offer, offerDates, offerDurations, offerFees, disputeResolutionTerms; - - beforeEach(async function () { - offerId = "5"; - - mockProtocol = await deployMockProtocol(); - ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); - disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // reserve a range - start = "10"; - length = "1000"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // amount to mint - amount = "5"; - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // "void" the offer - offer.voided = true; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - }); - - it("Should emit Transfer events", async function () { - // Burn tokens, test for event - const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Number of events emitted should be equal to amount - assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); - - // Expect an event for every burn - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, ethers.constants.AddressZero, start.add(i)); - } - }); - - it("Should update state", async function () { - let sellerBalanceBefore = await bosonVoucher.balanceOf(assistant.address); - - // Burn tokens - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // All burned tokens should not have an owner - const startId = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - let tokenId = startId.add(i); - await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - - // Seller's balance should be decreased for the total burn amount - let sellerBalanceAfter = await bosonVoucher.balanceOf(assistant.address); - assert.equal(sellerBalanceAfter.toNumber(), sellerBalanceBefore.sub(amount).toNumber(), "Balance mismatch"); - - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), 0, "Available Premints mismatch"); - - // Last burned id should be updated - const tokenIdStart = deriveTokenId(offerId, start); - const lastBurnedId = tokenIdStart.add(amount - 1); - const range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - }); - - context("Contract owner is not owner of preminted vouchers", function () { - it("Ownership is transferred", async function () { - // Transfer ownership to rando - await bosonVoucher.connect(protocol).transferOwnership(rando.address); - - // Burn tokens, test for event - let tx; - await expect(() => { - tx = bosonVoucher.connect(rando).burnPremintedVouchers(offerId, amount); - return tx; - }).to.changeTokenBalance(bosonVoucher, assistant, Number(amount) * -1); - - // Number of events emitted should be equal to amount - tx = await tx; - assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); - - // Expect an event for every burn, where owner is the old owner (assistant) - const tokenIdStart = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, ethers.constants.AddressZero, tokenIdStart.add(i)); - } - }); - - it("Contract itself is the owner", async function () { - // create a new offer, so new range owner can be set - offerId = "6"; - offer.voided = false; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // reserve a range - start = "2000"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, bosonVoucher.address); - - // amount to mint - amount = "10"; - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // "void" the offer - offer.voided = true; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // Burn tokens, test for event - let tx; - await expect(() => { - tx = bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - return tx; - }).to.changeTokenBalance(bosonVoucher, bosonVoucher, Number(amount) * -1); - - // Number of events emitted should be equal to amount - tx = await tx; - assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); - - // Expect an event for every burn - const tokenIdStart = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(bosonVoucher.address, ethers.constants.AddressZero, tokenIdStart.add(i)); - } - }); - }); - - it("Should burn all vouchers", async function () { - // Burn tokens, test for event - let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Number of events emitted should be equal to amount - assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); - - // Last burned id should be updated - const tokenIdStart = deriveTokenId(offerId, start); - const lastBurnedId = tokenIdStart.add(amount - 1); - const range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // Second call should revert since there's nothing to burn - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN - ); - }); - - it("Should respect amount parameter", async function () { - // amout minted = 5 - // burn = 3 - const amountMinted = amount; - amount = 3; - - // Burn tokens, test for event - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Last burned id should be updated - const tokenIdStart = deriveTokenId(offerId, start); - const lastBurnedId = tokenIdStart.add(amount - 1); - let range = new Range(tokenIdStart.toString(), length, amountMinted, lastBurnedId.toString(), assistant.address); - let returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - - // burn remaining vouchers - amount = 2; - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - range.lastBurnedTokenId = tokenIdStart.add(amountMinted - 1).toString(); - returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - }); - - it("Should skip all vouchers were already committed", async function () { - let committedVouchers = [11, 14].map((tokenId) => deriveTokenId(offerId, tokenId).toString()); - - // Transfer some preminted vouchers - await mockProtocol.mock.commitToPreMintedOffer.returns(); - await Promise.all( - committedVouchers.map((tokenId) => - bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) - ) - ); - - // Burn tokens, test for event - let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Number of events emitted should be equal to amount of preminted vouchers decreased by length of committed vouchers - // We test this to indirectly verify that no events were emitted for committed vouchers - assert.equal( - (await tx.wait()).events.length, - Number(amount) - committedVouchers.length, - "Wrong number of events emitted" - ); - - // All burned tokens should not have an owner, but commited ones should - const startId = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - let tokenId = startId.add(i).toString(); - if (committedVouchers.includes(tokenId)) { - // Check that owner is buyer. - expect(await bosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); - } else { - // Check that Transfer event was emitted and owner does not exist anymore - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, ethers.constants.AddressZero, tokenId); - await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - } - - // Last burned id should be updated - const tokenIdStart = deriveTokenId(offerId, start); - const lastBurnedId = tokenIdStart.add(amount - 1); - const range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - }); - - it("Burning is possible if offer not voided, but just expired", async function () { - // make offer not voided so premint is possible - offer.voided = false; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); - - // Burn tokens, test for event - const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Number of events emitted should be equal to amount - assert.equal((await tx.wait()).events.length, Number(amount), "Wrong number of events emitted"); - - // Expect an event for every burn - start = deriveTokenId(offerId, start); - for (let i = 0; i < Number(amount); i++) { - await expect(tx) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, ethers.constants.AddressZero, start.add(i)); - } - }); - - context("💔 Revert Reasons", async function () { - it("Caller is not the owner", async function () { - await expect(bosonVoucher.connect(rando).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - }); - - it("Offer id is not associated with a range", async function () { - // Set invalid offer id - offerId = 15; - - // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.NO_RESERVED_RANGE_FOR_OFFER - ); - }); - - it("Offer is still valid", async function () { - // make offer not voided - offer.voided = false; - await mockProtocol.mock.getOffer - .withArgs(offerId) - .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); - - // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.OFFER_STILL_VALID - ); - }); - - it("Nothing to burn", async function () { - // Burn tokens - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Try to burn, it should fail - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN - ); - }); - - it("Amounts overflows minted range", async function () { - const amount = 6; - - await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( - RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN - ); - }); - }); - }); - - context("getAvailablePreMints()", function () { - let offerId, start, length, amount; - let offer, offerDates, offerDurations, offerFees; - let disputeResolutionTerms; - let mockProtocol; - - beforeEach(async function () { - // reserve a range - offerId = "5"; - start = "10"; - length = "1000"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // amount to mint - amount = 50; - - mockProtocol = await deployMockProtocol(); - ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); - disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - }); - - it("If nothing was preminted, return full range", async function () { - // Get available premints from contract - const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); - }); - - it("Part of range is preminted", async function () { - // Premint tokens - await bosonVoucher.connect(assistant).preMint(offerId, amount); - - // Get available premints from contract - let newAmount = Number(length) - Number(amount); - let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), newAmount, "Available Premints mismatch"); - - // Premint again - await bosonVoucher.connect(assistant).preMint(offerId, amount); - newAmount -= Number(amount); - availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), newAmount, "Available Premints mismatch"); - }); - - it("Range is fully minted", async function () { - // Premint tokens - await bosonVoucher.connect(assistant).preMint(offerId, length); - - // Get available premints from contract - let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), 0, "Available Premints mismatch"); - }); - - it("Range for offer does not exist", async function () { - // Set invalid offer id - offerId = "20"; - - // Get available premints from contract - let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), 0, "Available Premints mismatch"); - }); - - it("Should be 0 if offer is voided", async function () { - // void offer - offer.voided = true; - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // Get available premints from contract - let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), 0, "Available Premints mismatch"); - }); - - it("Should be 0 if offer is expired", async function () { - // Skip to after offer expiry - await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); - - // Get available premints from contract - let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); - assert.equal(availablePremints.toNumber(), 0, "Available Premints mismatch"); - }); - }); - - context("getRange()", function () { - let offerId, start, length, amount; - let range; - - beforeEach(async function () { - // reserve a range - offerId = "5"; - start = "10"; - length = "1000"; - const tokenIdStart = deriveTokenId(offerId, start); - - range = new Range(tokenIdStart.toString(), length, "0", "0", assistant.address); - - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - const mockProtocol = await deployMockProtocol(); - const { offer, offerDates, offerDurations, offerFees } = await mockOffer(); - const disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // amount to premint - amount = "50"; - range.minted = amount; - await bosonVoucher.connect(assistant).preMint(offerId, amount); - }); - - it("Get range object for offer with reserved range", async function () { - // Get range object from contract - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - }); - - it("Get empty range if offer has no reserved ranges", async function () { - // Set invalid offer and empty range - offerId = "20"; - range = new Range("0", "0", "0", "0", ethers.constants.AddressZero); - - // Get range object from contract - const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); - assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); - }); - }); - - context("ownerOf()", function () { - let offerId, start, length, amount; - let offer, offerDates, offerDurations, offerFees, disputeResolutionTerms; - let mockProtocol; - - context("No preminted tokens", async function () { - it("Returns true owner if token exists", async function () { - let tokenId = "100000"; - // Issue ordinary voucher - await bosonVoucher.connect(protocol).issueVoucher(tokenId, buyer.address); - - // Token owner should be the buyer - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, buyer.address, "Token owner mismatch"); - }); - - context("💔 Revert Reasons", async function () { - it("Token does not exist", async function () { - let tokenId = "10"; - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - }); - }); - }); - - context("With preminted tokens", async function () { - beforeEach(async function () { - // reserve a range - offerId = "5"; - start = "10"; - length = "150"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - mockProtocol = await deployMockProtocol(); - ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); - disputeResolutionTerms = new DisputeResolutionTerms("0", "0", "0", "0"); - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // amount to premint - amount = 50; - await bosonVoucher.connect(assistant).preMint(offerId, amount); - }); - - it("Returns true owner if token exists - via issue voucher", async function () { - let tokenId = "100000"; - - // Define what should be returned when getExchange is called - await mockProtocol.mock.getExchange.withArgs(tokenId).returns(true, mockExchange({ offerId }), mockVoucher()); - - // Issue ordinary voucher - await bosonVoucher.connect(protocol).issueVoucher(tokenId, buyer.address); - - // Token owner should be the buyer - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, buyer.address, "Token owner mismatch"); - }); - - it("Returns true owner if token exists - via preminted voucher transfer.", async function () { - let exchangeId = "25"; // tokens between 10 and 60 are preminted - const tokenId = deriveTokenId(offerId, exchangeId); - - const mockProtocol = await deployMockProtocol(); - - // Define what should be returned when commitToPreMintedOffer is called - await mockProtocol.mock.commitToPreMintedOffer.returns(); - - // Transfer preminted token - await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); - - // Token owner should be the buyer - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, buyer.address, "Token owner mismatch"); - }); - - it("Returns seller if token is preminted and not transferred yet", async function () { - // Token owner should be the seller for all preminted tokens - let startTokenId = deriveTokenId(offerId, start); - let endTokenId = startTokenId.add(amount); - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i.toString()}`); - } - }); - - it("Multiple ranges", async function () { - // Add five more ranges - // This tests more getPreMintStatus than ownerOf - // Might even be put into integration tests - let previousOfferId = Number(offerId); - let previousStartId = Number(start); - let ranges = [new Range(Number(start), length, amount, "0")]; - length = Number(length); - - for (let i = 0; i < 5; i++) { - offerId = previousOfferId + (i + 1) * 6; - start = previousStartId + length + 100; - - // reserve length - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // amount to premint - amount = length - i * 30; - await bosonVoucher.connect(assistant).preMint(offerId, amount); - ranges.push(new Range(start, length, amount, "0")); - - previousStartId = start; - previousOfferId = offerId; - } - - let endTokenId = previousStartId + length; // last range end - let rangeIndex = 0; - let currentRange = ranges[rangeIndex]; - let currentRangeMintEndId = currentRange.start + currentRange.minted - 1; - let currentRangeEndId = currentRange.start + length - 1; - - offerId = "5"; - for (let i = 0; i < endTokenId; i++) { - const tokenId = deriveTokenId(offerId, i); - if (i < currentRange.start) { - // tokenId not in range - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - } else if (i <= currentRangeMintEndId) { - // tokenId in range and minted. Seller should be the owner - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${tokenId.toString()}`); - } else if (i <= currentRangeEndId) { - // tokenId still in range, but not minted yet - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - } else { - // tokenId outside the current range - // Change current range - if (rangeIndex < ranges.length) { - currentRange = ranges[++rangeIndex]; - currentRangeMintEndId = currentRange.start + currentRange.minted - 1; - currentRangeEndId = currentRange.start + currentRange.length - 1; - offerId = Number(offerId) + rangeIndex * 6; - } - // Technically, next range could be consecutive and next call should return seller's address - // But range construction in this test ensures gaps between ranges - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - } - } - }); - - it("Consecutive ranges", async function () { - // Make two consecutive ranges - let nextOfferId = Number(offerId) + 1; - let nextStartId = Number(start) + Number(length); - let nextLength = "10"; - let nextAmount = "5"; - - // reserve length - await bosonVoucher.connect(protocol).reserveRange(nextOfferId, nextStartId, nextLength, assistant.address); - - // amount to premint - await bosonVoucher.connect(assistant).preMint(nextOfferId, nextAmount); - - // First range - preminted tokens - let startTokenId = deriveTokenId(offerId, start); - let endTokenId = startTokenId.add(amount); - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i.toString()}`); - } - - // First range - not preminted tokens - startTokenId = endTokenId; - let endExchangeId = Number(start) + Number(length); - endTokenId = deriveTokenId(offerId, endExchangeId); - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - - // Second range - preminted tokens - startTokenId = deriveTokenId(nextOfferId, endExchangeId); - endTokenId = startTokenId.add(nextAmount); - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i.toString()}`); - } - - // Second range - not preminted tokens - startTokenId = endTokenId; - endExchangeId += Number(nextLength); - endTokenId = deriveTokenId(nextOfferId, endExchangeId); - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - }); - - context("💔 Revert Reasons", async function () { - it("Token is outside any range and not minted", async function () { - let tokenId = "200000"; - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - }); - - it("Token is inside a range, but not minted yet", async function () { - let startTokenId = deriveTokenId(offerId, Number(start) + Number(amount)); - let endTokenId = deriveTokenId(offerId, Number(start) + Number(length)); - - // None of reserved but not preminted tokens should have an owner - for (let i = startTokenId; i.lt(endTokenId); i = i.add(1)) { - await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - } - }); - - it("Token was preminted, transferred and burned", async function () { - let exchangeId = "26"; - const tokenId = deriveTokenId(offerId, exchangeId); - - // Mock exchange handler methods (easier and more efficient than creating a real offer) - const mockProtocol = await deployMockProtocol(); - - // Define what should be returned when commitToPreMintedOffer is called - await mockProtocol.mock.commitToPreMintedOffer.returns(); - - // Token owner should be the seller - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, assistant.address, "Token owner mismatch"); - - // Transfer preminted token - await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); - - // Token owner should be the buyer - tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, buyer.address, "Token owner mismatch"); - - // Simulate burn - await bosonVoucher.connect(protocol).burnVoucher(tokenId); - - // Token should have no owner - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - }); - - it("Token was preminted, not transferred and burned", async function () { - let exchangeId = "26"; - const tokenId = deriveTokenId(offerId, exchangeId); - - // Token owner should be the seller - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, assistant.address, "Token owner mismatch"); - - // Void the offer - offer.voided = true; - await mockProtocol.mock.getOffer.returns( - true, - offer, - offerDates, - offerDurations, - disputeResolutionTerms, - offerFees - ); - - // Burn preminted voucher - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); - - // Token should have no owner - await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( - RevertReasons.ERC721_NON_EXISTENT - ); - }); - }); - }); - }); - - context("Token transfers", function () { - let bosonVoucher; - - afterEach(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - const transferFunctions = { - "transferFrom()": { - selector: "transferFrom(address,address,uint256)", - }, - "safeTransferFrom()": { - selector: "safeTransferFrom(address,address,uint256)", - }, - "safeTransferFrom() with bytes": { - selector: "safeTransferFrom(address,address,uint256,bytes)", - additionalArgs: ["0x"], - }, - }; - - beforeEach(async function () { - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); - - // Prepare the AuthToken and VoucherInitValues - emptyAuthToken = mockAuthToken(); - voucherInitValues = mockVoucherInitValues(); - await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - - agentId = "0"; // agent id is optional while creating an offer - - // Create a valid dispute resolver - disputeResolver = mockDisputeResolver( - assistantDR.address, - adminDR.address, - clerkDR.address, - treasuryDR.address, - true - ); - - // Create DisputeResolverFee array so offer creation will succeed - disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; - const sellerAllowList = []; - - // Register the dispute resolver - await accountHandler - .connect(adminDR) - .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); - }); - - Object.keys(transferFunctions).forEach(function (transferFunction) { - context(transferFunction, function () { - let tokenId, offerId; - let selector = transferFunctions[transferFunction].selector; - let additionalArgs = transferFunctions[transferFunction].additionalArgs ?? []; - - context("Transfer of an actual voucher", async function () { - beforeEach(async function () { - // Create an offer - const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); - await offerHandler - .connect(assistant) - .createOffer( - offer.toStruct(), - offerDates.toStruct(), - offerDurations.toStruct(), - disputeResolverId, - agentId - ); - await fundsHandler - .connect(admin) - .depositFunds(seller.id, ethers.constants.AddressZero, offer.sellerDeposit, { - value: offer.sellerDeposit, - }); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id, { value: offer.price }); - - exchangeId = offerId = "1"; - tokenId = deriveTokenId(offerId, exchangeId); - mockBuyer(); // call it just so accountId is correct - - // Update boson voucher address to actual seller's voucher - const voucherAddress = calculateContractAddress(accountHandler.address, "1"); - bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); - }); - - it("Should emit a Transfer event", async function () { - await expect( - bosonVoucher.connect(buyer)[selector](buyer.address, rando.address, tokenId, ...additionalArgs) - ) - .to.emit(bosonVoucher, "Transfer") - .withArgs(buyer.address, rando.address, tokenId); - }); - - it("Should update state", async function () { - // Before transfer, buyer should be the owner - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, buyer.address, "Buyer is not the owner"); - - await bosonVoucher.connect(buyer)[selector](buyer.address, rando.address, tokenId, ...additionalArgs); - - // After transfer, rando should be the owner - tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, rando.address, "Rando is not the owner"); - }); - - it("Should call onVoucherTransferred", async function () { - const randoBuyer = mockBuyer(); - await expect( - bosonVoucher.connect(buyer)[selector](buyer.address, rando.address, tokenId, ...additionalArgs) - ) - .to.emit(exchangeHandler, "VoucherTransferred") - .withArgs(offerId, exchangeId, randoBuyer.id, bosonVoucher.address); - }); - - it("Transfer on behalf of should work normally", async function () { - // Approve another address to transfer the voucher - await bosonVoucher.connect(buyer).setApprovalForAll(rando2.address, true); - - await expect( - bosonVoucher.connect(rando2)[selector](buyer.address, rando.address, tokenId, ...additionalArgs) - ) - .to.emit(bosonVoucher, "Transfer") - .withArgs(buyer.address, rando.address, tokenId); - }); - - it("If seller is the true owner of voucher, transfer should work same as for others", async function () { - mockBuyer(); // Call to properly update nextAccountId - await bosonVoucher.connect(buyer)[selector](buyer.address, assistant.address, tokenId, ...additionalArgs); - - const tx = await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - - await expect(tx).to.emit(bosonVoucher, "Transfer").withArgs(assistant.address, rando.address, tokenId); - - const randoBuyer = mockBuyer(); - - await expect(tx) - .to.emit(exchangeHandler, "VoucherTransferred") - .withArgs(offerId, exchangeId, randoBuyer.id, bosonVoucher.address); - }); - - context("💔 Revert Reasons", async function () { - it("From does not own the voucher", async function () { - await expect( - bosonVoucher.connect(rando)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); - }); - }); - }); - - context("Transfer of a preminted voucher when owner is assistant", async function () { - let voucherRedeemableFrom, voucherValid, offerValid; - beforeEach(async function () { - // Create preminted offer - const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); - offer.quantityAvailable = "2"; - await offerHandler - .connect(assistant) - .createOffer( - offer.toStruct(), - offerDates.toStruct(), - offerDurations.toStruct(), - disputeResolverId, - agentId - ); - - // Reserve range to assistant - await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); - // Pool needs to cover both seller deposit and price - const pool = ethers.BigNumber.from(offer.sellerDeposit).add(offer.price); - await fundsHandler.connect(admin).depositFunds(seller.id, ethers.constants.AddressZero, pool, { - value: pool, - }); - - // Store correct values - voucherRedeemableFrom = offerDates.voucherRedeemableFrom; - voucherValid = offerDurations.voucherValid; - offerValid = offerDates.validUntil; - exchangeId = offerId = "1"; - tokenId = deriveTokenId(offerId, exchangeId); - - // Update boson voucher address to actual seller's voucher - const voucherAddress = calculateContractAddress(accountHandler.address, "1"); - bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); - - // amount to premint - await bosonVoucher.connect(assistant).preMint(offerId, offer.quantityAvailable); - }); - - it("Should emit a Transfer event", async function () { - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, rando.address, tokenId); - }); - - it("Should update state", async function () { - // Before transfer, seller should be the owner - let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, assistant.address, "Seller is not the owner"); - - await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - - // After transfer, rando should be the owner - tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, rando.address, "Rando is not the owner"); - }); - - it("Should call commitToPreMintedOffer", async function () { - const randoBuyer = mockBuyer(); - const tx = await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - - // Get the block timestamp of the confirmed tx - const blockNumber = tx.blockNumber; - const block = await ethers.provider.getBlock(blockNumber); - - // Prepare exchange and voucher for validation - const exchange = mockExchange({ id: exchangeId, offerId, buyerId: randoBuyer.id, finalizedDate: "0" }); - const voucher = mockVoucher({ redeemedDate: "0" }); - - // Update the committed date in the expected exchange struct with the block timestamp of the tx - voucher.committedDate = block.timestamp.toString(); - // Update the validUntilDate date in the expected exchange struct - voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); - // First transfer should call commitToPreMintedOffer - await expect(tx) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs( - offerId, - randoBuyer.id, - exchangeId, - exchange.toStruct(), - voucher.toStruct(), - bosonVoucher.address - ); - }); - - it("Second transfer should behave as normal voucher transfer", async function () { - // First transfer should call commitToPreMintedOffer, and not onVoucherTransferred - let tx = await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - await expect(tx).to.emit(exchangeHandler, "BuyerCommitted"); - await expect(tx).to.not.emit(exchangeHandler, "VoucherTransferred"); - - // Second transfer should call onVoucherTransferred, and not commitToPreMintedOffer - tx = await bosonVoucher - .connect(rando) - [selector](rando.address, assistant.address, tokenId, ...additionalArgs); - await expect(tx).to.emit(exchangeHandler, "VoucherTransferred"); - await expect(tx).to.not.emit(exchangeHandler, "BuyerCommitted"); - - // Next transfer should call onVoucherTransferred, and not commitToPreMintedOffer, even if seller is the owner - tx = await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - await expect(tx).to.emit(exchangeHandler, "VoucherTransferred"); - await expect(tx).to.not.emit(exchangeHandler, "BuyerCommitted"); - }); - - it("Transfer on behalf of should work normally", async function () { - // Approve another address to transfer the voucher - await bosonVoucher.connect(assistant).setApprovalForAll(rando2.address, true); - - await expect( - bosonVoucher.connect(rando2)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ) - .to.emit(bosonVoucher, "Transfer") - .withArgs(assistant.address, rando.address, tokenId); - }); - - context("💔 Revert Reasons", async function () { - it("Cannot transfer preminted voucher twice", async function () { - // Make first transfer - await bosonVoucher - .connect(assistant) - [selector](assistant.address, buyer.address, tokenId, ...additionalArgs); - - // Second transfer should fail, since voucher has an owner - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); - }); - - it("Transfer preminted voucher, which was committed and burned already", async function () { - await bosonVoucher - .connect(assistant) - [selector](assistant.address, buyer.address, tokenId, ...additionalArgs); - - // Redeem voucher, effectively burning it - await setNextBlockTimestamp(ethers.BigNumber.from(voucherRedeemableFrom).toHexString()); - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // Transfer should fail, since voucher has been burned - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - }); - - it("Transfer preminted voucher, which was not committed but burned already", async function () { - // Void offer - await offerHandler.connect(assistant).voidOffer(offerId); - - // Burn preminted vouchers - await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, "1"); - - // None of reserved but not preminted tokens should have an owner - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); - }); - - it("Transfer preminted voucher, where offer was voided", async function () { - // Void offer - await offerHandler.connect(assistant).voidOffer(offerId); - - // Transfer should fail, since protocol reverts - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); - }); - - it("Transfer preminted voucher, where offer has expired", async function () { - // Skip past offer expiry - await setNextBlockTimestamp(ethers.BigNumber.from(offerValid).toHexString()); - - // Transfer should fail, since protocol reverts - await expect( - bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); - }); - - it("Transfer preminted voucher, but from is not the voucher owner", async function () { - await bosonVoucher - .connect(assistant) - [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - - // next token id. Make sure that assistant is the owner - tokenId = tokenId.add(1); - let tokenOwner = await bosonVoucher.ownerOf(tokenId.toString()); - assert.equal(tokenOwner, assistant.address, "Seller is not the owner"); - - // Following call should fail, since rando is not the owner of preminted voucher - await expect( - bosonVoucher.connect(rando)[selector](rando.address, rando.address, tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.NO_SILENT_MINT_ALLOWED); - }); - }); - }); - - context("Transfer of a preminted voucher when owner is contract", async function () { - beforeEach(async function () { - // Create preminted offer - const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); - offer.quantityAvailable = "2"; - - await offerHandler - .connect(assistant) - .createOffer( - offer.toStruct(), - offerDates.toStruct(), - offerDurations.toStruct(), - disputeResolverId, - agentId - ); - - // Update boson voucher address to actual seller's voucher - const voucherAddress = calculateContractAddress(accountHandler.address, "1"); - bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); - - // Reserve range to contract - await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, bosonVoucher.address); - - // Pool needs to cover both seller deposit and price - const pool = ethers.BigNumber.from(offer.sellerDeposit).add(offer.price); - await fundsHandler.connect(admin).depositFunds(seller.id, ethers.constants.AddressZero, pool, { - value: pool, - }); - - // Store correct values - exchangeId = offerId = "1"; - tokenId = deriveTokenId(offerId, exchangeId); - - // amount to premint - await bosonVoucher.connect(assistant).preMint(offerId, offer.quantityAvailable); - }); - - it("If voucher contract is the owner of voucher, transfer on behalf of should work normally", async function () { - // Approve another address to transfer the voucher - await bosonVoucher.connect(assistant).setApprovalForAllToContract(rando2.address, true); - - const tx = await bosonVoucher - .connect(rando2) - [selector](bosonVoucher.address, rando.address, tokenId, ...additionalArgs); - - await expect(tx).to.emit(bosonVoucher, "Transfer").withArgs(bosonVoucher.address, rando.address, tokenId); - }); - }); - }); - }); - }); - - context("burnVoucher()", function () { - after(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - it("should burn a voucher with success", async function () { - const buyerStruct = mockBuyer(buyer.address).toStruct(); - const buyerWallet = buyerStruct[1]; - await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); - - const balanceBefore = await bosonVoucher.balanceOf(buyer.address); - - await bosonVoucher.connect(protocol).burnVoucher(0); - - const balanceAfter = await bosonVoucher.balanceOf(buyer.address); - - expect(balanceBefore.sub(balanceAfter)).eq(1); - }); - - it("should revert if caller does not have PROTOCOL role", async function () { - // Expect revert if random user attempts to burn voucher - await expect(bosonVoucher.connect(rando).burnVoucher(0)).to.be.revertedWith(RevertReasons.ACCESS_DENIED); - - // Grant PROTOCOL role to random user address - await accessController.grantRole(Role.PROTOCOL, rando.address); - - // Prepare to burn voucher as a random user - const buyerStruct = mockBuyer(buyer.address).toStruct(); - const buyerWallet = buyerStruct[1]; - await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); - const balanceBefore = await bosonVoucher.balanceOf(buyer.address); - - //Attempt to burn voucher as a random user - await bosonVoucher.connect(protocol).burnVoucher(0); - const balanceAfter = await bosonVoucher.balanceOf(buyer.address); - - expect(balanceBefore.sub(balanceAfter)).eq(1); - }); - }); - - context("tokenURI", function () { - let metadataUri, offerId, offerPrice; - let bosonVoucher; - - beforeEach(async function () { - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); - - // prepare the VoucherInitValues - voucherInitValues = mockVoucherInitValues(); - expect(voucherInitValues.isValid()).is.true; - - // AuthToken - emptyAuthToken = mockAuthToken(); - expect(emptyAuthToken.isValid()).is.true; - - await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - - agentId = "0"; // agent id is optional while creating an offer - - // Create a valid dispute resolver - disputeResolver = mockDisputeResolver( - assistantDR.address, - adminDR.address, - clerkDR.address, - treasuryDR.address, - true - ); - expect(disputeResolver.isValid()).is.true; - - // Create DisputeResolverFee array so offer creation will succeed - disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; - - // Make empty seller list, so every seller is allowed - const sellerAllowList = []; - - // Register the dispute resolver - await accountHandler - .connect(adminDR) - .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); - - const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); - offerId = offer.id; - offerPrice = offer.price; - - await offerHandler - .connect(assistant) - .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); - - const pool = ethers.BigNumber.from(offer.sellerDeposit).add(offer.price); - - await fundsHandler.connect(admin).depositFunds(seller.id, ethers.constants.AddressZero, pool, { value: pool }); - - metadataUri = offer.metadataUri; - - // Update boson voucher address to actual seller's voucher - const voucherAddress = calculateContractAddress(accountHandler.address, "1"); - bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); - }); - - afterEach(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - it("should return the correct tokenURI", async function () { - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: offerPrice }); - const tokenId = deriveTokenId(offerId, 1); - const tokenURI = await bosonVoucher.tokenURI(tokenId); - expect(tokenURI).eq(metadataUri); - }); - - it("should return empty tokenURI if token does not exist", async function () { - const tokenURI = await bosonVoucher.tokenURI(10); - expect(tokenURI).eq(""); - }); - - context("pre-minted", async function () { - let start, tokenId; - beforeEach(async function () { - // reserve a range - start = "10"; - const length = "1"; - await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, assistant.address); - - // premint - await bosonVoucher.connect(assistant).preMint(offerId, 1); - - tokenId = deriveTokenId(offerId, start); - }); - - it("should return the correct tokenURI", async function () { - const tokenURI = await bosonVoucher.tokenURI(tokenId); - expect(tokenURI).eq(metadataUri); - }); - - it("should return correct tokenURI when token is preminted and transferred", async function () { - await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); - - const tokenURI = await bosonVoucher.tokenURI(tokenId); - expect(tokenURI).eq(metadataUri); - }); - }); - }); - - context("transferOwnership()", function () { - it("should emit OwnershipTransferred", async function () { - const ownable = await ethers.getContractAt("OwnableUpgradeable", bosonVoucher.address); - await expect(bosonVoucher.connect(protocol).transferOwnership(rando.address)) - .to.emit(ownable, "OwnershipTransferred") - .withArgs(assistant.address, rando.address); - }); - - it("should transfer ownership with success", async function () { - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - const ownable = await ethers.getContractAt("OwnableUpgradeable", bosonVoucher.address); - const owner = await ownable.owner(); - - expect(owner).eq(assistant.address, "Wrong owner"); - }); - - context("💔 Revert Reasons", async function () { - it("should revert if caller does not have PROTOCOL role", async function () { - await expect(bosonVoucher.connect(rando).transferOwnership(assistant.address)).to.be.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("Even the current owner cannot transfer the ownership", async function () { - // successfully transfer to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - // owner tries to transfer, it should fail - await expect(bosonVoucher.connect(assistant).transferOwnership(rando.address)).to.be.revertedWith( - RevertReasons.ACCESS_DENIED - ); - }); - - it("Current owner cannot renounce the ownership", async function () { - // successfully transfer to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - const ownable = await ethers.getContractAt("OwnableUpgradeable", bosonVoucher.address); - - // owner tries to renounce ownership, it should fail - await expect(ownable.connect(assistant).renounceOwnership()).to.be.revertedWith(RevertReasons.ACCESS_DENIED); - }); - - it("Transferring ownership to 0 is not allowed", async function () { - // try to transfer ownership to address 0, should fail - await expect(bosonVoucher.connect(protocol).transferOwnership(ethers.constants.AddressZero)).to.be.revertedWith( - RevertReasons.OWNABLE_ZERO_ADDRESS - ); - }); - }); - }); - - context("setContractURI()", function () { - beforeEach(async function () { - // give ownership to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - contractURI = "newContractURI"; - }); - - it("should emit ContractURIChanged event", async function () { - await expect(bosonVoucher.connect(assistant).setContractURI(contractURI)) - .to.emit(bosonVoucher, "ContractURIChanged") - .withArgs(contractURI); - }); - - it("should set new contract with success", async function () { - await bosonVoucher.connect(assistant).setContractURI(contractURI); - - const returnedContractURI = await bosonVoucher.contractURI(); - - expect(returnedContractURI).eq(contractURI, "Wrong contractURI"); - }); - - it("should revert if caller is not the owner", async function () { - // random caller - await expect(bosonVoucher.connect(rando).setContractURI(contractURI)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - - // protocol as the caller - await expect(bosonVoucher.connect(protocol).setContractURI(contractURI)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - }); - }); - - context("EIP2981 NFT Royalty fee", function () { - beforeEach(async function () { - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); - - // prepare the VoucherInitValues - voucherInitValues = mockVoucherInitValues(); - voucherInitValues.royaltyPercentage = "1000"; // 10% - expect(voucherInitValues.isValid()).is.true; - - // AuthToken - emptyAuthToken = mockAuthToken(); - expect(emptyAuthToken.isValid()).is.true; - - await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - - agentId = "0"; // agent id is optional while creating an offer - - // Create a valid dispute resolver - disputeResolver = mockDisputeResolver( - assistantDR.address, - adminDR.address, - clerkDR.address, - treasuryDR.address, - true - ); - expect(disputeResolver.isValid()).is.true; - - //Create DisputeResolverFee array so offer creation will succeed - disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; - - // Make empty seller list, so every seller is allowed - const sellerAllowList = []; - - // Register the dispute resolver - await accountHandler - .connect(adminDR) - .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); - - const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); - await offerHandler - .connect(assistant) - .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); - await fundsHandler - .connect(admin) - .depositFunds(seller.id, ethers.constants.AddressZero, offer.sellerDeposit, { value: offer.sellerDeposit }); - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id, { value: offer.price }); - - exchangeId = "1"; - - offerPrice = offer.price; - }); - - afterEach(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - context("setRoyaltyPercentage()", function () { - beforeEach(async function () { - // give ownership to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - }); - - it("should emit RoyaltyPercentageChanged event", async function () { - royaltyPercentage = "0"; //0% - await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)) - .to.emit(bosonVoucher, "RoyaltyPercentageChanged") - .withArgs(royaltyPercentage); - }); - - it("should set a royalty fee percentage", async function () { - // First, set royalty fee as 0 - royaltyPercentage = "0"; //0% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); - - // Expectations - let expectedRecipient = seller.treasury; - let expectedRoyaltyAmount = "0"; - - assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); - - // Now, set royalty fee as 10% - royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); - - // Expectations - expectedRecipient = seller.treasury; - expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); - - assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); - }); - - context("💔 Revert Reasons", async function () { - it("should revert if caller is not the owner", async function () { - // random caller - await expect(bosonVoucher.connect(rando).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - - // protocol as the caller - await expect(bosonVoucher.connect(protocol).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - }); - - it("should revert if royaltyPercentage is greater than max royalty percentage defined in the protocol", async function () { - // Set royalty fee as 15% (protocol limit is 10%) - royaltyPercentage = "1500"; //15% - - // royalty percentage too high, expectig revert - await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( - RevertReasons.ROYALTY_FEE_INVALID - ); - }); - }); - }); - - context("getRoyaltyPercentage()", function () { - it("should return the royalty fee percentage", async function () { - // give ownership to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - expect(await bosonVoucher.connect(rando).getRoyaltyPercentage()).to.equal( - royaltyPercentage, - "Invalid royalty percentage" - ); - }); - }); - - context("royaltyInfo()", function () { - beforeEach(async function () { - // give ownership to assistant - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - }); - - it("should return a recipient and royalty fee", async function () { - // First, set royalty fee as 0 - royaltyPercentage = "0"; //0% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); - - // Expectations - let expectedRecipient = seller.treasury; - let expectedRoyaltyAmount = "0"; - - assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); - - // Now, set royalty fee as 10% - royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); - - // Expectations - expectedRecipient = seller.treasury; - expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); - - assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); - - // Any random address can check the royalty info - // Now, set royalty fee as 8% - royaltyPercentage = "800"; //8% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); - - // Expectations - expectedRecipient = seller.treasury; - expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); - - assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); - }); - - it("if exchange doesn't exist it should return 0 values", async function () { - // Set royalty fee as 10% - royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - - // Set inexistent exchangeId - exchangeId = "100000"; - const [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); - - // Receiver and amount should be 0 - assert.equal(receiver, ethers.constants.AddressZero, "Recipient address is incorrect"); - assert.equal(royaltyAmount.toNumber(), 0, "Royalty amount is incorrect"); - }); - }); - - context("💔 Revert Reasons", async function () { - it("should revert during create seller if royaltyPercentage is greater than max royalty percentage defined in the protocol", async function () { - // create invalid voucherInitValues - royaltyPercentage = "2000"; // 20% - voucherInitValues = new VoucherInitValues("ContractURI", royaltyPercentage); - - // create another seller - seller = mockSeller(rando.address, rando.address, ethers.constants.AddressZero, rando.address); - seller.id = "2"; - - // royalty percentage too high, expectig revert - await expect( - accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues) - ).to.be.revertedWith(RevertReasons.ROYALTY_FEE_INVALID); - }); - }); - }); - - context("getSellerId()", function () { - it("should return the seller id", async function () { - // prepare the VoucherInitValues - voucherInitValues = mockVoucherInitValues(); - expect(voucherInitValues.isValid()).is.true; - - // AuthToken - emptyAuthToken = mockAuthToken(); - expect(emptyAuthToken.isValid()).is.true; - - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); - - await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - - await bosonVoucher.connect(protocol).transferOwnership(assistant.address); - - expect(await bosonVoucher.connect(rando).getSellerId()).to.equal(seller.id, "Invalid seller id returned"); - - // Reset the accountId iterator - accountId.next(true); - }); - - it("should return 0 if the seller doesn't exist", async function () { - await bosonVoucher.connect(protocol).transferOwnership(rando.address); - expect(await bosonVoucher.getSellerId()).to.equal(0, "Invalid seller id returned"); - }); - }); - - context("callExternalContract()", function () { - let mockSimpleContract, calldata; - - beforeEach(async function () { - // Deploy a random contract - const MockSimpleContract = await ethers.getContractFactory("MockSimpleContract"); - mockSimpleContract = await MockSimpleContract.deploy(); - await mockSimpleContract.deployed(); - - // Generate calldata - calldata = mockSimpleContract.interface.encodeFunctionData("testEvent"); - }); - - it("Should call external contract and emit its events", async function () { - const tx = await bosonVoucher.connect(assistant).callExternalContract(mockSimpleContract.address, calldata); - - const receipt = await tx.wait(); - const event = getEvent(receipt, mockSimpleContract, "TestEvent"); - - assert.equal(event._value.toString(), "1"); - }); - - context("💔 Revert Reasons", async function () { - it("_to is the zero address", async function () { - await expect( - bosonVoucher.connect(assistant).callExternalContract(ethers.constants.AddressZero, calldata) - ).to.be.revertedWith(RevertReasons.INVALID_ADDRESS); - }); - - it("Caller is not the contract owner", async function () { - await expect( - bosonVoucher.connect(rando).callExternalContract(mockSimpleContract.address, calldata) - ).to.be.revertedWith(RevertReasons.OWNABLE_NOT_OWNER); - }); - - it("External call reverts", async function () { - calldata = mockSimpleContract.interface.encodeFunctionData("testRevert"); - - await expect( - bosonVoucher.connect(assistant).callExternalContract(mockSimpleContract.address, calldata) - ).to.be.revertedWith("Reverted"); - }); - - it("To address is not a contract", async function () { - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.reverted; - }); - - it("Owner tries to invoke method to transfer funds", async function () { - const erc20 = await ethers.getContractFactory("Foreign20"); - - // transfer - calldata = erc20.interface.encodeFunctionData("transfer", [assistant.address, 20]); - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.revertedWith( - RevertReasons.FUNCTION_NOT_ALLOWLISTED - ); - - // transferFrom - calldata = erc20.interface.encodeFunctionData("transferFrom", [bosonVoucher.address, assistant.address, 20]); - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.revertedWith( - RevertReasons.FUNCTION_NOT_ALLOWLISTED - ); - - // approve - calldata = erc20.interface.encodeFunctionData("approve", [assistant.address, 20]); - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.revertedWith( - RevertReasons.FUNCTION_NOT_ALLOWLISTED - ); - - // DAI - const dai = await ethers.getContractAt("DAIAliases", ethers.constants.AddressZero); - - // push - calldata = dai.interface.encodeFunctionData("push", [assistant.address, 20]); - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.revertedWith( - RevertReasons.FUNCTION_NOT_ALLOWLISTED - ); - - // move - calldata = dai.interface.encodeFunctionData("move", [bosonVoucher.address, assistant.address, 20]); - await expect(bosonVoucher.connect(assistant).callExternalContract(rando.address, calldata)).to.be.revertedWith( - RevertReasons.FUNCTION_NOT_ALLOWLISTED - ); - }); - }); - }); - - context("setApprovalForAllToContract", function () { - it("Should emit ApprovalForAll event", async function () { - await expect(bosonVoucher.connect(assistant).setApprovalForAllToContract(rando.address, true)) - .to.emit(bosonVoucher, "ApprovalForAll") - .withArgs(bosonVoucher.address, rando.address, true); - }); - - context("💔 Revert Reasons", async function () { - it("should revert if caller is not the owner", async function () { - // Expect revert if random user attempts to set approval - await expect(bosonVoucher.connect(rando).setApprovalForAllToContract(rando.address, true)).to.revertedWith( - RevertReasons.OWNABLE_NOT_OWNER - ); - }); - - it("should revert if operator is zero address", async function () { - // Expect revert if random user attempts to set approval - await expect( - bosonVoucher.connect(assistant).setApprovalForAllToContract(ethers.constants.AddressZero, true) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); - }); - }); - }); - - context("withdrawToProtocol", function () { - beforeEach(async function () { - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); - - // Prepare the AuthToken and VoucherInitValues - emptyAuthToken = mockAuthToken(); - voucherInitValues = mockVoucherInitValues(); - await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - }); - - afterEach(async function () { - // Reset the accountId iterator - accountId.next(true); - }); - - it("Can withdraw native token", async function () { - // Sellers available funds should be empty - const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - let expectedAvailableFunds = new FundsList([]); - expect(sellersFundsBefore).to.eql(expectedAvailableFunds); - - const amount = ethers.utils.parseUnits("1", "ether"); - await admin.sendTransaction({ to: bosonVoucher.address, value: amount }); - - await expect(() => - bosonVoucher.connect(rando).withdrawToProtocol([ethers.constants.AddressZero]) - ).to.changeEtherBalances([bosonVoucher, fundsHandler], [amount.mul(-1), amount]); - }); - - it("Can withdraw ERC20", async function () { - // Sellers available funds should be empty - const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - let expectedAvailableFunds = new FundsList([]); - expect(sellersFundsBefore).to.eql(expectedAvailableFunds); - - const amount = ethers.utils.parseUnits("1", "ether"); - await foreign20.connect(deployer).mint(deployer.address, amount); - await foreign20.connect(deployer).transfer(bosonVoucher.address, amount); - - await expect(() => bosonVoucher.connect(rando).withdrawToProtocol([foreign20.address])).to.changeTokenBalances( - foreign20, - [bosonVoucher, fundsHandler], - [amount.mul(-1), amount] - ); - - // Seller's available balance should increase - expectedAvailableFunds = new FundsList([new Funds(foreign20.address, "Foreign20", amount.toString())]); - const sellerFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - expect(sellerFundsAfter).to.eql(expectedAvailableFunds); - }); - - it("Should withdraw all tokens when list length > 1", async function () { - // Sellers available funds should be empty - const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - let expectedAvailableFunds = new FundsList([]); - expect(sellersFundsBefore).to.eql(expectedAvailableFunds); - - const amount = ethers.utils.parseUnits("1", "ether"); - await admin.sendTransaction({ to: bosonVoucher.address, value: amount }); - await foreign20.connect(deployer).mint(deployer.address, amount); - await foreign20.connect(deployer).transfer(bosonVoucher.address, amount); - - let tx; - await expect(() => { - tx = bosonVoucher.connect(rando).withdrawToProtocol([ethers.constants.AddressZero, foreign20.address]); - return tx; - }).to.changeTokenBalances(foreign20, [bosonVoucher, fundsHandler], [amount.mul(-1), amount]); - await expect(() => tx).to.changeEtherBalances([bosonVoucher, fundsHandler], [amount.mul(-1), amount]); - }); - }); - - context("onERC721Received", function () { - it("Should return correct selector value", async function () { - const expectedSelector = bosonVoucher.interface.getSighash("onERC721Received(address,address,uint256,bytes)"); - const returnedSelector = await bosonVoucher.callStatic.onERC721Received( - assistant.address, - rando.address, - "1", - "0x" - ); - expect(returnedSelector).to.equal(expectedSelector); - }); - }); - - async function deployMockProtocol() { - const exchangeHandlerABI = exchangeHandler.interface.format(FormatTypes.json); - const configHandlerABI = configHandler.interface.format(FormatTypes.json); - const offerHandlerABI = offerHandler.interface.format(FormatTypes.json); - const mockProtocol = await deployMockContract(deployer, [ - ...JSON.parse(exchangeHandlerABI), - ...JSON.parse(configHandlerABI), - ...JSON.parse(offerHandlerABI), - ]); //deploys mock - - // Update protocol address on beacon - await beacon.connect(deployer).setProtocolAddress(mockProtocol.address); - - await mockProtocol.mock.getAccessControllerAddress.returns(accessController.address); - - return mockProtocol; - } -}); +const { ethers } = require("hardhat"); const { assert, expect } = require("chai"); const { ZeroAddress, getSigners, getContractAt, getContractFactory, provider, parseUnits, MaxUint256 } = ethers; const { getInterfaceIds } = require("../../../scripts/config/supported-interfaces.js"); const Role = require("../../../scripts/domain/Role"); const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); const Range = require("../../../scripts/domain/Range"); const VoucherInitValues = require("../../../scripts/domain/VoucherInitValues"); const { Funds, FundsList } = require("../../../scripts/domain/Funds"); const { RevertReasons } = require("../../../scripts/config/revert-reasons"); const { mockDisputeResolver, mockSeller, mockVoucherInitValues, mockAuthToken, mockBuyer, accountId, mockVoucher, mockExchange, mockOffer, } = require("../../util/mock"); const { applyPercentage, calculateContractAddress, calculateVoucherExpiry, setNextBlockTimestamp, setupTestEnvironment, getSnapshot, revertToSnapshot, prepareDataSignatureParameters, getEvent, deriveTokenId, } = require("../../util/utils.js"); const { deployMockTokens } = require("../../../scripts/util/deploy-mock-tokens"); describe("IBosonVoucher", function () { let interfaceIds; let accessController; let bosonVoucher, offerHandler, accountHandler, exchangeHandler, fundsHandler, configHandler; let deployer, protocol, buyer, rando, rando2, assistant, admin, clerk, treasury, assistantDR, adminDR, clerkDR, treasuryDR, seller, foreign20; let disputeResolver, disputeResolverFees; let emptyAuthToken; let voucherInitValues, contractURI, royaltyPercentage, exchangeId, offerPrice; let forwarder; let snapshotId; before(async function () { accountId.next(true); // Get interface id const { IBosonVoucher, IERC721, IERC2981 } = await getInterfaceIds(); interfaceIds = { IBosonVoucher, IERC721, IERC2981 }; // Mock forwarder to test metatx const MockForwarder = await getContractFactory("MockForwarder"); forwarder = await MockForwarder.deploy(); // Specify contracts needed for this test const contracts = { accountHandler: "IBosonAccountHandler", offerHandler: "IBosonOfferHandler", exchangeHandler: "IBosonExchangeHandler", fundsHandler: "IBosonFundsHandler", configHandler: "IBosonConfigHandler", }; ({ signers: [protocol, buyer, rando, rando2, admin, treasury, adminDR, treasuryDR], contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, configHandler }, extraReturnValues: { bosonVoucher, accessController }, } = await setupTestEnvironment(contracts, { forwarderAddress: [await forwarder.getAddress()], })); // make all account the same assistant = admin; assistantDR = adminDR; clerk = clerkDR = { address: ZeroAddress }; [deployer] = await getSigners(); // Grant protocol role to eoa so it's easier to test await accessController.grantRole(Role.PROTOCOL, await protocol.getAddress()); // Initialize voucher contract const sellerId = 1; // prepare the VoucherInitValues voucherInitValues = mockVoucherInitValues(); const bosonVoucherInit = await getContractAt("BosonVoucher", await bosonVoucher.getAddress()); await bosonVoucherInit.initializeVoucher(sellerId, await assistant.getAddress(), voucherInitValues); [foreign20] = await deployMockTokens(["Foreign20", "BosonToken"]); // Get snapshot id snapshotId = await getSnapshot(); }); afterEach(async function () { await revertToSnapshot(snapshotId); snapshotId = await getSnapshot(); // Reset accountId.next(true); }); // Interface support context("📋 Interfaces", async function () { context("👉 supportsInterface()", async function () { it("should indicate support for IBosonVoucher, IERC721 and IERC2981 interface", async function () { // IBosonVoucher interface let support = await bosonVoucher.supportsInterface(interfaceIds["IBosonVoucher"]); expect(support, "IBosonVoucher interface not supported").is.true; // IERC721 interface support = await bosonVoucher.supportsInterface(interfaceIds["IERC721"]); expect(support, "IERC721 interface not supported").is.true; // IERC2981 interface support = await bosonVoucher.supportsInterface(interfaceIds["IERC2981"]); expect(support, "IERC2981 interface not supported").is.true; }); }); }); context("General", async function () { it("Contract can receive native token", async function () { const balanceBefore = await provider.getBalance(await bosonVoucher.getAddress()); const amount = parseUnits("1", "ether"); await admin.sendTransaction({ to: await bosonVoucher.getAddress(), value: amount }); const balanceAfter = await provider.getBalance(await bosonVoucher.getAddress()); expect(balanceAfter - balanceBefore).to.eq(amount); }); it("Cannot initialize voucher twice", async function () { const initalizableClone = await getContractAt("IInitializableVoucherClone", await bosonVoucher.getAddress()); await expect( initalizableClone.initializeVoucher(2, await assistant.getAddress(), voucherInitValues) ).to.be.revertedWith(RevertReasons.INITIALIZABLE_ALREADY_INITIALIZED); }); }); context("Offer must exist", async function () { let offer, offerDates, offerDurations, disputeResolverId; before(async function () { const bosonVoucherCloneAddress = calculateContractAddress(await exchangeHandler.getAddress(), "1"); bosonVoucher = await getContractAt("IBosonVoucher", bosonVoucherCloneAddress); seller = mockSeller( await assistant.getAddress(), await admin.getAddress(), clerk.address, await treasury.getAddress() ); // Prepare the AuthToken and VoucherInitValues emptyAuthToken = mockAuthToken(); voucherInitValues = mockVoucherInitValues(); await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); // Create a valid dispute resolver disputeResolver = mockDisputeResolver( await assistantDR.getAddress(), await adminDR.getAddress(), clerkDR.address, await treasuryDR.getAddress(), true ); // Create DisputeResolverFee array so offer creation will succeed disputeResolverFees = [new DisputeResolverFee(ZeroAddress, "Native", "0")]; const sellerAllowList = []; // Register the dispute resolver await accountHandler .connect(adminDR) .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); ({ offer, offerDates, offerDurations, disputeResolverId } = await mockOffer()); offer.quantityAvailable = "1000"; await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); const amount = BigInt(offer.sellerDeposit) * BigInt(offer.quantityAvailable); await fundsHandler.connect(admin).depositFunds(seller.id, ZeroAddress, amount, { value: amount, }); // Get snapshot id snapshotId = await getSnapshot(); }); context("issueVoucher()", function () { let buyerStruct; let buyerWallet; before(async function () { buyerStruct = mockBuyer(await buyer.getAddress()).toStruct(); buyerWallet = buyerStruct[1]; }); it("should issue a voucher with success", async function () { const balanceBefore = await bosonVoucher.balanceOf(await buyer.getAddress()); await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); const balanceAfter = await bosonVoucher.balanceOf(await buyer.getAddress()); expect(balanceAfter - balanceBefore).eq(1); }); it("should issue a voucher if it does not overlap with range", async function () { const offerId = "1"; const start = "10"; const length = "123"; const tokenId = deriveTokenId(offerId, start); // token within reserved range // Reserve a range await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // Token id just below the range await expect(() => bosonVoucher.connect(protocol).issueVoucher(tokenId - 1n, buyerWallet) ).to.changeTokenBalance(bosonVoucher, buyer, 1); // Token id just above the range await expect(() => bosonVoucher.connect(protocol).issueVoucher(tokenId + BigInt(length), buyerWallet) ).to.changeTokenBalance(bosonVoucher, buyer, 1); }); context("💔 Revert Reasons", async function () { it("should revert if caller does not have PROTOCOL role", async function () { // Expect revert if random user attempts to issue voucher await expect(bosonVoucher.connect(rando).issueVoucher(0, buyerWallet)).to.be.revertedWith( RevertReasons.ACCESS_DENIED ); // Grant PROTOCOL role to random user address await accessController.grantRole(Role.PROTOCOL, await rando.getAddress()); // Attempt to issue voucher again as a random user const balanceBefore = await bosonVoucher.balanceOf(await buyer.getAddress()); await bosonVoucher.connect(rando).issueVoucher(0, buyerWallet); const balanceAfter = await bosonVoucher.balanceOf(await buyer.getAddress()); expect(balanceAfter - balanceBefore).eq(1); }); it("issueVoucher should revert if exchange id falls within a pre-minted offer's range", async function () { const offerId = "1"; const start = "10"; const length = "123"; const tokenId = deriveTokenId(offerId, "15"); // token within reserved range // Reserve a range await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // Expect revert if random user attempts to issue voucher await expect(bosonVoucher.connect(protocol).issueVoucher(tokenId, buyerWallet)).to.be.revertedWith( RevertReasons.EXCHANGE_ID_IN_RESERVED_RANGE ); }); }); }); context("reserveRange()", function () { let offerId, start, length; let range; beforeEach(async function () { offerId = "1"; start = "10"; length = "123"; const tokenStartId = deriveTokenId(offerId, start); range = new Range(tokenStartId.toString(), length, "0", "0", await assistant.getAddress()); }); it("Should emit event RangeReserved", async function () { // Reserve range, test for event await expect(bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress())) .to.emit(bosonVoucher, "RangeReserved") .withArgs(offerId, range.toStruct()); }); it("Should update state", async function () { // Reserve range await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // Get range object from contract const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); }); context("Owner range is contract", async function () { beforeEach(async function () { range.owner = await bosonVoucher.getAddress(); }); it("Should emit event RangeReserved", async function () { // Reserve range, test for event await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await bosonVoucher.getAddress()) ) .to.emit(bosonVoucher, "RangeReserved") .withArgs(offerId, range.toStruct()); }); it("Should update state", async function () { // Reserve range await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await bosonVoucher.getAddress()); // Get range object from contract const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); }); }); context("💔 Revert Reasons", async function () { it("caller does not have PROTOCOL role", async function () { await expect( bosonVoucher.connect(rando).reserveRange(offerId, start, length, await assistant.getAddress()) ).to.be.revertedWith(RevertReasons.ACCESS_DENIED); }); it("Start id is not greater than zero for the first range", async function () { // Set start id to 0 start = 0; // Try to reserve range, it should fail await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()) ).to.be.revertedWith(RevertReasons.INVALID_RANGE_START); }); it("Range length is zero", async function () { // Set length to 0 length = "0"; // Try to reserve range, it should fail await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()) ).to.be.revertedWith(RevertReasons.INVALID_RANGE_LENGTH); }); it("Range length is too large, i.e., would cause an overflow", async function () { // Set such numbers that would cause an overflow start = MaxUint256 / 2n + 2n; length = MaxUint256 / 2n; // Try to reserve range, it should fail await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()) ).to.be.revertedWith(RevertReasons.INVALID_RANGE_LENGTH); }); it("Offer id is already associated with a range", async function () { // Reserve range for an offer await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); start = Number(start) + Number(length) + 1; // Try to reserve range for the same offer, it should fail await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()) ).to.be.revertedWith(RevertReasons.OFFER_RANGE_ALREADY_RESERVED); }); it("_to address isn't contract address or contract owner address", async function () { // Try to reserve range for rando address, it should fail await expect( bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await rando.getAddress()) ).to.be.revertedWith(RevertReasons.INVALID_TO_ADDRESS); }); }); }); context("preMint()", function () { let offerId, start, length, amount; beforeEach(async function () { // reserve a range offerId = "1"; start = 10; length = "990"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to mint amount = "50"; }); it("Should emit Transfer events", async function () { // Premint tokens, test for event const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect an event for every mint start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(ZeroAddress, await assistant.getAddress(), start + BigInt(i)); } }); it("Should emit VouchersPreMinted event", async function () { // Premint tokens, test for event const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); start = deriveTokenId(offerId, start); await expect(tx) .to.emit(bosonVoucher, "VouchersPreMinted") .withArgs(offerId, start, start + BigInt(amount) - 1n); }); context("Owner range is contract", async function () { beforeEach(async function () { offer.id = offerId = ++offerId; await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); // reserve a range start = "1010"; length = "1000"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await bosonVoucher.getAddress()); }); it("Transfer event should emit contract address", async function () { // Premint tokens, test for event const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect an event for every mint start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(ZeroAddress, await bosonVoucher.getAddress(), start + BigInt(i)); } }); it("Should update state", async function () { let contractBalanceBefore = await bosonVoucher.balanceOf(await bosonVoucher.getAddress()); // Premint tokens await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect a correct owner for all preminted tokens start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { let tokenId = start + BigInt(i); let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await bosonVoucher.getAddress(), `Wrong token owner for token ${tokenId}`); } // Token that is inside a range, but wasn't preminted yet should not have an owner await expect(bosonVoucher.ownerOf(start + amount + 1)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); // Contract's balance should be updated for the total mint amount let contractBalanceAfter = await bosonVoucher.balanceOf(await bosonVoucher.getAddress()); assert.equal(contractBalanceAfter, contractBalanceBefore + BigInt(amount), "Balance mismatch"); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, BigInt(length) - BigInt(amount), "Available Premints mismatch"); }); }); it("Should update state", async function () { let sellerBalanceBefore = await bosonVoucher.balanceOf(await assistant.getAddress()); // Premint tokens await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect a correct owner for all preminted tokens start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { let tokenId = start + BigInt(i); let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await assistant.getAddress(), `Wrong token owner for token ${tokenId}`); } // Token that is inside a range, but wasn't preminted yet should not have an owner await expect(bosonVoucher.ownerOf(start + BigInt(amount) + 1n)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); // Seller's balance should be updated for the total mint amount let sellerBalanceAfter = await bosonVoucher.balanceOf(await assistant.getAddress()); assert.equal(sellerBalanceAfter, sellerBalanceBefore + BigInt(amount), "Balance mismatch"); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, BigInt(length) - BigInt(amount), "Available Premints mismatch"); }); it("MetaTx: forwarder can execute preMint on behalf of seller", async function () { const nonce = Number(await forwarder.getNonce(await assistant.getAddress())); const types = { ForwardRequest: [ { name: "from", type: "address" }, { name: "to", type: "address" }, { name: "nonce", type: "uint256" }, { name: "data", type: "bytes" }, ], }; const functionSignature = bosonVoucher.interface.encodeFunctionData("preMint", [offerId, amount]); const message = { from: await assistant.getAddress(), to: await bosonVoucher.getAddress(), nonce: nonce, data: functionSignature, }; const { signature } = await prepareDataSignatureParameters( assistant, types, "ForwardRequest", message, await forwarder.getAddress(), "MockForwarder", "0.0.1", "0Z" ); const tx = await forwarder.execute(message, signature); // Expect an event for every mint start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(ZeroAddress, await assistant.getAddress(), start + BigInt(i)); } }); context("💔 Revert Reasons", async function () { it("Caller is not the owner", async function () { await expect(bosonVoucher.connect(rando).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); }); it("Offer id is not associated with a range", async function () { // Set invalid offer id offerId = 15; // Try to premint, it should fail await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.NO_RESERVED_RANGE_FOR_OFFER ); }); it("Amount to mint is more than remaining un-minted in range", async function () { // Mint 50 tokens await bosonVoucher.connect(assistant).preMint(offerId, amount); // Set invalid amount amount = "990"; // length is 1000, already minted 50 // Try to premint, it should fail await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.INVALID_AMOUNT_TO_MINT ); }); it("Offer already expired", async function () { // Skip to after offer expiration await setNextBlockTimestamp(Number(BigInt(offerDates.validUntil) + 1n)); // Try to premint, it should fail await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_EXPIRED_OR_VOIDED ); }); it("Offer is voided", async function () { await offerHandler.connect(assistant).voidOffer(offerId); // Try to premint, it should fail await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_EXPIRED_OR_VOIDED ); }); }); }); context("burnPremintedVouchers()", function () { let offerId, start, length, amount; beforeEach(async function () { offerId = "1"; // reserve a range start = "1"; length = "1000"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to mint amount = "5"; await bosonVoucher.connect(assistant).preMint(offerId, amount); // Void offer await offerHandler.connect(assistant).voidOffer(offerId); }); it("Should emit Transfer events", async function () { // Burn tokens, test for event const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Expect an event for every burn start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), ZeroAddress, start + BigInt(i)); } }); it("Should update state", async function () { let sellerBalanceBefore = await bosonVoucher.balanceOf(await assistant.getAddress()); // Burn tokens await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // All burned tokens should not have an owner const startId = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { let tokenId = startId + BigInt(i); await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); } // Seller's balance should be decreased for the total burn amount let sellerBalanceAfter = await bosonVoucher.balanceOf(await assistant.getAddress()); assert.equal(sellerBalanceAfter, sellerBalanceBefore - BigInt(amount), "Balance mismatch"); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, 0n, "Available Premints mismatch"); // Last burned id should be updated const tokenIdStart = deriveTokenId(offerId, start); const lastBurnedId = tokenIdStart + BigInt(amount) - 1n; const range = new Range( tokenIdStart.toString(), length, amount, lastBurnedId.toString(), await assistant.getAddress() ); const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); }); context("Contract owner is not owner of preminted vouchers", function () { it("Ownership is transferred", async function () { // Transfer ownership to rando await bosonVoucher.connect(protocol).transferOwnership(await rando.getAddress()); // Burn tokens, test for event let tx; await expect(() => { tx = bosonVoucher.connect(rando).burnPremintedVouchers(offerId,amount); return tx; }).to.changeTokenBalance(bosonVoucher, assistant, BigInt(amount) * -1n); // Expect an event for every burn, where owner is the old owner (assistant) const tokenIdStart = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), ZeroAddress, tokenIdStart + BigInt(i)); } }); it("Contract itself is the owner", async function () { offer.id = offerId = ++offerId; offer.quantityAvailable = "2000"; await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); // reserve a range start = "2000"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await bosonVoucher.getAddress()); // amount to mint amount = "10"; await bosonVoucher.connect(assistant).preMint(offerId, amount); await offerHandler.connect(assistant).voidOffer(offerId); // Burn tokens, test for event let tx; await expect(() => { tx = bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); return tx; }).to.changeTokenBalance(bosonVoucher, bosonVoucher, BigInt(amount) * -1n); // Expect an event for every burn const tokenIdStart = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await bosonVoucher.getAddress(), ZeroAddress, tokenIdStart + BigInt(i)); } }); }); it("Should burn all vouchers if there is less than MaxPremintedVouchers to burn", async function () { // Burn tokens, test for event let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Number of events emitted should be equal to amount assert.equal((await tx.wait()).logs.length, Number(amount), "Wrong number of events emitted"); // Last burned id should be updated const tokenIdStart = deriveTokenId(offerId, start); const lastBurnedId = tokenIdStart + BigInt(amount) - 1n; const range = new Range(tokenIdStart.toString(), length, amount, lastBurnedId.toString(), assistant.address); const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); // Second call should revert since there's nothing to burn await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN ); }); context("Test that require non-voided offer", function () { let assistantAddress; beforeEach(async function () { // make offer not voided so premint is possible offer.voided = false; // make offer not voided offer.id = offerId = ++offerId; length = amount = "10"; start = "1"; assistantAddress = await assistant.getAddress(); await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); await offerHandler.connect(assistant).reserveRange(offerId, length, assistantAddress); await bosonVoucher.connect(assistant).preMint(offerId, length); }); it("Should skip all vouchers that were already committed", async function () { let committedVouchers = [2, 4].map((tokenId) => deriveTokenId(offerId, tokenId)); // Transfer some preminted vouchers const buyerAddress = await buyer.getAddress(); await Promise.all( committedVouchers.map((tokenId) => bosonVoucher.connect(assistant).transferFrom(assistantAddress, buyerAddress, tokenId) ) ); await offerHandler.connect(assistant).voidOffer(offerId); // Burn tokens, test for event let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // All burned tokens should not have an owner, but committed ones should const startId = deriveTokenId(offerId, start); for (let i = 0; i < Number(length); i++) { let tokenId = startId + BigInt(i); if (committedVouchers.includes(tokenId)) { // Check that owner is buyer. expect(await bosonVoucher.ownerOf(tokenId)).to.equal(buyerAddress); } else { // Check that Transfer event was emitted and owner does not exist anymore await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), ZeroAddress, tokenId); await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); } } // Last burned id should be updated const tokenIdStart = deriveTokenId(offerId, start); const lastBurnedId = tokenIdStart + BigInt(amount) - 1n; const range = new Range( tokenIdStart.toString(), length, amount, lastBurnedId.toString(), await assistant.getAddress() ); const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); }); it("Burning is possible if offer not voided, but just expired", async function () { // skip to after offer expiration await setNextBlockTimestamp(Number(BigInt(offerDates.validUntil) + 1n)); // Burn tokens, test for event const tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Expect an event for every burn start = deriveTokenId(offerId, start); for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), ZeroAddress, start + BigInt(i)); } }); }); context("💔 Revert Reasons", async function () { it("Caller is not the owner", async function () { await expect(bosonVoucher.connect(rando).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); }); it("Offer id is not associated with a range", async function () { // Set invalid offer id offerId = 15; // Try to burn, it should fail await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.NO_RESERVED_RANGE_FOR_OFFER ); }); it("Offer is still valid", async function () { // make offer not voided offer.id = offerId = ++offerId; await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // Mint another 10 vouchers, so that there are 15 in total await bosonVoucher.connect(assistant).preMint(offerId, 10); // Try to burn, it should fail await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_STILL_VALID ); }); it("Nothing to burn", async function () { // Burn tokens await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Try to burn, it should fail await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount)).to.be.revertedWith( RevertReasons.AMOUNT_EXCEEDS_RANGE_OR_NOTHING_TO_BURN ); }); }); }); context("getAvailablePreMints()", function () { let offerId, start, length, amount; beforeEach(async function () { // reserve a range offerId = "1"; start = "10"; length = "990"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to mint amount = 50; }); it("If nothing was preminted, return full range", async function () { // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints.toString(), length, "Available Premints mismatch"); }); it("Part of range is preminted", async function () { // Premint tokens await bosonVoucher.connect(assistant).preMint(offerId, amount); // Get available premints from contract let newAmount = BigInt(length) - BigInt(amount); let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, newAmount, "Available Premints mismatch"); // Premint again await bosonVoucher.connect(assistant).preMint(offerId, amount); newAmount -= BigInt(amount); availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, newAmount, "Available Premints mismatch"); }); it("Range is fully minted", async function () { // Premint tokens await bosonVoucher.connect(assistant).preMint(offerId, length); // Get available premints from contract let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, 0, "Available Premints mismatch"); }); it("Range for offer does not exist", async function () { // Set invalid offer id offerId = "20"; // Get available premints from contract let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, 0, "Available Premints mismatch"); }); it("Should be 0 if offer is voided", async function () { await offerHandler.connect(assistant).voidOffer(offerId); // Get available premints from contract let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, 0, "Available Premints mismatch"); }); it("Should be 0 if offer is expired", async function () { // Skip to after offer expiry await setNextBlockTimestamp(Number(BigInt(offerDates.validUntil) + 1n)); // Get available premints from contract let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints, 0, "Available Premints mismatch"); }); }); context("getRange()", function () { let offerId, start, length, amount; let range; beforeEach(async function () { // reserve a range offerId = "1"; start = "10"; length = "990"; const tokenIdStart = deriveTokenId(offerId, start); range = new Range(tokenIdStart.toString(), length, "0", "0", await assistant.getAddress()); await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to premint amount = "50"; range.minted = amount; await bosonVoucher.connect(assistant).preMint(offerId, amount); }); it("Get range object for offer with reserved range", async function () { // Get range object from contract const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); }); it("Get empty range if offer has no reserved ranges", async function () { // Set invalid offer and empty range offerId = "20"; range = new Range("0", "0", "0", "0", ZeroAddress); // Get range object from contract const returnedRange = Range.fromStruct(await bosonVoucher.getRangeByOfferId(offerId)); assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); }); }); context("ownerOf()", function () { let offerId, start, length, amount; context("No preminted tokens", async function () { it("Returns true owner if token exists", async function () { let tokenId = "100000"; // Issue ordinary voucher await bosonVoucher.connect(protocol).issueVoucher(tokenId, await buyer.getAddress()); // Token owner should be the buyer let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await buyer.getAddress(), "Token owner mismatch"); }); context("💔 Revert Reasons", async function () { it("Token does not exist", async function () { let tokenId = "10"; await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); }); }); }); context("With preminted tokens", async function () { beforeEach(async function () { // reserve a range offerId = "1"; start = "10"; length = "150"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to premint amount = 50; await bosonVoucher.connect(assistant).preMint(offerId, amount); }); it("Returns true owner if token exists - via issue voucher", async function () { let tokenId = "100000"; // Issue ordinary voucher await bosonVoucher.connect(protocol).issueVoucher(tokenId, await buyer.getAddress()); // Token owner should be the buyer let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await buyer.getAddress(), "Token owner mismatch"); }); it("Returns true owner if token exists - via preminted voucher transfer.", async function () { let exchangeId = "25"; // tokens between 10 and 60 are preminted const tokenId = deriveTokenId(offerId, exchangeId); // Transfer preminted token await bosonVoucher .connect(assistant) .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId); // Token owner should be the buyer let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await buyer.getAddress(), "Token owner mismatch"); }); it("Returns seller if token is preminted and not transferred yet", async function () { // Token owner should be the seller for all preminted tokens let startTokenId = deriveTokenId(offerId, start); let endTokenId = startTokenId + BigInt(amount); for (let i = startTokenId; i < endTokenId; i = i + 1n) { let tokenOwner = await bosonVoucher.ownerOf(i); assert.equal(tokenOwner, await assistant.getAddress(), `Token owner mismatch ${i.toString()}`); } }); it("Multiple ranges", async function () { // Add five more ranges // This tests more getPreMintStatus than ownerOf // Might even be put into integration tests let previousOfferId = Number(offerId); let previousStartId = Number(start); let ranges = [new Range(Number(start), length, amount, "0")]; length = Number(length); offerId = ++previousOfferId; while (offerId <= 6) { start = previousStartId + length + 100; await offerHandler .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); // reserve length await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // amount to premint amount = length - (offerId - 2) * 30; await bosonVoucher.connect(assistant).preMint(offerId, amount); ranges.push(new Range(start, length, amount, "0")); previousStartId = start; offerId++; } let endTokenId = previousStartId + length; // last range end let rangeIndex = 0; let currentRange = ranges[rangeIndex]; let currentRangeMintEndId = currentRange.start + currentRange.minted - 1; let currentRangeEndId = currentRange.start + length - 1; offerId = 1; for (let i = 0; i < endTokenId; i++) { const tokenId = deriveTokenId(offerId, i); if (i < currentRange.start) { // tokenId not in range await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } else if (i <= currentRangeMintEndId) { // tokenId in range and minted. Seller should be the owner let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await assistant.getAddress(), `Token owner mismatch ${tokenId.toString()}`); } else if (i <= currentRangeEndId) { // tokenId still in range, but not minted yet await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } else { // tokenId outside the current range // Change current range if (rangeIndex < ranges.length) { currentRange = ranges[++rangeIndex]; currentRangeMintEndId = currentRange.start + currentRange.minted - 1; currentRangeEndId = currentRange.start + currentRange.length - 1; offerId++; } // Technically, next range could be consecutive and next call should return seller's address // But range construction in this test ensures gaps between ranges await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } } }); it("Consecutive ranges", async function () { // Make two consecutive ranges let nextOfferId = Number(offerId) + 1; let nextStartId = Number(start) + Number(length); let nextLength = "10"; let nextAmount = "5"; await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); // reserve length await bosonVoucher .connect(protocol) .reserveRange(nextOfferId, nextStartId, nextLength, await assistant.getAddress()); // amount to premint await bosonVoucher.connect(assistant).preMint(nextOfferId, nextAmount); // First range - preminted tokens let startTokenId = deriveTokenId(offerId, start); let endTokenId = startTokenId + BigInt(amount); for (let i = startTokenId; i < endTokenId; i = i + 1n) { let tokenOwner = await bosonVoucher.ownerOf(i); assert.equal(tokenOwner, await assistant.getAddress(), `Token owner mismatch ${i.toString()}`); } // First range - not preminted tokens startTokenId = endTokenId; let endExchangeId = Number(start) + Number(length); endTokenId = deriveTokenId(offerId, endExchangeId); for (let i = startTokenId; i < endTokenId; i = i + 1n) { await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } // Second range - preminted tokens startTokenId = deriveTokenId(nextOfferId, endExchangeId); endTokenId = startTokenId + BigInt(nextAmount); for (let i = startTokenId; i < endTokenId; i = i + 1n) { let tokenOwner = await bosonVoucher.ownerOf(i); assert.equal(tokenOwner, await assistant.getAddress(), `Token owner mismatch ${i.toString()}`); } // Second range - not preminted tokens startTokenId = endTokenId; endExchangeId += Number(nextLength); endTokenId = deriveTokenId(nextOfferId, endExchangeId); for (let i = startTokenId; i < endTokenId; i = i + 1n) { await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } }); context("💔 Revert Reasons", async function () { it("Token is outside any range and not minted", async function () { let tokenId = "200000"; await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); }); it("Token is inside a range, but not minted yet", async function () { let startTokenId = deriveTokenId(offerId, Number(start) + Number(amount)); let endTokenId = deriveTokenId(offerId, Number(start) + Number(length)); // None of reserved but not preminted tokens should have an owner for (let i = startTokenId; i < endTokenId; i = i + 1n) { await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); } }); it("Token was preminted, transferred and burned", async function () { let exchangeId = "26"; const tokenId = deriveTokenId(offerId, exchangeId); // Token owner should be the seller let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await assistant.getAddress(), "Token owner mismatch"); // Transfer preminted token await bosonVoucher .connect(assistant) .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId); // Token owner should be the buyer tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await buyer.getAddress(), "Token owner mismatch"); // Simulate burn await bosonVoucher.connect(protocol).burnVoucher(tokenId); // Token should have no owner await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); }); it("Token was preminted, not transferred and burned", async function () { let exchangeId = "26"; const tokenId = deriveTokenId(offerId, exchangeId); // Token owner should be the seller let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await assistant.getAddress(), "Token owner mismatch"); await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted voucher await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, amount); // Token should have no owner await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( RevertReasons.ERC721_INVALID_TOKEN_ID ); }); }); }); }); context("Token transfers", function () { const transferFunctions = { "transferFrom()": { selector: "transferFrom(address,address,uint256)", }, "safeTransferFrom()": { selector: "safeTransferFrom(address,address,uint256)", }, "safeTransferFrom() with bytes": { selector: "safeTransferFrom(address,address,uint256,bytes)", additionalArgs: ["0x"], }, }; Object.keys(transferFunctions).forEach(function (transferFunction) { context(transferFunction, function () { let tokenId, offerId, buyerId; let selector = transferFunctions[transferFunction].selector; let additionalArgs = transferFunctions[transferFunction].additionalArgs ?? []; context("Transfer of an actual voucher", async function () { beforeEach(async function () { exchangeId = offerId = "1"; tokenId = deriveTokenId(offerId, exchangeId); // commit and create buyer account await exchangeHandler.commitToOffer(await buyer.getAddress(), offerId, { value: offer.price }); }); it("Should emit a Transfer event", async function () { await expect( bosonVoucher .connect(buyer) [selector](await buyer.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") .withArgs(await buyer.getAddress(), await rando.getAddress(), tokenId); }); it("Should update state", async function () { // Before transfer, buyer should be the owner let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await buyer.getAddress(), "Buyer is not the owner"); await bosonVoucher .connect(buyer) [selector](await buyer.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); // After transfer, rando should be the owner tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await rando.getAddress(), "Rando is not the owner"); }); it("Should call onVoucherTransferred", async function () { buyerId = 4n; await expect( bosonVoucher .connect(buyer) [selector](await buyer.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ) .to.emit(exchangeHandler, "VoucherTransferred") .withArgs(offerId, exchangeId, buyerId, await bosonVoucher.getAddress()); }); it("Transfer on behalf of should work normally", async function () { // Approve another address to transfer the voucher await bosonVoucher.connect(buyer).setApprovalForAll(await rando2.getAddress(), true); await expect( bosonVoucher .connect(rando2) [selector](await buyer.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") .withArgs(await buyer.getAddress(), await rando.getAddress(), tokenId); }); it("If seller is the true owner of voucher, transfer should work same as for others", async function () { buyerId = 5n; await bosonVoucher .connect(buyer) [selector](await buyer.getAddress(), await assistant.getAddress(), tokenId, ...additionalArgs); const tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), await rando.getAddress(), tokenId); await expect(tx) .to.emit(exchangeHandler, "VoucherTransferred") .withArgs(offerId, exchangeId, buyerId, await bosonVoucher.getAddress()); }); context("💔 Revert Reasons", async function () { it("From does not own the voucher", async function () { await expect( bosonVoucher .connect(rando) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); }); }); context("Transfer of a preminted voucher when owner is assistant", async function () { let voucherRedeemableFrom, voucherValid, offerValid; beforeEach(async function () { exchangeId = offerId = "1"; const amount = "5"; buyerId = 3n; await offerHandler.connect(assistant).reserveRange(offerId, amount, await assistant.getAddress()); // amount to premint await bosonVoucher.connect(assistant).preMint(offerId, amount); tokenId = deriveTokenId(offerId, exchangeId); voucherRedeemableFrom = offerDates.voucherRedeemableFrom; voucherValid = offerDurations.voucherValid; offerValid = offerDates.validUntil; }); it("Should emit a Transfer event", async function () { await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), await rando.getAddress(), tokenId); }); it("Should update state", async function () { // Before transfer, seller should be the owner let tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await assistant.getAddress(), "Seller is not the owner"); await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); // After transfer, rando should be the owner tokenOwner = await bosonVoucher.ownerOf(tokenId); assert.equal(tokenOwner, await rando.getAddress(), "Rando is not the owner"); }); it("Should call commitToPreMintedOffer", async function () { const tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); // Get the block timestamp of the confirmed tx const blockNumber = tx.blockNumber; const block = await provider.getBlock(blockNumber); // Prepare exchange and voucher for validation const exchange = mockExchange({ id: exchangeId, offerId, buyerId, finalizedDate: "0" }); const voucher = mockVoucher({ redeemedDate: "0" }); // Update the committed date in the expected exchange struct with the block timestamp of the tx voucher.committedDate = block.timestamp; // Update the validUntilDate date in the expected exchange struct voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); // First transfer should call commitToPreMintedOffer await expect(tx) .to.emit(exchangeHandler, "BuyerCommitted") .withArgs( offerId, buyerId, exchangeId, exchange.toStruct(), voucher.toStruct(), await bosonVoucher.getAddress() ); }); it("Second transfer should behave as normal voucher transfer", async function () { // First transfer should call commitToPreMintedOffer, and not onVoucherTransferred let tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); await expect(tx).to.emit(exchangeHandler, "BuyerCommitted"); await expect(tx).to.not.emit(exchangeHandler, "VoucherTransferred"); // Second transfer should call onVoucherTransferred, and not commitToPreMintedOffer tx = await bosonVoucher .connect(rando) [selector](await rando.getAddress(), await assistant.getAddress(), tokenId, ...additionalArgs); await expect(tx).to.emit(exchangeHandler, "VoucherTransferred"); await expect(tx).to.not.emit(exchangeHandler, "BuyerCommitted"); // Next transfer should call onVoucherTransferred, and not commitToPreMintedOffer, even if seller is the owner tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); await expect(tx).to.emit(exchangeHandler, "VoucherTransferred"); await expect(tx).to.not.emit(exchangeHandler, "BuyerCommitted"); }); it("Transfer on behalf of should work normally", async function () { // Approve another address to transfer the voucher await bosonVoucher.connect(assistant).setApprovalForAll(await rando2.getAddress(), true); await expect( bosonVoucher .connect(rando2) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") .withArgs(await assistant.getAddress(), await rando.getAddress(), tokenId); }); context("💔 Revert Reasons", async function () { it("Cannot transfer preminted voucher twice", async function () { // Make first transfer await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await buyer.getAddress(), tokenId, ...additionalArgs); // Second transfer should fail, since voucher has an owner await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); it("Transfer preminted voucher, which was committed and burned already", async function () { await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await buyer.getAddress(), tokenId, ...additionalArgs); // Redeem voucher, effectively burning it await setNextBlockTimestamp(Number(voucherRedeemableFrom)); await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); // Transfer should fail, since voucher has been burned await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); }); it("Transfer preminted voucher, which was not committed but burned already", async function () { // Void offer await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted vouchers await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId, "1"); // None of reserved but not preminted tokens should have an owner await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); }); it("Transfer preminted voucher, where offer was voided", async function () { // Void offer await offerHandler.connect(assistant).voidOffer(offerId); // Transfer should fail, since protocol reverts await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); }); it("Transfer preminted voucher, where offer has expired", async function () { // Skip past offer expiry await setNextBlockTimestamp(Number(offerValid)); // Transfer should fail, since protocol reverts await expect( bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); }); it("Transfer preminted voucher, but from is not the voucher owner", async function () { await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); // next token id. Make sure that assistant is the owner tokenId = tokenId + 1n; let tokenOwner = await bosonVoucher.ownerOf(tokenId.toString()); assert.equal(tokenOwner, await assistant.getAddress(), "Seller is not the owner"); // Following call should fail, since rando is not the owner of preminted voucher await expect( bosonVoucher .connect(rando) [selector](await rando.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.NO_SILENT_MINT_ALLOWED); }); }); }); context("Transfer of a preminted voucher when owner is contract", async function () { beforeEach(async function () { exchangeId = offerId = "1"; tokenId = deriveTokenId(offerId, exchangeId); const amount = "5"; buyerId = 3n; await offerHandler.connect(assistant).reserveRange(offerId, amount, await bosonVoucher.getAddress()); // amount to premint await bosonVoucher.connect(assistant).preMint(offerId, amount); }); it("If voucher contract is the owner of voucher, transfer on behalf of should work normally", async function () { // Approve another address to transfer the voucher await bosonVoucher.connect(assistant).setApprovalForAllToContract(await rando2.getAddress(), true); const tx = await bosonVoucher .connect(rando2) [selector](await bosonVoucher.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); await expect(tx) .to.emit(bosonVoucher, "Transfer") .withArgs(await bosonVoucher.getAddress(), await rando.getAddress(), tokenId); }); }); }); }); }); context("tokenURI", function () { let metadataUri, offerId; beforeEach(async function () { offerId = "1"; metadataUri = offer.metadataUri; }); it("should return the correct tokenURI", async function () { const buyerAddress = await buyer.getAddress(); await exchangeHandler.connect(buyer).commitToOffer(buyerAddress, offerId, { value: offer.price }); const tokenId = deriveTokenId(offerId, 1); const tokenURI = await bosonVoucher.tokenURI(tokenId); expect(tokenURI).eq(metadataUri); }); context("pre-minted", async function () { let start, tokenId; beforeEach(async function () { // reserve a range start = "10"; const length = "1"; await bosonVoucher.connect(protocol).reserveRange(offerId, start, length, await assistant.getAddress()); // premint await bosonVoucher.connect(assistant).preMint(offerId, 1); tokenId = deriveTokenId(offerId, start); }); it("should return the correct tokenURI", async function () { const tokenURI = await bosonVoucher.tokenURI(tokenId); expect(tokenURI).eq(metadataUri); }); it("should return correct tokenURI when token is preminted and transferred", async function () { await bosonVoucher .connect(assistant) .transferFrom(await assistant.getAddress(), await buyer.getAddress(), tokenId); const tokenURI = await bosonVoucher.tokenURI(tokenId); expect(tokenURI).eq(metadataUri); }); }); context("💔 Revert Reasons", async function () { it("should revert if tokenId does not exist", async function () { await expect(bosonVoucher.tokenURI(10)).to.be.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); }); }); }); context("EIP2981 NFT Royalty fee", function () { let offerId; beforeEach(async function () { offerId = "1"; exchangeId = "1"; offerPrice = offer.price; await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerId, { value: offer.price }); }); context("setRoyaltyPercentage()", function () { beforeEach(async function () { // give ownership to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); }); it("should emit RoyaltyPercentageChanged event", async function () { royaltyPercentage = "0"; //0% await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)) .to.emit(bosonVoucher, "RoyaltyPercentageChanged") .withArgs(royaltyPercentage); }); it("should set a royalty fee percentage", async function () { // First, set royalty fee as 0 royaltyPercentage = "0"; //0% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); let receiver, royaltyAmount; [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = seller.treasury; let expectedRoyaltyAmount = "0"; assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); // Now, set royalty fee as 10% royaltyPercentage = "1000"; //10% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); // Expectations expectedRecipient = seller.treasury; expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); }); context("💔 Revert Reasons", async function () { it("should revert if caller is not the owner", async function () { // random caller await expect(bosonVoucher.connect(rando).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); // protocol as the caller await expect(bosonVoucher.connect(protocol).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); }); it("should revert if royaltyPercentage is greater than max royalty percentage defined in the protocol", async function () { // Set royalty fee as 15% (protocol limit is 10%) royaltyPercentage = "1500"; //15% // royalty percentage too high, expectig revert await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( RevertReasons.ROYALTY_FEE_INVALID ); }); }); }); context("getRoyaltyPercentage()", function () { it("should return the royalty fee percentage", async function () { // give ownership to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); royaltyPercentage = "1000"; //10% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); expect(await bosonVoucher.connect(rando).getRoyaltyPercentage()).to.equal( royaltyPercentage, "Invalid royalty percentage" ); }); }); context("royaltyInfo()", function () { beforeEach(async function () { // give ownership to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); }); it("should return a recipient and royalty fee", async function () { // First, set royalty fee as 0 royaltyPercentage = "0"; //0% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); let receiver, royaltyAmount; [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = seller.treasury; let expectedRoyaltyAmount = "0"; assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); // Now, set royalty fee as 10% royaltyPercentage = "1000"; //10% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations expectedRecipient = seller.treasury; expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); // Any random address can check the royalty info // Now, set royalty fee as 8% royaltyPercentage = "800"; //8% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); // Expectations expectedRecipient = seller.treasury; expectedRoyaltyAmount = applyPercentage(offerPrice, royaltyPercentage); assert.equal(receiver, expectedRecipient, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), expectedRoyaltyAmount, "Royalty amount is incorrect"); }); it("if exchange doesn't exist it should return 0 values", async function () { // Set royalty fee as 10% royaltyPercentage = "1000"; //10% await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); // Set inexistent exchangeId exchangeId = "100000"; const [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Receiver and amount should be 0 assert.equal(receiver, ZeroAddress, "Recipient address is incorrect"); assert.equal(royaltyAmount, 0n, "Royalty amount is incorrect"); }); }); context("💔 Revert Reasons", async function () { it("should revert during create seller if royaltyPercentage is greater than max royalty percentage defined in the protocol", async function () { // create invalid voucherInitValues royaltyPercentage = "2000"; // 20% voucherInitValues = new VoucherInitValues("ContractURI", royaltyPercentage); // create another seller seller = mockSeller( await rando.getAddress(), await rando.getAddress(), ZeroAddress, await rando.getAddress() ); // royalty percentage too high, expectig revert await expect( accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues) ).to.be.revertedWith(RevertReasons.ROYALTY_FEE_INVALID); }); }); }); context("withdrawToProtocol", function () { let availableFundsAddresses; beforeEach(async function() { availableFundsAddresses = [ZeroAddress]; }); it("Can withdraw native token", async function () { const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); const amount = parseUnits("1", "ether"); await admin.sendTransaction({ to: await bosonVoucher.getAddress(), value: amount }); await expect(() => bosonVoucher.connect(rando).withdrawToProtocol([ZeroAddress])).to.changeEtherBalances( [bosonVoucher, fundsHandler], [amount * -1n, amount] ); const { availableAmount } = sellersFundsBefore.funds.find((fund) => fund.tokenAddress == ZeroAddress); // Seller's available balance should increase const expectedAvailableFunds = new FundsList([ new Funds(ZeroAddress, "Native currency", (BigInt(availableAmount) + BigInt(amount)).toString()), ]); const sellerFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellerFundsAfter).to.eql(expectedAvailableFunds); }); it("Can withdraw ERC20", async function () { const amount = parseUnits("1", "ether"); await foreign20.connect(deployer).mint(await deployer.getAddress(), amount); await foreign20.connect(deployer).transfer(await bosonVoucher.getAddress(), amount); const foreign20Address = await foreign20.getAddress(); await expect(() => bosonVoucher.connect(rando).withdrawToProtocol([foreign20Address])).to.changeTokenBalances( foreign20, [bosonVoucher, fundsHandler], [amount * -1n, amount] ); // Seller's available balance should increase const expectedAvailableFunds = new Funds(await foreign20.getAddress(), "Foreign20", amount.toString()); // first item is AddressZero availableFundsAddresses.push(await foreign20.getAddress()); const [,sellerFundsAfter] = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)).funds; expect(sellerFundsAfter).to.eql(expectedAvailableFunds); }); it("Should withdraw all tokens when list length > 1", async function () { availableFundsAddresses.push(await foreign20.getAddress()); const { funds: sellerFundsBefore } = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); sellerFundsBefore[1] = new Funds(await foreign20.getAddress(), "Foreign20", "0"); const amount = parseUnits("1", "ether"); await admin.sendTransaction({ to: await bosonVoucher.getAddress(), value: amount }); await foreign20.connect(deployer).mint(await deployer.getAddress(), amount); await foreign20.connect(deployer).transfer(await bosonVoucher.getAddress(), amount); const foreign20Address = await foreign20.getAddress(); let tx; await expect(() => { tx = bosonVoucher.connect(rando).withdrawToProtocol([ZeroAddress, foreign20Address]); return tx; }).to.changeTokenBalances(foreign20, [bosonVoucher, fundsHandler], [amount * -1n, amount]); await expect(() => tx).to.changeEtherBalances([bosonVoucher, fundsHandler], [amount * -1n, amount]); const { funds: sellerFundsAfter } = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect( sellerFundsBefore.map((f) => { return { ...f, availableAmount: (BigInt(f.availableAmount) + amount).toString() }; }) ).to.eql(sellerFundsAfter); }); }); context("getSellerId()", function () { it("should return the seller id", async function () { await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); expect(await bosonVoucher.connect(rando).getSellerId()).to.equal(seller.id, "Invalid seller id returned"); // Reset the accountId iterator accountId.next(true); }); it("should return 0 if the seller doesn't exist", async function () { await bosonVoucher.connect(protocol).transferOwnership(await rando.getAddress()); expect(await bosonVoucher.getSellerId()).to.equal(0, "Invalid seller id returned"); }); }); }); context("burnVoucher()", function () { it("should burn a voucher with success", async function () { const buyerStruct = mockBuyer(await buyer.getAddress()).toStruct(); const buyerWallet = buyerStruct[1]; await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); const balanceBefore = await bosonVoucher.balanceOf(await buyer.getAddress()); await bosonVoucher.connect(protocol).burnVoucher(0); const balanceAfter = await bosonVoucher.balanceOf(await buyer.getAddress()); expect(balanceBefore - balanceAfter).eq(1); }); it("should revert if caller does not have PROTOCOL role", async function () { // Expect revert if random user attempts to burn voucher await expect(bosonVoucher.connect(rando).burnVoucher(0)).to.be.revertedWith(RevertReasons.ACCESS_DENIED); // Grant PROTOCOL role to random user address await accessController.grantRole(Role.PROTOCOL, await rando.getAddress()); // Prepare to burn voucher as a random user const buyerStruct = mockBuyer(await buyer.getAddress()).toStruct(); const buyerWallet = buyerStruct[1]; await bosonVoucher.connect(protocol).issueVoucher(0, buyerWallet); const balanceBefore = await bosonVoucher.balanceOf(await buyer.getAddress()); //Attempt to burn voucher as a random user await bosonVoucher.connect(protocol).burnVoucher(0); const balanceAfter = await bosonVoucher.balanceOf(await buyer.getAddress()); expect(balanceBefore - balanceAfter).eq(1); }); }); context("transferOwnership()", function () { it("should emit OwnershipTransferred", async function () { const ownable = await getContractAt("OwnableUpgradeable", await bosonVoucher.getAddress()); await expect(bosonVoucher.connect(protocol).transferOwnership(await rando.getAddress())) .to.emit(ownable, "OwnershipTransferred") .withArgs(await assistant.getAddress(), await rando.getAddress()); }); it("should transfer ownership with success", async function () { await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); const ownable = await getContractAt("OwnableUpgradeable", await bosonVoucher.getAddress()); const owner = await ownable.owner(); expect(owner).eq(await assistant.getAddress(), "Wrong owner"); }); context("💔 Revert Reasons", async function () { it("should revert if caller does not have PROTOCOL role", async function () { await expect(bosonVoucher.connect(rando).transferOwnership(await assistant.getAddress())).to.be.revertedWith( RevertReasons.ACCESS_DENIED ); }); it("Even the current owner cannot transfer the ownership", async function () { // successfully transfer to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); // owner tries to transfer, it should fail await expect(bosonVoucher.connect(assistant).transferOwnership(await rando.getAddress())).to.be.revertedWith( RevertReasons.ACCESS_DENIED ); }); it("Current owner cannot renounce the ownership", async function () { // successfully transfer to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); const ownable = await getContractAt("OwnableUpgradeable", await bosonVoucher.getAddress()); // owner tries to renounce ownership, it should fail await expect(ownable.connect(assistant).renounceOwnership()).to.be.revertedWith(RevertReasons.ACCESS_DENIED); }); it("Transferring ownership to 0 is not allowed", async function () { // try to transfer ownership to address 0, should fail await expect(bosonVoucher.connect(protocol).transferOwnership(ZeroAddress)).to.be.revertedWith( RevertReasons.OWNABLE_ZERO_ADDRESS ); }); }); }); context("setContractURI()", function () { beforeEach(async function () { // give ownership to assistant await bosonVoucher.connect(protocol).transferOwnership(await assistant.getAddress()); contractURI = "newContractURI"; }); it("should emit ContractURIChanged event", async function () { await expect(bosonVoucher.connect(assistant).setContractURI(contractURI)) .to.emit(bosonVoucher, "ContractURIChanged") .withArgs(contractURI); }); it("should set new contract with success", async function () { await bosonVoucher.connect(assistant).setContractURI(contractURI); const returnedContractURI = await bosonVoucher.contractURI(); expect(returnedContractURI).eq(contractURI, "Wrong contractURI"); }); it("should revert if caller is not the owner", async function () { // random caller await expect(bosonVoucher.connect(rando).setContractURI(contractURI)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); // protocol as the caller await expect(bosonVoucher.connect(protocol).setContractURI(contractURI)).to.be.revertedWith( RevertReasons.OWNABLE_NOT_OWNER ); }); }); context("callExternalContract()", function () { let mockSimpleContract, calldata; beforeEach(async function () { // Deploy a random contract const MockSimpleContract = await getContractFactory("MockSimpleContract"); mockSimpleContract = await MockSimpleContract.deploy(); await mockSimpleContract.waitForDeployment(); // Generate calldata calldata = mockSimpleContract.interface.encodeFunctionData("testEvent"); }); it("Should call external contract and emit its events", async function () { const tx = await bosonVoucher .connect(assistant) .callExternalContract(await mockSimpleContract.getAddress(), calldata); const receipt = await tx.wait(); const event = getEvent(receipt, mockSimpleContract, "TestEvent"); assert.equal(event._value.toString(), "1"); }); it("Should return the external contract return value", async function () { const calldata = mockSimpleContract.interface.encodeFunctionData("testReturn"); const returnedValueRaw = await bosonVoucher .connect(assistant) .callExternalContract.staticCall(await mockSimpleContract.getAddress(), calldata); const abiCoder = new ethers.AbiCoder(); const [returnedValue] = abiCoder.decode(["string"], returnedValueRaw); expect(returnedValue).to.equal("TestValue"); }); context("💔 Revert Reasons", async function () { it("_to is the zero address", async function () { await expect(bosonVoucher.connect(assistant).callExternalContract(ZeroAddress, calldata)).to.be.revertedWith( RevertReasons.INVALID_ADDRESS ); }); it("Caller is not the contract owner", async function () { await expect( bosonVoucher.connect(rando).callExternalContract(await mockSimpleContract.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.OWNABLE_NOT_OWNER); }); it("External call reverts", async function () { calldata = mockSimpleContract.interface.encodeFunctionData("testRevert"); await expect( bosonVoucher.connect(assistant).callExternalContract(await mockSimpleContract.getAddress(), calldata) ).to.be.revertedWith("Reverted"); }); it("To address is not a contract", async function () { await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.reverted; }); it("Owner tries to invoke method to transfer funds", async function () { const erc20 = await getContractFactory("Foreign20"); // transfer calldata = erc20.interface.encodeFunctionData("transfer", [await assistant.getAddress(), 20]); await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.FUNCTION_NOT_ALLOWLISTED); // transferFrom calldata = erc20.interface.encodeFunctionData("transferFrom", [ await bosonVoucher.getAddress(), await assistant.getAddress(), 20, ]); await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.FUNCTION_NOT_ALLOWLISTED); // approve calldata = erc20.interface.encodeFunctionData("approve", [await assistant.getAddress(), 20]); await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.FUNCTION_NOT_ALLOWLISTED); // DAI const dai = await getContractAt("DAIAliases", ZeroAddress); // push calldata = dai.interface.encodeFunctionData("push", [await assistant.getAddress(), 20]); await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.FUNCTION_NOT_ALLOWLISTED); // move calldata = dai.interface.encodeFunctionData("move", [ await bosonVoucher.getAddress(), await assistant.getAddress(), 20, ]); await expect( bosonVoucher.connect(assistant).callExternalContract(await rando.getAddress(), calldata) ).to.be.revertedWith(RevertReasons.FUNCTION_NOT_ALLOWLISTED); }); }); }); context("setApprovalForAllToContract", function () { it("Should emit ApprovalForAll event", async function () { await expect(bosonVoucher.connect(assistant).setApprovalForAllToContract(await rando.getAddress(), true)) .to.emit(bosonVoucher, "ApprovalForAll") .withArgs(await bosonVoucher.getAddress(), await rando.getAddress(), true); }); context("💔 Revert Reasons", async function () { it("should revert if caller is not the owner", async function () { // Expect revert if random user attempts to set approval await expect( bosonVoucher.connect(rando).setApprovalForAllToContract(await rando.getAddress(), true) ).to.revertedWith(RevertReasons.OWNABLE_NOT_OWNER); }); it("should revert if operator is zero address", async function () { // Expect revert if random user attempts to set approval await expect(bosonVoucher.connect(assistant).setApprovalForAllToContract(ZeroAddress, true)).to.revertedWith( RevertReasons.INVALID_ADDRESS ); }); }); }); context("onERC721Received", function () { it("Should return correct selector value", async function () { const expectedSelector = bosonVoucher.interface.fragments.find((f) => f.name == "onERC721Received").selector; const returnedSelector = await bosonVoucher.onERC721Received.staticCall( await assistant.getAddress(), await rando.getAddress(), "1", "0x" ); expect(returnedSelector).to.equal(expectedSelector); }); }); }); \ No newline at end of file From 0bfe075161b18b6a04397975d9eece778e4db10d Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 20:43:05 -0300 Subject: [PATCH 17/22] Remediate tests on MetaTx and ExchangeHandler --- test/integration/seaport/utils.js | 2 +- test/protocol/ExchangeHandlerTest.js | 5 ++-- test/protocol/MetaTransactionsHandlerTest.js | 31 +++++++++++++------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/test/integration/seaport/utils.js b/test/integration/seaport/utils.js index 19fcfc268..dbae9e415 100644 --- a/test/integration/seaport/utils.js +++ b/test/integration/seaport/utils.js @@ -2,7 +2,7 @@ const { BigNumber, constants, utils } = require("ethers"); const getOfferOrConsiderationItem = function ( itemType = 0, - token = constants.AddressZero, + token = ZeroAddress, identifierOrCriteria = 0, startAmount = 1, endAmount = 1, diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 177083b35..e4eedab81 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -600,8 +600,9 @@ describe("IBosonExchangeHandler", function () { }); it("Should not decrement seller funds if offer price and sellerDeposit is 0", async function () { + let availableFundsAddresses = [ZeroAddress]; // Seller funds before - const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + const sellersFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); // Set protocolFee to zero so we don't get the error AGENT_FEE_AMOUNT_TOO_HIGH let protocolFeePercentage = "0"; @@ -630,7 +631,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId); // Seller funds after - const sellerFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); + const sellerFundsAfter = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); expect(sellerFundsAfter.toString()).to.equal( sellersFundsBefore.toString(), "Seller funds should not be decremented" diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index 9287b79e2..4c4314a14 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -3387,10 +3387,18 @@ describe("IBosonMetaTransactionsHandler", function () { }); context("Should emit MetaTransactionExecuted event and update state", async () => { + let availableFundsAddresses; beforeEach(async function () { + availableFundsAddresses = [await mockToken.getAddress(), ZeroAddress]; + // Read on chain state +<<<<<<< HEAD buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); buyerBalanceBefore = await mockToken.balanceOf(buyer.address); +======= + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + buyerBalanceBefore = await mockToken.balanceOf(await buyer.getAddress()); +>>>>>>> 1b3488e6 (Remediate tests on MetaTx and ExchangeHandler) // Chain state should match the expected available funds before the withdrawal expectedBuyerAvailableFunds = new FundsList([ @@ -3436,17 +3444,14 @@ describe("IBosonMetaTransactionsHandler", function () { .withArgs(buyer.address, deployer.address, message.functionName, nonce); // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - buyerBalanceAfter = await mockToken.balanceOf(buyer.address); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + buyerBalanceAfter = await mockToken.balanceOf(await buyer.getAddress()); // Chain state should match the expected available funds after the withdrawal // Since all tokens are withdrawn, token should be removed from the list expectedBuyerAvailableFunds = new FundsList([ - new Funds( - ethers.constants.AddressZero, - "Native currency", - ethers.BigNumber.from(buyerPayoff).div("2").toString() - ), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", (BigInt(buyerPayoff) / 2n).toString()), ]); expect(buyerAvailableFunds).to.eql( expectedBuyerAvailableFunds, @@ -3504,12 +3509,16 @@ describe("IBosonMetaTransactionsHandler", function () { .withArgs(buyer.address, deployer.address, message.functionName, nonce); // Read on chain state - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - buyerBalanceAfter = await mockToken.balanceOf(buyer.address); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + buyerBalanceAfter = await mockToken.balanceOf(await buyer.getAddress()); // Chain state should match the expected available funds after the withdrawal - // Since all tokens are withdrawn, funds list should be empty. - expectedBuyerAvailableFunds = new FundsList([]); + // Since all tokens are withdrawn, values should be zero + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = emptyFundsList; expect(buyerAvailableFunds).to.eql( expectedBuyerAvailableFunds, "Buyer available funds mismatch after withdrawal" From e66481467333a330f2141e3241122166e79ae660 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 23:17:25 -0300 Subject: [PATCH 18/22] Fix tests broken on FundsHandler --- test/protocol/FundsHandlerTest.js | 165 ++++-------------------------- 1 file changed, 22 insertions(+), 143 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index cb97e310b..ef69c87b7 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -2113,6 +2113,7 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ @@ -2133,11 +2134,14 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price - protocolFee - agentFee // protocol: protocolFee // agent: agentFee - expectedSellerAvailableFunds.funds.push(new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff)); - expectedProtocolAvailableFunds.funds.push( - new Funds(await mockToken.getAddress(), "Foreign20", agentOfferProtocolFee) - ); - expectedAgentAvailableFunds.funds.push(new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff)); + expectedSellerAvailableFunds.funds[0] = new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", agentOfferProtocolFee), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"),]); + sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -2675,24 +2679,25 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); + + const emptyFundsList = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", "0")]) + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Retract from the dispute, so the funds are released await disputeHandler.connect(buyer).retractDispute(exchangeId); @@ -2706,15 +2711,11 @@ describe("IBosonFundsHandler", function () { "Foreign20", (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); - expectedProtocolAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - protocolPayoff - ); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + expectedProtocolAvailableFunds = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), new Funds(ZeroAddress, "Native currency", "0")]); + sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2774,127 +2775,6 @@ describe("IBosonFundsHandler", function () { }); it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); - - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before - // protocol: protocolFee - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() - ); - expectedProtocolAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - - context("Offer has an agent", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - // agentPayoff: agentFee - agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = ( - BigInt(agentOffer.sellerDeposit) + - BigInt(agentOffer.price) - - BigInt(agentOfferProtocolFee) - - BigInt(agentFee) - ).toString(); - - // protocol: 0 - protocolPayoff = agentOfferProtocolFee; - - // Exchange id - exchangeId = "2"; - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); - }); - - it("should emit a FundsReleased event", async function () { - // Retract from the dispute, expecting event - const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); - - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); - }); - - it("should update state", async function () { // Read on chain state sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -4867,4 +4747,3 @@ describe("IBosonFundsHandler", function () { }); }); }); -}); From dafd2bc5439f508be1ce4c25af0496bb84470e4f Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Wed, 5 Jul 2023 23:21:59 -0300 Subject: [PATCH 19/22] Tidy --- .../handlers/IBosonFundsHandler.sol | 5 +- .../protocol/facets/FundsHandlerFacet.sol | 5 +- test/protocol/FundsHandlerTest.js | 2474 ++++++++--------- 3 files changed, 1245 insertions(+), 1239 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonFundsHandler.sol b/contracts/interfaces/handlers/IBosonFundsHandler.sol index c4591c901..16cd95ddd 100644 --- a/contracts/interfaces/handlers/IBosonFundsHandler.sol +++ b/contracts/interfaces/handlers/IBosonFundsHandler.sol @@ -81,5 +81,8 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { * @param _tokenList - list of tokens addresses to get available funds * @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn */ - function getAvailableFunds(uint256 _entityId, address[] calldata _tokenList) external view returns (BosonTypes.Funds[] memory availableFunds); + function getAvailableFunds( + uint256 _entityId, + address[] calldata _tokenList + ) external view returns (BosonTypes.Funds[] memory availableFunds); } diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 301c19947..f7b554d0a 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -158,7 +158,10 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * @param _tokenList - list of tokens addresses to get available funds * @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn */ - function getAvailableFunds(uint256 _entityId, address[] calldata _tokenList) external view override returns (Funds[] memory availableFunds) { + function getAvailableFunds( + uint256 _entityId, + address[] calldata _tokenList + ) external view override returns (Funds[] memory availableFunds) { // Cache protocol lookups for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index ef69c87b7..4da7c63c6 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -2139,8 +2139,10 @@ describe("IBosonFundsHandler", function () { new Funds(await mockToken.getAddress(), "Foreign20", agentOfferProtocolFee), new Funds(ZeroAddress, "Native currency", "0"), ]); - expectedAgentAvailableFunds = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), - new Funds(ZeroAddress, "Native currency", "0"),]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -2679,10 +2681,18 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Read on chain state - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ @@ -2690,14 +2700,16 @@ describe("IBosonFundsHandler", function () { new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", "0")]) + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Retract from the dispute, so the funds are released await disputeHandler.connect(buyer).retractDispute(exchangeId); @@ -2711,11 +2723,22 @@ describe("IBosonFundsHandler", function () { "Foreign20", (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() ); - expectedProtocolAvailableFunds = new FundsList([new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), new Funds(ZeroAddress, "Native currency", "0")]); - sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses)); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -2774,122 +2797,6 @@ describe("IBosonFundsHandler", function () { .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; - - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); - - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agentFee; - // protocol: protocolFee - // agent: agentFee - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - BigInt(sellerPayoff).toString() - ); - expectedProtocolAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedAgentAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - }); - - context("Final state DISPUTED - RETRACTED via expireDispute", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // seller: sellerDeposit + price - protocolFee - sellerPayoff = ( - BigInt(offerToken.sellerDeposit) + - BigInt(offerToken.price) - - BigInt(offerTokenProtocolFee) - ).toString(); - - // protocol: protocolFee - protocolPayoff = offerTokenProtocolFee; - await setNextBlockTimestamp(Number(timeout)); - }); - - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, rando.address); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); - - //check that FundsReleased event was NOT emitted with buyer Id - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - buyerId, - offerToken.exchangeToken, - buyerPayoff, - rando.address, - ]); - expect(match).to.be.false; - }); - it("should update state", async function () { // Read on chain state sellersAvailableFunds = FundsList.fromStruct( @@ -2907,7 +2814,7 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ @@ -2924,23 +2831,27 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Expire the dispute, so the funds are released - await disputeHandler.connect(rando).expireDispute(exchangeId); + // Retract from the dispute, so the funds are released + await disputeHandler.connect(buyer).retractDispute(exchangeId); // Available funds should be increased for // buyer: 0 - // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before + // seller: sellerDeposit + price - protocol fee - agentFee; // protocol: protocolFee - // agent: 0 + // agent: agentFee expectedSellerAvailableFunds.funds[0] = new Funds( await mockToken.getAddress(), "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + BigInt(sellerPayoff).toString() ); expectedProtocolAvailableFunds = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), new Funds(ZeroAddress, "Native currency", "0"), ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -2958,158 +2869,412 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); + }); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + context("Final state DISPUTED - RETRACTED via expireDispute", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // agentPayoff: agentFee - agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agent fee - sellerPayoff = ( - BigInt(agentOffer.sellerDeposit) + - BigInt(agentOffer.price) - - BigInt(agentOfferProtocolFee) - - BigInt(agentFee) - ).toString(); - // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; - - // Exchange id - exchangeId = "2"; + // seller: sellerDeposit + price - protocolFee + sellerPayoff = ( + BigInt(offerToken.sellerDeposit) + + BigInt(offerToken.price) - + BigInt(offerTokenProtocolFee) + ).toString(); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // protocol: protocolFee + protocolPayoff = offerTokenProtocolFee; + await setNextBlockTimestamp(Number(timeout)); + }); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); + await expect(tx) + .to.emit(disputeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, rando.address); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = BigInt(disputedDate) + resolutionPeriod.toString(); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); - await setNextBlockTimestamp(Number(timeout)); - }); + //check that FundsReleased event was NOT emitted with buyer Id + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + buyerId, + offerToken.exchangeToken, + buyerPayoff, + rando.address, + ]); + expect(match).to.be.false; + }); - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // Complete the exchange, expecting event - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, rando.address); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, rando.address); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, rando.address); - }); + // Expire the dispute, so the funds are released + await disputeHandler.connect(rando).expireDispute(exchangeId); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee; note that seller has sellerDeposit in availableFunds from before + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // Expire the dispute, so the funds are released - await disputeHandler.connect(rando).expireDispute(exchangeId); + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agent fee; - // protocol: protocolFee - // agent: agent fee - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; - expectedProtocolAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedAgentAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // seller: sellerDeposit + price - protocolFee - agent fee + sellerPayoff = ( + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee) + ).toString(); + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; + + // Exchange id + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = BigInt(disputedDate) + resolutionPeriod.toString(); + + await setNextBlockTimestamp(Number(timeout)); + }); + + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); + + // Complete the exchange, expecting event + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, rando.address); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, rando.address); + + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, rando.address); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Expire the dispute, so the funds are released + await disputeHandler.connect(rando).expireDispute(exchangeId); + + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee - agent fee; + // protocol: protocolFee + // agent: agent fee + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + + expectedProtocolAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); }); + }); + + context("Final state DISPUTED - RESOLVED", async function () { + beforeEach(async function () { + buyerPercentBasisPoints = "5566"; // 55.66% + + // expected payoffs + // buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / + 10000n; + + // seller: (price + sellerDeposit)*(1-buyerPercentage) + sellerPayoff = BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) - buyerPayoff; + + // protocol: 0 + protocolPayoff = 0; + + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + }); + + it("should emit a FundsReleased event", async function () { + // Resolve the dispute, expecting event + const tx = await disputeHandler + .connect(assistant) + .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); - context("Final state DISPUTED - RESOLVED", async function () { + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Resolve the dispute, so the funds are released + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + + // Available funds should be increased for + // buyer: (price + sellerDeposit)*buyerPercentage + // seller: (price + sellerDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + + context("Offer has an agent", async function () { beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); + buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = - ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / - 10000n; + buyerPayoff = ( + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) - buyerPayoff; + sellerPayoff = ( + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) - + BigInt(buyerPayoff) + ).toString(); // protocol: 0 protocolPayoff = 0; @@ -3139,22 +3304,6 @@ describe("IBosonFundsHandler", function () { )); }); - it("should emit a FundsReleased event", async function () { - // Resolve the dispute, expecting event - const tx = await disputeHandler - .connect(assistant) - .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); - - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); - it("should update state", async function () { // Read on chain state sellersAvailableFunds = FundsList.fromStruct( @@ -3172,7 +3321,7 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); const emptyFundsList = new FundsList([ @@ -3194,18 +3343,19 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // seller: (price + sellerDeposit)*(1-buyerPercentage); // protocol: 0 // agent: 0 expectedSellerAvailableFunds.funds[0] = new Funds( await mockToken.getAddress(), "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + sellerPayoff ); expectedBuyerAvailableFunds = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), new Funds(ZeroAddress, "Native currency", "0"), ]); + sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -3224,185 +3374,166 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); + }); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { + beforeEach(async function () { + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - exchangeId = "2"; + // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit + sellerPayoff = ( + BigInt(offerToken.sellerDeposit) + + BigInt(offerToken.price) - + BigInt(offerTokenProtocolFee) + + BigInt(buyerEscalationDeposit) + ).toString(); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // protocol: 0 + protocolPayoff = offerTokenProtocolFee; - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - buyerPercentBasisPoints = "5566"; // 55.66% + it("should emit a FundsReleased event", async function () { + // Retract from the dispute, expecting event + const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); - // expected payoffs - // buyer: (price + sellerDeposit)*buyerPercentage - buyerPayoff = ( - ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit)) * BigInt(buyerPercentBasisPoints)) / - 10000n - ).toString(); - - // seller: (price + sellerDeposit)*(1-buyerPercentage) - sellerPayoff = ( - BigInt(agentOffer.price) + - BigInt(agentOffer.sellerDeposit) - - BigInt(buyerPayoff) - ).toString(); + await expect(tx) + .to.emit(disputeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); - // protocol: 0 - protocolPayoff = 0; + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - await disputeHandler.getAddress() - )); - }); + //check that FundsReleased event was NOT emitted with buyer Id + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + buyerId, + offerToken.exchangeToken, + buyerPayoff, + await buyer.getAddress(), + ]); + expect(match).to.be.false; + }); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - // Resolve the dispute, so the funds are released - await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Available funds should be increased for - // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage); - // protocol: 0 - // agent: 0 - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - sellerPayoff - ); - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); + // Retract from the dispute, so the funds are released + await disputeHandler.connect(buyer).retractDispute(exchangeId); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocol fee + buyerEscalationDeposit; note that seller has sellerDeposit in availableFunds from before + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + ); + expectedProtocolAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); - context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { + context("Offer has an agent", async function () { beforeEach(async function () { // expected payoffs // buyer: 0 buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; + + // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit sellerPayoff = ( - BigInt(offerToken.sellerDeposit) + - BigInt(offerToken.price) - - BigInt(offerTokenProtocolFee) + + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee) + BigInt(buyerEscalationDeposit) ).toString(); // protocol: 0 - protocolPayoff = offerTokenProtocolFee; + protocolPayoff = agentOfferProtocolFee; - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // Exchange id + exchangeId = "2"; + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - it("should emit a FundsReleased event", async function () { - // Retract from the dispute, expecting event - const tx = await disputeHandler.connect(buyer).retractDispute(exchangeId); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, await buyer.getAddress()); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - //check that FundsReleased event was NOT emitted with buyer Id - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - buyerId, - offerToken.exchangeToken, - buyerPayoff, - await buyer.getAddress(), - ]); - expect(match).to.be.false; + // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -3422,19 +3553,18 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", "0"), ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3445,18 +3575,22 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: 0 - // seller: sellerDeposit + price - protocol fee + buyerEscalationDeposit; note that seller has sellerDeposit in availableFunds from before + // seller: sellerDeposit + price - protocol fee - agentFee + buyerEscalationDeposit; // protocol: protocolFee - // agent: 0 + // agent: agentFee expectedSellerAvailableFunds.funds[0] = new Funds( await mockToken.getAddress(), "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + sellerPayoff ); expectedProtocolAvailableFunds = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), new Funds(ZeroAddress, "Native currency", "0"), ]); + expectedAgentAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) @@ -3476,145 +3610,178 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); + }); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // agentPayoff: agentFee - agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit - sellerPayoff = ( - BigInt(agentOffer.sellerDeposit) + - BigInt(agentOffer.price) - - BigInt(agentOfferProtocolFee) - - BigInt(agentFee) + - BigInt(buyerEscalationDeposit) - ).toString(); + context("Final state DISPUTED - ESCALATED - RESOLVED", async function () { + beforeEach(async function () { + buyerPercentBasisPoints = "5566"; // 55.66% - // protocol: 0 - protocolPayoff = agentOfferProtocolFee; + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); - // Exchange id - exchangeId = "2"; - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(offerToken.price) + + BigInt(offerToken.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(await buyer.getAddress(), agentOffer.price); - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + // protocol: 0 + protocolPayoff = 0; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; + + customSignatureType = { + Resolution: resolutionType, + }; + + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; + + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + buyer, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + await disputeHandler.getAddress() + )); + + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + it("should emit a FundsReleased event", async function () { + // Resolve the dispute, expecting event + const tx = await disputeHandler + .connect(assistant) + .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); - // escalate the dispute - await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Resolve the dispute, so the funds are released + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + + // Available funds should be increased for + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + ); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - // Available funds should be increased for - // buyer: 0 - // seller: sellerDeposit + price - protocol fee - agentFee + buyerEscalationDeposit; - // protocol: protocolFee - // agent: agentFee - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - sellerPayoff - ); - expectedProtocolAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", protocolPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedAgentAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", agentPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); + exchangeId = "2"; - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - }); + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); - context("Final state DISPUTED - ESCALATED - RESOLVED", async function () { - beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage buyerPayoff = ( - ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * BigInt(buyerPercentBasisPoints)) / 10000n ).toString(); // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) sellerPayoff = ( - BigInt(offerToken.price) + - BigInt(offerToken.sellerDeposit) + + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit) - BigInt(buyerPayoff) ).toString(); @@ -3646,26 +3813,12 @@ describe("IBosonFundsHandler", function () { await disputeHandler.getAddress() )); - // Escalate the dispute + // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); - it("should emit a FundsReleased event", async function () { - // Resolve the dispute, expecting event - const tx = await disputeHandler - .connect(assistant) - .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistant.getAddress()); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistant.getAddress()); - - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); - it("should update state", async function () { // Read on chain state sellersAvailableFunds = FundsList.fromStruct( @@ -3683,10 +3836,9 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); - const emptyFundsList = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", "0"), @@ -3695,6 +3847,7 @@ describe("IBosonFundsHandler", function () { expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); @@ -3705,18 +3858,17 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); // protocol: 0 // agent: 0 + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); expectedBuyerAvailableFunds = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), new Funds(ZeroAddress, "Native currency", "0"), ]); - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() - ); sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -3729,172 +3881,164 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); + }); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + context("Final state DISPUTED - ESCALATED - DECIDED", async function () { + beforeEach(async function () { + buyerPercentBasisPoints = "5566"; // 55.66% - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(await buyer.getAddress(), agentOffer.price); + // expected payoffs + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + buyerPayoff = ( + ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + BigInt(buyerPercentBasisPoints)) / + 10000n + ).toString(); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) + sellerPayoff = ( + BigInt(offerToken.price) + + BigInt(offerToken.sellerDeposit) + + BigInt(buyerEscalationDeposit) - + BigInt(buyerPayoff) + ).toString(); - exchangeId = "2"; + // protocol: 0 + protocolPayoff = 0; - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + it("should emit a FundsReleased event", async function () { + // Decide the dispute, expecting event + const tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await assistantDR.getAddress()); - buyerPercentBasisPoints = "5566"; // 55.66% + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistantDR.getAddress()); - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ( - ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * - BigInt(buyerPercentBasisPoints)) / - 10000n - ).toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ( - BigInt(agentOffer.price) + - BigInt(agentOffer.sellerDeposit) + - BigInt(buyerEscalationDeposit) - - BigInt(buyerPayoff) - ).toString(); + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); + }); - // protocol: 0 - protocolPayoff = 0; + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); - // Set the message Type, needed for signature - resolutionType = [ - { name: "exchangeId", type: "uint256" }, - { name: "buyerPercentBasisPoints", type: "uint256" }, - ]; - - customSignatureType = { - Resolution: resolutionType, - }; - - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; - - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - await disputeHandler.getAddress() - )); - - // escalate the dispute - await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; + // Decide the dispute, so the funds are released + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Available funds should be increased for + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() + ); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); - // Resolve the dispute, so the funds are released - await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + context("Offer has an agent", async function () { + beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); - // protocol: 0 - // agent: 0 - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - }); + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set disputedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + disputedDate = block.timestamp.toString(); + timeout = (BigInt(disputedDate) + BigInt(resolutionPeriod)).toString(); - context("Final state DISPUTED - ESCALATED - DECIDED", async function () { - beforeEach(async function () { buyerPercentBasisPoints = "5566"; // 55.66% // expected payoffs // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage buyerPayoff = ( - ((BigInt(offerToken.price) + BigInt(offerToken.sellerDeposit) + BigInt(buyerEscalationDeposit)) * + ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * BigInt(buyerPercentBasisPoints)) / 10000n ).toString(); // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) sellerPayoff = ( - BigInt(offerToken.price) + - BigInt(offerToken.sellerDeposit) + + BigInt(agentOffer.price) + + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit) - BigInt(buyerPayoff) ).toString(); @@ -3903,26 +4047,117 @@ describe("IBosonFundsHandler", function () { protocolPayoff = 0; // escalate the dispute + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = + expectedProtocolAvailableFunds = + expectedAgentAvailableFunds = + emptyFundsList; + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Decide the dispute, so the funds are released + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + + // Available funds should be increased for + // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage + // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds( + await mockToken.getAddress(), + "Foreign20", + sellerPayoff + ); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); + }); + }); + context( + "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", + async function () { + beforeEach(async function () { + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); + + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; + + // protocol: 0 + protocolPayoff = 0; + + // Escalate the dispute + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); + + await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); + }); + it("should emit a FundsReleased event", async function () { - // Decide the dispute, expecting event - const tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs( - exchangeId, - seller.id, - offerToken.exchangeToken, - sellerPayoff, - await assistantDR.getAddress() - ); - + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, rando.address); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistantDR.getAddress()); - + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3951,21 +4186,20 @@ describe("IBosonFundsHandler", function () { new Funds(ZeroAddress, "Native currency", "0"), ]); expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = expectedAgentAvailableFunds = + expectedProtocolAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // Expire the escalated dispute, so the funds are released + await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); note that seller has sellerDeposit in availableFunds from before + // buyer: price + buyerEscalationDeposit + // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ @@ -4017,37 +4251,27 @@ describe("IBosonFundsHandler", function () { // raise the dispute tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - // Get the block timestamp of the confirmed tx and set disputedDate - blockNumber = tx.blockNumber; - block = await provider.getBlock(blockNumber); - disputedDate = block.timestamp.toString(); - timeout = (BigInt(disputedDate) + BigInt(resolutionPeriod)).toString(); - - buyerPercentBasisPoints = "5566"; // 55.66% - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ( - ((BigInt(agentOffer.price) + BigInt(agentOffer.sellerDeposit) + BigInt(buyerEscalationDeposit)) * - BigInt(buyerPercentBasisPoints)) / - 10000n - ).toString(); - - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ( - BigInt(agentOffer.price) + - BigInt(agentOffer.sellerDeposit) + - BigInt(buyerEscalationDeposit) - - BigInt(buyerPayoff) - ).toString(); + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); + + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; // protocol: 0 protocolPayoff = 0; - // escalate the dispute + // Escalate the dispute await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); + + await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); }); it("should update state", async function () { @@ -4078,30 +4302,28 @@ describe("IBosonFundsHandler", function () { expectedProtocolAvailableFunds = expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // Expire the escalated dispute, so the funds are released + await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage); + // buyer: price + buyerEscalationDeposit + // seller: sellerDeposit; // protocol: 0 // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); expectedSellerAvailableFunds.funds[0] = new Funds( await mockToken.getAddress(), "Foreign20", sellerPayoff ); - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -4114,239 +4336,157 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); }); - }); - context( - "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", - async function () { - beforeEach(async function () { - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + } + ); - // protocol: 0 - protocolPayoff = 0; + context( + "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", + async function () { + beforeEach(async function () { + // expected payoffs + // buyer: price + buyerEscalationDeposit + buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + // seller: sellerDeposit + sellerPayoff = offerToken.sellerDeposit; - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); + // protocol: 0 + protocolPayoff = 0; - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); - }); + // Escalate the dispute + tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + }); - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, rando.address); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs( + exchangeId, + seller.id, + offerToken.exchangeToken, + sellerPayoff, + await assistantDR.getAddress() ); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedAgentAvailableFunds = - expectedProtocolAvailableFunds = - emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistantDR.getAddress()); - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedSellerAvailableFunds.funds[0] = new Funds( + //check that FundsReleased event was NOT emitted with rando address + const txReceipt = await tx.wait(); + const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ + exchangeId, + seller.id, + offerToken.exchangeToken, + sellerPayoff, + rando.address, + ]); + expect(match).to.be.false; + }); + + it("should update state", async function () { + // Read on chain state + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + const emptyFundsList = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", "0"), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedBuyerAvailableFunds = emptyFundsList; + expectedProtocolAvailableFunds = emptyFundsList; + expectedAgentAvailableFunds = emptyFundsList; + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + + // Expire the escalated dispute, so the funds are released + await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); + + // Available funds should be increased for + // buyer: price + buyerEscalationDeposit + // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds = new FundsList([ + new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), + new Funds(ZeroAddress, "Native currency", "0"), + ]); + expectedSellerAvailableFunds = new FundsList([ + new Funds( await mockToken.getAddress(), "Foreign20", (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() - ); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(await buyer.getAddress(), agentOffer.price); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - - exchangeId = "2"; - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); - - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; - - // protocol: 0 - protocolPayoff = 0; - - // Escalate the dispute - await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); - - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); - }); - - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = - expectedProtocolAvailableFunds = - expectedAgentAvailableFunds = - emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); - - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedSellerAvailableFunds.funds[0] = new Funds( - await mockToken.getAddress(), - "Foreign20", - sellerPayoff - ); - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - } - ); + ), + new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), + ]); + sellersAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) + ); + buyerAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) + ); + protocolAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) + ); + agentAvailableFunds = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) + ); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + }); - context( - "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", - async function () { + context("Offer has an agent", async function () { beforeEach(async function () { + // Create Agent offer + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // approve protocol to transfer the tokens + await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); + await mockToken.mint(await buyer.getAddress(), agentOffer.price); + + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); + + exchangeId = "2"; + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // raise the dispute + await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // expected payoffs // buyer: price + buyerEscalationDeposit buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); @@ -4358,39 +4498,9 @@ describe("IBosonFundsHandler", function () { protocolPayoff = 0; // Escalate the dispute - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); - - it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event - const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs( - exchangeId, - seller.id, - offerToken.exchangeToken, - sellerPayoff, - await assistantDR.getAddress() - ); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, await assistantDR.getAddress()); - - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - - //check that FundsReleased event was NOT emitted with rando address - const txReceipt = await tx.wait(); - const match = eventEmittedWithArgs(txReceipt, disputeHandler, "FundsReleased", [ - exchangeId, - seller.id, - offerToken.exchangeToken, - sellerPayoff, - rando.address, - ]); - expect(match).to.be.false; + await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); + await disputeHandler.connect(buyer).escalateDispute(exchangeId); }); it("should update state", async function () { @@ -4410,9 +4520,10 @@ describe("IBosonFundsHandler", function () { // Chain state should match the expected available funds expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerDeposit), + new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); + const emptyFundsList = new FundsList([ new Funds(await mockToken.getAddress(), "Foreign20", "0"), new Funds(ZeroAddress, "Native currency", "0"), @@ -4430,7 +4541,7 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; note that seller has sellerDeposit in availableFunds from before + // seller: sellerDeposit; // protocol: 0 // agent: 0 expectedBuyerAvailableFunds = new FundsList([ @@ -4438,13 +4549,10 @@ describe("IBosonFundsHandler", function () { new Funds(ZeroAddress, "Native currency", "0"), ]); expectedSellerAvailableFunds = new FundsList([ - new Funds( - await mockToken.getAddress(), - "Foreign20", - (BigInt(sellerDeposit) + BigInt(sellerPayoff)).toString() - ), + new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), ]); + sellersAvailableFunds = FundsList.fromStruct( await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) ); @@ -4462,119 +4570,79 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); }); + }); + } + ); + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // Create Agent offer - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamondAddress, agentOffer.price); - await mockToken.mint(await buyer.getAddress(), agentOffer.price); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - - exchangeId = "2"; - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); - - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = (BigInt(offerToken.price) + BigInt(buyerEscalationDeposit)).toString(); - - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; - - // protocol: 0 - protocolPayoff = 0; - - // Escalate the dispute - await mockToken.mint(await buyer.getAddress(), buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); - - it("should update state", async function () { - // Read on chain state - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - - const emptyFundsList = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", "0"), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedBuyerAvailableFunds = emptyFundsList; - expectedProtocolAvailableFunds = emptyFundsList; - expectedAgentAvailableFunds = emptyFundsList; - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // seller: sellerDeposit; - // protocol: 0 - // agent: 0 - expectedBuyerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", buyerPayoff), - new Funds(ZeroAddress, "Native currency", "0"), - ]); - expectedSellerAvailableFunds = new FundsList([ - new Funds(await mockToken.getAddress(), "Foreign20", sellerPayoff), - new Funds(ZeroAddress, "Native currency", `${2 * sellerDeposit}`), - ]); - - sellersAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(seller.id, availableFundsAddresses) - ); - buyerAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(buyerId, availableFundsAddresses) - ); - protocolAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(protocolId, availableFundsAddresses) - ); - agentAvailableFunds = FundsList.fromStruct( - await fundsHandler.getAvailableFunds(agentId, availableFundsAddresses) - ); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); - } - ); + context("Changing the protocol fee", async function () { + beforeEach(async function () { + // Cast Diamond to IBosonConfigHandler + configHandler = await getContractAt("IBosonConfigHandler", protocolDiamondAddress); + + // expected payoffs + // buyer: 0 + buyerPayoff = 0; + + // seller: sellerDeposit + price - protocolFee + sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price) - BigInt(offerTokenProtocolFee); + }); + + it("Protocol fee for existing exchanges should be the same as at the offer creation", async function () { + // set the new procol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); + + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // Complete the exchange, expecting event + const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); + + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); + }); + + it("Protocol fee for new exchanges should be the same as at the offer creation", async function () { + // set the new procol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); + // similar as teste before, excpet the commit to offer is done after the procol fee change + + // commit to offer and get the correct exchangeId + tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); + txReceipt = await tx.wait(); + event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); + exchangeId = event.exchangeId.toString(); + + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + + // Complete the exchange, expecting event + tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); + + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); }); - context("Changing the protocol fee", async function () { + context("Offer has an agent", async function () { beforeEach(async function () { + exchangeId = "2"; + // Cast Diamond to IBosonConfigHandler configHandler = await getContractAt("IBosonConfigHandler", protocolDiamondAddress); @@ -4582,15 +4650,34 @@ describe("IBosonFundsHandler", function () { // buyer: 0 buyerPayoff = 0; - // seller: sellerDeposit + price - protocolFee - sellerPayoff = BigInt(offerToken.sellerDeposit) + BigInt(offerToken.price) - BigInt(offerTokenProtocolFee); - }); + // agentPayoff: agentFee + agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); + agentPayoff = agentFee; + + // seller: sellerDeposit + price - protocolFee - agentFee + sellerPayoff = + BigInt(agentOffer.sellerDeposit) + + BigInt(agentOffer.price) - + BigInt(agentOfferProtocolFee) - + BigInt(agentFee); + + // protocol: protocolFee + protocolPayoff = agentOfferProtocolFee; + + // Create Agent Offer before setting new protocol fee as 3% + await offerHandler + .connect(assistant) + .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); + + // Commit to Agent Offer + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - it("Protocol fee for existing exchanges should be the same as at the offer creation", async function () { // set the new procol fee protocolFeePercentage = "300"; // 3% await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); + }); + it("Protocol fee for existing exchanges should be the same as at the agent offer creation", async function () { // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -4599,23 +4686,36 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); - it("Protocol fee for new exchanges should be the same as at the offer creation", async function () { - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - // similar as teste before, excpet the commit to offer is done after the procol fee change + it("Protocol fee for new exchanges should be the same as at the agent offer creation", async function () { + // similar as tests before, excpet the commit to offer is done after the protocol fee change + + // top up seller's and buyer's account + await mockToken.mint(await assistant.getAddress(), sellerDeposit); + await mockToken.mint(await buyer.getAddress(), price); + + // approve protocol to transfer the tokens + await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); + await mockToken.connect(buyer).approve(protocolDiamondAddress, price); + + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, await mockToken.getAddress(), sellerDeposit); // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerToken.id); + tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); exchangeId = event.exchangeId.toString(); @@ -4628,122 +4728,22 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + + // Complete the exchange, expecting event await expect(tx) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, await buyer.getAddress()); + .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, await buyer.getAddress()); - }); - - context("Offer has an agent", async function () { - beforeEach(async function () { - exchangeId = "2"; - - // Cast Diamond to IBosonConfigHandler - configHandler = await getContractAt("IBosonConfigHandler", protocolDiamondAddress); - - // expected payoffs - // buyer: 0 - buyerPayoff = 0; - - // agentPayoff: agentFee - agentFee = ((BigInt(agentOffer.price) * BigInt(agentFeePercentage)) / 10000n).toString(); - agentPayoff = agentFee; - - // seller: sellerDeposit + price - protocolFee - agentFee - sellerPayoff = - BigInt(agentOffer.sellerDeposit) + - BigInt(agentOffer.price) - - BigInt(agentOfferProtocolFee) - - BigInt(agentFee); - - // protocol: protocolFee - protocolPayoff = agentOfferProtocolFee; - - // Create Agent Offer before setting new protocol fee as 3% - await offerHandler - .connect(assistant) - .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); - - // Commit to Agent Offer - await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - }); - - it("Protocol fee for existing exchanges should be the same as at the agent offer creation", async function () { - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); - - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); - - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); - }); - - it("Protocol fee for new exchanges should be the same as at the agent offer creation", async function () { - // similar as tests before, excpet the commit to offer is done after the protocol fee change - - // top up seller's and buyer's account - await mockToken.mint(await assistant.getAddress(), sellerDeposit); - await mockToken.mint(await buyer.getAddress(), price); - - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamondAddress, sellerDeposit); - await mockToken.connect(buyer).approve(protocolDiamondAddress, price); - - // deposit to seller's pool - await fundsHandler - .connect(assistant) - .depositFunds(seller.id, await mockToken.getAddress(), sellerDeposit); - - // commit to offer and get the correct exchangeId - tx = await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), agentOffer.id); - txReceipt = await tx.wait(); - event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); - exchangeId = event.exchangeId.toString(); - - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); - - // Complete the exchange, expecting event - tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); - - // Complete the exchange, expecting event - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, await buyer.getAddress()); - - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); + .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, await buyer.getAddress()); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); - }); + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, await buyer.getAddress()); }); }); }); }); }); +}); From f0b7a92cabf65422cd780f534f9f81d43cf44aca Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Thu, 6 Jul 2023 10:50:25 -0300 Subject: [PATCH 20/22] Add MaxPremintedVouchersChanged event back --- contracts/interfaces/events/IBosonConfigEvents.sol | 1 + .../facets/ProtocolInitializationHandlerFacet.sol | 1 + test/protocol/ProtocolInitializationHandlerTest.js | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/contracts/interfaces/events/IBosonConfigEvents.sol b/contracts/interfaces/events/IBosonConfigEvents.sol index 67b64a59c..1842275b3 100644 --- a/contracts/interfaces/events/IBosonConfigEvents.sol +++ b/contracts/interfaces/events/IBosonConfigEvents.sol @@ -27,5 +27,6 @@ interface IBosonConfigEvents { event MinResolutionPeriodChanged(uint256 minResolutionPeriod, address indexed executedBy); event MaxResolutionPeriodChanged(uint256 maxResolutionPeriod, address indexed executedBy); event MinDisputePeriodChanged(uint256 minDisputePeriod, address indexed executedBy); + event MaxPremintedVouchersChanged(uint256 maxPremintedVouchers, address indexed executedBy); event AccessControllerAddressChanged(address indexed accessControllerAddress, address indexed executedBy); } diff --git a/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol b/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol index 8b8150db6..f854425bb 100644 --- a/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol +++ b/contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol @@ -125,6 +125,7 @@ contract ProtocolInitializationHandlerFacet is IBosonProtocolInitializationHandl uint256 _maxPremintedVouchers = abi.decode(_initializationData, (uint256)); require(_maxPremintedVouchers != 0, VALUE_ZERO_NOT_ALLOWED); protocolLimits().maxPremintedVouchers = _maxPremintedVouchers; + emit MaxPremintedVouchersChanged(_maxPremintedVouchers, msgSender()); } /** diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 1dabc09bd..6ce420ca0 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -467,6 +467,20 @@ describe("ProtocolInitializationHandler", async function () { ); }); + it("Should emit MaxPremintedVouchersChanged event", async function () { + // Make the cut, check the event + await expect( + await diamondCutFacet.diamondCut( + [facetCut], + await deployedProtocolInitializationHandlerFacet.getAddress(), + calldataProtocolInitialization, + await getFees(maxPriorityFeePerGas) + ) + ) + .to.emit(configHandler, "MaxPremintedVouchersChanged") + .withArgs(maxPremintedVouchers, await deployer.getAddress()); + }); + it("Should update state", async function () { // Make the cut, check the event await diamondCutFacet.diamondCut( From 30bbfcd1d222b7308ad72331bb11397028146402 Mon Sep 17 00:00:00 2001 From: Mischa Date: Thu, 6 Jul 2023 16:11:40 +0100 Subject: [PATCH 21/22] This cleans up the comments in our interfaces and facets ... --- contracts/interfaces/handlers/IBosonAccountHandler.sol | 5 ----- contracts/interfaces/handlers/IBosonBundleHandler.sol | 2 -- contracts/interfaces/handlers/IBosonDisputeHandler.sol | 1 - contracts/interfaces/handlers/IBosonExchangeHandler.sol | 1 - contracts/interfaces/handlers/IBosonGroupHandler.sol | 3 --- contracts/interfaces/handlers/IBosonOfferHandler.sol | 3 --- contracts/interfaces/handlers/IBosonOrchestrationHandler.sol | 2 -- contracts/protocol/facets/BundleHandlerFacet.sol | 2 -- contracts/protocol/facets/DisputeResolverHandlerFacet.sol | 4 ---- contracts/protocol/facets/GroupHandlerFacet.sol | 2 -- 10 files changed, 25 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonAccountHandler.sol b/contracts/interfaces/handlers/IBosonAccountHandler.sol index 9adb7d019..17990d6f2 100644 --- a/contracts/interfaces/handlers/IBosonAccountHandler.sol +++ b/contracts/interfaces/handlers/IBosonAccountHandler.sol @@ -66,7 +66,6 @@ interface IBosonAccountHandler is IBosonAccountEvents { * - Any address is zero address * - Any address is not unique to this dispute resolver * - EscalationResponsePeriod is invalid - * - Number of seller ids in _sellerAllowList array exceeds max * - Some seller does not exist * - Some seller id is duplicated * - DisputeResolver is not active (if active == false) @@ -240,7 +239,6 @@ interface IBosonAccountHandler is IBosonAccountEvents { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of DisputeResolverFee structs in array exceeds max * - Number of DisputeResolverFee structs in array is zero * - DisputeResolverFee array contains duplicates * - Fee amount is a non-zero value. Protocol doesn't yet support fees for dispute resolvers @@ -263,7 +261,6 @@ interface IBosonAccountHandler is IBosonAccountEvents { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of DisputeResolverFee structs in array exceeds max * - Number of DisputeResolverFee structs in array is zero * - DisputeResolverFee does not exist for the dispute resolver * @@ -281,7 +278,6 @@ interface IBosonAccountHandler is IBosonAccountEvents { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of seller ids in array exceeds max * - Number of seller ids in array is zero * - Some seller does not exist * - Seller id is already approved @@ -300,7 +296,6 @@ interface IBosonAccountHandler is IBosonAccountEvents { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of seller ids in array exceeds max * - Number of seller ids structs in array is zero * - Seller id is not approved * diff --git a/contracts/interfaces/handlers/IBosonBundleHandler.sol b/contracts/interfaces/handlers/IBosonBundleHandler.sol index 03c42493e..4065067a4 100644 --- a/contracts/interfaces/handlers/IBosonBundleHandler.sol +++ b/contracts/interfaces/handlers/IBosonBundleHandler.sol @@ -24,10 +24,8 @@ interface IBosonBundleHandler is IBosonBundleEvents { * - Any of the offers belongs to different seller * - Any of the offers does not exist * - Offer exists in a different bundle - * - Number of offers exceeds maximum allowed number per bundle * - Any of the twins belongs to different seller * - Any of the twins does not exist - * - Number of twins exceeds maximum allowed number per bundle * - Duplicate twins added in same bundle * - Exchange already exists for the offer id in bundle * - Offers' total quantity is greater than twin supply when token is nonfungible diff --git a/contracts/interfaces/handlers/IBosonDisputeHandler.sol b/contracts/interfaces/handlers/IBosonDisputeHandler.sol index 3ed87ed6f..58c026ac2 100644 --- a/contracts/interfaces/handlers/IBosonDisputeHandler.sol +++ b/contracts/interfaces/handlers/IBosonDisputeHandler.sol @@ -87,7 +87,6 @@ interface IBosonDisputeHandler is IBosonDisputeEvents, IBosonFundsLibEvents { * * Reverts if: * - The disputes region of protocol is paused - * - Number of disputes exceeds maximum allowed number per batch * - For any dispute: * - Exchange does not exist * - Exchange is not in a Disputed state diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index 1afbc5fe6..5eede25d2 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -88,7 +88,6 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * * Reverts if: * - The exchanges region of protocol is paused - * - Number of exchanges exceeds maximum allowed number per batch * - For any exchange: * - Exchange does not exist * - Exchange is not in Redeemed state diff --git a/contracts/interfaces/handlers/IBosonGroupHandler.sol b/contracts/interfaces/handlers/IBosonGroupHandler.sol index 2b0aa03ff..fd4075221 100644 --- a/contracts/interfaces/handlers/IBosonGroupHandler.sol +++ b/contracts/interfaces/handlers/IBosonGroupHandler.sol @@ -22,7 +22,6 @@ interface IBosonGroupHandler is IBosonGroupEvents { * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group - * - Number of offers exceeds maximum allowed number per group * * @param _group - the fully populated struct with group id set to 0x0 * @param _condition - the fully populated condition struct @@ -37,7 +36,6 @@ interface IBosonGroupHandler is IBosonGroupEvents { * Reverts if: * - Caller is not the seller * - Offer ids param is an empty list - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - Group does not exist * - Any of offers belongs to different seller * - Any of offers does not exist @@ -58,7 +56,6 @@ interface IBosonGroupHandler is IBosonGroupEvents { * - The groups region of protocol is paused * - Caller is not the seller * - Offer ids param is an empty list - * - Number of offers exceeds maximum allowed number per group * - Group does not exist * - Any offer is not part of the group * diff --git a/contracts/interfaces/handlers/IBosonOfferHandler.sol b/contracts/interfaces/handlers/IBosonOfferHandler.sol index bdad13bdf..ac1892e61 100644 --- a/contracts/interfaces/handlers/IBosonOfferHandler.sol +++ b/contracts/interfaces/handlers/IBosonOfferHandler.sol @@ -60,7 +60,6 @@ interface IBosonOfferHandler is IBosonOfferEvents { * * Reverts if: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - Number of elements in offers, offerDates and offerDurations do not match * - For any offer: * - Caller is not an assistant @@ -145,7 +144,6 @@ interface IBosonOfferHandler is IBosonOfferEvents { * * Reverts if, for any offer: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - Offer id is invalid * - Caller is not the assistant of the offer * - Offer has already been voided @@ -178,7 +176,6 @@ interface IBosonOfferHandler is IBosonOfferEvents { * * Reverts if: * - The offers region of protocol is paused - * - Number of offers exceeds maximum allowed number per batch * - For any of the offers: * - Offer does not exist * - Caller is not the assistant of the offer diff --git a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol index e686a0d40..a32e52b86 100644 --- a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol +++ b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol @@ -326,7 +326,6 @@ interface IBosonOrchestrationHandler is * - When adding to the group if: * - Group does not exists * - Caller is not the assistant of the group - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - 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 @@ -380,7 +379,6 @@ interface IBosonOrchestrationHandler is * - When adding to the group if: * - Group does not exists * - Caller is not the assistant of the group - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - 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 diff --git a/contracts/protocol/facets/BundleHandlerFacet.sol b/contracts/protocol/facets/BundleHandlerFacet.sol index 4cf20de85..9afd4bb8f 100644 --- a/contracts/protocol/facets/BundleHandlerFacet.sol +++ b/contracts/protocol/facets/BundleHandlerFacet.sol @@ -32,10 +32,8 @@ contract BundleHandlerFacet is IBosonBundleHandler, BundleBase { * - Any of the offers belongs to different seller * - Any of the offers does not exist * - Offer exists in a different bundle - * - Number of offers exceeds maximum allowed number per bundle * - Any of the twins belongs to different seller * - Any of the twins does not exist - * - Number of twins exceeds maximum allowed number per bundle * - Duplicate twins added in same bundle * - Exchange already exists for the offer id in bundle * - Offers' total quantity is greater than twin supply when token is nonfungible diff --git a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol index 574ba2208..c6a56b5d3 100644 --- a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol @@ -370,7 +370,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of DisputeResolverFee structs in array exceeds max * - Number of DisputeResolverFee structs in array is zero * - DisputeResolverFee array contains duplicates * - Fee amount is a non-zero value. Protocol doesn't yet support fees for dispute resolvers @@ -432,7 +431,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of DisputeResolverFee structs in array exceeds max * - Number of DisputeResolverFee structs in array is zero * - DisputeResolverFee does not exist for the dispute resolver * @@ -501,7 +499,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of seller ids in array exceeds max * - Number of seller ids in array is zero * - Some seller does not exist * - Seller id is already approved @@ -544,7 +541,6 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * - The dispute resolvers region of protocol is paused * - Caller is not the admin address associated with the dispute resolver account * - Dispute resolver does not exist - * - Number of seller ids in array exceeds max * - Number of seller ids structs in array is zero * - Seller id is not approved * diff --git a/contracts/protocol/facets/GroupHandlerFacet.sol b/contracts/protocol/facets/GroupHandlerFacet.sol index 68c4ded8b..f23e5fbc2 100644 --- a/contracts/protocol/facets/GroupHandlerFacet.sol +++ b/contracts/protocol/facets/GroupHandlerFacet.sol @@ -31,7 +31,6 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group - * - Number of offers exceeds maximum allowed number per group * * @param _group - the fully populated struct with group id set to 0x0 * @param _condition - the fully populated condition struct @@ -51,7 +50,6 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { * Reverts if: * - Caller is not the seller * - Offer ids param is an empty list - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - Group does not exist * - Any of offers belongs to different seller * - Any of offers does not exist From 16604ed82e636154c7fc58be88b6efa13685285f Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 6 Jul 2023 17:38:08 +0200 Subject: [PATCH 22/22] remove natspec comments --- contracts/interfaces/handlers/IBosonFundsHandler.sol | 2 -- contracts/protocol/facets/FundsHandlerFacet.sol | 2 -- contracts/protocol/facets/OrchestrationHandlerFacet1.sol | 2 -- 3 files changed, 6 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonFundsHandler.sol b/contracts/interfaces/handlers/IBosonFundsHandler.sol index 16cd95ddd..39730a1a5 100644 --- a/contracts/interfaces/handlers/IBosonFundsHandler.sol +++ b/contracts/interfaces/handlers/IBosonFundsHandler.sol @@ -42,7 +42,6 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { * - The funds region of protocol is paused * - Caller is not associated with the entity id * - Token list length does not match amount list length - * - Token list length exceeds the maximum allowed number of tokens * - Caller tries to withdraw more that they have in available funds * - There is nothing to withdraw * - Transfer of funds is not successful @@ -64,7 +63,6 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { * - The funds region of protocol is paused * - Caller does not have the FEE_COLLECTOR role * - Token list length does not match amount list length - * - Token list length exceeds the maximum allowed number of tokens * - Caller tries to withdraw more that they have in available funds * - There is nothing to withdraw * - Transfer of funds is not successful diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index f7b554d0a..df4cf0376 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -78,7 +78,6 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * - The funds region of protocol is paused * - Caller is not associated with the entity id * - Token list length does not match amount list length - * - Token list length exceeds the maximum allowed number of tokens * - Caller tries to withdraw more that they have in available funds * - There is nothing to withdraw * - Transfer of funds is not successful @@ -135,7 +134,6 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * - The funds region of protocol is paused * - Caller does not have the FEE_COLLECTOR role * - Token list length does not match amount list length - * - Token list length exceeds the maximum allowed number of tokens * - Caller tries to withdraw more that they have in available funds * - There is nothing to withdraw * - Transfer of funds is not successful diff --git a/contracts/protocol/facets/OrchestrationHandlerFacet1.sol b/contracts/protocol/facets/OrchestrationHandlerFacet1.sol index 5e51ee04e..1a6708e8e 100644 --- a/contracts/protocol/facets/OrchestrationHandlerFacet1.sol +++ b/contracts/protocol/facets/OrchestrationHandlerFacet1.sol @@ -334,7 +334,6 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - When adding to the group if: * - Group does not exists * - Caller is not the assistant of the group - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - 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 @@ -396,7 +395,6 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - When adding to the group if: * - Group does not exists * - Caller is not the assistant of the group - * - Current number of offers plus number of offers added exceeds maximum allowed number per group * - 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