From 22fe29f15faa2747a83d7afdbfdab4da276681ff Mon Sep 17 00:00:00 2001 From: zajck Date: Wed, 25 Jan 2023 08:36:27 +0100 Subject: [PATCH 01/47] 1st iteration --- contracts/domain/BosonTypes.sol | 5 + .../protocol/facets/ExchangeHandlerFacet.sol | 109 +++++++++++++++++- contracts/protocol/libs/ProtocolLib.sol | 2 + 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 975af496f..8cc9be4f5 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -183,6 +183,11 @@ contract BosonTypes { ExchangeState state; } + struct SequentialCommit { + uint256 buyerId; + uint256 price; + } + struct Voucher { uint256 committedDate; uint256 validUntilDate; diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 56531feaf..9e8401120 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -15,6 +15,7 @@ import { Address } from "../../ext_libs/Address.sol"; import { IERC1155 } from "../../interfaces/IERC1155.sol"; import { IERC721 } from "../../interfaces/IERC721.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; +import { Math } from "../../ext_libs/Math.sol"; /** * @title ExchangeHandlerFacet @@ -81,6 +82,107 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { commitToOfferInternal(_buyer, offer, 0, false); } + // temporary struct for development purposes + struct Order { + uint256 exchangeId; + uint256 quantity; + uint256 price; + uint256 nonce; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + } + + function sequentialCommitToOffer(address payable _buyer, Order calldata _order) + external + payable + exchangesNotPaused + buyersNotPaused + nonReentrant + { + /* need to know + seller (== current owner of voucher) + buyer (== new owner) + price + */ + + // verify that order can be fulfilled [// pass forward to 0x or seaport] + // offer must be done in a way that boson protocol receives minimal necesarry funds to go in escrow + + // Exchange must exist + (Exchange storage exchange, Voucher storage voucher) = getValidExchange( + _order.exchangeId, + ExchangeState.Committed + ); + + // Make sure the voucher is still valid + require(block.timestamp <= voucher.validUntilDate, "VOUCHER_INVALID"); + + // Fetch offer + (, Offer storage offer) = fetchOffer(exchange.offerId); + + // Authorize the buyer to commit if original offer is in a conditional group + require(authorizeCommit(_buyer, offer, _order.exchangeId), CANNOT_COMMIT); + + // Get sequential commits for this exchange + SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_order.exchangeId]; + + // Get token address + address tokenAddress = offer.exchangeToken; + + // Calculate the amount to be immediately released to current voucher owner + uint256 currentBuyerAmount; + { + // Calculate fees + uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token + ? protocolFees().flatBoson + : (protocolFees().percentage * _order.price) / 10000; + + // Calculate royalties + (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( + _order.exchangeId, + _order.price + ); + + // Verify that fees and royalties are not higher than the price. + require((protocolFeeAmount + royaltyAmount) <= _order.price, "FEE_AMOUNT_TOO_HIGH"); + + // Get price paid by current buyer + uint256 len = sequentialCommits.length; + uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; + + // Calculate the amount to be immediately released to current voucher owner + currentBuyerAmount = Math.min(currentPrice, _order.price - protocolFeeAmount - royaltyAmount); + } + + // Update sequential commit + sequentialCommits.push(SequentialCommit({ buyerId: getValidBuyer(_buyer), price: _order.price })); + + // Release funds to current buyer + FundsLib.increaseAvailableFunds(exchange.buyerId, tokenAddress, currentBuyerAmount); // must be called before transfer is done + + // Amount of funds to go in escrow + uint256 escrowAmount = _order.price - currentBuyerAmount; + + // Verify that order actually sends the correct amount of funds to escrow + uint256 protocolBalanceBefore = tokenAddress == address(0) + ? address(this).balance + : IERC20(tokenAddress).balanceOf(address(this)); + + // TODO: make call to settlement contract + + uint256 protocolBalanceAfter = tokenAddress == address(0) + ? address(this).balance + : IERC20(tokenAddress).balanceOf(address(this)); + + // Make sure that expected amount of tokens was transferred + require(protocolBalanceAfter - protocolBalanceBefore == escrowAmount, INSUFFICIENT_VALUE_RECEIVED); + + + // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred + } + /** * @notice Commits to a preminted offer (first step of an exchange). * @@ -493,12 +595,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * @param _exchangeId - the id of the exchange * @param _newBuyer - the address of the new buyer */ - function onVoucherTransferred(uint256 _exchangeId, address payable _newBuyer) - external - override - buyersNotPaused - nonReentrant - { + function onVoucherTransferred(uint256 _exchangeId, address payable _newBuyer) external override buyersNotPaused { // Cache protocol lookups for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); diff --git a/contracts/protocol/libs/ProtocolLib.sol b/contracts/protocol/libs/ProtocolLib.sol index 72873ee03..0910fed27 100644 --- a/contracts/protocol/libs/ProtocolLib.sol +++ b/contracts/protocol/libs/ProtocolLib.sol @@ -114,6 +114,8 @@ library ProtocolLib { mapping(uint256 => BosonTypes.Twin) twins; //entity id => auth token mapping(uint256 => BosonTypes.AuthToken) authTokens; + // exchange id => sequential commit info + mapping(uint256 => BosonTypes.SequentialCommit[]) sequentialCommits; } // Protocol lookups storage From 3c69e06497b5a7b3cfb67621c0551bd9b2fa4b75 Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 2 Feb 2023 09:29:13 +0100 Subject: [PATCH 02/47] buy order --- .../protocol/facets/ExchangeHandlerFacet.sol | 231 +++++++++++++----- contracts/protocol/libs/FundsLib.sol | 18 +- 2 files changed, 188 insertions(+), 61 deletions(-) diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 9e8401120..29eba5273 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -17,6 +17,20 @@ import { IERC721 } from "../../interfaces/IERC721.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { Math } from "../../ext_libs/Math.sol"; +interface WETH9Like { + function withdraw(uint256) external; + + function deposit() external payable; + + function transfer(address, uint256) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); +} + /** * @title ExchangeHandlerFacet * @@ -25,6 +39,12 @@ import { Math } from "../../ext_libs/Math.sol"; contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { using Address for address; + WETH9Like private immutable weth; // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) + + constructor(address _weth) { + weth = WETH9Like(_weth); + } + /** * @notice Initializes facet. * This function is callable only once. @@ -83,24 +103,29 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } // temporary struct for development purposes - struct Order { - uint256 exchangeId; - uint256 quantity; + struct PriceDiscovery { uint256 price; - uint256 nonce; - uint256 deadline; - uint8 v; - bytes32 r; - bytes32 s; + address validator; + bytes proof; + Direction direction; } - function sequentialCommitToOffer(address payable _buyer, Order calldata _order) - external - payable - exchangesNotPaused - buyersNotPaused - nonReentrant - { + // enum ValidatorType { + // None, + // Simple, + // Advanced + // } + + enum Direction { + Buy, + Sell + } + + function sequentialCommitToOffer( + address payable _buyer, + uint256 _exchangeId, + PriceDiscovery calldata _priceDiscovery + ) external payable exchangesNotPaused buyersNotPaused nonReentrant { /* need to know seller (== current owner of voucher) buyer (== new owner) @@ -111,10 +136,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // offer must be done in a way that boson protocol receives minimal necesarry funds to go in escrow // Exchange must exist - (Exchange storage exchange, Voucher storage voucher) = getValidExchange( - _order.exchangeId, - ExchangeState.Committed - ); + (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); // Make sure the voucher is still valid require(block.timestamp <= voucher.validUntilDate, "VOUCHER_INVALID"); @@ -123,64 +145,155 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { (, Offer storage offer) = fetchOffer(exchange.offerId); // Authorize the buyer to commit if original offer is in a conditional group - require(authorizeCommit(_buyer, offer, _order.exchangeId), CANNOT_COMMIT); - - // Get sequential commits for this exchange - SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_order.exchangeId]; + require(authorizeCommit(_buyer, offer, _exchangeId), CANNOT_COMMIT); // Get token address address tokenAddress = offer.exchangeToken; // Calculate the amount to be immediately released to current voucher owner - uint256 currentBuyerAmount; + uint256 escrowAmount; { - // Calculate fees - uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token - ? protocolFees().flatBoson - : (protocolFees().percentage * _order.price) / 10000; - - // Calculate royalties - (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( - _order.exchangeId, - _order.price - ); - - // Verify that fees and royalties are not higher than the price. - require((protocolFeeAmount + royaltyAmount) <= _order.price, "FEE_AMOUNT_TOO_HIGH"); + // Get sequential commits for this exchange + SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; + + { + // Calculate fees + uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token + ? protocolFees().flatBoson + : (protocolFees().percentage * _priceDiscovery.price) / 10000; + + // Calculate royalties + (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( + _exchangeId, + _priceDiscovery.price + ); + + // Verify that fees and royalties are not higher than the price. + require((protocolFeeAmount + royaltyAmount) <= _priceDiscovery.price, "FEE_AMOUNT_TOO_HIGH"); + + // Get price paid by current buyer + uint256 len = sequentialCommits.length; + uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; + + // Calculate the amount to be immediately released to current voucher owner + escrowAmount = + _priceDiscovery.price - + Math.min(currentPrice, _priceDiscovery.price - protocolFeeAmount - royaltyAmount); + } + // Update sequential commit + sequentialCommits.push(SequentialCommit({ buyerId: getValidBuyer(_buyer), price: _priceDiscovery.price })); + } - // Get price paid by current buyer - uint256 len = sequentialCommits.length; - uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; + // Release funds to current buyer + // FundsLib.increaseAvailableFunds(exchange.buyerId, tokenAddress, currentBuyerAmount); // must be called before transfer is done - // Calculate the amount to be immediately released to current voucher owner - currentBuyerAmount = Math.min(currentPrice, _order.price - protocolFeeAmount - royaltyAmount); + { + // Amount of funds to go in escrow + // uint256 escrowAmount = ; + + // Get current buyer address. This is actually the seller in sequential commit + (, Buyer storage currentBuyer) = fetchBuyer(exchange.buyerId); + + fulFilOrder( + _exchangeId, + tokenAddress, + _priceDiscovery, + escrowAmount, + currentBuyer.wallet, + _buyer, + offer.sellerId + ); } - // Update sequential commit - sequentialCommits.push(SequentialCommit({ buyerId: getValidBuyer(_buyer), price: _order.price })); + // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred + } - // Release funds to current buyer - FundsLib.increaseAvailableFunds(exchange.buyerId, tokenAddress, currentBuyerAmount); // must be called before transfer is done + function fulFilOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + uint256 _escrowAmount, + address _seller, + address _buyer, + uint256 _initialSellerId + ) internal { + if (_priceDiscovery.direction == Direction.Buy) { + fulfilBuyOrder( + _exchangeId, + _exchangeToken, + _priceDiscovery, + _escrowAmount, + _seller, + _buyer, + _initialSellerId + ); + } else { + // fulfilSellOrder(_priceDiscovery, _escrowAmount); + } + } + + function fulfilBuyOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + uint256 _escrowAmount, + address _seller, + address _buyer, + uint256 _initialSellerId + ) internal { + // Transfer buyers funds to protocol + FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); - // Amount of funds to go in escrow - uint256 escrowAmount = _order.price - currentBuyerAmount; + // At this point, protocol temporary holds buyer's payment + uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // Verify that order actually sends the correct amount of funds to escrow - uint256 protocolBalanceBefore = tokenAddress == address(0) - ? address(this).balance - : IERC20(tokenAddress).balanceOf(address(this)); + // If token is ERC20, approve validator to transfer funds + if (_exchangeToken != address(0)) { + IERC20(_exchangeToken).approve(address(_priceDiscovery.validator), _priceDiscovery.price); + } - // TODO: make call to settlement contract + { + // Call the validator + (bool success, bytes memory returnData) = address(_priceDiscovery.validator).call{ value: msg.value }( + _priceDiscovery.proof + ); - uint256 protocolBalanceAfter = tokenAddress == address(0) - ? address(this).balance - : IERC20(tokenAddress).balanceOf(address(this)); + // If error, return error message + string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); + require(success, errorMessage); + } + // If token is ERC20, reset approval + if (_exchangeToken != address(0)) { + IERC20(_exchangeToken).approve(address(_priceDiscovery.validator), 0); + } - // Make sure that expected amount of tokens was transferred - require(protocolBalanceAfter - protocolBalanceBefore == escrowAmount, INSUFFICIENT_VALUE_RECEIVED); + // Check the escrow amount + uint256 protocolBalanceAfter = getBalance(_exchangeToken); + + uint256 expectedBalanceAfter = protocolBalanceBefore - _priceDiscovery.price + _escrowAmount; + if (protocolBalanceAfter > expectedBalanceAfter) { + // Escrowed too much, return the difference. TO WHO? + } else if (protocolBalanceAfter < expectedBalanceAfter) { + uint256 diff = expectedBalanceAfter - protocolBalanceAfter; + // Not enough in the escrow, pull it form seller + if (_exchangeToken == address(0)) { + FundsLib.transferFundsToProtocol(address(weth), _seller, diff); + weth.withdraw(diff); + } else { + FundsLib.transferFundsToProtocol(_exchangeToken, _seller, diff); + } + } + // Transfer voucher to buyer + IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]).transferFrom( + address(this), + _buyer, + _exchangeId + ); + } - // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred + function getBalance(address _tokenAddress) internal view returns (uint256) { + return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); } /** diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index cf594b9f2..a17807132 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -202,6 +202,11 @@ library FundsLib { } } + // Original seller and last buyer are done + // Release funds to intermediate sellers (if they exist) + // and add the protocol fee to the total + // protocolFee += releaseFundsToIntermediateSellers(_exchangeId, sellerPayoff); + // Store payoffs to availablefunds and notify the external observers address exchangeToken = offer.exchangeToken; uint256 sellerId = offer.sellerId; @@ -238,15 +243,20 @@ library FundsLib { * - Received ERC20 token amount differs from the expected value * * @param _tokenAddress - address of the token to be transferred + * @param _from - address to transfer funds from * @param _amount - amount to be transferred */ - function transferFundsToProtocol(address _tokenAddress, uint256 _amount) internal { + function transferFundsToProtocol( + address _tokenAddress, + address _from, + uint256 _amount + ) internal { if (_amount > 0) { // protocol balance before the transfer uint256 protocolTokenBalanceBefore = IERC20(_tokenAddress).balanceOf(address(this)); // transfer ERC20 tokens from the caller - IERC20(_tokenAddress).safeTransferFrom(EIP712Lib.msgSender(), address(this), _amount); + IERC20(_tokenAddress).safeTransferFrom(_from, address(this), _amount); // protocol balance after the transfer uint256 protocolTokenBalanceAfter = IERC20(_tokenAddress).balanceOf(address(this)); @@ -256,6 +266,10 @@ library FundsLib { } } + function transferFundsToProtocol(address _tokenAddress, uint256 _amount) internal { + transferFundsToProtocol(_tokenAddress, EIP712Lib.msgSender(), _amount); + } + /** * @notice Tries to transfer native currency or tokens from the protocol to the recipient. * From 667816588fc57f19e65ac17a6030112f945b8a92 Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 2 Feb 2023 10:00:34 +0100 Subject: [PATCH 03/47] fulfilSellOrder --- .../protocol/facets/ExchangeHandlerFacet.sol | 67 ++++++++++++++++++- contracts/protocol/libs/FundsLib.sol | 15 ++++- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 29eba5273..36f36b7ce 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -228,7 +228,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { _initialSellerId ); } else { - // fulfilSellOrder(_priceDiscovery, _escrowAmount); + fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _escrowAmount, _seller, _initialSellerId); } } @@ -272,7 +272,12 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 expectedBalanceAfter = protocolBalanceBefore - _priceDiscovery.price + _escrowAmount; if (protocolBalanceAfter > expectedBalanceAfter) { - // Escrowed too much, return the difference. TO WHO? + // Escrowed too much, return the difference to buyer + FundsLib.transferFundsFromProtocol( + _exchangeToken, + payable(_buyer), + protocolBalanceAfter - expectedBalanceAfter + ); } else if (protocolBalanceAfter < expectedBalanceAfter) { uint256 diff = expectedBalanceAfter - protocolBalanceAfter; // Not enough in the escrow, pull it form seller @@ -292,6 +297,64 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { ); } + function fulfilSellOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + uint256 _escrowAmount, + address _seller, + uint256 _initialSellerId + ) internal { + // what about non-zero msg.value? + + IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); + // Transfer seller's voucher to protocol + bosonVoucher.transferFrom(_seller, address(this), _exchangeId); + + // Get protocol balance before the exchange + uint256 protocolBalanceBefore = getBalance(_exchangeToken); + + // Approve validator to transfer voucher + bosonVoucher.approve(_priceDiscovery.validator, _exchangeId); + + { + // Call the validator + (bool success, bytes memory returnData) = address(_priceDiscovery.validator).call{ value: msg.value }( + _priceDiscovery.proof + ); + + // If error, return error message + string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); + require(success, errorMessage); + } + + // Reset approval + bosonVoucher.approve(address(0), _exchangeId); + + // Check the escrow amount + uint256 protocolBalanceAfter = getBalance(_exchangeToken); + + uint256 expectedBalanceAfter = protocolBalanceBefore + _escrowAmount; // what about potential non zero msg.value? + + if (protocolBalanceAfter > expectedBalanceAfter) { + // Return the difference to the seller + FundsLib.transferFundsFromProtocol( + _exchangeToken, + payable(_seller), + protocolBalanceAfter - expectedBalanceAfter + ); + } else if (protocolBalanceAfter < expectedBalanceAfter) { + uint256 diff = expectedBalanceAfter - protocolBalanceAfter; + // Not enough in the escrow, pull it form seller + if (_exchangeToken == address(0)) { + FundsLib.transferFundsToProtocol(address(weth), _seller, diff); + weth.withdraw(diff); + } else { + FundsLib.transferFundsToProtocol(_exchangeToken, _seller, diff); + } + } + } + function getBalance(address _tokenAddress) internal view returns (uint256) { return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); } diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index a17807132..0e3b68066 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -294,6 +294,18 @@ library FundsLib { // first decrease the amount to prevent the reentrancy attack decreaseAvailableFunds(_entityId, _tokenAddress, _amount); + // try to transfer the funds + transferFundsFromProtocol(_tokenAddress, _to, _amount); + + // notify the external observers + emit FundsWithdrawn(_entityId, _to, _tokenAddress, _amount, EIP712Lib.msgSender()); + } + + function transferFundsFromProtocol( + address _tokenAddress, + address payable _to, + uint256 _amount + ) internal { // try to transfer the funds if (_tokenAddress == address(0)) { // transfer native currency @@ -303,9 +315,6 @@ library FundsLib { // transfer ERC20 tokens IERC20(_tokenAddress).safeTransfer(_to, _amount); } - - // notify the external observers - emit FundsWithdrawn(_entityId, _to, _tokenAddress, _amount, EIP712Lib.msgSender()); } /** From a6d14f3e6b5be0a9dab0f9bb30cb3be1dde7bfbf Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 3 Feb 2023 08:37:28 +0100 Subject: [PATCH 04/47] releaseFundsToIntermediateSellers --- contracts/domain/BosonTypes.sol | 4 +- .../protocol/facets/ExchangeHandlerFacet.sol | 21 ++- contracts/protocol/libs/FundsLib.sol | 127 ++++++++++++++++-- .../util/deploy-protocol-handler-facets.js | 10 +- 4 files changed, 144 insertions(+), 18 deletions(-) diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 8cc9be4f5..496af5657 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -184,8 +184,10 @@ contract BosonTypes { } struct SequentialCommit { - uint256 buyerId; + uint256 resellerId; uint256 price; + uint256 protocolFeeAmount; + uint256 royaltyAmount; } struct Voucher { diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 36f36b7ce..536d04670 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -176,12 +176,25 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; // Calculate the amount to be immediately released to current voucher owner + // escrowAmount = + // _priceDiscovery.price - + // Math.min(currentPrice, _priceDiscovery.price - protocolFeeAmount - royaltyAmount); + // escrowAmount = + // Math.max(_priceDiscovery.price-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow escrowAmount = - _priceDiscovery.price - - Math.min(currentPrice, _priceDiscovery.price - protocolFeeAmount - royaltyAmount); + Math.max(_priceDiscovery.price, protocolFeeAmount + royaltyAmount + currentPrice) - + currentPrice; + + // Update sequential commit + sequentialCommits.push( + SequentialCommit({ + resellerId: exchange.buyerId, + price: _priceDiscovery.price, + protocolFeeAmount: protocolFeeAmount, + royaltyAmount: royaltyAmount + }) + ); } - // Update sequential commit - sequentialCommits.push(SequentialCommit({ buyerId: getValidBuyer(_buyer), price: _priceDiscovery.price })); } // Release funds to current buyer diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 0e3b68066..10738f649 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -7,6 +7,7 @@ import { EIP712Lib } from "../libs/EIP712Lib.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { SafeERC20 } from "../../ext_libs/SafeERC20.sol"; +import { Math } from "../../ext_libs/Math.sol"; /** * @title FundsLib @@ -149,12 +150,11 @@ library FundsLib { uint256 agentFee; BosonTypes.OfferFees storage offerFee = pe.offerFees[exchange.offerId]; - + uint256 price = offer.price; { // scope to avoid stack too deep errors BosonTypes.ExchangeState exchangeState = exchange.state; uint256 sellerDeposit = offer.sellerDeposit; - uint256 price = offer.price; if (exchangeState == BosonTypes.ExchangeState.Completed) { // COMPLETED @@ -202,24 +202,31 @@ library FundsLib { } } + address exchangeToken = offer.exchangeToken; + // Original seller and last buyer are done // Release funds to intermediate sellers (if they exist) // and add the protocol fee to the total - // protocolFee += releaseFundsToIntermediateSellers(_exchangeId, sellerPayoff); + { + (uint256 sequentialProtocolFee, uint256 sequentialRoyalties) = releaseFundsToIntermediateSellers( + _exchangeId, + exchange.state, + price, + exchangeToken + ); + sellerPayoff += sequentialRoyalties; // revisit this + protocolFee += sequentialProtocolFee; + } // Store payoffs to availablefunds and notify the external observers - address exchangeToken = offer.exchangeToken; - uint256 sellerId = offer.sellerId; - uint256 buyerId = exchange.buyerId; address sender = EIP712Lib.msgSender(); if (sellerPayoff > 0) { - increaseAvailableFunds(sellerId, exchangeToken, sellerPayoff); - emit FundsReleased(_exchangeId, sellerId, exchangeToken, sellerPayoff, sender); + increaseAvailableFundsAndEmitEvent(_exchangeId, offer.sellerId, exchangeToken, sellerPayoff, sender); } if (buyerPayoff > 0) { - increaseAvailableFunds(buyerId, exchangeToken, buyerPayoff); - emit FundsReleased(_exchangeId, buyerId, exchangeToken, buyerPayoff, sender); + increaseAvailableFundsAndEmitEvent(_exchangeId, exchange.buyerId, exchangeToken, buyerPayoff, sender); } + if (protocolFee > 0) { increaseAvailableFunds(0, exchangeToken, protocolFee); emit ProtocolFeeCollected(_exchangeId, exchangeToken, protocolFee, sender); @@ -227,9 +234,105 @@ library FundsLib { if (agentFee > 0) { // Get the agent for offer uint256 agentId = ProtocolLib.protocolLookups().agentIdByOffer[exchange.offerId]; - increaseAvailableFunds(agentId, exchangeToken, agentFee); - emit FundsReleased(_exchangeId, agentId, exchangeToken, agentFee, sender); + increaseAvailableFundsAndEmitEvent(_exchangeId, agentId, exchangeToken, agentFee, sender); + } + } + + function releaseFundsToIntermediateSellers( + uint256 _exchangeId, + BosonTypes.ExchangeState _exchangeState, + uint256 _initialPrice, + address _exchangeToken + ) internal returns (uint256 protocolFee, uint256 royalties) { + // ProtocolLib.protocolEntities() and sequentialCommits.length are not stored to memory due to stack too deep errors + // Revisit when update to newest compiler version + + BosonTypes.SequentialCommit[] storage sequentialCommits = ProtocolLib.protocolEntities().sequentialCommits[ + _exchangeId + ]; + + if (sequentialCommits.length == 0) { + return (0, 0); + } + + // calculate effective price multiplier + uint256 effectivePriceMultiplier; + { + if (_exchangeState == BosonTypes.ExchangeState.Completed) { + // COMPLETED, buyer pays full price + effectivePriceMultiplier = 10000; + } else if ( + _exchangeState == BosonTypes.ExchangeState.Revoked || + _exchangeState == BosonTypes.ExchangeState.Canceled + ) { + // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) + effectivePriceMultiplier = 0; + } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { + // DISPUTED + // get the information about the dispute, which must exist + BosonTypes.Dispute storage dispute = ProtocolLib.protocolEntities().disputes[_exchangeId]; + BosonTypes.DisputeState disputeState = dispute.state; + + if (disputeState == BosonTypes.DisputeState.Retracted) { + // RETRACTED - same as "COMPLETED" + effectivePriceMultiplier = 10000; + } else if (disputeState == BosonTypes.DisputeState.Refused) { + // REFUSED, buyer pays nothing + effectivePriceMultiplier = 0; + } else { + // RESOLVED or DECIDED + effectivePriceMultiplier = 10000 - dispute.buyerPercent; + } + } + } + + uint256 resellerBuyPrice = _initialPrice; + address msgSender = EIP712Lib.msgSender(); + uint256 nextResellerAmount; + for (uint256 i = 0; i < sequentialCommits.length; i++) { + BosonTypes.SequentialCommit memory sc = sequentialCommits[i]; // we need all members of the struct + + protocolFee += sc.protocolFeeAmount; + royalties += sc.royaltyAmount; + + uint256 currentResellerAmount; + + // escrowed for exchange between buyer i and i+1 + { + uint256 escrowAmount = Math.max(sc.price, sc.protocolFeeAmount + sc.royaltyAmount + resellerBuyPrice) - + resellerBuyPrice; + + currentResellerAmount = (escrowAmount * effectivePriceMultiplier) / 10000 + nextResellerAmount; + nextResellerAmount = escrowAmount + nextResellerAmount - currentResellerAmount; + // uint256 nextResellerAmountTemp = escrowAmount - currentResellerAmount; // TODO: is it cheaper to make another memory variable and save one subtraction? + // currentResellerAmount += nextResellerAmount; + // nextResellerAmount = nextResellerAmountTemp; + } + if (currentResellerAmount > 0) { + increaseAvailableFundsAndEmitEvent( + _exchangeId, + sc.resellerId, + _exchangeToken, + currentResellerAmount, + msgSender + ); + } } + + protocolFee = (protocolFee * effectivePriceMultiplier) / 10000; + royalties = (royalties * effectivePriceMultiplier) / 10000; + // ? do we need to return nextResellerAmount and add it to buyerPayoff? + } + + function increaseAvailableFundsAndEmitEvent( + uint256 _exchangeId, + uint256 _entityId, + address _tokenAddress, + uint256 _amount, + address _sender + ) internal { + increaseAvailableFunds(_entityId, _tokenAddress, _amount); + emit FundsReleased(_exchangeId, _entityId, _tokenAddress, _amount, _sender); } /** diff --git a/scripts/util/deploy-protocol-handler-facets.js b/scripts/util/deploy-protocol-handler-facets.js index 41dc7b375..fde60f250 100644 --- a/scripts/util/deploy-protocol-handler-facets.js +++ b/scripts/util/deploy-protocol-handler-facets.js @@ -79,10 +79,18 @@ async function deployAndCutFacets( async function deployProtocolFacets(facetNames, facetsToInit, maxPriorityFeePerGas) { let deployedFacets = []; + // TODO: get constructorArguments from a config file + let constructorArguments = { + ExchangeHandlerFacet: ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], + }; + // Deploy all handler facets for (const facetName of facetNames) { let FacetContractFactory = await ethers.getContractFactory(facetName); - const facetContract = await FacetContractFactory.deploy(await getFees(maxPriorityFeePerGas)); + const facetContract = await FacetContractFactory.deploy( + ...(constructorArguments[facetName] || []), + await getFees(maxPriorityFeePerGas) + ); await facetContract.deployTransaction.wait(confirmations); const deployedFacet = { From b0909b4593241312abdfb394e394dff2ff049837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 08:05:21 +0100 Subject: [PATCH 05/47] Bump eslint-config-prettier from 8.5.0 to 8.6.0 (#547) Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.5.0 to 8.6.0. - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.5.0...v8.6.0) --- updated-dependencies: - dependency-name: eslint-config-prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e20f0eb70..238dc9a55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "dotenv": "^16.0.3", "eip55": "^2.1.0", "eslint": "^8.32.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-prettier": "^8.6.0", "eslint-plugin-no-only-tests": "^3.1.0", "ethereum-waffle": "^3.4.0", "ethers": "^5.7.2", @@ -9047,9 +9047,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -38798,9 +38798,9 @@ } }, "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index e4f2471d7..551e3a97a 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "dotenv": "^16.0.3", "eip55": "^2.1.0", "eslint": "^8.32.0", - "eslint-config-prettier": "^8.5.0", + "eslint-config-prettier": "^8.6.0", "eslint-plugin-no-only-tests": "^3.1.0", "ethereum-waffle": "^3.4.0", "ethers": "^5.7.2", From 7626faec8b0b502571abd7e935afcd58e6c89c21 Mon Sep 17 00:00:00 2001 From: zajck <64400885+zajck@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:59:12 +0100 Subject: [PATCH 06/47] Protocol Initalization Handler Facet documentation (#538) * Initalizer Facet documentation * Apply suggestions from code review Co-authored-by: Ana Julia Bittencourt * Expand initialization description * Review fixes * upgrade configs * Update docs/tasks.md Co-authored-by: Ana Julia Bittencourt Co-authored-by: Ana Julia Bittencourt --- ...ol_V2_-_Protocol_Initialization_Hander.png | Bin 0 -> 157521 bytes docs/local-development.md | 6 +- docs/protocol-initialization-facet.md | 57 ++++++++ docs/tasks.md | 3 + docs/upgrade-configs.md | 130 ++++++++++++++++++ 5 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 docs/images/Boson_Protocol_V2_-_Protocol_Initialization_Hander.png create mode 100644 docs/protocol-initialization-facet.md create mode 100644 docs/upgrade-configs.md diff --git a/docs/images/Boson_Protocol_V2_-_Protocol_Initialization_Hander.png b/docs/images/Boson_Protocol_V2_-_Protocol_Initialization_Hander.png new file mode 100644 index 0000000000000000000000000000000000000000..f3146d0b2d3fbe0a828f28e78dba1561f373b969 GIT binary patch literal 157521 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 literal 0 HcmV?d00001 diff --git a/docs/local-development.md b/docs/local-development.md index 6dfb4817c..925dded16 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -113,7 +113,7 @@ To get the examples how to use the admin to perform actions, refer to unit test - FEE_COLLECTOR role: `test/protocol/FundsHandlerTest.js` ### Upgrade facets -To test the upgrade functionality, you first need to setup upgrader account as described in previous section. +To test the upgrade functionality, you first need to setup an upgrader account as described in previous section. To perform the upgrade you then - Update some of the existing facets or create new one. @@ -127,8 +127,10 @@ To perform the upgrade you then - Update `version` in `package.json`. If the version in `package.json` matches the existing version in addresses file, you will have to explicitly confirm that you want to proceed. - Run `npm run upgrade-facets:local`. This will deploy new facets and make all necessary diamond cuts. It also updates the existing addresses file `addresses/-.json` (for example `addresses/31337-localhost.json` if you are using a default local hardhat node) and outputs the upgrade log to the console. +Protocol initialization facet is explained in more detail on a separate page: [Protocol initialization handler facet](protocol-initialization-facet.md). + ### Upgrade clients -To test the upgrade functionality, you first need to setup upgrader account as described in section [Manage roles](local-development.md#optional-manage-roles) +To test the upgrade functionality, you first need to setup an upgrader account as described in section [Manage roles](local-development.md#optional-manage-roles) To perform the upgrade you then - Update some of the existing clients diff --git a/docs/protocol-initialization-facet.md b/docs/protocol-initialization-facet.md new file mode 100644 index 000000000..62df3e470 --- /dev/null +++ b/docs/protocol-initialization-facet.md @@ -0,0 +1,57 @@ +[![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 initialization handler facet + +The Protocol initialization handler facet plays a specialized role in the overall [architecture](architecture.md). On one side we have Diamond-specific facets - Loupe and Cut - which enable lookups and facet management. On the other hand we have Protocol specific facets which implement all the features needed for protocol to function as intended. Most of these facets need to be initialized at the time when cuts are performed, an example being the act of registering its own interface in the diamond. However, in some cases initialization of a facet is different depending on whether the whole protocol is deployed from scratch or whether it is being upgraded. Moreover, certain upgrades might introduce logic that requires data that was not produced in an older version of the protocol which also need to be handled in the initialization step. To this end, we introduce the Protocol initialization handler facet, which conceptually lives between the Diamond-specific and the Protocol-specific facets. Its role is to enable smooth atomic upgrades between versions. One of its functions is to backfill contract storage if needed for a given upgrade. + +Protocol initialization handler facets responsibilities are to: +- store the version of the protocol, +- forward initialization calls to the appropriate facets, +- properly handle the initialization of data when called during an upgrade, +- remove supported interfaces, +- add supported interfaces. + +Two biggest benefits of the Protocol initialization handler are: +- It allows for atomic cuts of multiple facets, this can at times alleviate the need to pause the protocol to perform an upgrade. +- It allows for custom storage manipulation, enabling the upgrades to backfill data as needed. + +### Versioning + +Initialize accepts version numbers that are encoded as bytes32. Every time an upgrade takes place, a unique version must be supplied, otherwise the initialization will revert. Checks can be performed against these version numbers, e.g. an upgrade to a given version can only be performed if the current protocol version is set to exactly one version below. This kind of restriction is planned to be used on every upgrade. This means that skipping versions during the upgrade will not be possible. If the latest version of protocol code is more than one version higher than currently deployed protocol, all intermediate upgrades need to be done to upgrade to the latest version. + +Facets allows to query the current version by calling `getVersion`. + +### Facet initialization + +Each Protocol facet should have an initialization function (even if no-op) to ensure consistency across the codebase and the different versions. The initialization functions should be written as if the protocol is deployed for the first time (i.e. not taking into account potential effects on the upgrade). Although initialization functions must be external, they should not be part of facet interface and therefore should never be added to diamond function list. This is important to prevent facet reinitialization and clashes between initializers with same argument types. + +When protocol is deployed for the first time, `initialize` on Protocol initialization handler should get a list of all facet implementation addresses together with corresponding initialization data. This data is passed directly on to individual facets where whole initialization takes place. + +However, before an upgrade is performed, the effects of facet initializers should be carefully considered before passing data to initialization handler. If a facet's initialization function does not affect the protocol state in a harmful way, it should be passed to initialization handler in the same way as for the initial deployment. However, when initialize affects the storage (for example Config handler sets all counters to 1), the facet should be omitted from the initialization call and the approach detailed in the next section should be followed. + +### Data initialization + +When facet initializers clash with existing data or if a specific bit of storage needs population, the initializer facet must be updated to cater for this kind of change. Suppose that in a new version X.Y.Z there is a need to modify the storage, a new function ought to be created, called `initVX_Y_Z`. This function can make additional version checks and can accept arbitrary data which can be handled in any desired way. This allows populating custom storage slots during the upgrade or mimicking actions of the facet initializers that would otherwise be harmful. For example if another limit is added to the Config handler, `initVX_Y_Z` could simply store new value to desired location, without the need to overwrite other config values and at the same time leaves counters intact. +Initialization data is passed in as bytes, so `initVX_Y_Z` must be decoded into correct types as needed. + +### Managing supported interfaces + +Although most facets initializers automatically register their EIP165 interfaces, old interfaces are not automatically removed during upgrade/removal. Protocol initialization handler therefore allows upgrader to supply list of interfaces to remove or add, which happens atomically during the upgrade. + + +### Initialization diagram +The diagram represents a simple protocol upgrade whereby two facets are upgraded: +- Facet 1 has an initializer that writes to two slots, one of which is already populated. +- Facet 2 has an initializer that writes to an empty slot. + +During the upgrade, the following happens: +- diamondCut is invoked externally. After it performs cut actions, it calls initialize on Protocol Initialization Facet. +- Since Facet 2 has upgrade-safe initializer, it is invoked directly on Facet itself. +- On the other hand, calling `initialize` on Facet 1 would affect already populated storage slot #102 and another empty slot #104. To avoid undesired effects, `initialize` cannot be called directly. Instead, `initVX_Y_Z` updates slot #104 and leaves #102 untouched. +- Additionally `initVX_Y_Z` also touches slot #106, not because some initializer would otherwise do it, but for example because some updated method from Facet 1 needs to read from that slot. + +![Protocol Initialization Handler](images/Boson_Protocol_V2_-_Protocol_Initialization_Hander.png) \ No newline at end of file diff --git a/docs/tasks.md b/docs/tasks.md index e6510418f..29537c722 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -70,6 +70,9 @@ For upgrade to succeed you need an account with UPGRADER role. Refer to [Manage - **Ethereum Mainnet**. This upgrades the existing diamond on Ethereum Mainnet. ```npm run upgrade-facets:ethereum:mainnet``` +Each upgrade requires correct config parameters. We provide [correct configurations for all release versions](). +If you want to upgrade to any intermediate version (for example to a release candidate), you can use the same config as for the actual release, however it might result in interface clashes, which prevent subsequent upgrades. Workaround for this problem is to temporarily disable `onlyUninitialized` modifier on all contracts that clash. Since this is generally an unsafe operation, you should never do that in the production environment. Production should always be upgraded only to actual releases. + ### Upgrade clients Upgrade existing clients (currently only BosonVoucher). Script deploys new implementation and updates address on beacon. We provide different npm scripts for different use cases. A script for Hardhat network does not exist. Since contracts are discarded after the deployment, they cannot be upgraded. diff --git a/docs/upgrade-configs.md b/docs/upgrade-configs.md new file mode 100644 index 000000000..0242a0f07 --- /dev/null +++ b/docs/upgrade-configs.md @@ -0,0 +1,130 @@ +[![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) + +## Upgrade configurations +This page provides correct configuration upgrades for all releases. +If you want to upgrade to any intermediate version (for example to a release candidate), you can use the same config as for the actual release, however it might result in interface clashes, which might prevent subsequent upgrades. Workaround for this problem is to temporary disable `onlyUninitialized` modifier on all contracts that clash. Since this is generally an unsafe operation, you should never do that in production environment. Production should always be upgraded only to actual releases. + +Replace contents of file `scripts/config/facet-upgrade.js` with a configuration below, corresponding to desired upgrade. + +### 2.0.0 -> 2.1.0 + +```javascript +{ + addOrUpgrade: ["ERC165Facet", "AccountHandlerFacet", "SellerHandlerFacet", "DisputeResolverHandlerFacet"], + remove: [], + skipSelectors: {}, + initArgs: {}, + skipInit: ["ERC165Facet"], +} +``` +Note: format to upgrade from `2.0.0` to `2.1.0` does not match the latest upgrade script format. When you are upgrading to this version, checkout the tag `v2.1.0-scripts` which is the latest version of old upgrade script that works with upgrades up to `2.1.0`. + +### 2.1.0 -> 2.2.0 + +```javascript +{ + addOrUpgrade: [ + "AccountHandlerFacet", + "BundleHandlerFacet", + "ConfigHandlerFacet", + "DisputeHandlerFacet", + "DisputeResolverHandlerFacet", + "ExchangeHandlerFacet", + "MetaTransactionsHandlerFacet", + "OfferHandlerFacet", + "SellerHandlerFacet", + "TwinHandlerFacet", + "ProtocolInitializationHandlerFacet", + "OrchestrationHandlerFacet1", + "OrchestrationHandlerFacet2", + ], + remove: ["OrchestrationHandlerFacet"], + skipSelectors: {}, + facetsToInit: { + OrchestrationHandlerFacet1: [], // init only OrchestrationHandlerFacet1, OrchestrationHandlerFacet2 is no-op + MetaTransactionsHandlerFacet: [ + [ + "0xaaea2fdc2fe9e42a5c77e98666352fc2dbf7b32b9cbf91944089d3602b1a941d", + "0xf7e469fd36ada03a455f9ae348527498cb8d8a6b6137aa769c2bc8f4cc0ad7e6", + "0x90b9d701120ba03749f7021324e5fc97438c847380b773bb897ffa9fabab647c", + "0x6adb0d9c3c70c5c3ec9332cf7e4c4d2c33eb29cb3e55037c79434ef3fada4044", + "0x0ae2126d006c21c5824ec10ee0dc64d5ef05f858080dd1a92e3dbe198b5f8499", + "0xa880fd89e679d8c813150b269255a8a9ae46984310cddeb8f344a77bbb4cb0c8", + "0x1227dbbba1af7882df0c2f368ac78fb2c624a77dcfa783b3512a331d08541945", + "0x1843b3a936e72dc3423a7820b79df54578eb2321480ad2f0c6191b7a2c500174", + "0x2230dd12e924aa859ef57ec4dade101275616eb92217d01ec74d9efe2f6e6aa6", + "0x4e534c9650f9ac7d5c03f8c48b0522522a613d6214bf7ba579412924ab0f9295", + "0xfa92792a82bab95011388f166520331cc3b3a016362e01f3df45575f206a088c", + "0x125e35ecb0e80f32093bffe0ee126e07e3081113653234a73157ad1a9428e7b5", + "0xda14451cc7bdf6d3eb088cbf8cf16395d91652dfeb49155fea8e548623cf58fb", + "0x0eb1de1c910b5f6b3e081ea9d70ea55b1f63e8687221289e0fd95bb63b7f0267", + "0x7c016ad538b2b5ce85140e48fb6abe43e7ab43b31b114aae967303c96a22f901", + "0x04f63e12403cb2deb13216a684596fc0a94e03dd09a917ea334ad614d4265c56", + "0x20a68d25bd6cc258f86e9470a6f721a0c0d78f136b2cf060800568be00bae41b", + "0x42443efdfc55a8186beaa746c4c9cb35eb3548b30d041a7e394ad7056aaeb83a", + "0xae707f1e32af7522193cb7fc73109343421ddc883a6a0a4dfbc867ddfc7224ba", + "0xf7d95f3bb0dbce2f73cdc7ba81489e84901014795d94cdaa35d28446803e96e4", + "0xfb50e2350c3bb4f09d6e5db77ea5f63dcedbad1f5d95b8ec5b3baec8bfc13e94", + "0xa5c1674e620c21e2f21f5c1faae4ce84afbd8427e2ff31514412c9ce0737725a", + "0xdfdcd6135be7ca49767c5f46cf5299f807d20465cfa6bbfd2c53e78c1f5d5d43", + "0xb4dcefaf4091c503ee1183c6892061bd8b0d7bbfd691734068c3408c6080c512", + "0x65f65c948d22c455a7cdc716108ceb3e47b011fff5dcd04af5d01259045312cb", + "0xbed3ac5024241532a75d94ae773c337b68153785c80a53cc5d1fca9cc67edd1c", + "0x1f317d1c833d4ab29c44bcc446c31b0c3b34e8e9bd89a352476c6aa134ce819d", + "0x5f119c40938c3ae05dcadcac680748ccc14f460b341454412abc3329b6751bc9", + "0x58477db8afd1115f4a8e27ae4284ae8c1b4b9487b287273dca9d2adee8cfbf3d", + "0x492edd2840bfa58042eb1b8390dfb5353ea588da111ce69dcd63abdbc07de001", + "0x18170b73b12e66f1bfce19ffd5cd452a578d79a836db53be0d8ac67e8eaceb69", + "0x229f69432b8d68fefb34adffa3fb8b5c570c343c26221a6f6f12a8d430f7b3c3", + "0x7b02365d7cb10a5594ae9167f695d5f2b000c870c3e1234bce9b92ff11e81f1c", + "0x1c6d6b5ed1e326c8b9c8e98d9fbb0a5eb39512bcf9fb33d81c6f3db58599d526", + "0x79a5fea918bca323054271090b522a46b6d6cb3ccc95209defbcf4c45d492429", + "0xa07fcc70c56b1cb2044c9a67496d53c6f7917ba096b643debdf405e55e5e49cd", + "0x685ef733ac3593f8717a833b61899824a7dbdd369abcc9654f645969504f9c5b", + "0x11ec86930fbd8bd9e4eefe71220f517bf8452e8fa6bfa392f855308c0676c991", + "0x4ff2e05649742b3d27ffcc7d11ff8dbcc4d0505fb4e5fb809e3ad610a624a6d7", + "0xc1c96af89a34084bccc5d426d6b90b7261d20475e2cf693fa4b4b23274d43730", + "0x20dead55d3c8b1b471af73c8ce8e057cd9347260c6ee844343951965ce4bb5e5", + "0xcb5fcf36e049de1f1a0a7b504e72ef51aa215f0fff1ce6df3c50b5d7374d3ef1", + "0x44c64d38103a262e536c7d44b00850ee7d39568e0a5e23a07cc0db96d8ca588b", + "0xdf3ce320c0a4e7295bf81a5ad49769fcb25784d66f5d4a39b978501163fda0dd", + "0xcc00c0613629c16c77d0f383611bdc1dd0be75cce1266e5bb757a2c1b5fbf349", + "0xdb7af92f84da228054781ac9bf5783f28ebcc1d9ac26648188455e8b32e0f98e", + "0x032d340086ce77dc295769f1ad6aeac806c72c402c2c761e83547aeafc1eff38", + "0x4915907bc4b6b677a3fb6bb7862f7f63f7ef6bed8cfb0ccebd43a02556656376", + "0x12b52cf58b53505833cfe90f93fce86e29e512b64543a64e345eb11dde029659", + "0x34fa96a697bb35f3a16a0ecd1fbbca12f084d2fe335fc54eb4a7032092c74c33", + "0x3635882429ecf7427e1d090edff7038f6d8b32e69247f4dfcc8bb1aea18e7617", + "0x1b002277f50ea47601c7446146c19b0603fc425d8b3db37aa07e12271713db19", + "0x3e03b0f6491639aad746484d776881edb4b355319b1a7fbf77e1a05d5c0139a2", + "0xa9112e4fed4fa781999648359503a41c94960f39778f7b5c93d4145f77383a37", + "0xa29ec520951c13764482343b57316dfa475353ed15c39f96687d0b197d57798f", + "0xf11a3f76eef7527c0690e7680ed3685028bfce2c2a94de2daf0e4ddc1ebd8d99", + "0xc559f8c0fd3e54e987938ad25d75dcb8171e1ad327ae0fa8dfc896df0defad97", + "0x088177c89f07128f658d1605a229e33d6cea2f974744e08c17a7a400db6cb91d", + "0x0568624426a2ae4a64888e7e5938460834a3ed753a7f3726360d325908d47399", + "0x0b1bb60854d611e75ddd006cf363e547eec8e9b3f43d0bcc4979ab78d09eb6b8", + "0x97a6f155d7dafa8b1117ef2d0236f849f55cddfec075df22bbe28961298faae7", + "0x4d0b4b57e5bc08e4b53bf9c63e5bf33ab519052fb083fb51d10e7be52fc2284e", + "0xe82544ed1c543f9e425b2cec7421b24475dc2b482e7d84590821b2057f6a3147", + "0xc17de2b6bdd5f69e4bf051a92080e8844085dd3cb2d00833c3281b16574438c4", + "0x7abd4dabe991628e239c90bb55cd44fd805f97b6e306e55184f467072e5d08b1", + "0xf81f7640f0a8157fc42f4923d30a5a098624e1d0bfc6c4ee092486e9818042e6", + "0xb44d17ebf635b4c05bbf970f2099e25db03cec15fcbf5e2585b95c3bb2512362", + "0x6574e3baf5c3a80b6ede618c3c5c4db8242490b3f9432dd9cb566a5099f0e2ed", + "0xa290249cf9da6ed767f95dfdebfd34bfc08232a805970f973e56369975f04d6f", + "0x3f4ba83af89dc9793996d9e56b8abe6dc88cd97c9c2bb23027806e9c1ffd54dc", + "0x604d5225191b2563b0794fad331fdc1d5b71f1b55399cc9f8048726d984ecc1b", + "0x37ea92f536655c37b0e15405734272c79fd1ad9d766408b07645615c6783bd54", + "0xbaafa0e3cf07c4147b323108cb3139a059f27cf5650513fcd988ab2f8a5810aa", + ], + ], + }, + initializationData: "0x0000000000000000000000000000000000000000000000000000000000002710", // input for initV2_2_0, representing maxPremintedVoucher (0x2710=10000) +} + +``` \ No newline at end of file From 7c706af3d18f0483ee96203019bb682602884c13 Mon Sep 17 00:00:00 2001 From: Albert Folch Date: Wed, 25 Jan 2023 18:12:39 +0100 Subject: [PATCH 07/47] chore: add forwarder address in deploy suite script --- scripts/deploy-suite.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/deploy-suite.js b/scripts/deploy-suite.js index 6b0e03e48..fd31a3b66 100644 --- a/scripts/deploy-suite.js +++ b/scripts/deploy-suite.js @@ -128,7 +128,9 @@ async function main(env, facetConfig) { // Deploy the Protocol Client implementation/proxy pairs const protocolClientArgs = [protocolDiamond.address]; - const clientImplementationArgs = Object.values(clientConfig).map((config) => config[network]); + const clientImplementationArgs = Object.values(clientConfig).map( + (config) => process.env.FORWARDER_ADDRESS || config[network] + ); const [impls, beacons, proxies] = await deployProtocolClients( protocolClientArgs, maxPriorityFeePerGas, From df22455dc8ae1136979655835cc4be26ee900033 Mon Sep 17 00:00:00 2001 From: Albert Folch Date: Thu, 26 Jan 2023 11:04:36 +0100 Subject: [PATCH 08/47] chore: add forwarder address in .env.example --- .env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 1bc41a511..9262c47a4 100644 --- a/.env.example +++ b/.env.example @@ -45,4 +45,5 @@ ADMIN_ADDRESS_POLYGON=0x0000000000000000000000000000000000000000 # To override default auth token contracts addresses LENS_ADDRESS= -ENS_ADDRESS= \ No newline at end of file +ENS_ADDRESS= +FORWARDER_ADDRESS= \ No newline at end of file From fc9f87812e1a26cbab5dc18e1bd27fd0b42400d1 Mon Sep 17 00:00:00 2001 From: zajck <64400885+zajck@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:17:11 +0100 Subject: [PATCH 09/47] Rename operator to assistant (#556) * operator->assistant * remove console.log * diagram update * Revert assistant back to operator for ERC721/ERC1155 --- contracts/domain/BosonConstants.sol | 8 +- contracts/domain/BosonTypes.sol | 8 +- .../interfaces/clients/IBosonVoucher.sol | 4 +- .../handlers/IBosonAccountHandler.sol | 24 +- .../handlers/IBosonExchangeHandler.sol | 4 +- .../handlers/IBosonGroupHandler.sol | 2 +- .../handlers/IBosonOfferHandler.sol | 12 +- .../handlers/IBosonOrchestrationHandler.sol | 48 +- contracts/mock/MockExchangeHandlerFacet.sol | 24 +- contracts/protocol/bases/BundleBase.sol | 8 +- contracts/protocol/bases/GroupBase.sol | 10 +- contracts/protocol/bases/OfferBase.sol | 8 +- contracts/protocol/bases/ProtocolBase.sol | 20 +- contracts/protocol/bases/SellerBase.sol | 24 +- contracts/protocol/bases/TwinBase.sol | 12 +- .../protocol/clients/voucher/BosonVoucher.sol | 6 +- .../protocol/facets/DisputeHandlerFacet.sol | 14 +- .../facets/DisputeResolverHandlerFacet.sol | 76 +- .../protocol/facets/ExchangeHandlerFacet.sol | 24 +- .../protocol/facets/GroupHandlerFacet.sol | 6 +- .../protocol/facets/OfferHandlerFacet.sol | 16 +- .../facets/OrchestrationHandlerFacet1.sol | 48 +- .../protocol/facets/SellerHandlerFacet.sol | 52 +- .../protocol/facets/TwinHandlerFacet.sol | 4 +- contracts/protocol/libs/ProtocolLib.sol | 8 +- .../Boson_Protocol_V2_-_Domain_Model.png | Bin 526848 -> 529186 bytes docs/sequences.md | 2 +- docs/tasks.md | 6 +- scripts/config/revert-reasons.js | 8 +- scripts/domain/DisputeResolver.js | 26 +- scripts/domain/DisputeResolverUpdateFields.js | 4 +- scripts/domain/Seller.js | 26 +- scripts/domain/SellerUpdateFields.js | 4 +- scripts/util/create-dispute-resolver.js | 6 +- scripts/util/deploy-protocol-client-impls.js | 1 - test/domain/DisputeResolverTest.js | 34 +- test/domain/SellerTest.js | 34 +- test/example/SnapshotGateTest.js | 28 +- .../01-update-account-roles-addresses.js | 94 +-- test/integration/02-Upgraded-facet.js | 72 +- ...DR-removes-the-seller-from-allowed-list.js | 40 +- test/integration/04-DR-removes-fees.js | 32 +- test/protocol/AccountHandlerTest.js | 10 +- test/protocol/BundleHandlerTest.js | 120 +-- test/protocol/BuyerHandlerTest.js | 20 +- test/protocol/DisputeHandlerTest.js | 162 ++-- test/protocol/DisputeResolverHandlerTest.js | 160 ++-- test/protocol/ExchangeHandlerTest.js | 382 +++++----- test/protocol/FundsHandlerTest.js | 250 +++---- test/protocol/GroupHandlerTest.js | 174 ++--- test/protocol/MetaTransactionsHandlerTest.js | 206 +++--- test/protocol/OfferHandlerTest.js | 454 ++++++------ test/protocol/OrchestrationHandlerTest.js | 696 +++++++++--------- test/protocol/SellerHandlerTest.js | 306 ++++---- test/protocol/TwinHandlerTest.js | 206 +++--- test/protocol/clients/BosonVoucherTest.js | 342 ++++----- test/upgrade/2.0.0-2.1.0.js | 40 +- .../clients/BosonVoucher-2.1.0-2.2.0.js | 59 +- test/util/mock.js | 8 +- test/util/upgrade.js | 6 +- 60 files changed, 2265 insertions(+), 2223 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index e30f45e3e..9981c0bcc 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -36,15 +36,15 @@ string constant DIRECT_INITIALIZATION_NOT_ALLOWED = "Direct initializtion is not // Revert Reasons: Access related string constant ACCESS_DENIED = "Access denied, caller doesn't have role"; -string constant NOT_OPERATOR = "Not seller's operator"; +string constant NOT_ASSISTANT = "Not seller's assistant"; string constant NOT_ADMIN = "Not admin"; -string constant NOT_OPERATOR_AND_CLERK = "Not operator and clerk"; -string constant NOT_ADMIN_OPERATOR_AND_CLERK = "Not admin, operator and clerk"; +string constant NOT_ASSISTANT_AND_CLERK = "Not assistant and clerk"; +string constant NOT_ADMIN_ASSISTANT_AND_CLERK = "Not admin, assistant and clerk"; string constant NOT_BUYER_OR_SELLER = "Not buyer or seller"; string constant NOT_VOUCHER_HOLDER = "Not current voucher holder"; string constant NOT_BUYER_WALLET = "Not buyer's wallet address"; string constant NOT_AGENT_WALLET = "Not agent's wallet address"; -string constant NOT_DISPUTE_RESOLVER_OPERATOR = "Not dispute resolver's operator address"; +string constant NOT_DISPUTE_RESOLVER_ASSISTANT = "Not dispute resolver's assistant address"; // Revert Reasons: Account-related string constant NO_SUCH_SELLER = "No such seller"; diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 496af5657..9f85f203c 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -72,14 +72,14 @@ contract BosonTypes { enum SellerUpdateFields { Admin, - Operator, + Assistant, Clerk, AuthToken } enum DisputeResolverUpdateFields { Admin, - Operator, + Assistant, Clerk } @@ -90,7 +90,7 @@ contract BosonTypes { struct Seller { uint256 id; - address operator; + address assistant; address admin; address clerk; address payable treasury; @@ -106,7 +106,7 @@ contract BosonTypes { struct DisputeResolver { uint256 id; uint256 escalationResponsePeriod; - address operator; + address assistant; address admin; address clerk; address payable treasury; diff --git a/contracts/interfaces/clients/IBosonVoucher.sol b/contracts/interfaces/clients/IBosonVoucher.sol index f2faa0fa6..141d4d819 100644 --- a/contracts/interfaces/clients/IBosonVoucher.sol +++ b/contracts/interfaces/clients/IBosonVoucher.sol @@ -154,7 +154,7 @@ interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable { * causing the token range to be reserved, but only pre-minting * a certain amount monthly. * - * Caller must be contract owner (seller operator address). + * Caller must be contract owner (seller assistant address). * * Reverts if: * - Offer id is not associated with a range @@ -180,7 +180,7 @@ interface IBosonVoucher is IERC721Upgradeable, IERC721MetadataUpgradeable { * this method can be called multiple times, until the whole * range is burned. * - * Caller must be contract owner (seller operator address). + * Caller must be contract owner (seller assistant address). * * Reverts if: * - Offer id is not associated with a range diff --git a/contracts/interfaces/handlers/IBosonAccountHandler.sol b/contracts/interfaces/handlers/IBosonAccountHandler.sol index d2c34a2f6..70a013f41 100644 --- a/contracts/interfaces/handlers/IBosonAccountHandler.sol +++ b/contracts/interfaces/handlers/IBosonAccountHandler.sol @@ -19,7 +19,7 @@ interface IBosonAccountHandler is IBosonAccountEvents { * * Reverts if: * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - The sellers region of protocol is paused * - Address values are zero address * - Addresses are not unique to this seller @@ -59,7 +59,7 @@ interface IBosonAccountHandler is IBosonAccountEvents { * Emits a DisputeResolverCreated event if successful. * * Reverts if: - * - Caller is not the supplied admin, operator and clerk + * - Caller is not the supplied admin, assistant and clerk * - The dispute resolvers region of protocol is paused * - Any address is zero address * - Any address is not unique to this dispute resolver @@ -98,13 +98,13 @@ interface IBosonAccountHandler is IBosonAccountEvents { function createAgent(BosonTypes.Agent memory _agent) external; /** - * @notice Updates treasury address, if changed. Puts admin, operator, clerk and AuthToken in pending queue, if changed. + * @notice Updates treasury address, if changed. Puts admin, assistant, clerk and AuthToken in pending queue, if changed. * Pending updates can be completed by calling the optInToSellerUpdate function. * @dev Active flag passed in by caller will be ignored. The value from storage will be used. * * Emits a SellerUpdateApplied event if the seller has changed the treasury. - * Emits a SellerUpdatePending event if the seller has requested an update for admin, clerk, operator, or auth token. - * Holder of new auth token and/or owner(s) of new addresses for admin, clerk, operator must opt-in to the update. + * Emits a SellerUpdatePending event if the seller has requested an update for admin, clerk, assistant, or auth token. + * Holder of new auth token and/or owner(s) of new addresses for admin, clerk, assistant must opt-in to the update. * * Reverts if: * - The sellers region of protocol is paused @@ -160,7 +160,7 @@ interface IBosonAccountHandler is IBosonAccountEvents { function updateBuyer(BosonTypes.Buyer memory _buyer) external; /** - * @notice Updates treasury address, escalationResponsePeriod or metadataUri if changed. Puts admin, operator and clerk in pending queue, if changed. + * @notice Updates treasury address, escalationResponsePeriod or metadataUri if changed. Puts admin, assistant and clerk in pending queue, if changed. * Pending updates can be completed by calling the optInToDisputeResolverUpdate function. * * Update doesn't include DisputeResolverFees, allowed seller list or active flag. @@ -171,8 +171,8 @@ interface IBosonAccountHandler is IBosonAccountEvents { * @dev Active flag passed in by caller will be ignored. The value from storage will be used. * * Emits a DisputeResolverUpdated event if successful. - * Emits a DisputeResolverUpdatePending event if the dispute resolver has requested an update for admin, clerk or operator. - * Owner(s) of new addresses for admin, clerk, operator must opt-in to the update. + * Emits a DisputeResolverUpdatePending event if the dispute resolver has requested an update for admin, clerk or assistant. + * Owner(s) of new addresses for admin, clerk, assistant must opt-in to the update. * * Reverts if: * - The dispute resolvers region of protocol is paused @@ -320,11 +320,11 @@ interface IBosonAccountHandler is IBosonAccountEvents { ); /** - * @notice Gets the details about a seller by an address associated with that seller: operator, admin, or clerk address. + * @notice Gets the details about a seller by an address associated with that seller: assistant, admin, or clerk address. * A seller will have either an admin address or an auth token. * If seller's admin uses NFT Auth the seller should call `getSellerByAuthToken` instead. * - * @param _associatedAddress - the address associated with the seller. Must be an operator, admin, or clerk address. + * @param _associatedAddress - the address associated with the seller. Must be an assistant, admin, or clerk address. * @return exists - the seller was found * @return seller - the seller details. See {BosonTypes.Seller} * @return authToken - optional AuthToken struct that specifies an AuthToken type and tokenId that the seller can use to do admin functions @@ -389,9 +389,9 @@ interface IBosonAccountHandler is IBosonAccountEvents { ); /** - * @notice Gets the details about a dispute resolver by an address associated with that dispute resolver: operator, admin, or clerk address. + * @notice Gets the details about a dispute resolver by an address associated with that dispute resolver: assistant, admin, or clerk address. * - * @param _associatedAddress - the address associated with the dispute resolver. Must be an operator, admin, or clerk address. + * @param _associatedAddress - the address associated with the dispute resolver. Must be an assistant, admin, or clerk address. * @return exists - the dispute resolver was found * @return disputeResolver - the dispute resolver details. See {BosonTypes.DisputeResolver} * @return disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee} diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index 9de12f6c4..2f2863ad7 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -111,7 +111,7 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * * @param _exchangeId - the id of the exchange */ @@ -156,7 +156,7 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * - New date is not later than the current one * * @param _exchangeId - the id of the exchange diff --git a/contracts/interfaces/handlers/IBosonGroupHandler.sol b/contracts/interfaces/handlers/IBosonGroupHandler.sol index e38832dba..9eea9d1d7 100644 --- a/contracts/interfaces/handlers/IBosonGroupHandler.sol +++ b/contracts/interfaces/handlers/IBosonGroupHandler.sol @@ -18,7 +18,7 @@ interface IBosonGroupHandler is IBosonGroupEvents { * Emits a GroupCreated event if successful. * * Reverts if: - * - Caller is not an operator + * - Caller is not an assistant * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group diff --git a/contracts/interfaces/handlers/IBosonOfferHandler.sol b/contracts/interfaces/handlers/IBosonOfferHandler.sol index 93fb35300..014d86010 100644 --- a/contracts/interfaces/handlers/IBosonOfferHandler.sol +++ b/contracts/interfaces/handlers/IBosonOfferHandler.sol @@ -19,7 +19,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * * Reverts if: * - The offers region of protocol is paused - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -63,7 +63,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * - 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 operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -127,7 +127,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * Reverts if: * - The offers region of protocol is paused * - Offer id is invalid - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - Offer has already been voided * * @param _offerId - the id of the offer to void @@ -145,7 +145,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * - The offers region of protocol is paused * - Number of offers exceeds maximum allowed number per batch * - Offer id is invalid - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - Offer has already been voided * * @param _offerIds - list of ids of offers to void @@ -160,7 +160,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * Reverts if: * - The offers region of protocol is paused * - Offer does not exist - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - New valid until date is before existing valid until dates * - Offer has voucherRedeemableUntil set and new valid until date is greater than that * @@ -179,7 +179,7 @@ interface IBosonOfferHandler is IBosonOfferEvents { * - Number of offers exceeds maximum allowed number per batch * - For any of the offers: * - Offer does not exist - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - New valid until date is before existing valid until dates * - Offer has voucherRedeemableUntil set and new valid until date is greater than that * diff --git a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol index 190dda3a3..c57531ff3 100644 --- a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol +++ b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol @@ -67,7 +67,7 @@ interface IBosonOrchestrationHandler is * - The offers region of protocol is paused * - The orchestration region of protocol is paused * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller * - In seller struct: @@ -132,7 +132,7 @@ interface IBosonOrchestrationHandler is * - The offers region of protocol is paused * - The orchestration region of protocol is paused * - The exchanges region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -197,7 +197,7 @@ interface IBosonOrchestrationHandler is * - The groups region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -248,7 +248,7 @@ interface IBosonOrchestrationHandler is * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -299,7 +299,7 @@ interface IBosonOrchestrationHandler is * - The groups region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -317,7 +317,7 @@ interface IBosonOrchestrationHandler is * - Buyer cancel penalty is greater than price * - When adding to the group if: * - Group does not exists - * - Caller is not the operator of the group + * - 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 @@ -353,7 +353,7 @@ interface IBosonOrchestrationHandler is * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -371,7 +371,7 @@ interface IBosonOrchestrationHandler is * - Buyer cancel penalty is greater than price * - When adding to the group if: * - Group does not exists - * - Caller is not the operator of the group + * - 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 @@ -408,7 +408,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -467,7 +467,7 @@ interface IBosonOrchestrationHandler is * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -528,7 +528,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -592,7 +592,7 @@ interface IBosonOrchestrationHandler is * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -663,7 +663,7 @@ interface IBosonOrchestrationHandler is * - The groups region of protocol is paused * - The orchestration region of protocol is paused * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller * - In seller struct: @@ -671,7 +671,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -734,7 +734,7 @@ interface IBosonOrchestrationHandler is * - The groups region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -746,7 +746,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -814,7 +814,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller * - In seller struct: @@ -822,7 +822,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -893,7 +893,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -905,7 +905,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -981,7 +981,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller * - In seller struct: @@ -989,7 +989,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -1065,7 +1065,7 @@ interface IBosonOrchestrationHandler is * - The bundles region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -1077,7 +1077,7 @@ interface IBosonOrchestrationHandler is * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined diff --git a/contracts/mock/MockExchangeHandlerFacet.sol b/contracts/mock/MockExchangeHandlerFacet.sol index d28e23f14..c4074e674 100644 --- a/contracts/mock/MockExchangeHandlerFacet.sol +++ b/contracts/mock/MockExchangeHandlerFacet.sol @@ -127,7 +127,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * * @param _exchangeId - the id of the exchange */ @@ -138,14 +138,14 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { // Get seller id associated with caller bool sellerExists; uint256 sellerId; - (sellerExists, sellerId) = getSellerIdByOperator(msgSender()); + (sellerExists, sellerId) = getSellerIdByAssistant(msgSender()); // Get the offer, which will definitely exist Offer storage offer; (, offer) = fetchOffer(exchange.offerId); - // Only seller's operator may call - require(sellerExists && offer.sellerId == sellerId, NOT_OPERATOR); + // Only seller's assistant may call + require(sellerExists && offer.sellerId == sellerId, NOT_ASSISTANT); // Revoke the voucher revokeVoucherInternal(exchange); @@ -217,7 +217,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * - New date is not later than the current one * * @param _exchangeId - the id of the exchange @@ -238,10 +238,10 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { // Get seller id associated with caller bool sellerExists; uint256 sellerId; - (sellerExists, sellerId) = getSellerIdByOperator(sender); + (sellerExists, sellerId) = getSellerIdByAssistant(sender); - // Only seller's operator may call - require(sellerExists && offer.sellerId == sellerId, NOT_OPERATOR); + // Only seller's assistant may call + require(sellerExists && offer.sellerId == sellerId, NOT_ASSISTANT); // Make sure the proposed date is later than the current one require(_validUntilDate > voucher.validUntilDate, VOUCHER_EXTENSION_NOT_VALID); @@ -410,7 +410,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { // Get the twin (, Twin storage twin) = fetchTwin(twinIds[i]); - // Transfer the token from the seller's operator to the buyer + // Transfer the token from the seller's assistant to the buyer // N.B. Using call here so as to normalize the revert reason bytes memory result; bool success; @@ -430,7 +430,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "transferFrom(address,address,uint256)", - seller.operator, + seller.assistant, sender, twin.amount ) @@ -447,7 +447,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256,bytes)", - seller.operator, + seller.assistant, sender, tokenId, "" @@ -458,7 +458,7 @@ contract MockExchangeHandlerFacet is BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256,uint256,bytes)", - seller.operator, + seller.assistant, sender, tokenId, twin.amount, diff --git a/contracts/protocol/bases/BundleBase.sol b/contracts/protocol/bases/BundleBase.sol index 77fbd2bb4..df93eb0f3 100644 --- a/contracts/protocol/bases/BundleBase.sol +++ b/contracts/protocol/bases/BundleBase.sol @@ -44,8 +44,8 @@ contract BundleBase is ProtocolBase, IBosonBundleEvents { address sender = msgSender(); // get seller id, make sure it exists and store it to incoming struct - (bool exists, uint256 sellerId) = getSellerIdByOperator(sender); - require(exists, NOT_OPERATOR); + (bool exists, uint256 sellerId) = getSellerIdByAssistant(sender); + require(exists, NOT_ASSISTANT); // validate that offer ids and twin ids are not empty require( @@ -125,10 +125,10 @@ contract BundleBase is ProtocolBase, IBosonBundleEvents { require(exists, NO_SUCH_TWIN); // Get seller id, we assume seller id exists if twin exists - (, uint256 sellerId) = getSellerIdByOperator(msgSender()); + (, uint256 sellerId) = getSellerIdByAssistant(msgSender()); // Caller's seller id must match twin seller id - require(sellerId == twin.sellerId, NOT_OPERATOR); + require(sellerId == twin.sellerId, NOT_ASSISTANT); } /** diff --git a/contracts/protocol/bases/GroupBase.sol b/contracts/protocol/bases/GroupBase.sol index e4713cb12..bea95d6ae 100644 --- a/contracts/protocol/bases/GroupBase.sol +++ b/contracts/protocol/bases/GroupBase.sol @@ -18,7 +18,7 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { * Emits a GroupCreated event if successful. * * Reverts if: - * - Caller is not an operator + * - Caller is not an assistant * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group @@ -35,8 +35,8 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { address sender = msgSender(); // get seller id, make sure it exists and store it to incoming struct - (bool exists, uint256 sellerId) = getSellerIdByOperator(sender); - require(exists, NOT_OPERATOR); + (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); @@ -206,9 +206,9 @@ contract GroupBase is ProtocolBase, IBosonGroupEvents { require(exists, NO_SUCH_GROUP); // Get seller id, we assume seller id exists if group exists - (, sellerId) = getSellerIdByOperator(msgSender()); + (, sellerId) = getSellerIdByAssistant(msgSender()); // Caller's seller id must match group seller id - require(sellerId == group.sellerId, NOT_OPERATOR); + require(sellerId == group.sellerId, NOT_ASSISTANT); } } diff --git a/contracts/protocol/bases/OfferBase.sol b/contracts/protocol/bases/OfferBase.sol index 873fab905..24f401339 100644 --- a/contracts/protocol/bases/OfferBase.sol +++ b/contracts/protocol/bases/OfferBase.sol @@ -19,7 +19,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { * Emits an OfferCreated event if successful. * * Reverts if: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -53,8 +53,8 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { uint256 _agentId ) internal { // get seller id, make sure it exists and store it to incoming struct - (bool exists, uint256 sellerId) = getSellerIdByOperator(msgSender()); - require(exists, NOT_OPERATOR); + (bool exists, uint256 sellerId) = getSellerIdByAssistant(msgSender()); + require(exists, NOT_ASSISTANT); _offer.sellerId = sellerId; // Get the next offerId and increment the counter uint256 offerId = protocolCounters().nextOfferId++; @@ -293,7 +293,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { * @param _length - the length of the range */ function reserveRangeInternal(uint256 _offerId, uint256 _length) internal offersNotPaused exchangesNotPaused { - // Get offer, make sure the caller is the operator + // Get offer, make sure the caller is the assistant Offer storage offer = getValidOffer(_offerId); // Prevent reservation of an empty range diff --git a/contracts/protocol/bases/ProtocolBase.sol b/contracts/protocol/bases/ProtocolBase.sol index 5f007d8bb..06ae0f32b 100644 --- a/contracts/protocol/bases/ProtocolBase.sol +++ b/contracts/protocol/bases/ProtocolBase.sol @@ -113,15 +113,15 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase { } /** - * @notice Gets a seller id from storage by operator address + * @notice Gets a seller id from storage by assistant address * - * @param _operator - the operator address of the seller + * @param _assistant - the assistant address of the seller * @return exists - whether the seller id exists * @return sellerId - the seller id */ - function getSellerIdByOperator(address _operator) internal view returns (bool exists, uint256 sellerId) { + function getSellerIdByAssistant(address _assistant) internal view returns (bool exists, uint256 sellerId) { // Get the seller id - sellerId = protocolLookups().sellerIdByOperator[_operator]; + sellerId = protocolLookups().sellerIdByAssistant[_assistant]; // Determine existence exists = (sellerId > 0); @@ -207,19 +207,19 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase { } /** - * @notice Gets a dispute resolver id from storage by operator address + * @notice Gets a dispute resolver id from storage by assistant address * - * @param _operator - the operator address of the dispute resolver + * @param _assistant - the assistant address of the dispute resolver * @return exists - whether the dispute resolver id exists * @return disputeResolverId - the dispute resolver id */ - function getDisputeResolverIdByOperator(address _operator) + function getDisputeResolverIdByAssistant(address _assistant) internal view returns (bool exists, uint256 disputeResolverId) { // Get the dispute resolver id - disputeResolverId = protocolLookups().disputeResolverIdByOperator[_operator]; + disputeResolverId = protocolLookups().disputeResolverIdByAssistant[_assistant]; // Determine existence exists = (disputeResolverId > 0); @@ -561,8 +561,8 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase { // Get seller, we assume seller exists if offer exists (, seller, ) = fetchSeller(offer.sellerId); - // Caller must be seller's operator address - require(seller.operator == msgSender(), NOT_OPERATOR); + // Caller must be seller's assistant address + require(seller.assistant == msgSender(), NOT_ASSISTANT); } /** diff --git a/contracts/protocol/bases/SellerBase.sol b/contracts/protocol/bases/SellerBase.sol index ee58959b4..15f3cff2a 100644 --- a/contracts/protocol/bases/SellerBase.sol +++ b/contracts/protocol/bases/SellerBase.sol @@ -21,7 +21,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { * Emits a SellerCreated event if successful. * * Reverts if: - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - The sellers region of protocol is paused * - Address values are zero address * - Addresses are not unique to this seller @@ -46,7 +46,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Check for zero address require( - _seller.operator != address(0) && _seller.clerk != address(0) && _seller.treasury != address(0), + _seller.assistant != address(0) && _seller.clerk != address(0) && _seller.treasury != address(0), INVALID_ADDRESS ); @@ -63,8 +63,8 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Get message sender address sender = msgSender(); - // Check that caller is the supplied operator and clerk - require(_seller.operator == sender && _seller.clerk == sender, NOT_OPERATOR_AND_CLERK); + // Check that caller is the supplied assistant and clerk + require(_seller.assistant == sender && _seller.clerk == sender, NOT_ASSISTANT_AND_CLERK); // Do caller and uniqueness checks based on auth type if (_authToken.tokenType != AuthTokenType.None) { @@ -88,7 +88,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Check that the sender address is unique to one seller id, across all roles require( lookups.sellerIdByAdmin[sender] == 0 && - lookups.sellerIdByOperator[sender] == 0 && + lookups.sellerIdByAssistant[sender] == 0 && lookups.sellerIdByClerk[sender] == 0, SELLER_ADDRESS_MUST_BE_UNIQUE ); @@ -99,7 +99,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { storeSeller(_seller, _authToken, lookups); // Create clone and store its address cloneAddress - address voucherCloneAddress = cloneBosonVoucher(sellerId, _seller.operator, _voucherInitValues); + address voucherCloneAddress = cloneBosonVoucher(sellerId, _seller.assistant, _voucherInitValues); lookups.cloneAddress[sellerId] = voucherCloneAddress; // Notify watchers of state change @@ -123,7 +123,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Set seller props individually since memory structs can't be copied to storage seller.id = _seller.id; - seller.operator = _seller.operator; + seller.assistant = _seller.assistant; seller.admin = _seller.admin; seller.clerk = _seller.clerk; seller.treasury = _seller.treasury; @@ -144,7 +144,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { } // Map the seller's other addresses to the seller id. It's not necessary to map the treasury address, as it only receives funds - _lookups.sellerIdByOperator[_seller.operator] = _seller.id; + _lookups.sellerIdByAssistant[_seller.assistant] = _seller.id; _lookups.sellerIdByClerk[_seller.clerk] = _seller.id; } @@ -152,13 +152,13 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { * @notice Creates a minimal clone of the Boson Voucher Contract. * * @param _sellerId - id of the seller - * @param _operator - address of the operator + * @param _assistant - address of the assistant * @param _voucherInitValues - the fully populated BosonTypes.VoucherInitValues struct * @return cloneAddress - the address of newly created clone */ function cloneBosonVoucher( uint256 _sellerId, - address _operator, + address _assistant, VoucherInitValues calldata _voucherInitValues ) internal returns (address cloneAddress) { // Pointer to stored addresses @@ -178,7 +178,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Initialize the clone IInitializableVoucherClone(cloneAddress).initialize(pa.voucherBeacon); - IInitializableVoucherClone(cloneAddress).initializeVoucher(_sellerId, _operator, _voucherInitValues); + IInitializableVoucherClone(cloneAddress).initializeVoucher(_sellerId, _assistant, _voucherInitValues); } /** @@ -210,7 +210,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents { // Determine existence exists = sellerPendingUpdate.admin != address(0) || - sellerPendingUpdate.operator != address(0) || + sellerPendingUpdate.assistant != address(0) || sellerPendingUpdate.clerk != address(0) || authTokenPendingUpdate.tokenType != AuthTokenType.None; } diff --git a/contracts/protocol/bases/TwinBase.sol b/contracts/protocol/bases/TwinBase.sol index b94380ddb..f42b229b5 100644 --- a/contracts/protocol/bases/TwinBase.sol +++ b/contracts/protocol/bases/TwinBase.sol @@ -41,8 +41,8 @@ contract TwinBase is ProtocolBase, IBosonTwinEvents { address sender = msgSender(); // get seller id, make sure it exists and store it to incoming struct - (bool exists, uint256 sellerId) = getSellerIdByOperator(sender); - require(exists, NOT_OPERATOR); + (bool exists, uint256 sellerId) = getSellerIdByAssistant(sender); + require(exists, NOT_ASSISTANT); // Protocol must be approved to transfer seller’s tokens require(isProtocolApproved(_twin.tokenAddress, sender, address(this)), NO_TRANSFER_APPROVED); @@ -161,23 +161,23 @@ contract TwinBase is ProtocolBase, IBosonTwinEvents { * @notice Checks if protocol is approved to transfer the tokens. * * @param _tokenAddress - the address of the seller's twin token contract - * @param _operator - the seller's operator address + * @param _assistant - the seller's assistant address * @param _protocol - the protocol address * @return _approved - the approve status */ function isProtocolApproved( address _tokenAddress, - address _operator, + address _assistant, address _protocol ) internal view returns (bool _approved) { require(_tokenAddress != address(0), UNSUPPORTED_TOKEN); - try ITwinToken(_tokenAddress).allowance(_operator, _protocol) returns (uint256 _allowance) { + try ITwinToken(_tokenAddress).allowance(_assistant, _protocol) returns (uint256 _allowance) { if (_allowance > 0) { _approved = true; } } catch { - try ITwinToken(_tokenAddress).isApprovedForAll(_operator, _protocol) returns (bool _isApproved) { + try ITwinToken(_tokenAddress).isApprovedForAll(_assistant, _protocol) returns (bool _isApproved) { _approved = _isApproved; } catch { revert(UNSUPPORTED_TOKEN); diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 7a4b559c3..80fddf899 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -204,7 +204,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * causing the token range to be reserved, but only pre-minting * a certain amount monthly. * - * Caller must be contract owner (seller operator address). + * Caller must be contract owner (seller assistant address). * * Reverts if: * - Offer id is not associated with a range @@ -269,7 +269,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * this method can be called multiple times, until the whole * range is burned. * - * Caller must be contract owner (seller operator address). + * Caller must be contract owner (seller assistant address). * * Reverts if: * - Offer id is not associated with a range @@ -360,7 +360,7 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * @dev Returns the owner of the specified token. * * If the token IS a pre-mint, then the actual owner address hasn't been set, - * but will be reported as the owner of this contract (the seller operator). + * but will be reported as the owner of this contract (the seller assistant). * * If the token IS NOT a pre-mint, then the actual owner will be reported. * diff --git a/contracts/protocol/facets/DisputeHandlerFacet.sol b/contracts/protocol/facets/DisputeHandlerFacet.sol index f9e022c9a..8abd3af41 100644 --- a/contracts/protocol/facets/DisputeHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeHandlerFacet.sol @@ -114,7 +114,7 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { disputesNotPaused nonReentrant { - // Verify that the caller is the seller. Get exchange -> get offer id -> get seller id -> get operator address and compare to msg.sender + // Verify that the caller is the seller. Get exchange -> get offer id -> get seller id -> get assistant address and compare to msg.sender // Get the exchange, should be in disputed state (Exchange storage exchange, ) = getValidExchange(_exchangeId, ExchangeState.Disputed); @@ -127,8 +127,8 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { // get message sender address sender = msgSender(); - // Caller must be seller's operator address - require(seller.operator == sender, NOT_OPERATOR); + // Caller must be seller's assistant address + require(seller.assistant == sender, NOT_ASSISTANT); // Fetch the dispute, it exists if exchange is in Disputed state (, Dispute storage dispute, DisputeDates storage disputeDates) = fetchDispute(_exchangeId); @@ -260,7 +260,7 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { (, Offer storage offer) = fetchOffer(exchange.offerId); // get seller id to check if caller is the seller - (bool exists, uint256 sellerId) = getSellerIdByOperator(msgSender()); + (bool exists, uint256 sellerId) = getSellerIdByAssistant(msgSender()); // variable to store who the expected signer is address expectedSigner; @@ -279,7 +279,7 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { // caller is the buyer // get the seller's address, which should be the signer of the resolution (, Seller storage seller, ) = fetchSeller(offer.sellerId); - expectedSigner = seller.operator; + expectedSigner = seller.assistant; } // verify that the signature belongs to the expectedSigner @@ -567,10 +567,10 @@ contract DisputeHandlerFacet is DisputeBase, IBosonDisputeHandler { (, Offer storage offer) = fetchOffer(exchange.offerId); // get dispute resolver id to check if caller is the dispute resolver - uint256 disputeResolverId = protocolLookups().disputeResolverIdByOperator[msgSender()]; + uint256 disputeResolverId = protocolLookups().disputeResolverIdByAssistant[msgSender()]; require( disputeResolverId == fetchDisputeResolutionTerms(offer.id).disputeResolverId, - NOT_DISPUTE_RESOLVER_OPERATOR + NOT_DISPUTE_RESOLVER_ASSISTANT ); } diff --git a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol index 839ae2fbf..90229c3b4 100644 --- a/contracts/protocol/facets/DisputeResolverHandlerFacet.sol +++ b/contracts/protocol/facets/DisputeResolverHandlerFacet.sol @@ -28,7 +28,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * Emits a DisputeResolverCreated event if successful. * * Reverts if: - * - Caller is not the supplied admin, operator and clerk + * - Caller is not the supplied admin, assistant and clerk * - The dispute resolvers region of protocol is paused * - Any address is zero address * - Any address is not unique to this dispute resolver @@ -55,7 +55,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Check for zero address require( _disputeResolver.admin != address(0) && - _disputeResolver.operator != address(0) && + _disputeResolver.assistant != address(0) && _disputeResolver.clerk != address(0) && _disputeResolver.treasury != address(0), INVALID_ADDRESS @@ -69,12 +69,12 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Get message sender address sender = msgSender(); - // Check that caller is the supplied operator and clerk + // Check that caller is the supplied assistant and clerk require( _disputeResolver.admin == sender && - _disputeResolver.operator == sender && + _disputeResolver.assistant == sender && _disputeResolver.clerk == sender, - NOT_ADMIN_OPERATOR_AND_CLERK + NOT_ADMIN_ASSISTANT_AND_CLERK ); } @@ -82,18 +82,18 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { uint256 disputeResolverId = protocolCounters().nextAccountId++; // Check that the addresses are unique to one dispute resolver id, across all rolls - mapping(address => uint256) storage disputeResolverIdByOperator = lookups.disputeResolverIdByOperator; + mapping(address => uint256) storage disputeResolverIdByAssistant = lookups.disputeResolverIdByAssistant; mapping(address => uint256) storage disputeResolverIdByAdmin = lookups.disputeResolverIdByAdmin; mapping(address => uint256) storage disputeResolverIdByClerk = lookups.disputeResolverIdByClerk; require( - disputeResolverIdByOperator[_disputeResolver.operator] == 0 && - disputeResolverIdByOperator[_disputeResolver.admin] == 0 && - disputeResolverIdByOperator[_disputeResolver.clerk] == 0 && + disputeResolverIdByAssistant[_disputeResolver.assistant] == 0 && + disputeResolverIdByAssistant[_disputeResolver.admin] == 0 && + disputeResolverIdByAssistant[_disputeResolver.clerk] == 0 && disputeResolverIdByAdmin[_disputeResolver.admin] == 0 && - disputeResolverIdByAdmin[_disputeResolver.operator] == 0 && + disputeResolverIdByAdmin[_disputeResolver.assistant] == 0 && disputeResolverIdByAdmin[_disputeResolver.clerk] == 0 && disputeResolverIdByClerk[_disputeResolver.clerk] == 0 && - disputeResolverIdByClerk[_disputeResolver.operator] == 0 && + disputeResolverIdByClerk[_disputeResolver.assistant] == 0 && disputeResolverIdByClerk[_disputeResolver.admin] == 0, DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE ); @@ -159,7 +159,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { } /** - * @notice Updates treasury address, escalationResponsePeriod or metadataUri if changed. Puts admin, operator and clerk in pending queue, if changed. + * @notice Updates treasury address, escalationResponsePeriod or metadataUri if changed. Puts admin, assistant and clerk in pending queue, if changed. * Pending updates can be completed by calling the optInToDisputeResolverUpdate function. * * Update doesn't include DisputeResolverFees, allowed seller list or active flag. @@ -170,8 +170,8 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * @dev Active flag passed in by caller will be ignored. The value from storage will be used. * * Emits a DisputeResolverUpdated event if successful. - * Emits a DisputeResolverUpdatePending event if the dispute resolver has requested an update for admin, clerk or operator. - * Owner(s) of new addresses for admin, clerk, operator must opt-in to the update. + * Emits a DisputeResolverUpdatePending event if the dispute resolver has requested an update for admin, clerk or assistant. + * Owner(s) of new addresses for admin, clerk, assistant must opt-in to the update. * * Reverts if: * - The dispute resolvers region of protocol is paused @@ -194,7 +194,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Check for zero address require( _disputeResolver.admin != address(0) && - _disputeResolver.operator != address(0) && + _disputeResolver.assistant != address(0) && _disputeResolver.clerk != address(0) && _disputeResolver.treasury != address(0), INVALID_ADDRESS @@ -231,11 +231,11 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { needsApproval = true; } - if (_disputeResolver.operator != disputeResolver.operator) { - preUpdateDisputeResolverCheck(_disputeResolver.id, _disputeResolver.operator, lookups); + if (_disputeResolver.assistant != disputeResolver.assistant) { + preUpdateDisputeResolverCheck(_disputeResolver.id, _disputeResolver.assistant, lookups); - // If operator address exists, operator address owner must approve the update to prevent front-running - disputeResolverPendingUpdate.operator = _disputeResolver.operator; + // If assistant address exists, assistant address owner must approve the update to prevent front-running + disputeResolverPendingUpdate.assistant = _disputeResolver.assistant; needsApproval = true; } @@ -350,24 +350,24 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { updateApplied = true; } else if ( - role == DisputeResolverUpdateFields.Operator && disputeResolverPendingUpdate.operator != address(0) + role == DisputeResolverUpdateFields.Assistant && disputeResolverPendingUpdate.assistant != address(0) ) { - // Approve operator update - require(disputeResolverPendingUpdate.operator == sender, UNAUTHORIZED_CALLER_UPDATE); + // Approve assistant update + require(disputeResolverPendingUpdate.assistant == sender, UNAUTHORIZED_CALLER_UPDATE); preUpdateDisputeResolverCheck(_disputeResolverId, sender, lookups); - // Delete old disputeResolver id by operator mapping - delete lookups.disputeResolverIdByOperator[disputeResolver.operator]; + // Delete old disputeResolver id by assistant mapping + delete lookups.disputeResolverIdByAssistant[disputeResolver.assistant]; - // Update operator - disputeResolver.operator = sender; + // Update assistant + disputeResolver.assistant = sender; - // Store new disputeResolver id by operator mapping - lookups.disputeResolverIdByOperator[sender] = _disputeResolverId; + // Store new disputeResolver id by assistant mapping + lookups.disputeResolverIdByAssistant[sender] = _disputeResolverId; - // Delete pending update operator - delete disputeResolverPendingUpdate.operator; + // Delete pending update assistant + delete disputeResolverPendingUpdate.assistant; updateApplied = true; } else if (role == DisputeResolverUpdateFields.Clerk && disputeResolverPendingUpdate.clerk != address(0)) { @@ -687,9 +687,9 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { } /** - * @notice Gets the details about a dispute resolver by an address associated with that dispute resolver: operator, admin, or clerk address. + * @notice Gets the details about a dispute resolver by an address associated with that dispute resolver: assistant, admin, or clerk address. * - * @param _associatedAddress - the address associated with the dispute resolver. Must be an operator, admin, or clerk address. + * @param _associatedAddress - the address associated with the dispute resolver. Must be an assistant, admin, or clerk address. * @return exists - the dispute resolver was found * @return disputeResolver - the dispute resolver details. See {BosonTypes.DisputeResolver} * @return disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee} @@ -707,7 +707,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { { uint256 disputeResolverId; - (exists, disputeResolverId) = getDisputeResolverIdByOperator(_associatedAddress); + (exists, disputeResolverId) = getDisputeResolverIdByAssistant(_associatedAddress); if (exists) { return getDisputeResolver(disputeResolverId); @@ -776,7 +776,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Set dispute resolver props individually since memory structs can't be copied to storage disputeResolver.id = _disputeResolver.id; disputeResolver.escalationResponsePeriod = _disputeResolver.escalationResponsePeriod; - disputeResolver.operator = _disputeResolver.operator; + disputeResolver.assistant = _disputeResolver.assistant; disputeResolver.admin = _disputeResolver.admin; disputeResolver.clerk = _disputeResolver.clerk; disputeResolver.treasury = _disputeResolver.treasury; @@ -784,7 +784,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { disputeResolver.active = _disputeResolver.active; // Map the dispute resolver's addresses to the dispute resolver id. - lookups.disputeResolverIdByOperator[_disputeResolver.operator] = _disputeResolver.id; + lookups.disputeResolverIdByAssistant[_disputeResolver.assistant] = _disputeResolver.id; lookups.disputeResolverIdByAdmin[_disputeResolver.admin] = _disputeResolver.id; lookups.disputeResolverIdByClerk[_disputeResolver.clerk] = _disputeResolver.id; } @@ -827,7 +827,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { * @notice Pre update dispute resolver checks * * Reverts if: - * - Address has already been used by another dispute resolver as operator, admin, or clerk + * - Address has already been used by another dispute resolver as assistant, admin, or clerk * * @param _disputeResolverId - the id of the disputeResolver to check * @param _role - the address to check @@ -839,7 +839,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { ProtocolLib.ProtocolLookups storage _lookups ) internal view { // Check that the role is unique to one dispute resolver id across all roles -- not used or is used by this dispute resolver id. - uint256 check1 = _lookups.disputeResolverIdByOperator[_role]; + uint256 check1 = _lookups.disputeResolverIdByAssistant[_role]; uint256 check2 = _lookups.disputeResolverIdByClerk[_role]; uint256 check3 = _lookups.disputeResolverIdByAdmin[_role]; @@ -872,7 +872,7 @@ contract DisputeResolverHandlerFacet is IBosonAccountEvents, ProtocolBase { // Determine existence exists = disputeResolverPendingUpdate.admin != address(0) || - disputeResolverPendingUpdate.operator != address(0) || + disputeResolverPendingUpdate.assistant != address(0) || disputeResolverPendingUpdate.clerk != address(0); } } diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 536d04670..5885ec6cc 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -593,7 +593,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * * @param _exchangeId - the id of the exchange */ @@ -604,14 +604,14 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get seller id associated with caller bool sellerExists; uint256 sellerId; - (sellerExists, sellerId) = getSellerIdByOperator(msgSender()); + (sellerExists, sellerId) = getSellerIdByAssistant(msgSender()); // Get the offer, which will definitely exist Offer storage offer; (, offer) = fetchOffer(exchange.offerId); - // Only seller's operator may call - require(sellerExists && offer.sellerId == sellerId, NOT_OPERATOR); + // Only seller's assistant may call + require(sellerExists && offer.sellerId == sellerId, NOT_ASSISTANT); // Revoke the voucher revokeVoucherInternal(exchange); @@ -683,7 +683,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * - The exchanges region of protocol is paused * - Exchange does not exist * - Exchange is not in Committed state - * - Caller is not seller's operator + * - Caller is not seller's assistant * - New date is not later than the current one * * @param _exchangeId - the id of the exchange @@ -704,10 +704,10 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get seller id associated with caller bool sellerExists; uint256 sellerId; - (sellerExists, sellerId) = getSellerIdByOperator(sender); + (sellerExists, sellerId) = getSellerIdByAssistant(sender); - // Only seller's operator may call - require(sellerExists && offer.sellerId == sellerId, NOT_OPERATOR); + // Only seller's assistant may call + require(sellerExists && offer.sellerId == sellerId, NOT_ASSISTANT); // Make sure the proposed date is later than the current one require(_validUntilDate > voucher.validUntilDate, VOUCHER_EXTENSION_NOT_VALID); @@ -1014,7 +1014,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get the twin (, Twin storage twin) = fetchTwin(twinIds[i]); - // Transfer the token from the seller's operator to the buyer + // Transfer the token from the seller's assistant to the buyer // N.B. Using call here so as to normalize the revert reason bytes memory result; bool success; @@ -1034,7 +1034,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "transferFrom(address,address,uint256)", - seller.operator, + seller.assistant, sender, twin.amount ) @@ -1051,7 +1051,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256,bytes)", - seller.operator, + seller.assistant, sender, tokenId, "" @@ -1062,7 +1062,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { (success, result) = twin.tokenAddress.call( abi.encodeWithSignature( "safeTransferFrom(address,address,uint256,uint256,bytes)", - seller.operator, + seller.assistant, sender, tokenId, twin.amount, diff --git a/contracts/protocol/facets/GroupHandlerFacet.sol b/contracts/protocol/facets/GroupHandlerFacet.sol index 53f46d037..481b35669 100644 --- a/contracts/protocol/facets/GroupHandlerFacet.sol +++ b/contracts/protocol/facets/GroupHandlerFacet.sol @@ -27,7 +27,7 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { * Emits a GroupCreated event if successful. * * Reverts if: - * - Caller is not an operator + * - Caller is not an assistant * - Any of offers belongs to different seller * - Any of offers does not exist * - Offer exists in a different group @@ -172,10 +172,10 @@ contract GroupHandlerFacet is IBosonGroupHandler, GroupBase { address sender = msgSender(); // Get seller id, we assume seller id exists if offer exists - (, uint256 sellerId) = getSellerIdByOperator(sender); + (, uint256 sellerId) = getSellerIdByAssistant(sender); // Caller's seller id must match group seller id - require(sellerId == group.sellerId, NOT_OPERATOR); + require(sellerId == group.sellerId, NOT_ASSISTANT); // Store new condition storeCondition(_groupId, _condition); diff --git a/contracts/protocol/facets/OfferHandlerFacet.sol b/contracts/protocol/facets/OfferHandlerFacet.sol index bd6a8963a..61e687bb9 100644 --- a/contracts/protocol/facets/OfferHandlerFacet.sol +++ b/contracts/protocol/facets/OfferHandlerFacet.sol @@ -27,7 +27,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * * Reverts if: * - The offers region of protocol is paused - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -73,7 +73,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * - 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 operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -155,13 +155,13 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * Reverts if: * - The offers region of protocol is paused * - Offer id is invalid - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - Offer has already been voided * * @param _offerId - the id of the offer to void */ function voidOffer(uint256 _offerId) public override offersNotPaused nonReentrant { - // Get offer, make sure the caller is the operator + // Get offer, make sure the caller is the assistant Offer storage offer = getValidOffer(_offerId); // Void the offer @@ -182,7 +182,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * - The offers region of protocol is paused * - Number of offers exceeds maximum allowed number per batch * - Offer id is invalid - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - Offer has already been voided * * @param _offerIds - list of ids of offers to void @@ -203,7 +203,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * Reverts if: * - The offers region of protocol is paused * - Offer does not exist - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - New valid until date is before existing valid until dates * - Offer has voucherRedeemableUntil set and new valid until date is greater than that * @@ -211,7 +211,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * @param _validUntilDate - new valid until date */ function extendOffer(uint256 _offerId, uint256 _validUntilDate) public override offersNotPaused nonReentrant { - // Make sure the caller is the operator, offer exists and is not voided + // Make sure the caller is the assistant, offer exists and is not voided Offer storage offer = getValidOffer(_offerId); // Fetch the offer dates @@ -242,7 +242,7 @@ contract OfferHandlerFacet is IBosonOfferHandler, OfferBase { * - Number of offers exceeds maximum allowed number per batch * - For any of the offers: * - Offer does not exist - * - Caller is not the operator of the offer + * - Caller is not the assistant of the offer * - New valid until date is before existing valid until dates * - Offer has voucherRedeemableUntil set and new valid until date is greater than that * diff --git a/contracts/protocol/facets/OrchestrationHandlerFacet1.sol b/contracts/protocol/facets/OrchestrationHandlerFacet1.sol index da83dc035..d6db1f302 100644 --- a/contracts/protocol/facets/OrchestrationHandlerFacet1.sol +++ b/contracts/protocol/facets/OrchestrationHandlerFacet1.sol @@ -43,7 +43,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The sellers region of protocol is paused * - The offers region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -112,7 +112,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The offers region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -189,7 +189,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The groups region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -254,7 +254,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -308,7 +308,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The groups region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -326,7 +326,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Buyer cancel penalty is greater than price * - When adding to the group if: * - Group does not exists - * - Caller is not the operator of the group + * - 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 @@ -370,7 +370,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -388,7 +388,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Buyer cancel penalty is greater than price * - When adding to the group if: * - Group does not exists - * - Caller is not the operator of the group + * - 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 @@ -428,7 +428,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -493,7 +493,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -557,7 +557,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The bundles region of protocol is paused * - The orchestration region of protocol is paused * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -628,7 +628,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Reserved range length is greater than quantity available * - Reserved range length is greater than maximum allowed range length * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -709,7 +709,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The offers region of protocol is paused * - The groups region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -718,7 +718,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -786,7 +786,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The groups region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -798,7 +798,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -878,7 +878,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The twins region of protocol is paused * - The bundles region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -887,7 +887,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -963,7 +963,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The bundles region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -975,7 +975,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -1063,7 +1063,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The twins region of protocol is paused * - The bundles region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -1072,7 +1072,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined @@ -1161,7 +1161,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - The bundles region of protocol is paused * - The exchanges region of protocol is paused * - The orchestration region of protocol is paused - * - Caller is not the supplied operator and clerk + * - Caller is not the supplied assistant and clerk * - Caller is not the supplied admin or does not own supplied auth token * - Admin address is zero address and AuthTokenType == None * - AuthTokenType is not unique to this seller @@ -1173,7 +1173,7 @@ contract OrchestrationHandlerFacet1 is PausableBase, SellerBase, OfferBase, Grou * - Addresses are not unique to this seller * - Seller is not active (if active == false) * - In offer struct: - * - Caller is not an operator + * - Caller is not an assistant * - Valid from date is greater than valid until date * - Valid until date is not in the future * - Both voucher expiration date and voucher expiration period are defined diff --git a/contracts/protocol/facets/SellerHandlerFacet.sol b/contracts/protocol/facets/SellerHandlerFacet.sol index 7fe5f8df1..9a9400899 100644 --- a/contracts/protocol/facets/SellerHandlerFacet.sol +++ b/contracts/protocol/facets/SellerHandlerFacet.sol @@ -29,7 +29,7 @@ contract SellerHandlerFacet is SellerBase { * * Reverts if: * - Caller is not the supplied admin or does not own supplied auth token - * - Caller is not the supplied operator and clerk revert reason + * - Caller is not the supplied assistant and clerk revert reason * - The sellers region of protocol is paused * - Address values are zero address * - Addresses are not unique to this seller @@ -52,13 +52,13 @@ contract SellerHandlerFacet is SellerBase { } /** - * @notice Updates treasury address, if changed. Puts admin, operator, clerk and AuthToken in pending queue, if changed. + * @notice Updates treasury address, if changed. Puts admin, assistant, clerk and AuthToken in pending queue, if changed. * Pending updates can be completed by calling the optInToSellerUpdate function. * @dev Active flag passed in by caller will be ignored. The value from storage will be used. * * Emits a SellerUpdateApplied event if the seller has changed the treasury. - * Emits a SellerUpdatePending event if the seller has requested an update for admin, clerk, operator, or auth token. - * Holder of new auth token and/or owner(s) of new addresses for admin, clerk, operator must opt-in to the update. + * Emits a SellerUpdatePending event if the seller has requested an update for admin, clerk, assistant, or auth token. + * Holder of new auth token and/or owner(s) of new addresses for admin, clerk, assistant must opt-in to the update. * * Reverts if: * - The sellers region of protocol is paused @@ -137,11 +137,11 @@ contract SellerHandlerFacet is SellerBase { needsApproval = true; } - if (_seller.operator != seller.operator) { - preUpdateSellerCheck(_seller.id, _seller.operator, lookups); - require(_seller.operator != address(0), INVALID_ADDRESS); - // Operator address owner must approve the update to prevent front-running - sellerPendingUpdate.operator = _seller.operator; + if (_seller.assistant != seller.assistant) { + preUpdateSellerCheck(_seller.id, _seller.assistant, lookups); + require(_seller.assistant != address(0), INVALID_ADDRESS); + // Assistant address owner must approve the update to prevent front-running + sellerPendingUpdate.assistant = _seller.assistant; needsApproval = true; } @@ -241,26 +241,26 @@ contract SellerHandlerFacet is SellerBase { delete protocolEntities().authTokens[_sellerId]; updateApplied = true; - } else if (role == SellerUpdateFields.Operator && sellerPendingUpdate.operator != address(0)) { - // Approve operator update - require(sellerPendingUpdate.operator == sender, UNAUTHORIZED_CALLER_UPDATE); + } else if (role == SellerUpdateFields.Assistant && sellerPendingUpdate.assistant != address(0)) { + // Approve assistant update + require(sellerPendingUpdate.assistant == sender, UNAUTHORIZED_CALLER_UPDATE); preUpdateSellerCheck(_sellerId, sender, lookups); - // Delete old seller id by operator mapping - delete lookups.sellerIdByOperator[seller.operator]; + // Delete old seller id by assistant mapping + delete lookups.sellerIdByAssistant[seller.assistant]; - // Update operator - seller.operator = sender; + // Update assistant + seller.assistant = sender; - // Transfer ownership of voucher contract to new operator + // Transfer ownership of voucher contract to new assistant IBosonVoucher(lookups.cloneAddress[_sellerId]).transferOwnership(sender); - // Store new seller id by operator mapping - lookups.sellerIdByOperator[sender] = _sellerId; + // Store new seller id by assistant mapping + lookups.sellerIdByAssistant[sender] = _sellerId; - // Delete pending update operator - delete sellerPendingUpdate.operator; + // Delete pending update assistant + delete sellerPendingUpdate.assistant; updateApplied = true; } else if (role == SellerUpdateFields.Clerk && sellerPendingUpdate.clerk != address(0)) { @@ -353,11 +353,11 @@ contract SellerHandlerFacet is SellerBase { } /** - * @notice Gets the details about a seller by an address associated with that seller: operator, admin, or clerk address. + * @notice Gets the details about a seller by an address associated with that seller: assistant, admin, or clerk address. * A seller will have either an admin address or an auth token. * If seller's admin uses NFT Auth the seller should call `getSellerByAuthToken` instead. * - * @param _associatedAddress - the address associated with the seller. Must be an operator, admin, or clerk address. + * @param _associatedAddress - the address associated with the seller. Must be an assistant, admin, or clerk address. * @return exists - the seller was found * @return seller - the seller details. See {BosonTypes.Seller} * @return authToken - optional AuthToken struct that specifies an AuthToken type and tokenId that the seller can use to do admin functions @@ -374,7 +374,7 @@ contract SellerHandlerFacet is SellerBase { { uint256 sellerId; - (exists, sellerId) = getSellerIdByOperator(_associatedAddress); + (exists, sellerId) = getSellerIdByAssistant(_associatedAddress); if (exists) { return fetchSeller(sellerId); } @@ -423,7 +423,7 @@ contract SellerHandlerFacet is SellerBase { * @notice Pre update Seller checks * * Reverts if: - * - Address has already been used by another seller as operator, admin, or clerk + * - Address has already been used by another seller as assistant, admin, or clerk * * @param _sellerId - the id of the seller to check * @param _role - the address to check @@ -436,7 +436,7 @@ contract SellerHandlerFacet is SellerBase { ) internal view { // Check that the role is unique to one seller id across all roles -- not used or is used by this seller id. if (_role != address(0)) { - uint256 check1 = _lookups.sellerIdByOperator[_role]; + uint256 check1 = _lookups.sellerIdByAssistant[_role]; uint256 check2 = _lookups.sellerIdByClerk[_role]; uint256 check3 = _lookups.sellerIdByAdmin[_role]; diff --git a/contracts/protocol/facets/TwinHandlerFacet.sol b/contracts/protocol/facets/TwinHandlerFacet.sol index 41e33a436..01339cbea 100644 --- a/contracts/protocol/facets/TwinHandlerFacet.sol +++ b/contracts/protocol/facets/TwinHandlerFacet.sol @@ -68,9 +68,9 @@ contract TwinHandlerFacet is IBosonTwinHandler, TwinBase { address sender = msgSender(); // Get seller id - (, uint256 sellerId) = getSellerIdByOperator(sender); + (, uint256 sellerId) = getSellerIdByAssistant(sender); // Caller's seller id must match twin seller id - require(sellerId == twin.sellerId, NOT_OPERATOR); + require(sellerId == twin.sellerId, NOT_ASSISTANT); // Check if there are bundles for this twin (bool bundleForTwinExist, ) = fetchBundleIdByTwin(_twinId); diff --git a/contracts/protocol/libs/ProtocolLib.sol b/contracts/protocol/libs/ProtocolLib.sol index 0910fed27..d99259446 100644 --- a/contracts/protocol/libs/ProtocolLib.sol +++ b/contracts/protocol/libs/ProtocolLib.sol @@ -130,16 +130,16 @@ library ProtocolLib { mapping(uint256 => uint256) groupIdByOffer; // offer id => agent id mapping(uint256 => uint256) agentIdByOffer; - // seller operator address => sellerId - mapping(address => uint256) sellerIdByOperator; + // seller assistant address => sellerId + mapping(address => uint256) sellerIdByAssistant; // seller admin address => sellerId mapping(address => uint256) sellerIdByAdmin; // seller clerk address => sellerId mapping(address => uint256) sellerIdByClerk; // buyer wallet address => buyerId mapping(address => uint256) buyerIdByWallet; - // dispute resolver operator address => disputeResolverId - mapping(address => uint256) disputeResolverIdByOperator; + // dispute resolver assistant address => disputeResolverId + mapping(address => uint256) disputeResolverIdByAssistant; // dispute resolver admin address => disputeResolverId mapping(address => uint256) disputeResolverIdByAdmin; // dispute resolver clerk address => disputeResolverId diff --git a/docs/images/Boson_Protocol_V2_-_Domain_Model.png b/docs/images/Boson_Protocol_V2_-_Domain_Model.png index 9def36f742151789b75afa67c77ebc27461bc448..851e12796c3296d5cf25f211df6e94e6f23d110e 100644 GIT binary patch literal 529186 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
Wp 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%0DuQF=Z9SMD;s4O$ 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ð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
XZBO7;K|A13DQWaOR}BXHfyMTxS>J4NHQS4xvj zxaFcp)jeca@@Pn)S5JA1JKz_;jPbg6uVOoT7m9=)+ko9GL)+M+$w9pfINnDbXop1% zW*ye|EdF?a$t9&fi+mAcTvexH;j zSgfM-Md^{cRvWu&A_i4@P+r!jqfVL~n``LvD0ZS`c%<=lxYLYMRIfnAuHD_Lwa(jv z);ThZW5QEPRrTpy^uY~Mst2T)Fx63Hb>3ThQc4=IP+D%ebKcf*mo~ND1s6HEpeNH5 zZM0vGLu0hg5hPs$h9D6=wR@m(6un5PJ##t8f7EsEoQhXyUmSOvtnEe)>-lz{dYVVP zDp@F({1iBK>wd=10`<?bCj+6>K$DE=Eic*&JH96o#Ek^4wnW@Z#$Go%0ZMnjWvUv8|X zObDe-LCeT8Qt#ZpeR{sc&6bJ{TjbsQp0)epuZ#1yQ9$pEsU2S zb&_REQJ%Yx+%@BJ_tI@OahVe$KxO#n{~a9!ZLN)kcH5Uk3e(z9%z z7}9rZHZXGr*+k@hU&S*5aVV+}wAJI-<=^+0a=3EILu6l{jN0J-hziI!8&|#&43{T6 zPQAqGw)?zgrYs($5S9Swwxjb@4>jWN{Ht!&<7Ft+pUbssEjN9gJx2rtJj|rZvtEOi z5wrao!(Vrw2U)i5XZ87=C^Ep4zmQBeKTErgw8ot}@nZJ&$#pxnq0WaH3zuWC5lDkx z@4wK~bu1*7fn*+4<;6M4)ZgC+czOo0E@=S+bE1DC-Gh z*m6ooc~foV?LlhwdM-hqHr?M}mAR=xdF{u<|7d-5ny7HoBnb>%c0?uPS5>tK?Pd}^ zYUK`6l^Xk2OTkyY$xFy-P{!~YXo)bdC&Fgt$j@$+ac=#g9DNJC7&wGSfQbz&BP}lO zcV;E6Bdy{b@0tR&*+1ttEa;O74aaW5jKKMCo#9o+{hpL4+_w+f4Pb{)yZzajN4bU` z0QMm1iz|z}1yTJJE$1HG{NPEZ##l*k`}Py{v0*epVj+C?qPr;z;@t*OE~e2yo$O7U*_E~}Qn+ljTY1G9gKLPD!uL^*ez zSjXuy=m@F-P`S@}9#oTUG`Rd2Soq4D^(x9DUGQwQ_RJrVuNku$45Wh&JdlO8Wc`BT zqEJAPk(43@Qe{NTA4S_iiJogQG7ZIH0o&!yBA97eHZG_*#TEgT=wGBBr%7M7pYki3 zq+xg1kBeOzku(C#L$E^iR9PQ*HzgDQJy8w{s(G9U@Pf2(FBcY~wI~25c_xjGc)j}q zFe7nuXa`?sF%;1q3XNWfC%7T2_vhT_^$n&1a|oqcOiGqhXXn%(li<%OA-rd)!fPKj zyDu-sd@Xhna^m@1$UDd{L4B>fGB@?GwR5EHi#S00PC6Ajr#H->)QOlUn1RB-LZ-Qa zi_>Quh*DeS7mAVM#W?~U`{joEnbph@!%Xzw;xm~9$)&qpi2PjhauU2 z>BXk%b!1~msfS3n*3){xbwpk0e%592EGFK84&bH$7$iy*!JMralM{Sw<*|?=A zSIK1tTnpmTOW-dFZOfgpO(U=huxTI^7JNAJEjMz!3Oc{aj*L^aRolzv=?oIyH4LGliN0$-=4RkBS7Q*Spk_wP9cx*PD(M5_5 z%BStI4&W8%7u6F>M#?e|mTNu%u>SJxGC^hM*6ZMP%;oefT;gZP4}#IwBzlrmPnkPEF3Va4I%4-jM~LG$kA8iCjl!WJWmeLqfv54H9E@H1TL=O;eHYC0<+? zA&&~CM;Uo(s)r^$Kqwpdj#_^LW`ov+jJPaU*Ed_XYqT+bv6+WAJw~Cst?pp=zE{0R&~MioSYBYc+58Q;Dr8`_ ztWw_y-e>pz{ijQ*EZhtJ7&+u5&CPY-iS}mI=`F;9$Q}^#kag~j9y3WouuVMwLFq=> zI+EHeG~}NvJ6O06^FH|G<&EUJO40iHui6ijj%iz1_U}TAupne&GuG01m1|j8&~5@% z*eR&WjELq_Li*=kw`<^D0<;^3_0MV3`klG=UNvOZ%uB4dc^hk`xC{FZTX^e z+zM%o(##5bde8rlqc?%M=@SlLJL)tuqjb!>VTqgd{N%u(9t4Gxsp^@W<(PQj4a4;r zkrJ=+Mlp?`mXu4pg>#{Lo^nCupmXA_78ez7T=j4XtDX){aty`bQpfj+_9uj6Bb$%nEPCO|e%mjXJ;?kAwLo{k>g1)U{8`G)<3O>=jFG9$jlk zQ}6JOwrA$$jr!BfI&ChWv1^D+3k^5&&)>gaF*nwvI?&o_>o9C$UH>8Nz~K$2{$DLT zes>xdRTTR(ecZkdnaIiub=wGUysh&qC862OAu2lM8%X-5r&qTiNwgxp!k}ysTo_kJ z<*OTTw5c61*2*6yw?Xe+@X26HY-)WVPG#mP;Z%!Pgc#TAbw;s+@YHgS3uW@WCUt3< zT$MuZ^WV37`}(g8ifeAI%eSImeZYcLnCp94X7!c_XV!ih_@*uJO#bqpHLl0k)7{$k z^vv#;8&Bv8ZcWfWw`%f&^|6N+ZY3&kv87(vUf(9g>FsmP%!N{@die9%M{U!S-+Ox? zAl30ujV_(l9i7%06n}LsZu4q~!rjB0hbEN@;=ItcdwbpY1N*`$VJ-3xHw#|;_D-RZ zljRh{GI`nS@b}qe_p3)s9^5%HKQ=9t`>XMC`FP{+2d*-)uTQ^CbhwfEAWVjRa#|^o z+HJuw>T)l%vhUtLOQknb3h@q46MdrXCFVJ6sedeP6>;~p4<2htPMy^l{qaEh(}Fbp z!#{G%KN4egvs&$>#|QjhB;{t(>bP0QyoZX7-Z)=yh;oNzhhN?IQCVl3bAd%LrqkrqZkV>Nbc{%|H{D5bqhRYpRMvAY^mn(j2(vy6@?ej2wU^O~Rv#}eW32MC+ zaz0(LEs@XZ+?o4s;@6A2Azr>AENP40PPEuXKKYbmec@5$Hf3AbTL;*m$hONh4Icax z;{Pe@vKKQ#ZYREfS&LP4y48R~ui*X*?HxkRLwpxxGYacE^_Q8&8=~j;`6~X-aa;MN zHY#moL^d?d;esO@wP?@o;iu;^+3C_LrL+v;w|h)}88h3jyMLtpo}`~aN?ICJxaG9& zZKYYG%qE>sFWn4v`t@+<{OcsGJTWnEomr6asZp?)oUU!Z#5BG1^Q3&M!1I%F(glYi zCTiGzC)6IYuJdM{aM*RJPK(oOHuBUu?!$_A*SCx=%9mxbEd4pjb>x}?;$TZ2SewJc z4}|On$qGIaUkk_XLJ)4Euz~cm%Zp+@y~eR#wn?D8{A(fkPtQfemr-}fTV}r3oT3yr z`fG;jN7W`(#<^ag)<&k|!%6*~tgMjq05n2g_I~x{5yyzTtN8@xJKL8U8fFka34&qO zLdwux1kGXAp@Mq*_^f|y_pa{xgOTD+#ogT3%4d7ZxW~e4j;#flM6$4D=qLV-7NOed z^&c%p*6|PewkB~_+ZLQ$SjvEbvsb9gwYu*+kFy&nQE0r4Pi}~uTa>HmvMn%Gy6rI$ z(bI3@eD2KGl=B6*VkY-RYcF<_6g)P*A{%8|OcaOQpP9TYwm~d(sDsnb1c4 zgQxjF??@tj>~Na|JoC?&Xv>%mm96^mo^I}$v47j7{{BSK&-cF>9O=5pkNb(+dVbax z@_e(e=LFH_@b6dd^?X;@jQ#VK(9+Prb2>Gn=efs3S@kzVwL?Z|Z=VzdQuE(ed>}8+ zzil)MuP$iKChT5S`6}MH%v(H@r{~kZZ9~M1bLkoSb2sOC;M(9S=I`=v3zKj7uI>Ld zHsMtbk5*M#?!}7!VE{oB@x`;pmfibttlO;Q=?mCNPPHx>=i3q!_i#0N$ebR5Ye9+X ze~nLg^~3M0s?b_Md^Myxo;!0vrH)Hj)+{aff8N8Qv+jY*zc2ddrT_fXxAoTl-J$>a zKF^x}p6&m9lSDet^WW$G-*5Y`pd%XV{y$#|TmJ8={`V&d3JU+*cl7U%HmuYu`Maq8 z=cT@_L!Npkor?Y>xUwQPI1hX3Rl2f1>*nQ2zP;GFT?BRYFsS<$Qd&sY4X_8$iS{Q+x)+q^Ei5q6BvktM~*xLrV>QM2mQ4u6-d|9g?{ zmj6BhzO|}aVngL9y6dDof2x%{_`iqTknle@TtV&Kit9Co?k~@NJuxYG;J@Fb@L=cv zJVEbWt3QS$qHptn$+aYIz5G2rCx|{v5^41TA)$J0L%6iS67q)u-^Y;Zfv;VK4uLcH zvnwm?2$k@AyusRl`#*4+Xb^m{tLrP=E=1j?u9z&Mg@3XPJwqga-^#x8Iyw>gSKD`m zRlk^;vOj$R&OD8;BVjlV_5KOZnlE3@r0eFLc@!{jg?#7^%MRPIoiL}2+A-eJT$= zYJHBL6X@X4R1|8BV7-W*X}FsxKO!0~OifKc(YDa>>|x*Rzj^Hy#oIy-7uJuQAuf$1 z8azMm{ybfp9o`sc&YcT%nWQ6~TJ_Hc@@FgPOh>Qdyp2Ttl^nE#`o5ET$FR17a7yEA zX=z#MS(^8Y%XQm+#^n_Xqk~a%fk%^S58~ZjC_Ofu4qb1qcRi-db)Q`GadH>$0Z%be zQ8l!5#or?ux-oa+poB!&JcQ4yEa37IDdT3}OzC`miJn+33qmXGA2z_NCl4ln#10~n zHXhiw?{%6T48u;hUk4OvwmP5ib4G)AOSz9v`&FC1>lH5FbrQEPtR;m+!Rkd{@ZwoB zrK4l6GTcv}K4lw4AnfWS#ozuUy@BaodX|noTzG|WGEjcdlNu((2IaW#f$gt85(KL% z*SLRH-SsAMrPt4DabtErf*2}(s~cMJ0|(%}H83zBHjD~`8f#h_A0J=K@PkCsz0*0a zg&H;STTQ^tZ-iNe(O@Yd-tuK7>5^_7@mDm->F1X745#5Eu8tSJ7r*OLL1$FgEE<_M zeSkHx)1P0Ls`JrqY6&mEih20Nh#9{nRm=U(Lr0iUz!l;?Y!Df2zY3dWa(~}Axty$B zS665I_}vk>{YrVvrkUz_dBJ;S#58RO-bcZSWRVC|;#2XeRhMCL@7=ld>#n>7tY;?$ z*-5!N#6^>GSp>rl_Ml;t=YU|CG9dE|_t~+RfM5%T74aiwqDh-?X1r4(>74*~@JSeI zv>UXic&hbl`Grn~6kxj*n=^0&D5yx{$w#aB1`CtWqPw36V)Uad3j24ClLZ`n55}Rmp_awj8iCbSsT*zZRcu{&ae$7ogM$N{)h?H`KDlxa#&4PsxWDa-EYugo z{$LybQHE~$JkPl)|A~u;MiLoqlYNyi4!RQ1yH32x=FRA4Ypf_VkyYePo>5WJf(w!N z(li_=b%+d2k%#XP;x0Cua_IHXQt7V&r^0e;!+B$s*Jpw*!kTLvzxIZy?O2gw2jV!y zuQt30Gqj3`#oQaUgIZ|~@blt-s!g$mOI!yWbKc%bfWrh+`Udwov-QD=(LMKVO-*0G zhM@nVTUeduZz#zcQ%E?LeOvcw8#M95S8KE_lcnP`YsQjU#$LuBoewQDKCA?EBsLDn$e(js8X76M!1G)=a8$x4 z-I;mmO*se2!g7#qUzQp7*-ht+&6QU_eVzES#P2%0M;De*qqa8|o*0bxTAG_vl9H4J z!(N@Q{2gn@pEK6d)>gUD5wUX3wV+wM{jENC%lr}}-OrL8_}1=f$nH496C}~74SS7_ zg|TO~2IarF)Hp`u!f;Ida*vAhh>{Ag^eD4g^jJyC<-(nRz$&!9T`ML!I_-sW*a5U& z{`i;@!Y$HVDxuBc0aL+-e5xA#-O^)uoysA3uP9xGO<&`_bIEY3I638^kKT$1GDp6P zn7)}j)MKr!u72EjJ&DgB4H!D}`d+yBs@`iU$RXO}*woe4F+s-Wi78;ZOO@x(40XuW zQe)vbva-t#faWDy9h{^zp~bs@e+`bS+g*fzZ$-`=%w%B0ohWAAY5@bKTKAY;TdnwS zHVtba%)&<@IXTw-K+lPlNIHoOXhXQmuV}j$e3Fvw*%RrmccOGE`X()q!O-yrl|l-h z2d&J*N@0n!S26ViGL-_d9PIkEyu7^LzKg($?Z`4Y`M{_ir#ds2l<3d5N0XcO!|75y z2kD)Ho*JxiY`cr*TEud4as);J8I9l+i%Q|GjgfB4lnalJ=7+|#-EOM?b0p3m8E)08 zKDXrA&?_}&YzMX{zd#(gv#uf}5hp6UY4Atjz+!Gw{SPaj)Gc=N8;6aG8s z)=WXv4kZu54vMYQYxy>Z=MPOiy;tzca6*$Md`#3~xX_FgBL<$DBd{_Ytc$T;ZDw%* z`(>iLm}7ub__AUZ?FnKh-lnD0!-aUE(p-_#XcB(=KDcb*OuQuD<`IPLe`sU{o`Fby zRB$I=3Gh@PbsEn2Az7YqD6>K!s36AJJeO((o-BsZL4BS=Wq&UB6|ZVD+ja_jNGV|Z zrRe0jjuX;-e-o`yi!vod6n0UIhl0clb={ZG#Yl;bT^}7C^;_av&n~(zVLpH|2~*9V zE;1D1lL6Z|Hj)FwI>(83Ag;FDp#*)H#CsuHC~7K^8i^4zL^`^>NVTqw6g%p79g|H` zjFzUtA`e|U_I_pG8+}1M2O`W|EU@hsb^NK;XBZn6#yTtU(KC*Ps&8FJnQL85Bo2~V zqU8p}Y?dC@)aBh;<)gL*ofou(f-9c`^46W`9rSQhmZy4@>rKzOk4l2d?T60sly=W` z)@Lis6CL_k_Nd0DVMX4OO+sb~L*UJm!XE;MPieq3s30wBv(v39%rWAq#{U2AdlgTF z7~B$g_j6rce!#uSptbh~=eCg=Ji*W;Ag3G{9JGSzzLfXE8;rBcg{_Fmj^w6c7Zyvw zK@SzctBUnWuuc;Xz^Isl zJz_i#KjlLzNJ@zaeCY!t-sS)z8qRA0d#)D_AuV32;!gaRrP_Pf3Nve57KcpNGKrEaYIB_;q?q zHr+*epWCuCGE@j#6vvDodgsqS!{$s)N~)kpZQHWtgRo}{kLwNrDT-P!5 zW**LQGr%!_eqfPr#x@q1se^{%H67vMSz(X(My!;#a>J zTW+_pMf*sWjB0ap^H02gD}Dp5a)FXBDTMO|Ba`;4zC z$$}Z_FA-I8$3I53r^AFnc#iA0t4yePf|>}6a_doZ#9LzQwkH5&oY0{I|a zefO}V75-!6j-34bR(lobFkQN?-<@<%q?7FV{v2;cTgmQG{@~Q6_Hbv5jGJMQywQ!;e!#-hH?|(0|Y`g~GSj3UOAL`6ja&%u_;d zM2GC*dX=7@x6nE?G{n@EiPSoQJcFHZXVHLKyN%NA0~4LqPj|P`y62JqwBaoZ<&g$! z;nokhUB~iLk{-qyC)TcV=_LgJOPx%fIh7tvd{LeDwSJCpsPfyhRk$FFb?a6gIbQAJ z`Kf{G53w%s)VV!>*1$tTufjZhvl9a z_v|kBnm6^O)E;a8dTbtPmkAAb_aefL!nqj#Gm#h-sz#DGpTc~GzyTy5942CqA3t`2 z;g}aNbj*f1534LBQW_U7Y;tam(SDhkSZXC8DV-z+rC_p{t>u&bLP9L<#>N>7@C!ssy-vj>tf^r=mghxt zE??K%y9M7y?*Aw)aED5=I(Gk|?8o6oYqfNcz@x;;TM#klwo5~@AQ_{=CFiLPbI@n! z3o!#s;(&|cy+@5eqv8w0=^vK*x!30Kz;_pys5cTRCMHJ5WW<Noha^8b~_ppkS?T{$s|h zdnSJ)qT49?2AJ6c`kadeM84zC9CS~5YHkj$FU6)h$*GV7e=Jzk@7>5Lc1??m1&_(!Y1vP*U+!uN-hZ!dW5q z+sy1N2p=@8=AoSS53;QD{{z$4>b}W8ri;V~itH_sOgbxn&|Jc!qF&)i^ii(iaHt?Z zb1@rNBj3Y$ga22+v>kycB%SzCXuuN$%r!KwD5hg*6iwI8I)bDPJI$C*e8 z+u(^j0Pw)OvzD5TBS*lXD7U>D?vkPogJ(^=;Cr>P)D8|O7528awr|Z_VUMhwaI33J zUwQHULh*#7R7<*E!Rw=vgC3uekMXbOOY|}QJhSw``H+peD}8zmy;-e}eW_CckrvaP z1-#h=tMPz={{Aa?r)G=2SY9|sns9Vnc3aOpQ=_h-p!kFi*Qj4W6b2(!x*^ z#$wgT&Tq8e!zNcD6<*VkVQcHhl9WZ6c7mJR_UaHBT9kR?X%&@xzS33B%+^*`3ub$v zZy{XX2iWr!WjB#F7`|p8c17rrot6Ew)9sUJ31nZr5U!E}ar?zgncWG91ObnOk24bm(lJ=pj7tw2CxuNY)+)c#) zL=cN^QPx!V0+KbO~DAW% z>5gX~2lX*{!bno{H_cti0u z+37Uol5tu3NRjwXV{f;@(V4w%DHu78dCnYH99wYNF@NFUE*V^@)TF%^Q%s}uFr#@dlq4M|B!C0e12 z88=xt(r}RH8K%Sg4qaB!mjkBzu)e=1QL~5Ek4~BdsUgKxs9T7kSlY4THo8?_cmRr{n3f!t3Ac+yjpyf`2PK$ z59?{e1><=K8|n-TM^U9mEp-xVeMBbEvGh1B%zX7GOciucG(}ml=19X{94_`Hz;^D) z)a_XM#bhEWE!}ycj#!1-7+A7ivPl=Ej;Vs(>zaH-n2n}OROT)27>Ti>ryjtvsL#@Z ze@29hj|jyaQ>_)Z2K0FZt-t$WQ-I0fxuU+UR)g{m4K^w6@1QHmMNWf!SM18RN??6# z)n(*N9qd=QNvGn7M8$j{b(HiyI$xYOC{vB9M_!j2(o-Gz9x2VdNzKvd8{wn$L~Swc z0&vj^B6vDQhl#oG1E97(bQZY&^?z!&e_4w0!H*f&DP%kTlV7XcP#d0f zEGu)Wy2ci*`^aru;wM7|DZn1bdiqe%6=r*!FzcoFRdPCVT-{@uGd4E%FozT#Y+goXyd1mptta{E(o?#I zYMJo%HWXUM{CA64Xx=J3umH(UySqkToaMV^00Nb`)%PcoIq_~Jb)LaQ>7Iz?_d2;2 z=NoRQ;SK>>VHYtw`!VM%5}mv2esGDH1t8;o5*ZnJXl5S|JH{aSSC^xlw8jJ*Upo5vjpuhBE3+-50=#%};>(rwr`CzNYGn6NZZ4W-Vm5KMY6|%n`HEG@LWYMVXV9(F zJeOi;_GsKZLYBfVyI14Fc@jHcab`2w_~EtHxgGbB3F)=g+OV6w(n>!Z3QU7OKd`y2 zO|SKM;M$Od(e3XZ^{gQ2Rsd9EZ&?y(*=xBzgK)h3vtXyPe$$xT?$hwgIUKqJsW9&w ziEr}L*PjnZv9#@tPU%oyWFj@pekz~gItswb8i)J2*wFgu;cTOF?@LrwzP@~EMB4Hg!HM;1sOxm#d_x!>#I56$`M4&{mE0|Ol#(*n9tg!2S$ewz%h=i$*Vsc zrNlgDulCI#L4hHz*O#iQ_zC9t+O@1T7CG%tqN1!CH2s zYH~6D0}(GyYK3kd{AE#O90vy73ZiVY=&C&-gbyz4iLDyz_2-+jlXmp`+^*iRW4Ls0 zD6)0HtpVgdR0{_Ix7wAXkgvAOmvpVFS*T&NC7nquAHC7#+-b9Vl4DHzVU>ixq*|fg z=+#n^Z+>mxB>4Ta!Lqa3k0&(Y_jRB8@*|v1+Avosz`POX@tiD;IFF0(yM>mj)xq7; zHIAFav!=Jh#*1b`z=l7at1U3~OsC^dTe{wmgx;J}ov?dOj&@ghJsFm;uB>b%-*A7QwK6ME)!egsZM<*rz6xK{0`m^JlJkB_z~dX- z6{SS(gA}RMO$-qU)wTMik|1h-=SxGE#@cQC4a5Lu%~x7;vO*{&**j70h$2zZq86+m zz58RP7kI__lEd2yz~C#^x6a`3dx>AfK8jR#Ba?Ud<1ERf2_t$ z$i)p#F_*TsW!1lS;~#!@yLX**rb$iER$B!nAoW>S>%=>|V!h`rfiG%&Ez}uY(d_^+ zyB$A@*p&;+GqoeM$8igMspJ{8nYHBjTFeA3{FI@(uZ)^~jT(AtJ_q5_@*imV2Sf6+ z@Xg0_C8%8v!5beUm3U5IJ|5X_d)}#u&`CJmi6REo^n{_y>2~N<^=JvZ*hXq0?Aw(U zf+RPd`{u?bPgy#KkGn%E}wBxUNES=xR;S7Tbq`b917tUVP84n40dF zl+-~fjlT$7j;s`1X%aDR3&e;n*x~oj^VRMqH)VKR3hc<+ishc=wt?0l+1v%I42 zLLrk(kc05zC5KTC6 zSF(zO)G+max8HB(35yKlP=y-m0rHTK!>%BtzjtJU=Da1)rvFcQbIG z7pdAt+O;7?zzp}b2=&m@*jR9f(Cy)DPjFXNc5Kzu($cMKk}g&_Qr6CN+#o0EP+MSR zj){a;&KAX}bB!(!A4SxfH31OFgVix=pW|@2ndaDT2$KeMP#_LlX6Ba6#`gSb)yP#~dctj-d0b}RRoe~8v z=o)uHpGcFGVyqBvloV8KY|;lJd~hQ+?hx6#_wK9($>Icv&dZTqLR~fHjdcNLu>idm zC{g|&WcoaK2FtAe(0)7+`YWHgd~{*H?nE6`zPv~Yr7PHh zsgcd}pXqoS5e8q=>k;fA|4FG;t-aSQU}ulsSmuEBy{Vh0(?oiu#&=m#Akfy=xKSO_{9=XWSb*PO(3KclCY%`sv++PS z5hY-0A;9(HmVWOo#ViP2SeTtJ0RKQ>5_w<@{ey;M4D4VKKmYT2@bA0w3r7Krh)F$* zQAy@pe^SwBNI@BGC>QA&`!Eqn9iVY~1|)^$%X{@YFeT#!Rx~l=_b7F^t1t)2(Z^FAUbI{*N4{@z|T z)XG^tdM`<=u4paEx1GrBbXdfEzjX<22%w0l@r^Uv+$&Tu^`4bzyN)P#T<+j}Yn-kX zqcKV0>aEdYF{%mX?l5&#dQ!T;U%c&CF$8EIlp@pxKyOC*rL3HE&YkH#_{=b0ZcM~eRMp8v2<@X@?(x_0P^ltUMAgDh1fjGey+%U zON5w6q7ioH=W{=w1%3T~qVL*~-%B(K0W^Y~7n>-?GbW}0(UvS8WqtJP9M5OCiVp^< zhD5)-LpN`;@AN6fO~#wkEwb6g#a%1YUWhCZ8Ih@}CEyY)AO8Uwo^J4bq&vNRC}gc`JY>kpd`%nMmnYrOX2y+U@ep-qAn9%TAj}Ti(2xR zFHBV*<;bq}`*aLo^F>?)@xhP!G2Z49C2(mIjN{nd69x@{pnj+xj(jl6($wC;l# zmJKz)Wfqo}!8_+*tVANN@>8)ZUEaXkXq3&jJ!KymGfz{J?36r=Le@o!MM*j&WsP|` zi?eVC_Oo@!Je1Hsd^`WGMAh8$V7WDgJ|^aE=$b1Gk2`+6g$rxWxaKgst&zh+3UZU< za`miRwrsCfNfx;p|AyjVn~=Bl)K^(*mkB?JR$WBBiagA~+E`m=o8f}icBy{1B7H#v z&&f{&$q9Vnn-!B3y@Fo0>*NpL&H_+J!<98K;(ZfmN2B2}FU&%*5(yaU`4=3`q9$&5 zUP=)}fXm5#k(8txy9^?1hiH2vUJHAX6ojZiE@>?^E1Ry%xK=GfCc~l{S?_Q`FeqJw z&OQST66x|C-)IJmH05WLSs_-`+}g^1RD5v#3u7)d=i=@QSsY;U-l8dYE$bbwYs8Xi z9S8U!9*TpIE1RySbCQ7UkPW%AmY(+%VO5Qlk^C;HJT&s5N2!^JCF%it4>)|d2B7rq zovQ_%Hf#(WUr>f$lG)239ea_$9^Os>H|L@N#o5-oLgMBm7tiMVcC@o8?ZJK+N6><7 zC13_1;oY;ZVWxk{Qgw}lZ(erx*Q)F`&Ya3Whs|@vSd7 zP(}_4=_1Q9@_F?L%49Di+i8MU26=xB6gp#9+qm;9h$(P*FmwDS)FhU7$FM}ma!@pY z$8I8sbu9UZ1At{iResq4ySrYP*5=xOKmZE z6PuVNvKydKg9(wDFxDzzI@IT%F4v`Epux*!=>N0l%-So^wN=>1e^)ocHg3k7;kiUS zq_^U|p}n9Z*r)oItvR-4>WXf+_$PZ`J$$HoLTQT9y%@zXtMH2=7VVjtamD zudt0?;`Sk7C1^mf`_Ga6Dogt$)_Bzp4rw^Vc^nxmYav)5&h@yvDf#(Yh>`!2RRhsr zrxwhbtOa|PR!qy2Nl5_WXycpH>boUQ6J;*?M$c5w83K(3t5=XK32j|ud!+ZOj~$3k zf$erk$s92T^cDKEiAmA8QZtPS0{OxqG!8#TqFpw|dKKI_{s6rEZF@MIuxY9$&{S&Z3Z9|5|J-|RyJ?J^S5dwldEz1ymbU1` zJpqBedkHBPphNtHw`s!zqi<~@z*GHV$XQ2m>%D1^b}7q#ui&s-7wPo|d)b}~pN`{N zc`>D|Gv%Z=VDeReVonV)9*fBA1IN!1X1yj{7X9?0Gmz@$-#6wIu`{kCazAjAC{jM# z?XWXef!qK8>l~jg4xaBRe&J9r z@os)sP0gZUkH;%VTi>gER38BFe2|-H5-0V1@kIIq2AY8#Gcil6uO|oPZ};^|F7Nst z?%>M9+gVAzioG1ytZmH=i=ERaWg<}4Bc*(W5Lw~16ltB&c$`JHpPB}R$jn$yJv;q1 z#9=3_UnE2q32`1s5u?t$554{t;$`4Q2q34mH#s> zXE4GC+;Nug=YuG#Kjce>k1`Do)ty=q{ldX!7l(vxGqzBm38<2jP(`$1))#-@rIPHN z96z~CU~zSX5RyonpglVJSP=Ek-DrWX0vmm)wmX#0WgQKTL?X+mZ*BbLWrTpRTTLPw zG%CU6V%(N+hx757`V8%?UB(`#94sv?Rz>|jpvFPNyW9*VMp{+OomulODHittskJ83 zqw5c14w6WPvY)$3cX&xZz<$eh8r6EfvvSyx zo#sy51|4C&N9e|IHH$T-2^f}y6H0yGRW|5Vh3ZUvBplo>9Y-VPC*+|kB%WSGZ!L&j zBzi_zgGyXSzTqS~y#B=DPp-T4z{%+ghQXLtOe}Q$$>A=Ztdf$F*L0GP2m*)@X@-`w z?RQ<2fu4B%Inrtq1nAI+GK@36!m$6$a^(0G01E`j@5=Ja?A`Rz`fb~`{S|Q9+uJvz z8xkDV`+S!X7?_2?Pb zJ?yrXSTYi>*L@Nl-IWrN=&LP_Jl)g_=g*|zh39#<-$vsqfk-vK33Fq5ywuC$^ZIuf zre29y7=Iy_AiW$R2|l%HC?I&H?f10l(yI^H*4VMU(2$8BquRa;y)+uVE%AxHU*x@Q@^&=K;= ziId{z5|fiT7F)~SJw^=?iEMJsnluOiE(^-0M}}LMvrZ7GDWz|3@wX zIy|d?)8){hP=MLzA`YK`ayX_j1cb69)I>IzJPR;49B|1^?zYZ{IafY!GC6dY`i4+A z?8$}`SiLKV^84iLC2Z;|ENiTL*7cn0?cBb3T)39@=o4mQbFCLR2qWM5vA8jt)re0XR}w|7)ff3o+i{6_?Q@J{5F%fQEx9n_%q*YC`6|I+W?5nrBY^ zU3R`r_azz{q{EZmy!j^7elN_$_pEy7c05=pZg--Iakxt%4$3=5&KgJ(G=O6QPwI1# zZ$2xCd$`fG*=7x-c{qSL2q+|YWDzBG=+!c4A9A8;cq0L7;`{f<$`i%$)QFrzKTIvo zJof3{Z{K4e*YLvR#p@CKE2V$7N6Q{*c7vMdR*6s;hL^@9^d1&*&oocDp)6un^SU`_ zSD})Cup#Lg9{_S=L48j%lns@AW?F$?smzOstmEjI|V-O&q*(RXE zFLQHq<86wDhlh!p8wl%y8L!I`PWbFKA*is0hj{CsvSh+y4mWw-(n{YWf13)rJMD-k43H0V zaW$NaWk<>?Fd>)_F;xij&B@9qCne2$dGpnr-teMg?~ke3iyjg5w-UTs?Zyfj9zlfJ zc=lZ*pro&mHCTA=CGw#FkD2=|ZfIvvl`-9O$j)@HzG!~8ZSb4R#9rg9kJfDF$Do-j z$5m3MLv*@03!5y?p_EBK3YhdCeO6j{%iYTcnP~UdIx9l;7%j>=GNc`2 z?a+L;w5Cl+dr>~JUgg|5G4@Y%@p-=(Q)^(H5XvYs~uNJ3h&lS0O)^8cxmh14G^ zgSFd#_ny1i`lKeON0G@8sshL6%I#nw!GdsSXM7FK?nnCd5}HpWqf^C{@SJ{kCN(>g)B;!{8c-Fp*_)hO?L3b+!%H9JCqm+^pkprQKRUhc@Qy@!rhzD)VwM=|*nLcc#o%DH9cSAJxb{ ziD9Ua#6rUa4cH%l@6uk%*ef*wXbLHuBGj#!DEc@?0qdnFly1iOn z;g3%tPN7EQ9qt-Cg2ZuBkeJD6hELT<#*F4W$=^ec1s*S8LJdut%$vD^r$sIBZLbdb5VC)U*o;Faf0W#T93y znHzQ_cN-DSk-p-C)k4eC=&<>?kWS*dhQ^C7O<)5 zy~8^xxPpBrX4T^)dQ>M%$<)reX-r(tHZfGCq|FpEzMI+PG@lNTpk`V7X*BtU(a+dyK0a`B(b zOt^MB`N2%Edh%j^Q`vfw{|n>KTZG0vPosN&1IR04&d{e~Jf--jm+s(V@bAK%2Y)Ff z%P$Y01Cx=odi<5olF0X2WhnzrN&@aUU$ljJ4I!rJGw3%PsC{^F@u0HcJ4`6dmAfd- z$k4TP^dTQ04#m-$h~>t;b$xV3(iE9H)}SYH#hQQN9elM0JA-n4J71A$>VMzCrcF!5!VFcS~-RY+-kLeOIvL0F}xLUAhK8LR*DIDCI=H*3|_CXzp`}#4? zYK8m`YH6y=--qIB_D>IP!LA}tgNXTBS61}m zk!pSa_7&q!eZM*FXQrl09Sw7w&&5Jw@V#X}pQ{%vp!Zz;sP~TY@$DP_{^nfv8!H?b zS-8`{&wMPl!0)&e|+v~#qW&gh`maKSvy+~{MmKkm&4ZnyfVveD`eR^f#^JO9Gd zq)58IO|l9HMWtvJzMZOzlz0cTFLb0~;vSbR`mAOP5OM!_D8_}mmKVRg@i5b17hI7i z1<{YJ@Ud+C6qZr5Wx*1~v$jfI`*rQDms0Tdq_u)a zmh7u2dFsjC2MiYvIu(7(2$3+zd>{xz-=yCGE+KUB zpFG{WUPzF8Hs|(L2L!-0jdacd5~wqex{Q|ad~l05gc!b*or!tHG!pF7!SQRxDC6J@ zFD}xC*5pcq4xjHPrJ8dp-@G!5D>hPo;N|fV8;H4dq}&VY4t6c|ta5C>XYl^+a|PwW zF2Zy?muh1q1OL7eUNqj|(={v0weZGp`G}L$_t9L82E1%KQDZq`-~M6}_EaQNwPNH4vpcyoY;LSuLJ=Z26mx4np<^@t z-hnmxsLSBN$g{8Vz3qi?=fJ#)kbCy)8fBJRYu39j*k=AZ zBjd2JI(MY0ZGfyFwH-Enx~zt7oGaZ`8J5Xyagp)K9`sj}=C&_>-4o;XZHbi=p_YZX(Bu5=Jj|JUOnCh{6ij_s?X2;cbmL}ziGkE6 z{+?F?bF_t-znI)*Vyi6}q-vU0lRp=}s^*pbUiRL_b;C9h z2-JyHX^=3UWIAlyI9U7j@vE^0R$dcKzSDOOdQBRZbJG9B@22YinMF3QSjHNXD8Q;6 ziwpNEW_9;phf`?`LQ74xx*HIg4jp3L$j{xonXb8kPE|0548W246d%A5=w3w92PJv5fv4U$#U}L=?j+0 zfBoU`ZStc3>xWw*nt99Gk#SI>9JkA1N<8A@H_YKsi_%c%23v9l!xQf+P=m@tc`_mN_2HLucKCbzYh44 z)l{2!Q9prh9K(4yTT%!sQsSv?3y&!^XrVdcX^YHI0fLxTC| z>Ks_vjOoy(cq-(;ht&@juXv}BXdvJ%;A5)=z6n=V%&a)!9I8-{s9U*ISfjFlJnw2GWjwh#~_I)wu6E znWf|RM9ASS!$AfU@UsjHB=EfM{dovE$TLZJbC^z*U#vQprAMCy8I7J);)t1_o6*uO zXOlkAdX$k8QTZ>Qf_yMILExltf_GyAU!CC!(2KK3?;jczJ_res+HCi$F?YthD?z78 z*R2tIXmXkTXG!i?Z}dI|{Fs}Wn3v!;k8m5kq|xkI1QzN+2R%e8us8c^#rEvEaD&lC zs(emvN-?s@4qN01C_{v?P4rym9SgbWfH%1+#ox1!3 z$7cqc#%r}K`>>sS>ay&zwMoa8VR;Zo-M33RExIxbae5{T^K;~`g%jXA*oO$RZDyY4 zA0WQ}m(|2M)xN;|*;`RI{{1SE7D5RhQp#l`c{$yit>@tfyDUb;1fJ4FLJwFSs>(F8 z81DZ?R*6DM8vnqxz((CJ-@`D(aOG2GRCRu1Dmg*Ph<=ZW{wMvy%%jK{56(eI_q-MCPCc|V|G-tz1=-Ar~CUS zuFTT%sS;^dI|>86_3Z|(Wr{CMq_6YQH)HztO?UNy9SKt*cbGU5dcw3=0n^+OdW77*&CudfeRW|t()7HNA5MYHj}iphsr1{|tgl}Y zsAS(FR=U3P(zV&iSDHBvkMJN-ykXjn?dytK<=HI_O6r0whv4>x2%W}u&eInY-ui5| z^MYNOc8#-qFCU^F`U#?xY;_qpj_-_=#YkLBF|HfR87;dh)e1Q}uEgRc5gV@<8=tlt z&y05>qnBxc0cNME`w~}Ah8YXTIQlNMW>Yt>kQHWNK9AX}gsCzyq40*ucXe)->`z?= z-lX3$%l=(Tzx%7-9s$3gJEiQyP*yYI@f;4Dp{83(JaD$TcI|t^N_~vs$ECeI##QSQ z(=X#pkZ9K#sWy`U>QYLDG}bLO-kB=m2gIP0menBP>~_}k&gy%;8F?=<7Z|f8oKh;E zQGM!k?RDyB-UVS92NDAZ3fg81lUtmXy?R`0U0P4(-tLyv6x}|znmWwvI-9g+%Ymyy zxeLK7GBI_iQnL!H(&|iWF^tm#r(?t5wdkm*-j_cmG^XiTv8>HGUoV#9Z%qek`2!a8 zN_w{ZTJVxDdKkaZMq0fT78X{FFN7qr2n4n+bj$=ey{2hS4Ag~RkivpSY+bp*Tg0Sl z5PUyzFqjwF|w2!&aTE56_jb^d3%2-e7~AHKx=jlT;n=x^~GBkU0Ff_5*99s z)-_Ta}d8^d7!`EcEP-#0Iro;Phct9;bfa<)LQmj0wmcg>roF3d16tz%U9>^GGY z?8M|*Q~7ZPjUCmo#PV;&Is8?fRzLe$lghH1#P%pAnmN^s&_tbUU8e`BX-8c1x@{gr zxm`nI?$M?gvp_vGCug+jYuf}Y?m(bv_%;>7JGE%Am89k#Z0&L@XC+I=Ion64nPT|v z0oTI0WutC{=S#SExTIVFIPCZKoxYpP*;<2i!(7^}I&3V%ZG&a4IGGz{!@j^2-g2p( z8}>CaF8HQ;Os^1or(bU9i%Lyce?-wQ&~zrv!qC1>ut=34e(%O2c!q;8; zaQ3yJBCwFOaZOq+n60Fo@e$%aOcjW*oVEDcKKCG9D3pt8n8;I zGPD(Z`VX3JQl;6fQbN%5wx! z#t(P2toMG5_e_=o%cwOm*GD%sJx?nX%M3MCR`rh-8IJZ5yt8F?@`p0lTlx-ikVvdR ze*f*oPd-Yd}sy`!R-xkg2J}v)6QZwShmbU4FLLb3`95<9OQXNpPm@AdAUsif#wv)wo zW?4s6G*jw#T9o@7Q`)(<=Yg^N zvvNW&r&A?5n&}*D0`YPd_CJJf?7JVxQEXyDH$KUtJiTnT$xM9CO!s=1M&~V7x2;sA zN@H5vLU^II!iQOpd%P$${N+BHsG&T70}?X$65(~>2(dfxT7rGePfOLrbFX->c-LUo z|LjQ=4DKclBgcrb`uRjg9t+aEydc<8c*Nc?P znl?;K4|MULIcD?zG(IvyWYil@C;G)`iHPZ8&bnreA6u`szo~cJXP5NbOD{g@$Pvr= zLCZfco+tF64Y;c)9=nRaW_~)+d6Bb16B{O}g{GHb*0*4F{DDEmAo^)zaf}He9@(4o zR^Q&-xG_~YPhNIn&PeiRQ*4c64@$&q55LQIpwV`5J}N7%N`bi@YtGW^8|DrBZt0$W z6fAulgQ7*g?whSBr4K)_laS*tgpSzB@pJC75wS8g_!#rdJZz44uVVMUY%|!c6z)Q zve)$I#;&_`v`&_!l#BJZ_6|GnEm=k0+P%>XE5RAuL|HoX`})(?bvo6Wv)RM7HkYiJ zO6^ilFO=BDTC@n%o&03E(y4@`Eqbo+wU0{y_*NRp0Mviymy&aL&^xO*y=JF4yMRww zS(5p#Zo^sIFp zoWYr2c_7#a(^Jx1Qx77?#y^=XIK6ahA1>08c2wY-WT-6(6Ori6LpPT2rfTV8Z^1U9 zAguls?*?z0_e;xPPM_3IDRHpbGU%YUW9q>Yim_tJLa0ZHe>lRxJF7Pgueo`^aU_c8 z_2|yf;9QqHl_FZRh)n0aT!?sS?>Q3xa8CAlD(t(0MwEB)62}WHh=q8JsUhnZk+3Qr z3!rK`PTDU{%9a6-GT|#~cRT8e~qeD@j!%Ru=2z zlTB*J2z7Wi;G6M*yD3eBPlFVpwg96%HtOgCkQIV69^=T^Xd4A%yw}9IAy&IYNaNLF1g%%bo_gBq3}&iJGZi z=-B(~bVB?_yOFNo>$4A(OV4P(e@{b!#vF%J?DEyyAR0ZB*F&P-h(SdqC6NGMI4rqm zZp!r$3l~TVtFcgHsn^6YYax!t@yYmn(y3e$1s z%~mCIS{Md3xd=N=oxgopW284yh`CV6D6l0dGUsRlJK3ESV~kD`_qn=Z6DByV5gogE zntwu+VDkIN>Kz_DeiuTzzuoUy^!GnrF*CT7N?rXvfHYL1fAet?;T2kIHolKgIEs2P z(i3H`Co3owc^hQ4H;jpm@*VteV_VhMH8n5-h+D?13IjbSRJEO3>;lGb-@fe-?v&DS zRyCSXPO38po;I)fwsFz)KBRk1Sc3RwYD07O?rjw(`G$0_eN~8ty(>%IR8~(|Uu;XZ zvY@$(hSaquj9XpCbE)oa>f((2V(E2yf)f2n*d}v^b3#f=9L7OQ`eEB!E_D%_$+cW< z)92-iF%je=XoBcb2000$gQH@mr6^7C4d6ALJ$(3Zg?pYpr&eUS+(L6QXTJ*v3`8hc zk<*J=c3u34ekAWM5RbB3>&+ZxEy&IQP2=AAZSlYLE!ZTE-{QSLHzOxZ9$DMKKv2BN z=$4nZ@3F&&=@v5v)FrNTFA09mu<@^+^?`6}qOB~Y-a;1XQvDY~Y`VMz_O;eqb-eg? z-~JPm#Ld5-UXs;Sy0>dzD=lxqRVmFGK;;$0X@^@aE!kxx&DP$89CeEYbFh!Zy5!2V zoAM&`x8n}y&Q0VmHO`w0rQU3=liJN)w_MXxAHLQbiw2;8}$-Q$>&fEVuZ=m1YEmnSw(Nj zF@&RRM{*t4U83n;N@u3uFH!L~SeWleW8(9RZCl4698LpxI-R!qi zhJ%qsAPEUwX|!u-??Ravg597QOEFZn%vNS$sBV2RQ5mE zaA2SAZVa-k=+KS6uaFnW{*@f_fOS>n<$~!v_I11YK!Ppd~D&n ze|Ljq@yQIy3MPr>3-o_2i;WCo6rU>lyE876VM@C;TIutQ6Kdfgmjflv2*=$`CR;^o zOGFOYK@sJby|qEOY~hHOW$wm6;YhoNlvm@k$A-Y2l`L!3e#D1U=Gl87l@&uE2CkZy z(%!p{vVEEOh$LX8xM18=(7|RAD<$CtSC^C? ziAB6~>2_}34=$LG7#28`;w36#hStCm+3NJO$NYeT1*7?qSNKmtLf;ka`UZi|#kR)) zl`5?aIG?cfi&%sn_)wLUdZ{b+ZgG8x#&Fc{C4$591htX|9Af+TgN|W`v2XhXN!qaN zSCKLE(q%grtOrFdw)Jp`x*~D;m{-K-C#fm@biht0n}Ksz*R||-FB~$%9J|>&vUc7F z|J@gL-5dv%b155kHD*$h(oCv%q3ur*bJn%KZeu&_ps2k_XM2|egulQyDv4=_50bc+ z`<&Hn&QytGibVN<3kEto8Q(d4joilKY>%K<*L_D8Few?Eox(Oj@aHHK>E#dK^YK{+DMOGn1Tjc?fOFj%uok)GXij!_=q6o5s>)w<*v0N8QV z@m92*w=qq;ZXhp;DJhf+PVJGC3y&aJcBoR!lgBo9kcd1)pd7xCqt}tY;M$Riv5K0k z(K~V^ATFLMQP_cyA{J4rL-=1Q*%Mg|d$imgO*)Ffkl_9~+&sT`d%k()>JbwG%iQsR zq%FI@G)V_P4S~kAyk2uh4P$d23ie0I9`C%rTI-w={q5&F$4? z`_t6Uots?NIcc5U@WaX)0yz=QxfzeYbZKggVh)v>--$1G2-6bDXN=FuCvsnKoV4Y%;o9kcuI{_iPfHBvl9ENQ{17(4*T>behJhzMe62CKBTh@rFF`G?)(7a1q zxCP2CWAw&P7wgTx=m~e%1!;!R#6g6mx=wvN(X+Ob3@%ZORa$$RPnUaOJE@-D-{xR} z1XUs56~xfrkyIDjd|xFvtBI)2`(d~a0N&THb2ZI>lws|XLzHw{S|48jtCjsu-uN50 zH{w&*GZ*yHj=GH{F@NM+F?nSXp`?i8k*{^kS3CeK!|z+fVyg#s+DAK5?8;m0`@*uR zRmJ?LU!PD^Ogh&Y=4fdCWe)V4Zo^(;v2jftBifpTs+(ZSh0{;=JZ>_3wQ2EH7)!T3 z$UMOy@l2$8**CKxQ#fUx2oB5Rk#&QQExck&Z^S#yf4R>{IUx#vv)*Ail56h3WS*WI zJGH$~8=TyWIaj#jMCP%bCq-zgs_-M$nbQ-4rhNIdq3mrm^9Mn;0e~EW){#JBF=!cJ zbY+MLV!SjpvTMZDr2E2Hzs$*nQyyvUv2h7!Fv_GC(Sl029lA3Yi_sBBL#}4pD)MGK z(N3vSTVG#f*@M2o2K79g^rb^7B*&h*SHy*$=rv9`db5{&u%S=WIxVtH8<{&?4{nYI z%{%~t7InSp&d1w|^jjsZZjfIIES8Lm|5f0?ny(@(JUcESGH;WC}Bl@I%>|M`1 zJEnjm(yYGFN-Ml&mh!vwK5N2`YQQbi8u}%kGNoHnu0Iv|N{5zEf+uQF(F<7N>cuJG zZEvOTJR}fkk=Lg;9)he`!N_q%54v+D0s_ITb0mE0m8<6g-iii%VrqXK6LYuE4#BEH z(D2n^znyLkUap>IHw+$6I7sh(Dk1o?%j+B<-J~3VhuaddD{uI% zv6z^rG>iqa7JH9Nv^8%Op8Uf}pK9^*hRF>f-d*S?vHPOn2{ZdNDI`F=qRMUk9{6rx z`$o1rwKapPA9BK%wX~)05NUqj!gVtLIEpzj<^H+jUV5ozAN0UVCl&x8hGFg$U^1uf zAT;r4Qz(3-n5E&q;7bKrT>P)2N^%cv69iwq8*z}H#*Sd8yG+ngMz`q;k-qkA@ePI6 z#A>9};_*c%-9@%BvxltPf1qJYrwei)1-hp={9z8#*X9%vylWb+Ui8wNxtHi+@Nlkd z&qF*{k>%PmsS1LR=&R|iQ925U@_gPOjbt!$!Rih z<8LBezKMC$U>!x@g7La;`KQDUk^>j@7~6d2`Ove8!{ihwNjC6*5Fx$_0|37rkXQ-k zy#28LNr!9UZ_Y$#Yl#-wKD}~vIEjN7vRz?I1o|#BPYSyJpcLnQm>6|^usW@6XXvkh zIlQao8OjkF<}jvXpC2pvkoOWT5NKBm2!|Z=nx1d1{Vh_eIElg&h&MHnK=c2sPtqrF zH#NKgrI1-&_un7F8Dw5&`WAvKtXu*gvsG^XH7N)ci=Zol{}yJ+{1I;G6_|8>F|$)1 zGCa4wG;}{uo;pCX4AQlp%=4lbb{3_-upxl;P>S6^fJf0>3pGuB?@ z^g}n6OVpEGH=kNQc z5Fs(px0h;jhS%>AnueXb9l!kW_HdQX3nC=pTvzw2Av!94x8{jmUB_^5?LC&ws`>kQ z3-|4FRw3txyls;FCbz8;G+XYV1D9*5;XXTD#5W#}!i3ZBSIovZ@}j7R1Sp&!!#rBD zZQj+uFty6sPy`|^0N&ntij2hPBTkxVLMuQt*`g=<(Wx92Y+1Icf)nbuHI}<-5M2~y ztHYA=Z%2HK2%om!cZkylh2zGQSB)dm_5=|qdQ56% zv52zo(F}}}xfUF51C!QD&_@tQF7I-DN2+4_#~mx!&ue+17HkY8saU*ibiC}nGZNX) zi%D|K@SA4iYXqTbu`V`7weEKW(@aa2PC|iFKrc`v(=)G*>O;z*s%fLY6PyPBx|%$4 zcz62BK6|?{8{yL!N}@g=c7sB*7AfTlE!{tkA(QI5FcWZXmCb~e9vT#uo!m7-%#iqI zONKOvr(|J%+^+reiu_;W1nq`4$Fp-2BxOJ<>cbu0#M|=n@ST3TG!1kM$3W4-Dxq~v z2_nWg^z4%9@>xkP36v%IYv+H$xqKt1qVXxg&H<^azMta~*efi|;gqsx4TrckX<~1( z%r_6AvM3AGu=vWI0=04K4j7Z^We4f45ppbpNcbG@`7j6T*N=wNQF4IIs6oowWu58m ztd7@US{`;KccF;p^ZphA*nu{o9F=Hr+RyS@%Y_>)zK9w~H$$aa-P`BSKOA7B9<a+Z|IEF0?vG@ zgB?YKvX5~g_ejYfW>F9toH#I1BG7wz{hi^&ckW4rSdV{B9`A^qJ7w6l^QKhh97=j&?mYXwX6-MCt0+fAUlYGv?`roTUhwv~iHc84f;U z%zHLiuVN8&@ue8aBS=ZL_(v_Ag~7odgaiEma-XBp{9^NdbXnSD&kQ-w4k>ppZq|WY ztl}|<6mPG|ZC$_nr>k-X--p5L5{@AUCqDJyiyq2(Au0sX2kNvNgF`RHt8 zS$6EBVM-q&CmKuDq}ztuGYXH%dKx8dt7CjGey?_t`Es2lKfbTj#af=N-yyK{CISotX3-!l6hiNrl2xnG*V43$I zH!-Jbw>{ds?Mc@_ACZ4T*Nsx`0Qv5uny^<(q#{5YSvPpnUm5mUR4A*pA$7{$Zfu<+{@mgW_7s~o(ciGhaP!pS3kXei7X8CLy zRGoS0EEW=rHla_~=ZvaqG?}g#fJdoVD{OLXFr_@d;^T2IG*b&MrQH4AqZji1?7rG* zb4#zmE>JZ-2ZY-P{fCOLk9j?ZdbVm`;Jm#5(&yzE$BKa>1$__s&Hwxu+x^qU zb)<{59SvpnfO4s>S0Cfv2xUBSfdieZI#%7QS&<7L#`W>u33&Mo)^oI*eZh2Hfqy`s|U2k$Mf%g-$qS z+!tu>fjj^Wj)uEV1o8BtZ>A2Tdt-$hmh{qvnAUv)ne%Y)td;~U+P8o2&i!-UV!JP8 zd7q&BlozLC=Od3qCQfN}jHH~!x1q}m!#w{i2$S+a>waT?*w?+6tkz;PynE-PrR}6dOB4*IF+M7w zF88`5eb>J?Sp4sabN@~tczhPU9> z_a_Iv#kc#P`z*ivr&H7;9GeqxpARqi zXRm3b0ilU*xM7teklDGOjD{u;0&91o=1L;1^PngmQ8ZF2R*Inyv`6CpX$kz#_51&6q1}(BDk(CbX3&Js)Jk0~ z(PH~p(tgOSfoR;gRXj&W>@Ap#@FsT%_{Z+4mS(5;B%bek3IfDQF;@SS$<|q8{ zB>Tu@X&NAQNdbc?5Vx8J?d-uPOFE4iYbTP%{{Q)L_*chD{!)Fud%l6|dygfAfTrV2 z0Lk@U23u5N(|MZ{g`b3s_WK#SJO1b51?#*wk&-$tDmh+I>dUsxd!wJ@RWY>gVfDxP z!3AykE;LoMkw1~XOOK0aG7K2!u*le!&;?#W4?X#^n=4q_HFy2b4e_Uk|9p|zM9f2L z)Pe<>rEWyqvCBVT9DH=2-$_%})7?9TlvC%#IR+RexrM3n#Q_;|m%HEo6_n!^nq8(=J%5l!1vB?PdlfwV|c|8AYu@z?c(gEWrk4gWRq#S-NuM_hMEuHGW}{CF{|D{R;QRf0kO*mo#f9&!g>%_j3}32OPta z8nvJuVW)0a^do2}X$k#bKOS6$f4npOALbtgnEj-9`@u$xk^J|e`{!|cR&z?%C(ow? zwN2=n>W@oopPu?(_d|5&{cjI8&uy^%Xw`zg+ZC{mh(%We*#$EE{QMRM{}|GWyxGxk zz5nTO-{H8H@;+AW+s3RPH&*>=pkG!RpPYP#5UJJTY(i`d?@|6gJCZO}5sm|OLgplT z@#D=5{~lWYLau@9_ZStceT#j|ID~1`)EOn$-xot;Tc;xDC%(QSQymfh8Bx^*o8>H` zsu*3A{&fC(vY`Tg8iUnIi#^%<{}J-z{_1`^1eW4kmo>tp*9FBR7-BO)RWQIj*O~TU zV0=l1TTn_HW_+)p!h~k)xN6OW=aw%I>(N^5!!f$wI5O?1+U&KWQ9v#V%KgKx^$B;FPmxQ zxnvUCzDckca!8josPM{?cKsWuO7sz-Bc*P~-yRv$EEXH%f0YPIVlgH8Q>K}+??x$i4LTt) zvAaG?_N{r7I2rfq`sP0@uo43vg*a#LI2~P+o|Kq5ObF`SW-m7YLJG(70ef z*Q--S_%a^ipZ!I%Wt+KPa7Uz@QxIiZ*CM&j>qJX=%hpDX38_ezEp=G5So*q%CR>rtUKh+gEu# z|E8tLg#p*+9+3O&MeDQQWv~jO3nSeIx}9LBdPriHC3Fm;nbLVhN)Y;J%3MOJ z;3Pbicx?|jW%eotTsOvuCs=9}+E^2|8@MG>^FY}Y`{qr}2T#Uv9623~Gy6qB*zN9K z@|%B%>>9y`4)OI@m}DLYQvw-lHYFSdXh9~V5`^0W|IDk1ZG9wiZ#bYqxEC}AKjb0* z++P*#C6GIIhKy*h^RIN7BVOX6zZ>@2q(`$0#>>-ED3_kE4kw8(}w`461V3%78%cI|>5)O=wQH ztx;7gnDL_+7hEwl#jFl28fgkZ_4h=#xEr?qEa{bG_pVMcVrH5ediU32ZylU7x>V4TL-tEc<%J+2tubA z)rIM05CE-^3IqE}`3dVJaDDiI+^;Hg_znhyS3X@WgAqmWel77O`bDq|cV#I6rTn26#Hc*}h!r@@eNpwli0 z(V|K>;zKal3I1>++;l2?CS=z-6l+7g<@e^!?(S*TC#41;FS?ttJrsOu+5<0MZ0A>h z%l(1|3mP#jo~f=H1hHA(MBoPl@lFp{#Cvd0lWS&!!+suIIDEI~DJ-QXT!Cucd6)KXqm2 zE#-iXe}izNq`2Ez&+M-$v~{l?9dX<_or`V;hx*L8?Y*Yy-Z@{{E-+E#dBF1Pq9bD* zkNhCr@b_2Dywwi+WE)j*oo6@?V&BfbcXIV~> zU&ZZzT*?oG%zVyL_J@{r+=TGMYlz@Y=C;iqtjheRgE0J)p+g+xEkS!Tcf$&7FpIic zAYlb3`3kLLmKVG%yQ*nVL20fK*&6)Ve%JQCX1ePw2~COLO0 z@VJx;t(R=F=K0C+BVZ#v^9Usj9G*(B?C}+ia;7-bhBPy-J~w!4tDkTYcYKj|TNmQR z2g(Xq^~|17!38Q_cCgvb^i=E31LV$SDR??!gR~nXDcqGH)aIZaqEcVd+NOHK6A_Xd zKZtPqbfzR=5 z%3Y$zaqaLX;_r?PG>&s**}44Sewgn%^7>RZ<`~zp z+?QbE#i7+ox2QoE8_}$U>d43XqCVSiV940Q`xFcgeEA6CQBKc-t>>S?!67VFMBf5D zPyRtc>|+k#Fd=dpU3!paiY!mTLXor5xC`{KV0DQA{}`ck!By4}PMODAK9jV&Qqh49 z3cQWt1G=C$+PinJEz#0}xsrsKNOTS;@UgTPQ(S!!&9GaquL51&b8u=X`PG7CnVu-j zn1Y?uN7e#VQbNnSJeg%rqwuosZ_r@=ONs*i2SWGfyp$+9?AZ!x2yx3+v&%F-jOgLb z7ecTK&lYLz0_@bIvI&jyRX;mH%z*ee28_eWU`WJ%Miy%JBpu>PL}HIUR|kqGE6h-R zJdMrhbJr1sCTNX0fx!L=a-CwH(}Ugx*}SXK(Sp6i{u7izF|gTrqM< zhi}%*zGhy}XAg`@B7iV1Kl?xgpv?~hiW-N7HJP8Cl0_fxzZcav_%Bdsdy#o|m*J2uc z7;L(g)OC093`0JkO##u#m0@8pS}Wh-c)-xyI?kbEJI-y0o(|EIbqQ@1>C?Ke<+vv6 zsqdI&I$;&uK2{^tslk&sZvEk6O81*ykn{fluPIt?j4OgC}aXW&Y(jlmle{jfp)7pwfMScf5oN3nM?BST;~$4(yd4a|RSZeFs3cvGlOdbz4>Af< zfp{Y^Md#4uv1&@%(k%cgrHwg`DLW?%g5y8UHhjQ{AN)$`NGs8$L`;Z+Cir8|9J8GH zD`vtID;p{O4@VQpv6$HQ%Y*Iy3^ogt=8RT*>t@@gr9^HC-u{J z`_r~y-X6+da3h!CrbO))+<%54pKw%mzAm_7jYZkX#tT|+u(#F$dWu10W`c0r36#Bf ztGyDzB9zqQD6~`UT|x3pR0;6Wsg`ZBu+?8O?zG^eBprTVsQAD`vMUlm_UX|mdu&~~ z&$aJu8*y~6%by#x900UYNeF{PMoYmnLl{{a6CVTn`mTnk;8IQ<_7#yAn+%g*kzL|K zrwcn2z^8GknS*)DfeVUm;?Sqfpce?1h% zJpE0%gbWc*SbATezyC6G-U-(LS|0B@zti9#(Ohqwi%4%txtgRzSaoy4Tc`0W`ALuP z^@O{ri<)UUP-of=hH~R@u+i9f@y0{IIhULJ)mDOJByHKt#=8Z^3~xkV=3GIUVTuWe z2MaIUht`~g@q4fIjy^P(DoDi)Wpu2GOyNJ5rlv^ZYFt>{3j#S?I1e4Gf{iLVchrBo za>sz=jLn8`p0nM3@lw`CT z(MYt%%Uk3yK?K7wO&E{(gl(&~QUKqIgP)2~=I~C&B_+mkkZ)X6@KH^ZY>Rh*<4ntu zgGoXBs0L|aq1}#X-+KnGtcr3W#v-%kjLO70Zuk<2cu2sVztjB=pBP=OWP*Gi1Jxyr>Mz!9*VINQEX;|mgFaD!zUN_ zAQ|U0aF8J2U2A(g4HRY_oR`*oF)_n*gzVRaR&_+*7{SJosm`b)OjU1IVYIJUo{bt2 z492Kq%n$eM-n~D7u7%?%*aq*jGpTe37ot;d=(_niwa_2#N9#Vhc8}C zbcXXv%vo?&FP2*Yi8@A6c5{;@R2QO{zHbzQ45Hos2RvYv`Y!yGd&iC)o%3LeIaUP5 zCLgfJ>Ak&|sn55ejfS9yaqs{fUU-r;v_Sx6fE)X@r`4s`5X3@U772oqek@jAt~E zQRnc)7!dHQbC75q8ymaZBntV8XSPf`O8KBjLYSk9SC?UR_^XBwK-E{@5mC*OL?09m z0RkHYm16ch4$S7I>S08vYL1Y$kkMO6MZe?OZfXGv=QDV;YlDr^;PqzAkpST~zee&A z#7q7g@&wkB#5dsYETmrw11tm<kVZAt=R;*$-w|587kwoF zy+=~tOSb~t{JaS>^z}F$oBf%_zP~R9^)Ytg%XhoW!xKTGD0ERO0I@#D)-9K@KJcSEs@w45w(J*)DDFzWtg}_l%_T>f zgR)kBIGOj7TM2MO5yK+k@_;+_y#UWGvif!r0S!#O_}E){L?mSO@0A((vSS*utHcN1 z4jJCHo)mQK^JjDxJB(j3P}UWH{kmehktnv%X-8JpckZb)ED&MLIr40yCD0ziANIZu z4l>fhRLJPJWeGW|{)Q5yTMo ze|Zj@27^FIwVOXE>bMs(C`~A(j*2~HG17MGi#{o>NOt*GZo?RBFh000uK$mBH?R6b(G|M3#3WE9(G= z%}__65oPvep<+cP94-L?0Ytit>dKl26;I+_(WYm4j^k*m#&F3JjFHHLN{vrp zJFtQCNR;e=ixx)lZwsi~uf6N&*Jt|+?I*&!uP!QQWYF^={`FOKW}~DUcJpWfvw>_9 z34IThUJd^O(F$`uBSXX6E$(0kJ{GR1)XcF2N4IT~!Sm%3N|K#Pk5aqN&6})44K?;9 zO_hwrUew zBJx5d`7)e~4yQc4r?&vmNy%w?s@y#xE+K1gYr&F3S?%}t*+N&v_~XqL#9@*01qqy; z;KqDh2^k=0{xtlaE?Frb7Z@DeWLA_12@Rq_l+r%6CW|Rxqa~t-%_OUnz4l98vBz*E z6ADp8HRqUJYeDb9S?ryU5MELGnu-L7rSn*k#Yd2zjn1dXz#nOw%&tk!Rv+pyTo61@n z4{TY3gyiTr=X!e(XGnuar0CMR!C!xf>W+?w$yW}7B|CMs6r}Dy)S0Lp`j-~OM&hJNb45r_EdI)XrS|({r5Xk=9HWKz`Xv7raAW$ zgmHxP8!Gc@W((aC`HI|YDD)5g{4Qxw+w$EbA5?2=3==dLs>6s~>Z*tmQzQ~SJjZ79 z!*FK4T1L7&ibk4}LZzs5zG%#cli=5s1?L6Y5-?{@6bgkN2dg0|vT_e5nXa`p;`*9) z18vZ33&s8YLa1#am$rzOQ`TcHujtSN9cW4hjGrjVB=BeJE5rTehq(CeIf|Ct3kBGB zA_2vv8{FKCDk99Ah!kV6o!I(T%KAQSBSNN4XP7x!`eQQ_d@V9CqW6k?d%8M&J_?v! zq<;BgI^O$OS=QuhF}n?}|5gf8?l!4wzbX)U@hB3|Eh*fQo$}D3yLsU(K>&Ac?SKqo zYP=jK28E<*QTyw#w`z>ZpY0MP$}S`)XmSTa8K;-;M)q&J&JiGx`WUP)Oa=$CV&Q_i zQ9&`}e!%k>)7ZFJ{HVIUBh-}R8OU0Bz$ds}z%!Di64b&M`DPemmr`yJ=}bgk0RlR) z=ms)wNH%a2serOV9HI-Lj*WS)&R(TY&z?Ec&gy*@DFni^-59x+aTpQ8o5)Twmhmlb$oS?t)WdOC~1&Ww_i)etJkH&-QEgHlBXtKac#DHcT zj-e7+J-=rZe0(f+8w7PFX2yk$HZ`MGTwijdvP`}DT(`!<+^*GKFvE-c%@+F&sw$Ou ziQe>DgjiS?wB-8TfuM0mDJ$iupBV^Kc2HC$CFte=cx~PvGY(T^m1)?0M2E1*|LymB zvBFiMjTWlIve`o*KUTMRqv%_rzZPn4NpH*h39~)MQ+9@OEc9@^y?iQ4S5q?w3B?@|@Ti34Jdpas2g1fsX1#yp3^c>Jari^gM za4^nr_YOB(*dL+ee|8o~IDnogn@~T%@kG>Vcp6o8Ek09j6f?_I)pVjs_dtL-XiC1r z`?C;YIuH69j9YD*%RzjMCTwCjzN4>@FHr__*8FE~FN&UYHyGa#qCyt9X}f{AH8J&0PvF`t)R$fK(m3mb?ESbR0CKB`vBlo%P(`FIVFtZ9R5;zAZvabKXc8@ z5%rHF(zhbhg&|tNNH|+#M@RX6105so99-H)h}k)8H1vm%APNTnD1U?KgM9!&oEKQ| zDDunIbnN_hoeD8-NW(X9U`3sgXE$<|=aNGD!0(lTkqXt~lxlX`l&;V(*_!g^s(QS_fy zo+CTtym)by3*Ap-U2LI#KoJWMmIaC2dA%1C*~^aka++(%p^IU$ z)d8#Nao!vc&rg>p)onV0zKw1k8Nm9B3kC87Zk=lj^IN09nVo1QB`gjYOTjU`1% zC=V>7#G0b2Qj32-husU)OmJ%gxK5yCu_lOZ$N3y{mh1CSKW|O8d^@*PMo&;Tr(XRU zY~q0}1g<80o1b0Z~NdQ=y0!R74LI0uqdqWkk^h2G*wsV~fSG^(Ya&Y)C{| zXV4#IcX?y@AoySL8+)LU@CI*vA5dh(6#@6vi9GH5$Lb(x`bR>6zfaraFSM58e7^*3 zv8tNd02BhxAuSvZyRb1ERQsSK-`sNsAsM_K>5w5Ad5AuK0a2Lw7W-fIN#s^}z*=y? ziqFW73c;3;YJ~~l5okujeJHiSlk?FDmxwG(LhvA+zR7{k2dUc_Duyiw+WzQ9v}TA8 z|09*1PuVug&5ja(hCt5mLx_7c7}|feEBrH##f!wzU8px1Btc~!mf;j?O(OrMG8I8rbXA1%AL1QtgFQb;@D z2pvCHsALqn{_Uq1oUZ|3ELvv)!YZqrT}EZ}@u@0pt6Ui8(M>k9^^>!&7_IL_6+&ds zT}wxW%de>|Y6(umru#p8cBPLsNZ8;$PSV_vOjYLlZPXtxp}mI2-qiZJb!3N86bW=e z%1ALeIn5&>mH~fM6w*&PB0r8c-~Qyn3<_-Zf)8>0RPQiz)jr&IxYJe=lH{tWtBLm6 zW9n%WpZeOd6yTU^IHhA&uNGK-$nq zyK_CP+8X`aUjs1|fS^g$m{J3%zn;gY{Gzaz|Et7ZaDJzrD8zSM%YkO+Wcp6<$OaRr z~J(0Si8>Pul{QTPM#KoXJ z6zN*rXS)|C4-l8ow{+Z`h%$b;&la!4L}{dJ`>cbCZWUY%##Ub7L%vv~;{xatoQE(& z;Y`lg;*ad&W>1Hup!>8J8xCl59718g`SBSNBp|b!&dmqD9WV~^KogQEA#QJ*dkI<@ z_`AemN0ft+#l7KKR%(H;S+95+yWt6>S$Wu`9DYYkP}>p#%o9mHV(%S7&^xcyF9T(8 zqEBX6-hl6^>pUn{Z05uvZpO_9?Hky*c4Sad2;&A8jHqRwB4exa+7_PlcW+U8zis0s zx?L}-u0O0;W4m5{*$U4a!fX|k8>_FJzFkZC>-Wa?>lHU1Z94rzc<=gKySH=iRWUYL ze#w+$`H>?h=tZMmh+i7ckyWk?lXJLYKltnn`@R)z<#tv&&V#vEYBWu$=QgU&=-Q*0 z)ao*NvYHmTZrwVPks*=D*>v&BT_|b;a}t83aOCG6f=z#c7K8}5$hVqr8nf);2;4}v zotdD3z$rrXzo$u>LW$YRv{3!cky~8+Jk+{~vwX1Ma=xb=frAj0ZuN6EQOyDqOiTKcm$ z`!c%bXQ&dbhjpl1(N+b%DxdNR*r#15pIshHLLD`RrmLKHzJ*ED7tJMZp5S!APmFoO z%uJZeh~mM}Q$Vq_#e?5e(JH1iwIh?U8LmWy%Imu#DDZ};c2bm0+d=x2`n!VYOx?e! z1lyRbAXTkSZO$JJc9Y&lHxJbBbiT+5$JOC=j^4{;N|;t9<4A7oz* zd~!H?Nnd{>2ZxsaYu3mYFZPBM!TvGizR)%gVF?7Rxmkk!=#e9&yZGINHkuT=(eGSx zPS&pfTue4lC(n+R=Yo)(wRxH)fvz=G+N3HfV$J{wC!L%p7HGE%TbTM7OAyigFsqt zSdz*?`7ZGx$5(Md~+bv)3ZZI;5*r-OaAzFl<(2y6K=6A3o|W6 zOsL+}nkTHb3#5EMyvZy2gW=o6`_DMzN)1ha=Uc&QoJP~I=q02?50hHd&p0f$6rR>d zIQkcIY5jP~p7kYvS@~<+jS`OQt@*r^Hw+upL{*wLeBK~Bx4vE-67H(7OW>aAaWHj? z_t&{sF*lnr0#wDPOvEZyemDwmD(JdT>8TE_i1GY9Vfe_VYDJe2Jhw)RrdMkKVoEffh6 zp;Ag^DO<@>$&xkMmqBT@w#mMfeXnHSS}9@dl4X*Z?CThYG2iv*|Ng)K_s#G3z9q~& z&;8u@IoG+)IoI*^^){zmea$l*9A%U_kf)zIBoBd>GWL%mjTc>ryl;|^j`qIhvehPT}uqfJeF_hPS# z>7W6C+BkALnLJWfm7jGe`$4uSVhq4Ef|6(#GI3jb#Lho?tkgq zN2L=pV%fa)J+`Z9*rV_uUv@TO9rPlU~@Q#4Suv6?^n)Dy>{spF>A()P6{ zn%D)Yp(=?w(eg(XsKmOCefxM!Zo(@-a3)8W*E1~FY$$sem*BygNqBQ1#gp%n6}fZN zh$8gMN1@@A;jQsnn^Gn>-W_{fdd`VWVja$$7ObIS`+qB-8)8RR+UrZm`8QZ=;T!lHqS<_IS zxQuJ|_a;`Kj`noK$2*@g`kcxm3troOW(gkh7`RmBCZq8LU0RWn<`Wl`pP)vf$A5VD z%-s_PUBPj&<)^&`WU&rjWe*7`=A~o~L|ZlqDn!@j$KS$WfAT-_+6B+!6z>Q2HM8by zB$>=%#r6|yN91&zM;?)xYxh~b7~14D{s>;qP#`j;R8H3L$us7Mm+XsfR!7)|+&-oh zu&FC|=pN0cSM;gCm`hsHpBNt>4)d;ixen!Cw_GP}e3>TKrZdG&-`aj`Rmy+qwV7T} zF9482S9o5Y7$4f_rkj5?Sv%uGO6g%4t*3&@D_yOb{>5$9%@tf{vai=KO{cud|8OMw za!*ZT>HS@*8T(WHEz$~|1|=5W94eIhY_Ce}z<2d`Uknku%-j&Zz2Ernj*GF)Ln%k& z(2pGXK$2bDj)wL{Yz=m$b9B%CanY3|fx_>b^qwzfOS(>CbXZ$%l9+vhmi1#=sVY#`YMCw=?s(=kn7@n#+E>P|?8WMS${2gu!vO2d zKYu12Z0GfAB-Z?u{U&amE#^molP^8rv+Fsh!{k7zV!IrkW6pGOp!<+b$G}zQ9KCd< z1D!1*@nvhcx2SgM8==e0=H|46IVaoPqRD=HaR4Ryzw#Z4;d$wa^30s~kPR&@>d3%h-1* z_xiUcD;d``&69yaO)ZCreWC98pN1D=eZ0&I6Re(*nUvt?*ZXtSgqnHSeiqrr{IMVv z)zek#7BT1?#PU#wcn^N~Zouq2T}tzqdZBmX1Z%~Xd|{UdWY<8oGvYag_)uK@_{HI} zYR`j9k}Bs|CQZuEBg9QNGg`C8{l`)>(;NLGSp!<+tFO&W$}H)ls(TK~=^gB7{RZxE4?O%peL;1En$Bk|t-R&66WujMA^mI>3^;whdw z{ax*hxtTJ@kg9|~lY`8r;@PqN>2Ce}Osme7jMfhuH#8?}+#Pg+U!dgY|Jjzh2O2ta zw5Md(p#_R7xl7iJOf%YT6JHnO)H6EokHt_waQP`BnSdMkxALF3gCqWV)QmZgz~Wrm za9B|O5!TezXpgB;Lv(n(bNMsS;@N*{G}pC1lK$+ue5;MgX5~JIewBieW7gN%*60Qs z3M6E-TOX(xE~RJSlUwZ03x@s4s>Xf`s~BDJkV`(-&8wo>P%woyIydf1_8(kJ@6frz zEOFZo2{IzE;*>=ZEetB%BDwu5RcCw7#`^eF8+T7nH;}XCn*=>()s}`PC8e4(rw?mQL@3OScqbQibp;Aripvxf zi`_Tm;$Ia)D{s$k&Pg2~C9cuTdHI)QBi7uY6my1>Hno(Et$LbnQ+gLeyyst%DHb1A z|8V*#%+Fw)Ql|;qd1KJqs0FSyK7dyVAca<@B85s$lqxW>kPb#v1-_ZKtRBMFQI3Rr zx=&urW}MS=f7?`E;MQEN>lx68Y?x#a>p<@dUAGJZUea#dTH56qVd}sL7Jo5>E1<@+ zY@=2?Y^-)$jC7-+CsXiH^(;jl2{N{Z_0VEdcroeWd0 z413y}#Y^}a?BSbU2ngZcy_Wu`7Ys;%C+!XDAwK?OuV`V^a#*z#$ThU1!-I{Uu&T+y zmVbHK0(H^$$OCQ5zg&dRGC6#RILIfKac(-g_sYTWIs0~+N9&Z4{fs$nt9tAE8#0kx zO}#$`cMi6VDyyg&`sYvna7-AhHHz1=oNqKTUpha`y%8KjFn^I+-Ubr30>g%GVFD~0KIIEcnFxaN_G$$K~TJzOp%5z_p? zdP+y;f#LimnO6}zT3%TywY=&fu{VN(SxJ~n6%_Thzi=3E(e1`KT1AqTpRT0A3~~2m zu$je$Nflmb*5i*)-PXG1&Z3z2q5DTg!)U)(Dc2F^8wRtfPe*4{`S59hbt9u|b!Ojd zipV&t*A6mGs}8}(y?p7d)FLkHCyFvQ8}%B|QfVRg+0|0^P4l?jz%WY}PGbFN^!HwdDY<FymS)^4BYN>O7{FLsqK=0 z-;`7s6##y$S-#2DxbbHD_i6HieudmEX9!HeAtqMv=VgWI9%83^9T8dO@P99xxcnIT zLfBmbeeXyHB>KIg%ik!OQ)N|(KTCA{x{(_#RhS0MDc{c=QOr+Jarv}cVbt@0yGhCD zs=DcleX#+B2=mMrzg~P3CstU7p|C*i=`O$E`re6)PUB@xei$cDAWBK`TCR}E3k848 zwdHW4N54F-(9_Ma{dfp z`yvBC8`@x_V)P)hVflvnN9(<;F;|Ck`}cn;O~&~Ku(HG<1lV{gQ1{^3Rt7#519fG9 zYSVRJpH*B+N!}m6FG;>&*=n4hhk$w~@Lrk6tZh2l073p44d9diws+}E6tB}Vw0>s&jf4m-&Yc8#flZ6wWTVM zj(IV(4yWK|eN}t$WV_hq%kc~4v`b4=v1ZNKze#umkKWITf4hThZ0VIO@_@7mu&k7E zX-j_BdW~0)QX}a(*7XnE?j?OuHea<%&Cu@ zN~LYy-tHHHo(g8iUdaq@$2ED4FXDv*|DRXPLKqpkzkulvshbTUr?YLRyRYa@n+ME| z{b}%`VPmNmDlm_wx9ah}GUz|3WBzSf>#WH;!tMY$UnE?l1#7B2hVi|t2@N@9X5Wv& z1TPwXTa!sT>G96%#g7{Z@Y1U7{Xv72o_3ktR0LuddTCo%n4f?r`W9i+X(uPIwen_4}@H5nhaY*quPv7jd!CQZx? zPXvRmxGW1@Cfp?uH|X6Z(K%`2@0@Jy;TB2I#g>+c7un-xTq%^M4{ zL3;^F1_qI=iHrZYarECC@ru#2e=M1F zK2^e8CdB`Hk!CYWfzLL}G;d|osM>}YkIS{_?@AqrW@-o-t+PB=~_L~pivF(&BDRYYw zZUTd^RxU}c7{shZLvd5%kV&m)cN1T15@*Plws>8=DwBeaFhc^SFI?t_ zV{%EZF|Tfvvah7`Es!0L{#y6_b=Q}qb=%T)vkxqZ&c>Ra9^3#pLsMtl@|!S^H6vUb z1=F*0?sF7hQiyisVf}r}H5aaQdN3WV6NZXnOT(nHgmR3QVP#`_s?sn6DMz<(a`mCe zYIUdaqWT=GXIEt%Yfw;dtbLxEA>P4U>+IQ+|Lo#CM<0@N7l@YmN2S)&GV#L)=8K^| zm#=y#Zc3-|U6QCg>u`hO03ZEsUBIU&OT_S#~aTY)E)tGC)}XVh7aA6cnd7+IrQ?FOQ;wk0h3*~wyj-b8vC$HmF)kH(;hr{-&OJf-HmolE*_OQ~}yRsAjU;4pdq@ z4on?K%h8Xg(V1;+4yRQGUQXRv?BLTM_dPF9Y2(N_>)ifWE5Y6`QWP@~FogDwb;+~| zt$6U@=TD#h1bX^$m~)If4L9YMmjvx-|IwvgPkJ<5%8Kf6W;$TBu?3p>@uu69ebi3X zcylIbr<7`^YKe*Q)2hoRV`N6YwP;q|^YPv?NfY#f;jAo4(+DleHx$qK zDWqI+aXbva=aAjH%6nnD z7F}|*>Kk!y?@u5zA11}(Ep^Zrl#rv=(6-KHymwj~^EW=}tGqAR6MEYgiXSf`nkoA| z+ML~83*e#m=`-FuKDtZFNk6_s_GlyN;t2Kv9{v1_cI-rGhd%xEP^;&bq|4qo3!83G zBZY)zdm95%(?>?0{}`#34PwtoA|GC)>+YYA8$*fGiMw<$kBcMm398CZckI*8(NnzN zu@6Qa;)t|@(gvn{Q^nHXd3MItw*FhCoFYxCD@NQ(nEOuejFe?DD9o>46({PxVPN#O zeDWoC_uD0`Az6%xx|X7$?O3;5LK!Ppo|vd3op7Z2MWI8!%~D$7+5Gkl*`Ytf4jgU9 zCe_3No~SN+_nzoame1ViG&3o1$4qv@P?S5Oi>K2g@6nF_Xalhlxh z(cz);4<9}>i0|3+lT&|4xNCad0J~chQ!oGO-Pa20IlU=whg%FNt=2313jRiEY+eOD z{ZU_j|K5rL?x;b{?=2~c%AHiN)w#dW1Ds08I)RS5FYCrTp6`PKb)c5E<9XQ0ppZ3! z_WO^M4w_7m5?pWslftbFlywa4{Ci8Pk*VCXb*q7($J>2B>D6d}E1Pt?l!K*Qg^t(`K)z}`CyggcD!#-piNkz3-{jo7* zVC#v4c2pI&i6pxm?U!#$0%sJAk>urfZ2C@2nrj!WS;eO2ebEb4dYoXrj_h4sfFptN zyro3{b8)V*Z*mo`WI@)aPrG)6^cCdxiYitIAu^Wk!IQckCt+_JFO%kXj^1v%`1iA* zNX@yScCMvm?m14G*R9L^3n|pijy}GUjc0HM`ZG!mkjT`&a^(g0;h8kJPl%MpL0YYD znc(lW&*qQ)5&oOZpO3?245S=~EEx-S;Iut?df#l?C&)m7kdob7%Fibo4J!|7 zKOEn+Ga>f5$fX{qpkk18sfMJ+sghV+zpDka)x%TWUM8~7#e%bI=0QWlr~zdGDz8F3 z9_SV`vb`1@&`40NM<$$jq@A7nh)%~+g|oy@L{TDVDsJE6-f3&N#|i+$XYl*`1|?_b zGh8HxQ?Sr+9cH09Gki8;Ktc(^f;iS{u?#$Tm z68ozzUUXSdW0?cf*}(UkPMX9Ld6+Y?eM@_)$7LpF+o|uDX%u?|S733TUHQnI6L@9c z`>kwP09!DK-g(lXInAx68rX25NCOuyZ*sJnY|*5Yjdpgacek+iCTc~o|K%**1xJh& zv3pTfBI((2ICh}m+^2ZBkuWKnlAbEJA5%ZYtp`HT76s3k&|9{c%(({UJ3ZLIz`P>n zX=L&7*r7r)b@dKT`4IEAyq|LmY-)c|Q)lmmJqr0V?)_v&Wz+iyIKm+z0cH1JK@m6W zLktg{lhIyCADMptiNx{o<0aP<0R#^D`Q9m1{#_uC*x{sAa`Wyc#)O!YE}ylS+LKYg zi1Tn#W#u7uP8*_FpXa?8Oa;2pQ{}xS-IsKI`egsC%0ztM&hr=Y3>OU*iC?~~R^4yI zeV|CF9ji}^I{n%AY&V~*6Nj`Ef+@ZZmvXcEXtd_3DLxrK zFxcNweE(%^EJ}5*h2?e>KNZreRY56?^LGnF>=@G2l_&v^KUqJQF!fh{&)EZ>YQd4kaUcASAO&L#EfZQs zF}B5BbD6*XwX)>)LL=EjE)OVfj04m;t!VeLhjDf*U1c|OL{oA_b*Ca;^RKV3HT@ZY z5UZ=cJ{!QT%j1}5=^}~6Xu98MBr8X{M|M+6*G|1@;Q8^wtCYV;dWI<%gU=~*#MQ6P z8&_2>*|=e4R?Ba<{K*tsa`pDe8ICq)hYER%h^Mdj2_+>IkT?6CC_NxfHTxC!_2?3V z^lvnhXYUzNQAy*MF5~G^Bm=3a<`<9eg~;>3Abv8Tp?0$#3f*sD zGzc7oV8u*-D7sO~_7;Dt|s^OS7z+i_8blJ-=bAlrdAIDGq`SSRxhT%=S<~?}4 z#5)^u4~WYRrDpoL=g`Nm-)~kR`&GZ+Br{Z@kDP z%AeY@$yt~2S$QW?4||%eZK+!A(?)Yil4mr{k7d~xRk{bBX@b{JgRd=X{M3`KN?r|A zWU*#V)~~M@`}tuY$~P!qp^0#?9vidYT-ck?Z_@3g z+!?p=V>j-4wD#90W{&bL0F%2$2FOft#lweXfg^aHGgZ2KoM+7sTi;!Brb8y=9+EogAgqT(YtT!`0N9N2L z63m-lha>HPk5|&-o*y*!>HXetz247=uxHCs!z}{D8LmZ8$)d@!62SzB(pUG!ra=rXbM=GBs*YAjw zJvmgqkm;orouBEGd-*-Z-C#W;WZa}kZ=1X4+re0MC%*aHU}M{`e!Y=pFdWe$dSL3! zOHmYFC_mON=%+|dDXdh7hgvliX-DdwD|W<26snvX8@aOrnIuQr<=;hfO2uCeumHmF z4>$ZoxqUysWecA%V)9Cfj7WY*O^s1&^9S>xl?jolo*M5a4-c!*Rvo!%%aSu{`!2>M za&IZya;Qc29rBdY&3kLZie7$&zWk;qPL<}=(XS3hclr$#ui>EyW)Qf9>04qM|k6-^9ZL%+RY%xEn&8MX{|0o{{0nv+ae#LD@aQhN|Hec#P`G$`2`?a|;DG zJ&E>UeAVQT=zxlKwpqu^)8zF69dABdj(+6FDi;*qJl@!>r;&WH-SgyF_b=b@(2A($ zBS5-LGW*?FWq^81|C{${E58#&o7AC^m+jeX*0_>#a!V6LGgud^m0q*1eaEI4q zxGfvrxW(DHA3K(GU(-|x-50MnS%Cln=5lV|Ki<+&oG@^7yHZo@`$ZhKk8)!M0zHd_MRJ0OzaO(^lHlTh#c;$9m30s>VqJ&^%};)y+{rE$C4B>JxLXEMETg`UK-PHcsEYb!$hSY36^JAm8Kh2?5Q32)r!~ z%+bo*0)I=7|uxr93gzaZPyUGDnu=M>*5pcQdh2|Tl77XP>Fn!0LN$sD9ESh#mgqKld$4A z_~^blNcz4C7>;Ps8(py?fp247qMB53E#oRdgT*~^`CWUGV9JzVH8Ixy`M7s}t0wVF@<#l$mm%f@FQw_9H6~C?@0;#2CmxYhol@;io`p;LHG^h+ ztZyBt$+IK3jSarGVP+Wc2pVR!g!38IsB9r{>~@N~(cS|DlQ4BlS0?vJRhr)_OySjt zWZn2z`R_d*Mjsp!5K1bTB0;N}Q$#FeFG@f`;!bTi1hujKm(GeDMkFpjn{;!frPQZY z{X-;G@LxbPLbNIEblqvWo|4NhDxQIYUxJqyZfY$#P?={sM9>!i?-mo&i}vl`8F}e} z+yz4BJSA#IPiIG^Nj*RwZEC{4d$=PVvF~+M2bOK4)p@@8hct53;BII<8(X$F2&LU5 zMppu7@PFu1caNlrqH`aEaU#MyyvVyyg^EONg=$|`v70|9Ki0>5Gj=r3#Qn!WI|iYS ze{F6T>dGaZYmP+o+8JGy3DcHKHP7j~T>dO z`UGEauw*=7&rFuRwbo&s=kW;k5jx0Uw3~lRSI@m|uw-sty_oOzaz=J4Fqgp~Dr(K~^ z4)Ak9tF56G4N^-l1pZDxNmAr&Q>dO;qt z-XuPA8{+62C7NMh>(^75iwn7~dOH&cT*+h*&t**n>Q)+M>Jj2xtAS!<#k3qk4IT z8Cx95?TUm+12?TyFX&lxo+&Dn;`z84VY0{N)7|#*tziP-wFZsCc}ldS1mrhU)e zvX>g(FqF(l8@Q&faIly)=e9}8(J$k{^5&3QdT4-_(+MeZdF&AKl@$>)I@+KA^1*EC znW!l<@2E`4v5IZTvC_OtF|zso>izc;Fp|*@2dLGOIaG4^#*xASutVBcj}?)JKk@m1 z7Xm%&ce6>Hym)rVX(Z;>X{0EOgL7&?jom4jxd($8`Rl66dy$pgvsI?;QF2euE)XSG zMpJY3LCxK{<4j~^EHbS58j8b_eqeU2?6a52hOR;EED)-J1NMHeOoXIe0>$nZj1%b2 z*oY|kk5p2sXh}%=Hxa8BKt~hrm|Y7eSuk!Y-Cw?ZIUVsZ3=I>PjxlE0)(8=gCMq?x zq?=p%mvzgVcmv^Qj5E<2u}c_R4kBb{mh`%rd>*B3{qWSSl`Lwo`4{iR;Nz2mk~whI zCO(#qfNBeWug}oIn?Xz7%slWb{kX!x_`SKIcrz11PxzuCdhgLo*!*DiHueXR%KvIA)q{8_D|x#|Tf1&7IWm^yC$D*N*vWU}UDr0OjyVFym< zQ>|m$0X^rFHYw4?Zo6ETbR#{qT0o+vfo$DwJvQx3&)>JVE%UwiQdj-fjLEjl{)bO@ z&RAyJP+I*4fgnAG6&%pq{`+w$k1@S8v*35hk?)8!pMhx;^Ux`K31r=`rOw0FfWpqc zt#qj}*c#W-&z9f;f-oQzU8(j|ex{_u=`bRV0X~q|K_}xvr&MI@%(B-6Q_Kh)oIooA z>LgI#K#-?l2OWF}ywL~q3k`7Plv9O{gJ-SO!evenS*Bk92^=>`fNl91>gh~sf3#^C zzb8#dx4WLHNCqJB=R6qIVz0rHtg(|vUG+#57G zXerL~ACJNvf#rl50b82tBA#02SE6R5L`nQ~$63`e+;QM% zRzT+PG_u|*bU$klZNd6Kj^SVs$zgpvhfCR3eDUDylp<$m<+e_D$YTQ`&#G=&&anxM z1lT2KX22VKPE|PT>^sM$oF(7v)vUn&>N{Rv+3s@E)1Q|VZ5zT)=j%?^ zsB0(+Z8RfG_6TqL;6HO2w4-@P(X@Q4U1R3g{+jj&Zo`O)DpXUPhIe$?6k4_JCJYmJ zBMGpdr}gC6YF=s126)Rz;WQ!k<0anltl)uoJDZq8nWZmSsK%An#+I;ZRZ@Q9A#WA% z%HPkAAabsMdeBJ(OgN(noMBNP@VQ-ODo07Xd6817UzUe0T~TfD%gHjzA}$Ie1o{|Ap+)qOfoy1jLnMDI14s}QC)xA;MzD}JpMk|xY#)U2>bBF5#73X=-T~wM>!yXZB zf=mS~Rg7dK<*vd`)vz?^hi#&``N3zMESXjUU#o7Z7ctjg$xCDilLPFB%6A$nFwRCu zv`m$hXTJ=ma$ZnP6#v83UhM>2Uq&YZ$q>T0LI*{v?fI3@qmBT;`YGA2E_c|fjP8YR z+aqNeayw5CS?V%>ev4=AI*Ie%$a$u(Mv8&|=8FRT4G>Nd7z^H_p58D-KV5L=er^NE z8X>(^vVaJn^&}AacgfPR$nvcbth3L_!T9?hB-d%w>Z;MWSBkh{hKyIwB<8ZD^NoQN_r!}qu=pE}zRykzDX%z(UtUmi|q%_rrj z-T^)wQ2GJ3#1i$7ucrJ1w1f1)hBq$!4iu8Xtz+-6v5jq3+;10xyiR?B9QVYszeN@5 z3i)q7=Xp%rNVjZ47J^^;#=9x`o%nPJy1od^AzgDf-4D}=RKD91)!z_>VXW=`7??9X z%kdt&34-y3CZIKMENx}yI z`D2Fu{AsF8&8b5T@V?j)x41NN7I0K=s-suY2lNl$wD zACOW@X1CO)XbHm}#psTA=j*1I$`>dE(q8(eOmvFWY%Y+OmS_l{AM?)UmAgkH@j9wH z64*A-2NBGG!LUN*iM>VOB`)y!%YAIqm-n!LK`K@9>ZcoBuK|fTr;L=3U46M{iy3&- zzHhfl@}+Lb9B587ZUv6m|MS?k3@X3O#E_Q*5q%>Am_$vN^BYA@rz+K|c}kd3OLga7 zUw;m9?^wlc{fopd0J=0~e>>)&sz=TTY1-G`cmF>-X$^i7pj)`oYyDo3vVe+0o@!Rv zy27*7MQ_XtE$v7QH_nG((iPV_|4T3u1TD@oVlfF`u(+~ZvfzsM& z_BlUA007U`v}-33*dr}gf1xFH*ebV|h*TB|=p7X!o?NhjYwd-H)!o-xl#Qz+EuMK# zU%6V*aNQits8W93R1!t2LyxpCx0Oh*8c9kimkC)S(&uy)dyGC((#2P5Kp(Ri()4l# zrDb{(pGY)Gau6-$=3aUE_tJ(@!dZ==}sS4t1av6EA*)BOHLV7#6z5eiN)NCwwKDA%2INe2GnwQdVLBoOi)XYpy2Pu409hBVv{oPG@ zQ1@yqoCNQ}mC{SR8S#o~(ntJ{zeJGd`dM?qWM;5_-8OW1&42g5|ML>O_9gir&j-ut z`K+mj!;O1#NJ)jb)4qo`fh4M(r!RNav3AM%AHO|$Us(5E!OC|oga5-4A1NkE>5OFe z?%g|IF1`>!$dd#W&tW^(+@ceG&GaU9Ck)kSSRWv6>ONE&cVb-K33=)*aX<81weOe6 z|H@{SvSvzYGZa<{`@V%}Csbp}blZ6zKXWOmaA`y{)DT`Gou^e8iM!-&m8|{S;CF;{ z0Je#ss$(AK?{DBYOA{%aSSM-Y__}${aI>kVYn?#f5Y%8?-*rmIZa-N{a~b%sayo=4 z{kPe8NpviqW%hNHxRRfWXZTW;{}W~dOU{0XjLJ^3_~0*3XLb3K(Eo+z$z}4)as|?m zjgwXD->d&^T_n;(ofD<=FL!80AI&8B_Oa-hj~_%<;>&+uUNR?+sP6OW=NYu~oa-QK z-5fYRzqZGcet%yk>y2ez8+wuep~&Aa%rQ%sdb9DBIVPw z<1|;hkKCEeX*Y%D6C0TdeYSSN>P~Cfs`*;wJGvF>Ei#8@4H1RLM9tumP#q}*{iMBr z$~R*`9HE_lPC&x!k#*tVK`0o!Rb|ZfXO=d#*lm3ac@PszCsf3sg?hO|@Frr?9;BP> zSb=Wn0BA%pkh;@61G;Jc4pw{iCCNoIHHghQK7|J|D7^Q{j_g&O8hQKn5{)Ztl4+F& z0{fSj$G2x&{cSBmi)~EDgVIqo`9(K(^v0GB*M*9+boAF*Uw*NkE$JUq7So1bVYK*Q zNulIVmW5ke1W#WE_S`!Rl(-4;5zkc3EddJ9n`lP4UH{(w)LY90FmF0tSF7LdZVhyG z#DZ3QN#;cWcc3Q+`q6*e-j3{p#43m1S>804m2ZfLzpzR6B6nL=0rA+cqvy?^&qYd@ z9Vb3-nmO#5=%%rBrb<4dly28O%+#~WEI8kJ<%)TGAG`idWjh=l+oVDcw$)AKL{Flb zDXWCR;4>0SJ~FiFMmYLSDR&l`Y(>;MFir9gxlN`a6UPUr zcw}A;E>gx=#sdOtgwQS_L4g|QV{+FdZlK<@%rO|kV{O&Qp8V6OcQ6F=tvkN#ufMS9 z<~>e90nK7a&!9K`+tn3==_V+ol&oIyw?4UV5n}lV!n>(=gAE&X+;1#uxPCtxd(?9YqnK=Tk1+bgQbV^K<&|V%FQq5z)~%CppSrP~ zPj!n*n8Z13g@z1$+0~n+)eSwS<00zN(}*N_NeaF?v1l2`Dq2mgSL5-^ciXKqM?I`s zbM|ehKPOrhaYzG-9<^_bzuC_W=H5>egs|xf+I;D>7p~gj&(;1DVF!9WxcC`L;T3QSFiv?Ul}e+TcleRIuuLRF%664G8MmsgZalL><4AS7w}u)u@{J_e51 zqo@R#IOQk!2@F(Jh>wfAvsbnXB)_7Z3Y+^>Wc|fUkrV43G|AP zaqPUiF-37AyKfCCJ%C%Vepey=^b>g&b#C5nH@pcioV(ck`Sb3v3hlPH04eIq88H!} z>>Oqv6F4VugpNUOLV9?%)8}~sJ?iLQ5j-Mw&PXVbLn(BGy-fMksTx%0#Ozc_@$^=O z2FkOLkR&J_>OsN%pd4{}xz?|bwa6?!*r0JP5ePIuP$2`4BMA$GxjLb`5P9;AH_z?L z%WZ~e7iI)rOm7m(%{C1K68oV9kh1d(KAj^;fSTFBgi2L59Yz|fUpmIZg zL-)}W{y9#+PYv#4$B08H9H&Hk&gIup-#zUQd;HsBkTy&)gnHf4T!$*j=G{UVU0uDs7rDh7Py) zp5vyBZz9ELD#tIHZ6L&u+D8%t3PduzPnXEOaIh&ov-B!klCfvr%lLIH_u`EH4Y9uI zY4(_>qb?XJ=cbK~I?7(gD`BRdek~nfJCYED+7;#eVIl(hF-@4z(U^8IOI=Ml{Zf(h zYY0DdNqL`EQo09G#M>MRE>Pu@Br)tu-!H{uIuAhz3fnE{M)2O+hzZTC*viVYI1Tj^ zQ`9Io^f1@=UpRjJ4u_z;WnVvR6LHj(39%zM2NB4C@OG(Ssv7#e_5r;?8Y3h?`TWtN zx1gyZJdi3H^tI$yhnf@W$LaZ{FDHF{eF@D!mq9YbWWQ4(?~wkXL2#=q>?Vu2BCR=Tqp_I@Ve`aIX^wL#4F_79g z@Y5MX5Z+$_u89d8UmMz*hMIX5i$@E`+H{-@8ReW12oprl zar0}8wQ2WcF+IFBk*@0IYI_1LWk@g*qt#7+o*^`0A((T(Z#EPC7|GBq@mQo0XJ;3N zpCS~JzntwNLn^Bg;KXZi7S*-|Jz*B+ma9N9i589A9xZ~nkSn%_15BV>qk~K&@%@DX zI)ngJ(jmK^JHK^T^w{Lb#2g`f7<5u5Dugp{$YH(MMjo>E;19fFj@m6rUH^8zVbE+j z#`>^pOapMBC*nfT4+gE-kcQbfVUrG+QK0Q@s|RU+XoUtvMp_MgBa@qq3;U82zFQOb8*yAS&14jDTuLgcur{n7fnN9@H!obdP~eX_3~3aY2O-W0im_0hKUJxV{9Y zOpE8)ZxpuZPhh04@{=eJ#`W!Pm}cbDY?pUjNr_=aHp${0%%|`9t5|d)er8wXw}SN> zH<~`{`t|EAEqSJbwIm5Mw|MF*j6&O)JOy8dFJ2fBS7dR5#)D1pWg~1Du7nLCbTv45%(*)Vy2u z@OVM>SDYc01;!c7x$1a@D9>+DU&4Th^SFB8j(CWaX>8F|RSf|8$gytMjU6YDV2thm z+vOJ%@D@{eG##ZkYgt=Nk98;9#Mvu))6|gg>ChVt_8g&NRkB7{ai>qMj0IHe(K2TT%QEBa`NDOY<& z_TWm$a`Xt|hmkVeA%y`iMyVcx5~=$x3Gh~Llc3Xogs10Uy}O{jm}=0ti0A;kWpaY`w}w8 zr|a@dqwy4?J+~9b3enlUzdAI;SHz3ziQy|SxYn+{vUnrPlr;>&B>Vkb4b}ugT8ky;U`qH}qe4yFW(GxttEMZ& z|MYbS8&2|G+pY{L&iy&ih$Q@XrVLR^_z0q+{ogQVILLP znwti_j)!f&+tercgW1xgc41ud=~Jin54IA+zUi?CVIJC^S^a)~$$G^F7Ou4kALsxD zr9o)8_UBrE{L{NN?_ z$AYX>(4rb6<(N4#tW`cPGYbjf-?_z8Q&YOUPffRkwOi9=N=&LYVdjz&#w8Y$y|y+x zw_{N7OKnE~S33@2Mynepv-%C*UAeJIS&)GE5_|tgy#rr2Jcg#d%aT`n4NLSe*DhUC zHY(IXDPS}28!@yqeAb-9;W^e6{NpF@g-+2g?>?JnBPy+!qK0yHV|q!#%@gm}N4?)P zGdVbZNlHdW=5h<=H6cSR-~Z|J=g^ABj~{!quCyO;C#Fdl&0utA%BvE$k#dM#E^PAm z^!-~_op{*q`!;C?l0x_)oaUAobHn%9>|3h%3qF) z_yVunlGnbhAyoXj(Sm{srgH=c?;FL2ZNfu4JCgxn&`18|;(ubnz~c6_fJ%-vCI0v& zdg-i#=7tUGq10l+4L$dk>sf~iMarpTDqu5h!^k3I{}GWDJhbM~qeqhpg;4Qp3h6bXqnB{s0skYLfv;sy_m*(y&SDMq zYCu-_sIC5j3LU~L?-uNs`JWIxeev(uDH2mldq3_KlaSd2K~dsdku=De^Q+dn!Uz1{(rmQvNA*r15;uk z&}}>*45|sUQJ+0}6oUx}yFo+1C8fVgS2roQeHaj6{dU12+G;6= zAKwQjT}c_3XW>3cD6J-tU^@FKrKLb{D(~+>3s)QVR9i}m2 zww$-WRaT}oXWAY~(%K+#`iN?KD+V~e8~@P1>BijucG|;l1b}+bgmiALAFue?qlcbg z!m-UY@^ze4i0vE83`KPxeIf$EK6TEMI)w4X&1$bv3QtteQ@CojF5G#eCARHiRNO7 z2F1}Q5cY&5=celxDEe=-HiEyDtP2KnWS-mU5*u6J`|R_&DQ*EewfOgwGv?R!Ch0>j zP|Uin4Xc{H=q8?s|KQ~e#lm)j*e)Q5gYC=PVAnEbhNR*VO?BAp5BS)bR9mv-8;K+J zriM71gf{95FeEE&e;$T|pdYQ5%ftF8&1I=S_r{GndkWj&4T0xJV*1s>B$UxQaH@E9 z(&~7urZXuj^`Tb;ewSwu4Z~(3|Gxr3wGPZdJ**4uPi$ZccG-&|)mXys2q`p?BnPnV z*%$F6I7*}Q4ZGF5vwi6=pb4BkY@+vh%~?h>r;82Y*x^FuTH zW8NUpFtOZ-0W7P`ZKXF$3fy=DEMz3RZQC($6_Wh)*+L#LT9lkQXcsk`QEqq3->=IL z4`1-poq9vszSSys3D<@Fc0FeaY5oR%rG111FEQp~9}ZH3{vn%z)?879U+OCVuc5tj z0hP!S7$1h8y8y*LpN9`uI0TQBA~;Qlor|3%26U$>U>+EF9?ZQtiJIjEves|ttNRUt z{E)xwHbmZtod@%C-}Q7~w$H){AqxZuTCQ#Au=N{61jas-4Q~K#{xcR$X`{Ro-Mn9D zWY4*Jn@bSnOsYNPjFozOqw`p7L{N~{6~{XS+N64*P6ozOyHHK*D;t5%0A^!n0lqb- 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"X{VjQo_Ng!V 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 Tz)qBitirX+TB>=M?mziog { // create dispute resolver with callers account let initialDisputeResolver = { ...disputeResolver }; initialDisputeResolver.admin = disputeResolverSigner.address; - initialDisputeResolver.operator = disputeResolverSigner.address; + initialDisputeResolver.assistant = disputeResolverSigner.address; initialDisputeResolver.clerk = disputeResolverSigner.address; tx = await accountHandler @@ -98,7 +98,7 @@ const createDisputeResolver = async (path) => { // this is primarily used when one does not have access to private key of dispute resolver or it does not exist (i.e. DR is a smart contract) if ( initialDisputeResolver.admin.toLowerCase() != disputeResolver.admin.toLowerCase() || - initialDisputeResolver.operator.toLowerCase() != disputeResolver.operator.toLowerCase() || + initialDisputeResolver.assistant.toLowerCase() != disputeResolver.assistant.toLowerCase() || initialDisputeResolver.clerk.toLowerCase() != disputeResolver.clerk.toLowerCase() ) { disputeResolver.id = initialDisputeResolver.id; diff --git a/scripts/util/deploy-protocol-client-impls.js b/scripts/util/deploy-protocol-client-impls.js index 8e930a127..b48a73557 100644 --- a/scripts/util/deploy-protocol-client-impls.js +++ b/scripts/util/deploy-protocol-client-impls.js @@ -24,7 +24,6 @@ async function deployProtocolClientImpls(implementationArgs, maxPriorityFeePerGa const BosonVoucher = await ethers.getContractFactory("BosonVoucher"); const bosonVoucher = await BosonVoucher.deploy(...implementationArgs, await getFees(maxPriorityFeePerGas)); await bosonVoucher.deployTransaction.wait(confirmations); - console.log("BosonVoucher deployed to:", bosonVoucher.address); return [bosonVoucher]; } diff --git a/test/domain/DisputeResolverTest.js b/test/domain/DisputeResolverTest.js index f82feba7d..e0441abf5 100644 --- a/test/domain/DisputeResolverTest.js +++ b/test/domain/DisputeResolverTest.js @@ -10,12 +10,12 @@ const { oneMonth } = require("../util/constants"); describe("DisputeResolver", function () { // Suite-wide scope let disputeResolver, object, promoted, clone, dehydrated, rehydrated, key, value, struct; - let accounts, id, escalationResponsePeriod, operator, admin, clerk, treasury, metadataUri, active; + let accounts, id, escalationResponsePeriod, assistant, admin, clerk, treasury, metadataUri, active; beforeEach(async function () { // Get a list of accounts accounts = await ethers.getSigners(); - operator = accounts[0].address; + assistant = accounts[0].address; admin = accounts[1].address; clerk = accounts[2].address; treasury = accounts[3].address; @@ -33,7 +33,7 @@ describe("DisputeResolver", function () { disputeResolver = new DisputeResolver( id, escalationResponsePeriod, - operator, + assistant, admin, clerk, treasury, @@ -42,7 +42,7 @@ describe("DisputeResolver", function () { ); expect(disputeResolver.idIsValid()).is.true; expect(disputeResolver.escalationResponsePeriodIsValid()).is.true; - expect(disputeResolver.operatorIsValid()).is.true; + expect(disputeResolver.assistantIsValid()).is.true; expect(disputeResolver.adminIsValid()).is.true; expect(disputeResolver.clerkIsValid()).is.true; expect(disputeResolver.treasuryIsValid()).is.true; @@ -58,7 +58,7 @@ describe("DisputeResolver", function () { disputeResolver = new DisputeResolver( id, escalationResponsePeriod, - operator, + assistant, admin, clerk, treasury, @@ -122,25 +122,25 @@ describe("DisputeResolver", function () { expect(disputeResolver.isValid()).is.true; }); - it("Always present, operator must be a string representation of an EIP-55 compliant address", async function () { + it("Always present, assistant must be a string representation of an EIP-55 compliant address", async function () { // Invalid field value - disputeResolver.operator = "0xASFADF"; - expect(disputeResolver.operatorIsValid()).is.false; + disputeResolver.assistant = "0xASFADF"; + expect(disputeResolver.assistantIsValid()).is.false; expect(disputeResolver.isValid()).is.false; // Invalid field value - disputeResolver.operator = "zedzdeadbaby"; - expect(disputeResolver.operatorIsValid()).is.false; + disputeResolver.assistant = "zedzdeadbaby"; + expect(disputeResolver.assistantIsValid()).is.false; expect(disputeResolver.isValid()).is.false; // Valid field value - disputeResolver.operator = accounts[0].address; - expect(disputeResolver.operatorIsValid()).is.true; + disputeResolver.assistant = accounts[0].address; + expect(disputeResolver.assistantIsValid()).is.true; expect(disputeResolver.isValid()).is.true; // Valid field value - disputeResolver.operator = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; - expect(disputeResolver.operatorIsValid()).is.true; + disputeResolver.assistant = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; + expect(disputeResolver.assistantIsValid()).is.true; expect(disputeResolver.isValid()).is.true; }); @@ -256,7 +256,7 @@ describe("DisputeResolver", function () { disputeResolver = new DisputeResolver( id, escalationResponsePeriod, - operator, + assistant, admin, clerk, treasury, @@ -269,7 +269,7 @@ describe("DisputeResolver", function () { object = { id, escalationResponsePeriod, - operator, + assistant, admin, clerk, treasury, @@ -278,7 +278,7 @@ describe("DisputeResolver", function () { }; // Struct representation - struct = [id, escalationResponsePeriod, operator, admin, clerk, treasury, metadataUri, active]; + struct = [id, escalationResponsePeriod, assistant, admin, clerk, treasury, metadataUri, active]; }); context("👉 Static", async function () { diff --git a/test/domain/SellerTest.js b/test/domain/SellerTest.js index 9ef401f59..89e42bf74 100644 --- a/test/domain/SellerTest.js +++ b/test/domain/SellerTest.js @@ -9,12 +9,12 @@ const Seller = require("../../scripts/domain/Seller"); describe("Seller", function () { // Suite-wide scope let seller, object, promoted, clone, dehydrated, rehydrated, key, value, struct; - let accounts, id, operator, admin, clerk, treasury, active; + let accounts, id, assistant, admin, clerk, treasury, active; beforeEach(async function () { // Get a list of accounts accounts = await ethers.getSigners(); - operator = accounts[0].address; + assistant = accounts[0].address; admin = accounts[1].address; clerk = accounts[2].address; treasury = accounts[3].address; @@ -27,9 +27,9 @@ describe("Seller", function () { context("📋 Constructor", async function () { it("Should allow creation of valid, fully populated Seller instance", async function () { // Create a valid seller - seller = new Seller(id, operator, admin, clerk, treasury, active); + seller = new Seller(id, assistant, admin, clerk, treasury, active); expect(seller.idIsValid()).is.true; - expect(seller.operatorIsValid()).is.true; + expect(seller.assistantIsValid()).is.true; expect(seller.adminIsValid()).is.true; expect(seller.clerkIsValid()).is.true; expect(seller.treasuryIsValid()).is.true; @@ -41,7 +41,7 @@ describe("Seller", function () { context("📋 Field validations", async function () { beforeEach(async function () { // Create a valid seller, then set fields in tests directly - seller = new Seller(id, operator, admin, clerk, treasury, active); + seller = new Seller(id, assistant, admin, clerk, treasury, active); expect(seller.isValid()).is.true; }); @@ -72,25 +72,25 @@ describe("Seller", function () { expect(seller.isValid()).is.true; }); - it("Always present, operator must be a string representation of an EIP-55 compliant address", async function () { + it("Always present, assistant must be a string representation of an EIP-55 compliant address", async function () { // Invalid field value - seller.operator = "0xASFADF"; - expect(seller.operatorIsValid()).is.false; + seller.assistant = "0xASFADF"; + expect(seller.assistantIsValid()).is.false; expect(seller.isValid()).is.false; // Invalid field value - seller.operator = "zedzdeadbaby"; - expect(seller.operatorIsValid()).is.false; + seller.assistant = "zedzdeadbaby"; + expect(seller.assistantIsValid()).is.false; expect(seller.isValid()).is.false; // Valid field value - seller.operator = accounts[0].address; - expect(seller.operatorIsValid()).is.true; + seller.assistant = accounts[0].address; + expect(seller.assistantIsValid()).is.true; expect(seller.isValid()).is.true; // Valid field value - seller.operator = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; - expect(seller.operatorIsValid()).is.true; + seller.assistant = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; + expect(seller.assistantIsValid()).is.true; expect(seller.isValid()).is.true; }); @@ -186,13 +186,13 @@ describe("Seller", function () { context("📋 Utility functions", async function () { beforeEach(async function () { // Create a valid seller, then set fields in tests directly - seller = new Seller(id, operator, admin, clerk, treasury, active); + seller = new Seller(id, assistant, admin, clerk, treasury, active); expect(seller.isValid()).is.true; // Get plain object object = { id, - operator, + assistant, admin, clerk, treasury, @@ -200,7 +200,7 @@ describe("Seller", function () { }; // Struct representation - struct = [id, operator, admin, clerk, treasury, active]; + struct = [id, assistant, admin, clerk, treasury, active]; }); context("👉 Static", async function () { diff --git a/test/example/SnapshotGateTest.js b/test/example/SnapshotGateTest.js index 8c8754971..27ae42836 100644 --- a/test/example/SnapshotGateTest.js +++ b/test/example/SnapshotGateTest.js @@ -31,13 +31,13 @@ describe("SnapshotGate", function () { // Common vars let deployer, pauser, - operator, - operator2, + assistant, + assistant2, admin, clerk, treasury, rando, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -73,7 +73,7 @@ describe("SnapshotGate", function () { adminDR, treasuryDR, protocolTreasury, - operator2, + assistant2, bosonToken, holder1, holder2, @@ -83,8 +83,8 @@ describe("SnapshotGate", function () { ] = await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -185,11 +185,11 @@ describe("SnapshotGate", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // Create a second seller - seller2 = mockSeller(operator2.address, operator2.address, operator2.address, operator2.address); + seller2 = mockSeller(assistant2.address, assistant2.address, assistant2.address, assistant2.address); expect(seller2.isValid()).is.true; // AuthToken @@ -204,11 +204,11 @@ describe("SnapshotGate", function () { await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); // Create the second seller - await accountHandler.connect(operator2).createSeller(seller2, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant2).createSeller(seller2, emptyAuthToken, voucherInitValues); // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -314,7 +314,9 @@ describe("SnapshotGate", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); offers.push(offer); // Required constructor params for Group @@ -334,7 +336,7 @@ describe("SnapshotGate", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); groups.push(group); } } @@ -357,7 +359,7 @@ describe("SnapshotGate", function () { // Create the offer let tx = await offerHandler - .connect(operator2) + .connect(assistant2) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); offers.push(offer); diff --git a/test/integration/01-update-account-roles-addresses.js b/test/integration/01-update-account-roles-addresses.js index d78af5119..1b7f23d65 100644 --- a/test/integration/01-update-account-roles-addresses.js +++ b/test/integration/01-update-account-roles-addresses.js @@ -35,13 +35,13 @@ const DisputeResolverUpdateFields = require("../../scripts/domain/DisputeResolve describe("[@skip-on-coverage] Update account roles addresses", function () { let accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler; let deployer, - operator, + assistant, admin, clerk, treasury, buyer, rando, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -56,8 +56,8 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond const [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -164,14 +164,14 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { expect(voucherInitValues.isValid()).is.true; // Create a seller account - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); await expect(accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues)) .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, seller.toStruct(), expectedCloneAddress, emptyAuthToken.toStruct(), admin.address); // Create a dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -211,12 +211,12 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { // Register the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentAccount.id); // Deposit seller funds so the commit will succeed await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, offer.sellerDeposit, { value: offer.sellerDeposit }); // Create a buyer account @@ -245,20 +245,20 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { accountId.next(true); }); - it("Seller should be able to revoke the voucher after updating operator address", async function () { - seller.operator = rando.address; + it("Seller should be able to revoke the voucher after updating assistant address", async function () { + seller.assistant = rando.address; expect(seller.isValid()).is.true; - sellerPendingUpdate.operator = rando.address; + sellerPendingUpdate.assistant = rando.address; // Update the seller wallet, testing for the event await expect(accountHandler.connect(admin).updateSeller(seller, emptyAuthToken)) .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, sellerPendingUpdate.toStruct(), emptyAuthToken.toStruct(), admin.address); - sellerPendingUpdate.operator = ethers.constants.AddressZero; + sellerPendingUpdate.assistant = ethers.constants.AddressZero; // Approve the update - await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -275,20 +275,20 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { .withArgs(offer.id, exchangeId, rando.address); }); - it("Seller should be able to extend the voucher after updating operator address", async function () { - seller.operator = rando.address; + it("Seller should be able to extend the voucher after updating assistant address", async function () { + seller.assistant = rando.address; expect(seller.isValid()).is.true; - sellerPendingUpdate.operator = rando.address; + sellerPendingUpdate.assistant = rando.address; // Update the seller wallet, testing for the event await expect(accountHandler.connect(admin).updateSeller(seller, emptyAuthToken)) .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, sellerPendingUpdate.toStruct(), emptyAuthToken.toStruct(), admin.address); - sellerPendingUpdate.operator = ethers.constants.AddressZero; + sellerPendingUpdate.assistant = ethers.constants.AddressZero; // Approve the update - await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -479,20 +479,20 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { }; }); - it("Seller should be able to resolve dispute after updating operator address", async function () { - seller.operator = rando.address; + it("Seller should be able to resolve dispute after updating assistant address", async function () { + seller.assistant = rando.address; expect(seller.isValid()).is.true; - sellerPendingUpdate.operator = rando.address; + sellerPendingUpdate.assistant = rando.address; // Update the seller wallet, testing for the event await expect(accountHandler.connect(admin).updateSeller(seller, emptyAuthToken)) .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, sellerPendingUpdate.toStruct(), emptyAuthToken.toStruct(), admin.address); - sellerPendingUpdate.operator = ethers.constants.AddressZero; + sellerPendingUpdate.assistant = ethers.constants.AddressZero; // Approve the update - await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -512,12 +512,12 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { disputeHandler.address ); - // Attempt to resolve a dispute with old seller operator, should fail + // Attempt to resolve a dispute with old seller assistant, should fail await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercent, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercent, r, s, v) ).to.revertedWith(RevertReasons.NOT_BUYER_OR_SELLER); - // Attempt to resolve a dispute with new seller operator, should succeed + // Attempt to resolve a dispute with new seller assistant, should succeed await expect(disputeHandler.connect(rando).resolveDispute(exchangeId, buyerPercent, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") .withArgs(exchangeId, buyerPercent, rando.address); @@ -534,7 +534,7 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { // Collect the signature components const { r, s, v } = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer + assistant, // When buyer is the caller, seller should be the signer customSignatureType, "Resolution", message, @@ -572,24 +572,24 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { // Attempt to resolve a dispute with old buyer wallet, should fail await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercent, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercent, r, s, v) ).to.revertedWith(RevertReasons.SIGNER_AND_SIGNATURE_DO_NOT_MATCH); }); - it("If the seller operator address was changed, the buyer should not be able to resolve a dispute with the old signature", async function () { - seller.operator = rando.address; + it("If the seller assistant address was changed, the buyer should not be able to resolve a dispute with the old signature", async function () { + seller.assistant = rando.address; expect(seller.isValid()).is.true; - sellerPendingUpdate.operator = rando.address; + sellerPendingUpdate.assistant = rando.address; // Update the seller wallet, testing for the event await expect(accountHandler.connect(admin).updateSeller(seller, emptyAuthToken)) .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, sellerPendingUpdate.toStruct(), emptyAuthToken.toStruct(), admin.address); - sellerPendingUpdate.operator = ethers.constants.AddressZero; + sellerPendingUpdate.assistant = ethers.constants.AddressZero; // Approve the update - await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -602,7 +602,7 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { // Collect the signature components const { r, s, v } = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer + assistant, // When buyer is the caller, seller should be the signer customSignatureType, "Resolution", message, @@ -645,37 +645,37 @@ describe("[@skip-on-coverage] Update account roles addresses", function () { // Escalate the dispute await disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); - disputeResolver.operator = rando.address; + disputeResolver.assistant = rando.address; expect(disputeResolver.isValid()).is.true; - // Update the dispute resolver operator + // Update the dispute resolver assistant await accountHandler.connect(adminDR).updateDisputeResolver(disputeResolver); await accountHandler .connect(rando) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]); + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]); }); - it("Dispute resolver should be able to decide dispute after change the operator address", async function () { + it("Dispute resolver should be able to decide dispute after change the assistant address", async function () { const buyerPercent = "1234"; - // Attempt to decide a dispute with old dispute resolver operator, should fail - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercent)).to.revertedWith( - RevertReasons.NOT_DISPUTE_RESOLVER_OPERATOR + // Attempt to decide a dispute with old dispute resolver assistant, should fail + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercent)).to.revertedWith( + RevertReasons.NOT_DISPUTE_RESOLVER_ASSISTANT ); - // Attempt to decide a dispute with new dispute resolver operator, should fail + // Attempt to decide a dispute with new dispute resolver assistant, should fail await expect(disputeHandler.connect(rando).decideDispute(exchangeId, buyerPercent)) .to.emit(disputeHandler, "DisputeDecided") .withArgs(exchangeId, buyerPercent, rando.address); }); - it("Dispute resolver should be able to refuse to decide a dispute after change the operator address", async function () { - // Attempt to refuse to decide a dispute with old dispute resolver operator, should fail - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( - RevertReasons.NOT_DISPUTE_RESOLVER_OPERATOR + it("Dispute resolver should be able to refuse to decide a dispute after change the assistant address", async function () { + // Attempt to refuse to decide a dispute with old dispute resolver assistant, should fail + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + RevertReasons.NOT_DISPUTE_RESOLVER_ASSISTANT ); - // Attempt to refuse a dispute with new dispute resolver operator, should fail + // Attempt to refuse a dispute with new dispute resolver assistant, should fail await expect(disputeHandler.connect(rando).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") .withArgs(exchangeId, rando.address); diff --git a/test/integration/02-Upgraded-facet.js b/test/integration/02-Upgraded-facet.js index 0551890b2..5b225507b 100644 --- a/test/integration/02-Upgraded-facet.js +++ b/test/integration/02-Upgraded-facet.js @@ -35,13 +35,13 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation // Common vars let deployer, pauser, - operator, + assistant, admin, clerk, treasury, rando, buyer, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -77,8 +77,8 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -177,7 +177,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation agentId = "0"; // agent id is optional while creating an offer // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -192,11 +192,11 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation [mockToken] = await deployMockTokens(["Foreign20"]); - // top up operators account - await mockToken.mint(operator.address, "1000000"); + // top up assistants account + await mockToken.mint(assistant.address, "1000000"); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, "1000000"); + await mockToken.connect(assistant).approve(protocolDiamond.address, "1000000"); }); async function upgradeExchangeHandlerFacet(mockFacet) { @@ -236,7 +236,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation beforeEach(async function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -268,7 +268,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables price = offer.price; @@ -289,7 +289,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation // Deposit seller funds so the commit will succeed await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); }); @@ -366,7 +366,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation }); context("👉 revokeVoucher()", async function () { - it("should emit an VoucherRevoked2 event when seller's operator calls", async function () { + it("should emit an VoucherRevoked2 event when seller's assistant calls", async function () { // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -374,9 +374,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation await upgradeExchangeHandlerFacet("MockExchangeHandlerFacet"); // Revoke the voucher, expecting event - await expect(mockExchangeHandlerUpgrade.connect(operator).revokeVoucher(exchange.id)) + await expect(mockExchangeHandlerUpgrade.connect(assistant).revokeVoucher(exchange.id)) .to.emit(mockExchangeHandlerUpgrade, "VoucherRevoked2") - .withArgs(offerId, exchange.id, operator.address); + .withArgs(offerId, exchange.id, assistant.address); }); }); @@ -432,7 +432,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation }); context("👉 extendVoucher()", async function () { - it("should emit an VoucherExtended2 event when seller's operator calls", async function () { + it("should emit an VoucherExtended2 event when seller's assistant calls", async function () { // Commit to offer const tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -453,9 +453,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation await upgradeExchangeHandlerFacet("MockExchangeHandlerFacet"); // Extend the voucher, expecting event - await expect(mockExchangeHandlerUpgrade.connect(operator).extendVoucher(exchange.id, validUntilDate)) + await expect(mockExchangeHandlerUpgrade.connect(assistant).extendVoucher(exchange.id, validUntilDate)) .to.emit(mockExchangeHandlerUpgrade, "VoucherExtended2") - .withArgs(offerId, exchange.id, validUntilDate, operator.address); + .withArgs(offerId, exchange.id, validUntilDate, assistant.address); }); }); }); @@ -465,7 +465,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation beforeEach(async function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -498,7 +498,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables price = offer.price; @@ -511,7 +511,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation // Deposit seller funds so the commit will succeed const fundsToDeposit = ethers.BigNumber.from(sellerDeposit).mul(quantityAvailable); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { value: fundsToDeposit }); buyerId = accountId.next().value; @@ -578,9 +578,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation const newDisputeTimeout = ethers.BigNumber.from(timeout).add(oneMonth).toString(); // Extend the dispute timeout, testing for the event - await expect(disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout)) + await expect(disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout)) .to.emit(disputeHandler, "DisputeTimeoutExtended") - .withArgs(exchangeId, newDisputeTimeout, operator.address); + .withArgs(exchangeId, newDisputeTimeout, assistant.address); }); }); @@ -632,7 +632,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation it("should emit a DisputeResolved event", async function () { // Collect the signature components const { r, s, v } = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer. + assistant, // When buyer is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -658,9 +658,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation ); // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); }); }); @@ -691,9 +691,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation buyerPercentBasisPoints = "4321"; // Escalate the dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); }); }); @@ -731,9 +731,9 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation await disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); // Refuse the escalated dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); }); }); }); @@ -753,7 +753,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -797,10 +797,10 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation // Create both offers await Promise.all([ offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerNative, offerDates, offerDurations, disputeResolverId, agentId), offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId), ]); @@ -809,19 +809,19 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation sellerDeposit = offerToken.sellerDeposit; // top up seller's and buyer's account - await Promise.all([mockToken.mint(operator.address, sellerDeposit), mockToken.mint(buyer.address, price)]); + await Promise.all([mockToken.mint(assistant.address, sellerDeposit), mockToken.mint(buyer.address, price)]); // approve protocol to transfer the tokens await Promise.all([ - mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit), + mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit), mockToken.connect(buyer).approve(protocolDiamond.address, price), ]); // deposit to seller's pool await Promise.all([ - fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit), + fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit), fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit }), ]); diff --git a/test/integration/03-DR-removes-the-seller-from-allowed-list.js b/test/integration/03-DR-removes-the-seller-from-allowed-list.js index 97e6f5580..0433ff703 100644 --- a/test/integration/03-DR-removes-the-seller-from-allowed-list.js +++ b/test/integration/03-DR-removes-the-seller-from-allowed-list.js @@ -26,13 +26,13 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", // Common vars let deployer, pauser, - operator, + assistant, admin, clerk, treasury, buyer, other1, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -53,8 +53,8 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -157,7 +157,7 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", const agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; const seller2 = mockSeller(other1.address, other1.address, other1.address, other1.address); @@ -179,7 +179,7 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -213,7 +213,7 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", // Create the offer disputeResolverId = disputeResolver.id; - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables const price = offer.price; @@ -224,7 +224,7 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", // Deposit seller funds so the commit will succeed const fundsToDeposit = ethers.BigNumber.from(sellerDeposit).mul(quantityAvailable); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { value: fundsToDeposit }); // Set time forward to the offer's voucherRedeemableFrom @@ -261,9 +261,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", it("should decide dispute even when DR removes approved sellers", async function () { exchangeId = 1; // Decide the dispute - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); // Remove an approved seller let allowedSellersToRemove = ["1"]; @@ -276,9 +276,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", .withArgs(disputeResolverId, allowedSellersToRemove, adminDR.address); // Decide the dispute - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); // Remove another approved seller allowedSellersToRemove = ["2"]; @@ -291,9 +291,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", .withArgs(disputeResolverId, allowedSellersToRemove, adminDR.address); // Decide the dispute - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); }); }); @@ -311,9 +311,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", it("should refuse escalated dispute even when DR removes approved sellers", async function () { exchangeId = 1; // Refuse the escalated dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); // Remove an approved seller let allowedSellersToRemove = ["1"]; @@ -326,9 +326,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", .withArgs(disputeResolverId, allowedSellersToRemove, adminDR.address); // Refuse the escalated dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); // Remove another approved seller allowedSellersToRemove = ["2"]; @@ -341,9 +341,9 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list", .withArgs(disputeResolverId, allowedSellersToRemove, adminDR.address); // Refuse the escalated dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); }); }); }); diff --git a/test/integration/04-DR-removes-fees.js b/test/integration/04-DR-removes-fees.js index 42e5c35c8..f8e31b851 100644 --- a/test/integration/04-DR-removes-fees.js +++ b/test/integration/04-DR-removes-fees.js @@ -33,12 +33,12 @@ describe("[@skip-on-coverage] DR removes fee", function () { let accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler; let expectedCloneAddress, emptyAuthToken, voucherInitValues; let deployer, - operator, + assistant, admin, clerk, treasury, buyer, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -55,8 +55,8 @@ describe("[@skip-on-coverage] DR removes fee", function () { [deployer, admin, treasury, buyer, adminDR, treasuryDR, protocolTreasury, bosonToken] = await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond const [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -154,14 +154,14 @@ describe("[@skip-on-coverage] DR removes fee", function () { expect(voucherInitValues.isValid()).is.true; // Create a seller account - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues)) .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, seller.toStruct(), expectedCloneAddress, emptyAuthToken.toStruct(), admin.address); // Create a dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -191,11 +191,11 @@ describe("[@skip-on-coverage] DR removes fee", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, "0"); // Deposit seller funds so the commit will succeed const fundsToDeposit = ethers.BigNumber.from(offer.sellerDeposit).mul(offer.quantityAvailable); - await fundsHandler.connect(operator).depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { + await fundsHandler.connect(assistant).depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { value: fundsToDeposit, }); @@ -305,9 +305,9 @@ describe("[@skip-on-coverage] DR removes fee", function () { it("DR should be able to decide dispute even when DR removes fee", async function () { exchangeId = "1"; // Decide the dispute befor removing fee - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); // Removes fee await expect( @@ -320,17 +320,17 @@ describe("[@skip-on-coverage] DR removes fee", function () { // Decide the dispute after removing fee exchangeId = "2"; - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); }); it("DR should be able to refuse to decide dispute even when DR removes fee", async function () { // Refuse to decide the dispute before removing fee exchangeId = "1"; - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); // Removes fee await expect( @@ -343,9 +343,9 @@ describe("[@skip-on-coverage] DR removes fee", function () { // Refuse to decide the dispute after removing fee exchangeId = "2"; - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); }); }); }); diff --git a/test/protocol/AccountHandlerTest.js b/test/protocol/AccountHandlerTest.js index 7c349d2a2..bf56a355a 100644 --- a/test/protocol/AccountHandlerTest.js +++ b/test/protocol/AccountHandlerTest.js @@ -26,7 +26,7 @@ const { deployAndCutFacets } = require("../../scripts/util/deploy-protocol-handl describe("IBosonAccountHandler", function () { // Common vars let InterfaceIds; - let deployer, rando, operator, admin, clerk, treasury, other1, other2, other3, protocolTreasury, bosonToken; + let deployer, rando, assistant, admin, clerk, treasury, other1, other2, other3, protocolTreasury, bosonToken; let erc165, protocolDiamond, accessController, accountHandler; let seller; let emptyAuthToken; @@ -52,7 +52,7 @@ describe("IBosonAccountHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; + assistant = clerk = admin; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -151,7 +151,7 @@ describe("IBosonAccountHandler", function () { nextAccountId = "1"; // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -168,7 +168,7 @@ describe("IBosonAccountHandler", function () { expect(buyer.isValid()).is.true; // Create a valid dispute resolver - disputeResolver = mockDisputeResolver(operator.address, admin.address, clerk.address, treasury.address); + disputeResolver = mockDisputeResolver(assistant.address, admin.address, clerk.address, treasury.address); expect(disputeResolver.isValid()).is.true; //Create DisputeResolverFee array @@ -217,7 +217,7 @@ describe("IBosonAccountHandler", function () { it("should be incremented after a seller is created", async function () { //addresses need to be unique to seller Id, so setting them to random addresses here - seller.operator = rando.address; + seller.assistant = rando.address; seller.admin = rando.address; seller.clerk = rando.address; diff --git a/test/protocol/BundleHandlerTest.js b/test/protocol/BundleHandlerTest.js index 6f8e95ec6..c8c9a62ca 100644 --- a/test/protocol/BundleHandlerTest.js +++ b/test/protocol/BundleHandlerTest.js @@ -33,12 +33,12 @@ describe("IBosonBundleHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, buyer, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -93,8 +93,8 @@ describe("IBosonBundleHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -217,7 +217,7 @@ describe("IBosonBundleHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -232,7 +232,7 @@ describe("IBosonBundleHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -258,15 +258,17 @@ describe("IBosonBundleHandler", function () { expect(twin.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a twin. - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); } // create 5 offers for (let i = 0; i < 5; i++) { - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); } // The first bundle id @@ -296,7 +298,7 @@ describe("IBosonBundleHandler", function () { context("👉 createBundle()", async function () { it("should emit a BundleCreated event", async function () { - const tx = await bundleHandler.connect(operator).createBundle(bundle); + const tx = await bundleHandler.connect(assistant).createBundle(bundle); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, bundleHandlerFacet_Factory, "BundleCreated"); @@ -312,7 +314,7 @@ describe("IBosonBundleHandler", function () { it("should update state", async function () { // Create a a bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Get the bundle as a struct [, bundleStruct] = await bundleHandler.connect(rando).getBundle(bundleId); @@ -330,7 +332,7 @@ describe("IBosonBundleHandler", function () { bundle.id = "444"; // Create a bundle, testing for the event - const tx = await bundleHandler.connect(operator).createBundle(bundle); + const tx = await bundleHandler.connect(assistant).createBundle(bundle); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, bundleHandlerFacet_Factory, "BundleCreated"); @@ -357,7 +359,7 @@ describe("IBosonBundleHandler", function () { bundle.sellerId = "123"; // Create a bundle, testing for the event - const tx = await bundleHandler.connect(operator).createBundle(bundle); + const tx = await bundleHandler.connect(assistant).createBundle(bundle); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, bundleHandlerFacet_Factory, "BundleCreated"); @@ -366,13 +368,13 @@ describe("IBosonBundleHandler", function () { // Validate the instance expect(bundleInstance.isValid()).to.be.true; - //Get seller id by operator which created the bundle - const [, sellerStruct] = await accountHandler.connect(rando).getSellerByAddress(operator.address); + //Get seller id by assistant which created the bundle + const [, sellerStruct] = await accountHandler.connect(rando).getSellerByAddress(assistant.address); let expectedSellerId = sellerStruct.id; assert.equal(event.bundleId.toString(), nextBundleId, "Bundle Id is incorrect"); assert.equal(event.sellerId.toString(), expectedSellerId.toString(), "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(bundleInstance.toStruct().toString(), bundleStruct.toString(), "Bundle struct is incorrect"); }); @@ -385,11 +387,11 @@ describe("IBosonBundleHandler", function () { const newOfferId2 = "7"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(newOffer, offerDates, offerDurations, disputeResolverId, agentId); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(newOffer2, offerDates, offerDurations, disputeResolverId, agentId); // create a twin with almost unlimited supply @@ -398,14 +400,14 @@ describe("IBosonBundleHandler", function () { expect(twin.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler // Create a twin with id 6 - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); bundle.offerIds = [...bundle.offerIds, newOfferId, newOfferId2]; bundle.twinIds = ["6"]; - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.INSUFFICIENT_TWIN_SUPPLY_TO_COVER_BUNDLE_OFFERS ); @@ -415,13 +417,13 @@ describe("IBosonBundleHandler", function () { expect(twin.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, twin.supplyAvailable); // approving the twin handler // Create a twin with id 7 - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); bundle.twinIds = ["7"]; - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.not.reverted; + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.not.reverted; }); context("💔 Revert Reasons", async function () { @@ -430,21 +432,21 @@ describe("IBosonBundleHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Bundles]); // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(rando).createBundle(bundle)).to.revertedWith(RevertReasons.NOT_OPERATOR); + await expect(bundleHandler.connect(rando).createBundle(bundle)).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Bundle has no offers", async function () { bundle.offerIds = []; // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_REQUIRES_AT_LEAST_ONE_TWIN_AND_ONE_OFFER ); }); @@ -453,7 +455,7 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = []; // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_REQUIRES_AT_LEAST_ONE_TWIN_AND_ONE_OFFER ); }); @@ -463,7 +465,7 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = []; // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_REQUIRES_AT_LEAST_ONE_TWIN_AND_ONE_OFFER ); }); @@ -485,8 +487,8 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = ["2", "6"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( - RevertReasons.NOT_OPERATOR + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( + RevertReasons.NOT_ASSISTANT ); }); @@ -495,7 +497,7 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = ["1", "999"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -503,7 +505,7 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = ["0", "4"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); @@ -524,8 +526,8 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = ["2", "6"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( - RevertReasons.NOT_OPERATOR + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( + RevertReasons.NOT_ASSISTANT ); }); @@ -534,7 +536,7 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = ["1", "999"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.NO_SUCH_TWIN ); @@ -542,20 +544,20 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = ["0", "4"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.NO_SUCH_TWIN ); }); it("Offer is already part of another bundle", async function () { // create first bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Set add offer that is already part of another bundle bundle.offerIds = ["1", "2", "4"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_OFFER_MUST_BE_UNIQUE ); }); @@ -565,7 +567,7 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = ["1", "1", "4"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_OFFER_MUST_BE_UNIQUE ); }); @@ -575,7 +577,7 @@ describe("IBosonBundleHandler", function () { bundle.offerIds = [...Array(101).keys()]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.TOO_MANY_OFFERS ); }); @@ -585,7 +587,7 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = ["1", "1", "4"]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_TWIN_MUST_BE_UNIQUE ); }); @@ -595,7 +597,7 @@ describe("IBosonBundleHandler", function () { bundle.twinIds = [...Array(101).keys()]; // Attempt to create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.TOO_MANY_TWINS ); }); @@ -603,7 +605,7 @@ describe("IBosonBundleHandler", function () { it("Exchange already exists for the offerId in bundle", async function () { // Deposit seller funds so the commit will succeed await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit }); // Commit to an offer @@ -611,14 +613,14 @@ describe("IBosonBundleHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerIdToCommit, { value: price }); // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.EXCHANGE_FOR_OFFER_EXISTS ); }); it("Twin is already part of another bundle", async function () { // create first bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Set offer that is NOT already part of another bundle bundle.offerIds = ["1"]; @@ -630,7 +632,7 @@ describe("IBosonBundleHandler", function () { expectedBundle.id = expectedNextBundleId; // Attempt to Create a bundle, expecting revert - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.BUNDLE_TWIN_MUST_BE_UNIQUE ); }); @@ -639,10 +641,10 @@ describe("IBosonBundleHandler", function () { let expectedNewTwinId = "6"; const newTwin = twin.clone(); newTwin.amount = newTwin.supplyAvailable = "1"; // twin amount can't be greater than supply available. - await twinHandler.connect(operator).createTwin(newTwin); // creates a twin with id 6 + await twinHandler.connect(assistant).createTwin(newTwin); // creates a twin with id 6 bundle.twinIds = ["1", expectedNewTwinId]; - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.INSUFFICIENT_TWIN_SUPPLY_TO_COVER_BUNDLE_OFFERS ); }); @@ -653,11 +655,11 @@ describe("IBosonBundleHandler", function () { let expectedNewOfferId = "6"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(newOffer, offerDates, offerDurations, disputeResolverId, agentId); bundle.offerIds = [expectedNewOfferId]; - await expect(bundleHandler.connect(operator).createBundle(bundle)).to.revertedWith( + await expect(bundleHandler.connect(assistant).createBundle(bundle)).to.revertedWith( RevertReasons.INSUFFICIENT_TWIN_SUPPLY_TO_COVER_BUNDLE_OFFERS ); }); @@ -667,7 +669,7 @@ describe("IBosonBundleHandler", function () { context("👉 getBundle()", async function () { beforeEach(async function () { // Create a bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // increment nextBundleId nextBundleId++; @@ -704,7 +706,7 @@ describe("IBosonBundleHandler", function () { context("👉 getNextBundleId()", async function () { beforeEach(async function () { // Create a bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // increment nextBundleId nextBundleId++; @@ -725,7 +727,7 @@ describe("IBosonBundleHandler", function () { // Create another bundle bundle.offerIds = ["1", "4"]; bundle.twinIds = ["1"]; - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // What we expect the next bundle id to be expected = ++nextBundleId; @@ -758,7 +760,7 @@ describe("IBosonBundleHandler", function () { context("👉 getBundleIdByOffer()", async function () { beforeEach(async function () { // Create a bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Offer id that we want to test offerId = bundle.offerIds[0]; @@ -794,11 +796,11 @@ describe("IBosonBundleHandler", function () { context("👉 getBundleIdByTwin()", async function () { beforeEach(async function () { // Create a twin with id 6 - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler - await twinHandler.connect(operator).createTwin(twin); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler + await twinHandler.connect(assistant).createTwin(twin); // Create a bundle - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Twin id that we want to test twinId = "3"; diff --git a/test/protocol/BuyerHandlerTest.js b/test/protocol/BuyerHandlerTest.js index 436b168af..e9c9c803c 100644 --- a/test/protocol/BuyerHandlerTest.js +++ b/test/protocol/BuyerHandlerTest.js @@ -30,7 +30,7 @@ describe("BuyerHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, @@ -59,7 +59,7 @@ describe("BuyerHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; + assistant = clerk = admin; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -411,7 +411,7 @@ describe("BuyerHandler", function () { let agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); seller.id = id.toString(); expect(seller.isValid()).is.true; @@ -426,11 +426,17 @@ describe("BuyerHandler", function () { // Create a seller await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - [exists] = await accountHandler.connect(rando).getSellerByAddress(operator.address); + [exists] = await accountHandler.connect(rando).getSellerByAddress(assistant.address); expect(exists).is.true; // Create a valid dispute resolver - disputeResolver = mockDisputeResolver(operator.address, admin.address, clerk.address, treasury.address, true); + disputeResolver = mockDisputeResolver( + assistant.address, + admin.address, + clerk.address, + treasury.address, + true + ); disputeResolver.id = id.add(1).toString(); expect(disputeResolver.isValid()).is.true; @@ -460,7 +466,7 @@ describe("BuyerHandler", function () { // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); offerId = offer.id; @@ -468,7 +474,7 @@ describe("BuyerHandler", function () { // Deposit seller funds so the commit will succeed await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit }); //Commit to offer diff --git a/test/protocol/DisputeHandlerTest.js b/test/protocol/DisputeHandlerTest.js index 75dce1268..ea2e816aa 100644 --- a/test/protocol/DisputeHandlerTest.js +++ b/test/protocol/DisputeHandlerTest.js @@ -40,7 +40,7 @@ describe("IBosonDisputeHandler", function () { let InterfaceIds; let deployer, pauser, - operator, + assistant, admin, clerk, treasury, @@ -48,7 +48,7 @@ describe("IBosonDisputeHandler", function () { buyer, other1, other2, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -100,8 +100,8 @@ describe("IBosonDisputeHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -225,7 +225,7 @@ describe("IBosonDisputeHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -240,7 +240,7 @@ describe("IBosonDisputeHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -273,7 +273,7 @@ describe("IBosonDisputeHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables price = offer.price; @@ -287,7 +287,7 @@ describe("IBosonDisputeHandler", function () { // Deposit seller funds so the commit will succeed const fundsToDeposit = ethers.BigNumber.from(sellerDeposit).mul(quantityAvailable); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { value: fundsToDeposit }); buyerId = accountId.next().value; @@ -396,7 +396,7 @@ describe("IBosonDisputeHandler", function () { await setNextBlockTimestamp(newTime); // Complete exchange - await exchangeHandler.connect(operator).completeExchange(exchangeId); + await exchangeHandler.connect(assistant).completeExchange(exchangeId); // Attempt to raise a dispute, expecting revert await expect(disputeHandler.connect(buyer).raiseDispute(exchangeId)).to.revertedWith( @@ -589,14 +589,14 @@ describe("IBosonDisputeHandler", function () { it("should emit a DisputeTimeoutExtended event", async function () { // Extend the dispute timeout, testing for the event - await expect(disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout)) + await expect(disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout)) .to.emit(disputeHandler, "DisputeTimeoutExtended") - .withArgs(exchangeId, newDisputeTimeout, operator.address); + .withArgs(exchangeId, newDisputeTimeout, assistant.address); }); it("should update state", async function () { // Extend the dispute timeout - await disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout); + await disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout); dispute = new Dispute(exchangeId, DisputeState.Resolving, "0"); disputeDates = new DisputeDates(disputedDate, "0", "0", newDisputeTimeout); @@ -625,7 +625,7 @@ describe("IBosonDisputeHandler", function () { it("dispute timeout can be extended multiple times", async function () { // Extend the dispute timeout - await disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout); + await disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout); // not strictly necessary, but it shows that we can extend event if we are past original timeout await setNextBlockTimestamp(Number(timeout) + Number(oneWeek)); @@ -634,9 +634,9 @@ describe("IBosonDisputeHandler", function () { newDisputeTimeout = ethers.BigNumber.from(newDisputeTimeout).add(oneWeek).toString(); // Extend the dispute timeout, testing for the event - await expect(disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout)) + await expect(disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout)) .to.emit(disputeHandler, "DisputeTimeoutExtended") - .withArgs(exchangeId, newDisputeTimeout, operator.address); + .withArgs(exchangeId, newDisputeTimeout, assistant.address); }); context("💔 Revert Reasons", async function () { @@ -646,7 +646,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend a dispute timeout, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -656,7 +656,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend the dispute timeout, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); @@ -668,7 +668,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend the dispute timeout, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.INVALID_STATE); }); @@ -676,7 +676,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend the dispute timeout, expecting revert await expect( disputeHandler.connect(rando).extendDisputeTimeout(exchangeId, newDisputeTimeout) - ).to.revertedWith(RevertReasons.NOT_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Dispute has expired already", async function () { @@ -685,7 +685,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend the dispute timeout, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.DISPUTE_HAS_EXPIRED); }); @@ -694,7 +694,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to extend the dispute timeout, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_TIMEOUT); }); @@ -704,7 +704,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to expire the dispute, expecting revert await expect( - disputeHandler.connect(operator).extendDisputeTimeout(exchangeId, newDisputeTimeout) + disputeHandler.connect(assistant).extendDisputeTimeout(exchangeId, newDisputeTimeout) ).to.revertedWith(RevertReasons.INVALID_STATE); }); }); @@ -825,7 +825,7 @@ describe("IBosonDisputeHandler", function () { it("Dispute timeout has been extended", async function () { // Extend the dispute timeout await disputeHandler - .connect(operator) + .connect(assistant) .extendDisputeTimeout(exchangeId, Number(timeout) + 2 * Number(oneWeek)); // put past original timeout where normally it would not revert @@ -882,7 +882,7 @@ describe("IBosonDisputeHandler", function () { beforeEach(async function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer. + assistant, // When buyer is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -987,7 +987,7 @@ describe("IBosonDisputeHandler", function () { it("Dispute can be mutually resolved if it's past original timeout, but it was extended", async function () { // Extend the dispute timeout await disputeHandler - .connect(operator) + .connect(assistant) .extendDisputeTimeout(exchangeId, Number(timeout) + 2 * Number(oneWeek)); // put past original timeout where normally it would not revert @@ -1014,14 +1014,14 @@ describe("IBosonDisputeHandler", function () { it("should emit a DisputeResolved event", async function () { // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); it("should update state", async function () { // Resolve the dispute - tx = await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + tx = await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Get the block timestamp of the confirmed tx and set finalizedDate blockNumber = tx.blockNumber; @@ -1063,16 +1063,16 @@ describe("IBosonDisputeHandler", function () { assert.equal(returnedExchange.finalizedDate, finalizedDate, "Exchange finalizeDate is incorect"); }); - it("Operator can also have a buyer account and this will work", async function () { - // Create a valid buyer with operator's wallet - buyer = mockBuyer(operator.address); + it("Assistant can also have a buyer account and this will work", async function () { + // Create a valid buyer with assistant's wallet + buyer = mockBuyer(assistant.address); expect(buyer.isValid()).is.true; - await accountHandler.connect(operator).createBuyer(buyer); + await accountHandler.connect(assistant).createBuyer(buyer); // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); it("Dispute can be mutually resolved even if it's in escalated state", async function () { @@ -1080,9 +1080,9 @@ describe("IBosonDisputeHandler", function () { await disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); it("Dispute can be mutually resolved even if it's in escalated state and past the resolution period", async function () { @@ -1100,24 +1100,24 @@ describe("IBosonDisputeHandler", function () { await setNextBlockTimestamp(Number(timeout) + 10); // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); it("Dispute can be mutually resolved if it's past original timeout, but it was extended", async function () { // Extend the dispute timeout await disputeHandler - .connect(operator) + .connect(assistant) .extendDisputeTimeout(exchangeId, Number(timeout) + 2 * Number(oneWeek)); // put past original timeout where normally it would not revert await setNextBlockTimestamp(Number(timeout) + Number(oneWeek)); // Resolve the dispute, testing for the event - await expect(disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) + await expect(disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v)) .to.emit(disputeHandler, "DisputeResolved") - .withArgs(exchangeId, buyerPercentBasisPoints, operator.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistant.address); }); }); @@ -1139,7 +1139,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve a dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -1149,7 +1149,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.INVALID_BUYER_PERCENT); }); @@ -1159,7 +1159,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.DISPUTE_HAS_EXPIRED); }); @@ -1169,7 +1169,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); @@ -1181,7 +1181,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.INVALID_STATE); }); @@ -1227,7 +1227,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.SIGNER_AND_SIGNATURE_DO_NOT_MATCH); // Attempt to resolve the dispute, expecting revert @@ -1242,28 +1242,28 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.SIGNER_AND_SIGNATURE_DO_NOT_MATCH); }); it("signature has invalid field", async function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, "0") + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, "0") ).to.revertedWith(RevertReasons.INVALID_SIGNATURE); await expect( disputeHandler - .connect(operator) + .connect(assistant) .resolveDispute(exchangeId, buyerPercentBasisPoints, r, ethers.utils.hexZeroPad("0x", 32), v) ).to.revertedWith(RevertReasons.INVALID_SIGNATURE); await expect( disputeHandler - .connect(operator) + .connect(assistant) .resolveDispute(exchangeId, buyerPercentBasisPoints, ethers.utils.hexZeroPad("0x", 32), s, v) ).to.revertedWith(RevertReasons.INVALID_SIGNATURE); await expect( disputeHandler - .connect(operator) + .connect(assistant) .resolveDispute(exchangeId, buyerPercentBasisPoints, r, ethers.constants.MaxUint256, v) ).to.revertedWith(RevertReasons.INVALID_SIGNATURE); }); @@ -1274,7 +1274,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to resolve the dispute, expecting revert await expect( - disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) + disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v) ).to.revertedWith(RevertReasons.INVALID_STATE); }); }); @@ -1301,7 +1301,7 @@ describe("IBosonDisputeHandler", function () { // create an offer with erc20 exchange token await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // mint tokens to buyer and approve the protocol @@ -1470,7 +1470,7 @@ describe("IBosonDisputeHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer and put exchange all the way to dispute @@ -1560,7 +1560,7 @@ describe("IBosonDisputeHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // mint tokens and approve @@ -1606,14 +1606,14 @@ describe("IBosonDisputeHandler", function () { it("should emit a DisputeDecided event", async function () { // Escalate the dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints)) + await expect(disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints)) .to.emit(disputeHandler, "DisputeDecided") - .withArgs(exchangeId, buyerPercentBasisPoints, operatorDR.address); + .withArgs(exchangeId, buyerPercentBasisPoints, assistantDR.address); }); it("should update state", async function () { // Decide the dispute - tx = await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Get the block timestamp of the confirmed tx and set finalizedDate blockNumber = tx.blockNumber; @@ -1652,7 +1652,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide a dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -1662,7 +1662,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.INVALID_BUYER_PERCENT); }); @@ -1672,7 +1672,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); @@ -1684,7 +1684,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.INVALID_STATE); }); @@ -1692,7 +1692,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( disputeHandler.connect(rando).decideDispute(exchangeId, buyerPercentBasisPoints) - ).to.revertedWith(RevertReasons.NOT_DISPUTE_RESOLVER_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_DISPUTE_RESOLVER_ASSISTANT); }); it("Dispute state is not escalated", async function () { @@ -1709,7 +1709,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.INVALID_STATE); }); @@ -1719,7 +1719,7 @@ describe("IBosonDisputeHandler", function () { // Attempt to decide the dispute, expecting revert await expect( - disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints) + disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints) ).to.revertedWith(RevertReasons.DISPUTE_HAS_EXPIRED); }); }); @@ -1896,14 +1896,14 @@ describe("IBosonDisputeHandler", function () { it("should emit a EscalatedDisputeRefused event", async function () { // Refuse the escalated dispute, testing for the event - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)) + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)) .to.emit(disputeHandler, "EscalatedDisputeRefused") - .withArgs(exchangeId, operatorDR.address); + .withArgs(exchangeId, assistantDR.address); }); it("should update state", async function () { // Refuse the dispute - tx = await disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId); + tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Get the block timestamp of the confirmed tx and set finalizedDate blockNumber = tx.blockNumber; @@ -1951,7 +1951,7 @@ describe("IBosonDisputeHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Disputes]); // Attempt to refuse an escalated dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1961,7 +1961,7 @@ describe("IBosonDisputeHandler", function () { const exchangeId = "666"; // Attempt to refuse the escalated dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.NO_SUCH_EXCHANGE ); }); @@ -1973,7 +1973,7 @@ describe("IBosonDisputeHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); // Attempt to refuse the escalated dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.INVALID_STATE ); }); @@ -1992,7 +1992,7 @@ describe("IBosonDisputeHandler", function () { // dispute raised but not escalated // Attempt to refuse the escalated dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.INVALID_STATE ); @@ -2000,7 +2000,7 @@ describe("IBosonDisputeHandler", function () { await disputeHandler.connect(buyer).retractDispute(exchangeId); // Attempt to refuse the retracted dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.INVALID_STATE ); }); @@ -2010,7 +2010,7 @@ describe("IBosonDisputeHandler", function () { await setNextBlockTimestamp(Number(escalatedDate) + Number(escalationPeriod)); // Attempt to refuse the escalated dispute, expecting revert - await expect(disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( + await expect(disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId)).to.revertedWith( RevertReasons.DISPUTE_HAS_EXPIRED ); }); @@ -2018,7 +2018,7 @@ describe("IBosonDisputeHandler", function () { it("Caller is not the dispute resolver for this dispute", async function () { // Attempt to refuse the escalated dispute, expecting revert await expect(disputeHandler.connect(rando).refuseEscalatedDispute(exchangeId)).to.revertedWith( - RevertReasons.NOT_DISPUTE_RESOLVER_OPERATOR + RevertReasons.NOT_DISPUTE_RESOLVER_ASSISTANT ); }); }); @@ -2197,7 +2197,7 @@ describe("IBosonDisputeHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer. + assistant, // When buyer is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -2239,7 +2239,7 @@ describe("IBosonDisputeHandler", function () { buyerPercentBasisPoints = "4321"; // Decide dispute - await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Get the dispute state [exists, response] = await disputeHandler.connect(rando).getDisputeState(exchangeId); @@ -2253,7 +2253,7 @@ describe("IBosonDisputeHandler", function () { tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); // Dispute resolver refuses dispute - await disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId); + await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Get the dispute state [exists, response] = await disputeHandler.connect(rando).getDisputeState(exchangeId); @@ -2374,7 +2374,7 @@ describe("IBosonDisputeHandler", function () { )); // Retract dispute - await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Dispute in resolved state, ask if exchange is finalized [exists, response] = await disputeHandler.connect(rando).isDisputeFinalized(exchangeId); @@ -2391,7 +2391,7 @@ describe("IBosonDisputeHandler", function () { await disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); // Decide dispute - await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Dispute in decided state, ask if exchange is finalized [exists, response] = await disputeHandler.connect(rando).isDisputeFinalized(exchangeId); diff --git a/test/protocol/DisputeResolverHandlerTest.js b/test/protocol/DisputeResolverHandlerTest.js index 9cf005a4d..d48f4f5ce 100644 --- a/test/protocol/DisputeResolverHandlerTest.js +++ b/test/protocol/DisputeResolverHandlerTest.js @@ -23,7 +23,7 @@ describe("DisputeResolverHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, @@ -125,7 +125,7 @@ describe("DisputeResolverHandler", function () { ] = await ethers.getSigners(); // make all account the same - operator = clerk = admin; + assistant = clerk = admin; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -224,7 +224,7 @@ describe("DisputeResolverHandler", function () { expect(emptyAuthToken.isValid()).is.true; // Create two additional sellers and create seller allow list - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); seller2 = mockSeller(other1.address, other1.address, other1.address, other1.address); let seller3 = mockSeller(other2.address, other2.address, other2.address, other2.address); @@ -240,7 +240,7 @@ describe("DisputeResolverHandler", function () { sellerAllowList = ["3", "1"]; // Create a valid dispute resolver, then set fields in tests directly - disputeResolver = mockDisputeResolver(operator.address, admin.address, clerk.address, treasury.address); + disputeResolver = mockDisputeResolver(assistant.address, admin.address, clerk.address, treasury.address); expect(disputeResolver.isValid()).is.true; // How that dispute resolver looks as a returned struct @@ -251,7 +251,7 @@ describe("DisputeResolverHandler", function () { disputeResolverPendingUpdate.admin = ethers.constants.AddressZero; disputeResolverPendingUpdate.clerk = ethers.constants.AddressZero; disputeResolverPendingUpdate.treasury = ethers.constants.AddressZero; - disputeResolverPendingUpdate.operator = ethers.constants.AddressZero; + disputeResolverPendingUpdate.assistant = ethers.constants.AddressZero; disputeResolverPendingUpdate.escalationResponsePeriod = "0"; disputeResolverPendingUpdate.metadataUri = ""; disputeResolverPendingUpdate.active = false; @@ -535,14 +535,14 @@ describe("DisputeResolverHandler", function () { }); it("Any address is the zero address", async function () { - disputeResolver.operator = ethers.constants.AddressZero; + disputeResolver.assistant = ethers.constants.AddressZero; // Attempt to Create a DisputeResolver, expecting revert await expect( accountHandler.connect(admin).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); - disputeResolver.operator = operator.address; + disputeResolver.assistant = assistant.address; disputeResolver.admin = ethers.constants.AddressZero; // Attempt to Create a DisputeResolver, expecting revert @@ -569,10 +569,10 @@ describe("DisputeResolverHandler", function () { it("Address is not unique to this dispute resolver Id", async function () { disputeResolver2 = mockDisputeResolver( - operator.address, - operator.address, - operator.address, - operator.address + assistant.address, + assistant.address, + assistant.address, + assistant.address ); expect(disputeResolver2.isValid()).is.true; disputeResolver2Struct = disputeResolver2.toStruct(); @@ -656,33 +656,33 @@ describe("DisputeResolverHandler", function () { }); it("Caller is not the supplied admin", async function () { - disputeResolver.operator = rando.address; + disputeResolver.assistant = rando.address; disputeResolver.clerk = rando.address; // Create a dispute resolver await expect( accountHandler.connect(rando).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) - ).to.revertedWith(RevertReasons.NOT_ADMIN_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ADMIN_ASSISTANT_AND_CLERK); }); - it("Caller is not the supplied operator", async function () { + it("Caller is not the supplied assistant", async function () { disputeResolver.admin = rando.address; disputeResolver.clerk = rando.address; // Create a dispute resolver await expect( accountHandler.connect(rando).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) - ).to.revertedWith(RevertReasons.NOT_ADMIN_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ADMIN_ASSISTANT_AND_CLERK); }); it("Caller is not the supplied clerk", async function () { disputeResolver.admin = rando.address; - disputeResolver.operator = rando.address; + disputeResolver.assistant = rando.address; // Create a dispute resolver await expect( accountHandler.connect(rando).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList) - ).to.revertedWith(RevertReasons.NOT_ADMIN_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ADMIN_ASSISTANT_AND_CLERK); }); it("Active is false", async function () { @@ -834,7 +834,7 @@ describe("DisputeResolverHandler", function () { disputeResolver.escalationResponsePeriod = Number( Number(disputeResolver.escalationResponsePeriod) - oneWeek ).toString(); - disputeResolver.operator = disputeResolverPendingUpdate.operator = other1.address; + disputeResolver.assistant = disputeResolverPendingUpdate.assistant = other1.address; disputeResolver.admin = disputeResolverPendingUpdate.admin = other2.address; disputeResolver.clerk = disputeResolverPendingUpdate.clerk = other3.address; disputeResolver.treasury = other4.address; @@ -854,8 +854,8 @@ describe("DisputeResolverHandler", function () { .to.emit(accountHandler, "DisputeResolverUpdatePending") .withArgs(disputeResolver.id, disputeResolverPendingUpdateStruct, admin.address); - // Operator admin and clerk needs owner approval and won't be updated until then - expectedDisputeResolver.operator = operator.address; + // Assistant admin and clerk needs owner approval and won't be updated until then + expectedDisputeResolver.assistant = assistant.address; expectedDisputeResolver.admin = admin.address; expectedDisputeResolver.clerk = clerk.address; expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); @@ -883,7 +883,7 @@ describe("DisputeResolverHandler", function () { disputeResolver.escalationResponsePeriod = Number( Number(disputeResolver.escalationResponsePeriod) - oneWeek ).toString(); - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; disputeResolver.admin = other2.address; disputeResolver.clerk = other3.address; disputeResolver.treasury = other4.address; @@ -896,10 +896,10 @@ describe("DisputeResolverHandler", function () { // Update dispute resolver await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); - // Approve operator update + // Approve assistant update await accountHandler .connect(other1) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]); + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]); // Approve admin update await accountHandler @@ -933,7 +933,7 @@ describe("DisputeResolverHandler", function () { ); //Check that old addresses are no longer mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(operator.address); + [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(assistant.address); expect(exists).to.be.false; [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(admin.address); @@ -943,7 +943,7 @@ describe("DisputeResolverHandler", function () { expect(exists).to.be.false; //Check that new addresses are mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(disputeResolver.operator); + [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(disputeResolver.assistant); expect(exists).to.be.true; [exists] = await accountHandler.connect(rando).getDisputeResolverByAddress(disputeResolver.admin); @@ -1017,7 +1017,7 @@ describe("DisputeResolverHandler", function () { }); it("should update only one address", async function () { - disputeResolver.operator = other2.address; + disputeResolver.assistant = other2.address; expect(disputeResolver.isValid()).is.true; expectedDisputeResolver = disputeResolver.clone(); @@ -1029,7 +1029,7 @@ describe("DisputeResolverHandler", function () { // Approve the update await accountHandler .connect(other2) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]); + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]); // Get the disupte resolver as a struct [, disputeResolverStruct] = await accountHandler.connect(rando).getDisputeResolver(disputeResolver.id); @@ -1076,7 +1076,7 @@ describe("DisputeResolverHandler", function () { disputeResolver.escalationResponsePeriod = Number( Number(disputeResolver.escalationResponsePeriod) - oneWeek ).toString(); - disputeResolver.operator = rando.address; + disputeResolver.assistant = rando.address; disputeResolver.admin = rando.address; disputeResolver.clerk = rando.address; disputeResolver.treasury = rando.address; @@ -1089,11 +1089,11 @@ describe("DisputeResolverHandler", function () { // Update the first dispute resolver await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); - // Approve operator, clerk and admin update + // Approve assistant, clerk and admin update await accountHandler .connect(rando) .optInToDisputeResolverUpdate(disputeResolver.id, [ - DisputeResolverUpdateFields.Operator, + DisputeResolverUpdateFields.Assistant, DisputeResolverUpdateFields.Admin, DisputeResolverUpdateFields.Clerk, ]); @@ -1209,7 +1209,7 @@ describe("DisputeResolverHandler", function () { it("should be possible to use non-unique treasury address", async function () { // Update dispute resolver fields - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; disputeResolver.admin = other2.address; disputeResolver.clerk = other3.address; disputeResolver.active = true; @@ -1217,7 +1217,7 @@ describe("DisputeResolverHandler", function () { expectedDisputeResolverStruct = disputeResolver.toStruct(); - disputeResolverPendingUpdate.operator = other1.address; + disputeResolverPendingUpdate.assistant = other1.address; disputeResolverPendingUpdate.admin = other2.address; disputeResolverPendingUpdate.clerk = other3.address; disputeResolverPendingUpdateStruct = disputeResolverPendingUpdate.toStruct(); @@ -1227,10 +1227,10 @@ describe("DisputeResolverHandler", function () { .to.emit(accountHandler, "DisputeResolverUpdatePending") .withArgs(disputeResolver.id, disputeResolverPendingUpdateStruct, admin.address); - // Approve operator update + // Approve assistant update await accountHandler .connect(other1) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]); + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]); // Approve admin update await accountHandler @@ -1255,7 +1255,7 @@ describe("DisputeResolverHandler", function () { disputeResolver2Struct = disputeResolver2.toStruct(); disputeResolverPendingUpdate.admin = ethers.constants.AddressZero; - disputeResolverPendingUpdate.operator = ethers.constants.AddressZero; + disputeResolverPendingUpdate.assistant = ethers.constants.AddressZero; disputeResolverPendingUpdate.clerk = ethers.constants.AddressZero; disputeResolverPendingUpdateStruct = disputeResolverPendingUpdate.toStruct(); @@ -1265,9 +1265,9 @@ describe("DisputeResolverHandler", function () { .withArgs(disputeResolver2.id, disputeResolver2Struct, disputeResolverPendingUpdateStruct, rando.address); }); - it("should be possible to use the same address for operator, admin, clerk, and treasury", async function () { + it("should be possible to use the same address for assistant, admin, clerk, and treasury", async function () { // Update dispute resolver fields - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; disputeResolver.admin = other1.address; disputeResolver.clerk = other1.address; disputeResolver.treasury = other1.address; @@ -1275,12 +1275,12 @@ describe("DisputeResolverHandler", function () { // Treasury is the only address that doesn't need owner opt-in expectedDisputeResolver = disputeResolver.clone(); - expectedDisputeResolver.operator = operator.address; + expectedDisputeResolver.assistant = assistant.address; expectedDisputeResolver.admin = admin.address; expectedDisputeResolver.clerk = clerk.address; expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); - disputeResolverPendingUpdate.operator = other1.address; + disputeResolverPendingUpdate.assistant = other1.address; disputeResolverPendingUpdate.admin = other1.address; disputeResolverPendingUpdate.clerk = other1.address; disputeResolverPendingUpdateStruct = disputeResolverPendingUpdate.toStruct(); @@ -1306,17 +1306,17 @@ describe("DisputeResolverHandler", function () { expectedDisputeResolver = disputeResolver.clone(); expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); - disputeResolverPendingUpdate.operator = ethers.constants.AddressZero; + disputeResolverPendingUpdate.assistant = ethers.constants.AddressZero; disputeResolverPendingUpdate.admin = ethers.constants.AddressZero; disputeResolverPendingUpdate.clerk = ethers.constants.AddressZero; disputeResolverPendingUpdateStruct = disputeResolverPendingUpdate.toStruct(); - // Approve operator update + // Approve assistant update await expect( accountHandler .connect(other1) .optInToDisputeResolverUpdate(disputeResolver.id, [ - DisputeResolverUpdateFields.Operator, + DisputeResolverUpdateFields.Assistant, DisputeResolverUpdateFields.Admin, DisputeResolverUpdateFields.Clerk, ]) @@ -1367,14 +1367,14 @@ describe("DisputeResolverHandler", function () { }); it("Any address is the zero address", async function () { - disputeResolver.operator = ethers.constants.AddressZero; + disputeResolver.assistant = ethers.constants.AddressZero; // Attempt to update the disputer resolver, expecting revert await expect(accountHandler.connect(admin).updateDisputeResolver(disputeResolver)).to.revertedWith( RevertReasons.INVALID_ADDRESS ); - disputeResolver.operator = operator.address; + disputeResolver.assistant = assistant.address; disputeResolver.admin = ethers.constants.AddressZero; // Attempt to update the disputer resolver, expecting revert @@ -1408,14 +1408,14 @@ describe("DisputeResolverHandler", function () { .createDisputeResolver(disputeResolver2, disputeResolverFees, sellerAllowList); //Set each address value to be same as disputeResolver2 and expect revert - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; // Attempt to update dispute resolver 1 with non-unique admin address, expecting revert await expect(accountHandler.connect(admin).updateDisputeResolver(disputeResolver)).to.revertedWith( RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE ); - disputeResolver.operator = operator.address; + disputeResolver.assistant = assistant.address; disputeResolver.admin = other1.address; // Attempt to update dispute resolver 1 with non-unique admin address, expecting revert @@ -1441,25 +1441,25 @@ describe("DisputeResolverHandler", function () { .connect(other1) .createDisputeResolver(disputeResolver2, disputeResolverFees, sellerAllowList); - //Set dispute resolver 2's admin address to dispute resolver 1's operator address - disputeResolver2.admin = operator.address; + //Set dispute resolver 2's admin address to dispute resolver 1's assistant address + disputeResolver2.admin = assistant.address; // Attempt to update dispute resolver 1 with non-unique admin address, expecting revert await expect(accountHandler.connect(other1).updateDisputeResolver(disputeResolver2)).to.revertedWith( RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE ); - //Set dispute resolver 2's operator address to dispute resolver 1's clerk address + //Set dispute resolver 2's assistant address to dispute resolver 1's clerk address disputeResolver2.admin = other2.address; - disputeResolver2.operator = clerk.address; + disputeResolver2.assistant = clerk.address; - // Attempt to update dispute resolver 1 with non-unique operator address, expecting revert + // Attempt to update dispute resolver 1 with non-unique assistant address, expecting revert await expect(accountHandler.connect(other1).updateDisputeResolver(disputeResolver2)).to.revertedWith( RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE ); //Set dispute resolver 2's clerk address to dispute resolver 1's admin address - disputeResolver2.operator = other1.address; + disputeResolver2.assistant = other1.address; disputeResolver2.clerk = admin.address; // Attempt to update dispute resolver 1 with non-unique clerk address, expecting revert @@ -2253,9 +2253,9 @@ describe("DisputeResolverHandler", function () { .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); }); - it("New operator should opt-in to update disputeResolver", async function () { - disputeResolver.operator = other1.address; - expectedDisputeResolver.operator = other1.address; + it("New assistant should opt-in to update disputeResolver", async function () { + disputeResolver.assistant = other1.address; + expectedDisputeResolver.assistant = other1.address; expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); @@ -2263,7 +2263,7 @@ describe("DisputeResolverHandler", function () { await expect( accountHandler .connect(other1) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ) .to.emit(accountHandler, "DisputeResolverUpdateApplied") .withArgs( @@ -2316,10 +2316,10 @@ describe("DisputeResolverHandler", function () { ); }); - it("Should update admin, clerk and operator in a single call ", async function () { + it("Should update admin, clerk and assistant in a single call ", async function () { disputeResolver.clerk = expectedDisputeResolver.clerk = other1.address; disputeResolver.admin = expectedDisputeResolver.admin = other1.address; - disputeResolver.operator = expectedDisputeResolver.operator = other1.address; + disputeResolver.assistant = expectedDisputeResolver.assistant = other1.address; expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); @@ -2330,7 +2330,7 @@ describe("DisputeResolverHandler", function () { .optInToDisputeResolverUpdate(disputeResolver.id, [ DisputeResolverUpdateFields.Clerk, DisputeResolverUpdateFields.Admin, - DisputeResolverUpdateFields.Operator, + DisputeResolverUpdateFields.Assistant, ]) ) .to.emit(accountHandler, "DisputeResolverUpdateApplied") @@ -2343,7 +2343,7 @@ describe("DisputeResolverHandler", function () { }); it("If updateDisputeResolver is called twice with no optIn in between, disputeResolverPendingUpdate is populated with the data from second call", async function () { - disputeResolver.operator = disputeResolverPendingUpdate.operator = other1.address; + disputeResolver.assistant = disputeResolverPendingUpdate.assistant = other1.address; disputeResolverPendingUpdateStruct = disputeResolverPendingUpdate.toStruct(); await expect(accountHandler.connect(admin).updateDisputeResolver(disputeResolver)) @@ -2351,9 +2351,9 @@ describe("DisputeResolverHandler", function () { .withArgs(disputeResolver.id, disputeResolverPendingUpdateStruct, admin.address); const disputeResolverPendingUpdate2 = disputeResolverPendingUpdate.clone(); - disputeResolver.operator = - expectedDisputeResolver.operator = - disputeResolverPendingUpdate2.operator = + disputeResolver.assistant = + expectedDisputeResolver.assistant = + disputeResolverPendingUpdate2.assistant = other2.address; let disputeResolverPendingUpdate2Struct = disputeResolverPendingUpdate2.toStruct(); @@ -2364,10 +2364,10 @@ describe("DisputeResolverHandler", function () { await expect( accountHandler .connect(other1) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); - disputeResolverPendingUpdate.operator = ethers.constants.AddressZero; + disputeResolverPendingUpdate.assistant = ethers.constants.AddressZero; disputeResolverPendingUpdate2Struct = disputeResolverPendingUpdate.toStruct(); expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); @@ -2375,7 +2375,7 @@ describe("DisputeResolverHandler", function () { await expect( accountHandler .connect(other2) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ) .to.emit(accountHandler, "DisputeResolverUpdateApplied") .withArgs( @@ -2387,7 +2387,7 @@ describe("DisputeResolverHandler", function () { }); it("Should not emit 'DisputeResolverUpdateApplied' event if caller doesn't specify any field", async function () { - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); await expect(accountHandler.connect(other1).optInToDisputeResolverUpdate(disputeResolver.id, [])).to.not.emit( @@ -2397,7 +2397,7 @@ describe("DisputeResolverHandler", function () { }); it("Should not emit 'DisputeResolverUpdateApplied'event if there is no pending update for specified field", async function () { - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); await expect( @@ -2411,7 +2411,7 @@ describe("DisputeResolverHandler", function () { it("There are no pending updates", async function () { disputeResolver.clerk = other1.address; disputeResolver.admin = other1.address; - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; expectedDisputeResolver = disputeResolver.clone(); expectedDisputeResolver.active = true; expectedDisputeResolverStruct = expectedDisputeResolver.toStruct(); @@ -2425,7 +2425,7 @@ describe("DisputeResolverHandler", function () { .optInToDisputeResolverUpdate(disputeResolver.id, [ DisputeResolverUpdateFields.Clerk, DisputeResolverUpdateFields.Admin, - DisputeResolverUpdateFields.Operator, + DisputeResolverUpdateFields.Assistant, ]) ) .to.emit(accountHandler, "DisputeResolverUpdateApplied") @@ -2467,8 +2467,8 @@ describe("DisputeResolverHandler", function () { ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); }); - it("Caller is not the new operator", async function () { - disputeResolver.operator = other1.address; + it("Caller is not the new assistant", async function () { + disputeResolver.assistant = other1.address; disputeResolverStruct = disputeResolver.toStruct(); await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); @@ -2476,12 +2476,12 @@ describe("DisputeResolverHandler", function () { await expect( accountHandler .connect(other2) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); }); it("The DisputeResolvers region of protocol is paused", async function () { - disputeResolver.operator = other1.address; + disputeResolver.assistant = other1.address; await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); @@ -2535,12 +2535,12 @@ describe("DisputeResolverHandler", function () { ).to.revertedWith(RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE); }); - it("Operator is not unique to this disputeResolver", async function () { - // Update disputeResolver operator - disputeResolver.operator = other1.address; + it("Assistant is not unique to this disputeResolver", async function () { + // Update disputeResolver assistant + disputeResolver.assistant = other1.address; await accountHandler.connect(admin).updateDisputeResolver(disputeResolver); - // Create disputeResolver with same operator + // Create disputeResolver with same assistant disputeResolver2 = mockDisputeResolver(other1.address, other1.address, other1.address, other1.address); expect(disputeResolver2.isValid()).is.true; @@ -2548,11 +2548,11 @@ describe("DisputeResolverHandler", function () { .connect(other1) .createDisputeResolver(disputeResolver2, disputeResolverFees, sellerAllowList); - // Attemp to approve the update with non-unique operator, expecting revert + // Attemp to approve the update with non-unique assistant, expecting revert await expect( accountHandler .connect(other1) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.DISPUTE_RESOLVER_ADDRESS_MUST_BE_UNIQUE); }); }); diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 6f6cc1af2..cf0a54976 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -57,7 +57,7 @@ describe("IBosonExchangeHandler", function () { let InterfaceIds; let deployer, pauser, - operator, + assistant, admin, clerk, treasury, @@ -65,7 +65,7 @@ describe("IBosonExchangeHandler", function () { buyer, newOwner, fauxClient, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -131,8 +131,8 @@ describe("IBosonExchangeHandler", function () { ] = await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -316,7 +316,7 @@ describe("IBosonExchangeHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // AuthToken @@ -334,7 +334,7 @@ describe("IBosonExchangeHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -369,7 +369,7 @@ describe("IBosonExchangeHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables price = offer.price; @@ -391,7 +391,7 @@ describe("IBosonExchangeHandler", function () { // Deposit seller funds so the commit will succeed await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); }); @@ -575,7 +575,7 @@ describe("IBosonExchangeHandler", function () { // Make sure that vouchers have correct royalty fee for exchangeId 1 exchangeId = "1"; let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucherClone.connect(operator).royaltyInfo(exchangeId, offer.price); + [receiver, royaltyAmount] = await bosonVoucherClone.connect(assistant).royaltyInfo(exchangeId, offer.price); // Expectations let expectedRecipient = seller1Treasury; //Expect 1st seller's treasury address as exchange id exists @@ -590,7 +590,7 @@ describe("IBosonExchangeHandler", function () { seller2Treasury = seller.treasury; receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucherClone2.connect(operator).royaltyInfo(exchangeId, offer.price); + [receiver, royaltyAmount] = await bosonVoucherClone2.connect(assistant).royaltyInfo(exchangeId, offer.price); // Expectations expectedRecipient = seller2Treasury; //Expect 2nd seller's treasury address as exchange id exists @@ -612,7 +612,9 @@ describe("IBosonExchangeHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); exchange.offerId = offerId = "2"; // tested against second offer // Commit to offer, retrieving the event @@ -667,7 +669,7 @@ describe("IBosonExchangeHandler", function () { expect(offer.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, ...Object.values(details), agentId); + await offerHandler.connect(assistant).createOffer(offer, ...Object.values(details), agentId); exchange.offerId = offerId = "2"; // first offer is created on beforeEach // Commit to offer @@ -701,7 +703,9 @@ describe("IBosonExchangeHandler", function () { expect(offer.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId); @@ -754,7 +758,7 @@ describe("IBosonExchangeHandler", function () { it("offer is voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Attempt to commit to the voided offer, expecting revert await expect( @@ -775,7 +779,7 @@ describe("IBosonExchangeHandler", function () { offerDates.validUntil = ethers.BigNumber.from(offerDates.validFrom).add(10).toString(); // just after the valid from so it succeeds. await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to the not availabe offer, expecting revert @@ -798,7 +802,7 @@ describe("IBosonExchangeHandler", function () { // Create an offer with only 1 item offer.quantityAvailable = "1"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer, so it's not availble anymore await exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }); @@ -815,19 +819,19 @@ describe("IBosonExchangeHandler", function () { let tokenId; beforeEach(async function () { // Reserve range - await offerHandler.connect(operator).reserveRange(offer.id, offer.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable); // expected address of the first clone const voucherCloneAddress = calculateContractAddress(accountHandler.address, "1"); bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherCloneAddress); - await bosonVoucher.connect(operator).preMint(offer.id, offer.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); tokenId = "1"; }); it("should emit a BuyerCommitted event", async function () { // Commit to preminted offer, retrieving the event - tx = await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + tx = await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); txReceipt = await tx.wait(); event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); @@ -862,7 +866,7 @@ describe("IBosonExchangeHandler", function () { let nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); // Commit to preminted offer, creating a new exchange - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // Get the next exchange id and ensure it was incremented by the creation of the offer nextExchangeId = await exchangeHandler.connect(rando).getNextExchangeId(); @@ -877,7 +881,7 @@ describe("IBosonExchangeHandler", function () { await expect(bosonVoucher.ownerOf(nextExchangeId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); // Commit to preminted offer, creating a new exchange - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // Voucher with nextExchangeId still should not exist await expect(bosonVoucher.ownerOf(nextExchangeId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); @@ -886,18 +890,18 @@ describe("IBosonExchangeHandler", function () { it("ERC2981: issued voucher should have royalty fees", async function () { // set non zero royalty percentage const royaltyPercentage = "10"; - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); // Before voucher is transferred, it should have zero royalty fee - let [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(tokenId, offer.price); + let [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(tokenId, offer.price); assert.equal(receiver, ethers.constants.AddressZero, "Recipient address is incorrect"); assert.equal(royaltyAmount.toString(), "0", "Royalty amount is incorrect"); // Commit to preminted offer, creating a new exchange - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // After voucher is transferred, it should have royalty fee - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(tokenId, offer.price); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(tokenId, offer.price); assert.equal(receiver, treasury.address, "Recipient address is incorrect"); assert.equal( royaltyAmount.toString(), @@ -912,7 +916,7 @@ describe("IBosonExchangeHandler", function () { const quantityAvailableBefore = offer.quantityAvailable; // Commit to preminted offer - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // Offer qunantityAvailable should be decremented [, offer] = await offerHandler.connect(rando).getOffer(offerId); @@ -931,7 +935,9 @@ describe("IBosonExchangeHandler", function () { // Create the offer offer.quantityAvailable = "10"; const rangeLength = "5"; - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Deposit seller funds so the commit will succeed await fundsHandler @@ -939,7 +945,7 @@ describe("IBosonExchangeHandler", function () { .depositFunds(seller.id, ethers.constants.AddressZero, offer.sellerDeposit, { value: offer.sellerDeposit }); // reserve half of the offer, so it's still possible to commit directly - await offerHandler.connect(operator).reserveRange(offerId, rangeLength); + await offerHandler.connect(assistant).reserveRange(offerId, rangeLength); // Commit to offer directly expect( @@ -954,7 +960,7 @@ describe("IBosonExchangeHandler", function () { // Attempt to create an exchange, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -964,7 +970,7 @@ describe("IBosonExchangeHandler", function () { // Attempt to create a buyer, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -977,7 +983,7 @@ describe("IBosonExchangeHandler", function () { it("Exchange exists already", async function () { // Commit to preminted offer, creating a new exchange - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // impersonate voucher contract and give it some funds const impersonatedBosonVoucher = await ethers.getImpersonatedSigner(bosonVoucher.address); @@ -994,11 +1000,11 @@ describe("IBosonExchangeHandler", function () { it("offer is voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Attempt to commit to the voided offer, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); }); @@ -1017,17 +1023,17 @@ describe("IBosonExchangeHandler", function () { offerDates.validUntil = ethers.BigNumber.from(offerDates.validFrom).add(10).toString(); // just after the valid from so it succeeds. await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Reserve a range and premint vouchers tokenId = await exchangeHandler.getNextExchangeId(); - await offerHandler.connect(operator).reserveRange(offerId, "1"); - await bosonVoucher.connect(operator).preMint(offerId, "1"); + await offerHandler.connect(assistant).reserveRange(offerId, "1"); + await bosonVoucher.connect(assistant).preMint(offerId, "1"); // Attempt to commit to the not availabe offer, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); }); @@ -1037,7 +1043,7 @@ describe("IBosonExchangeHandler", function () { // Attempt to commit to the expired offer, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); }); @@ -1045,7 +1051,7 @@ describe("IBosonExchangeHandler", function () { // Create an offer with only 1 item offer.quantityAvailable = "1"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer, so it's not availble anymore await exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }); @@ -1072,7 +1078,7 @@ describe("IBosonExchangeHandler", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should emit a BuyerCommitted event if user meets condition", async function () { @@ -1143,7 +1149,7 @@ describe("IBosonExchangeHandler", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should emit a BuyerCommitted event if user meets condition", async function () { @@ -1215,7 +1221,7 @@ describe("IBosonExchangeHandler", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should emit a BuyerCommitted event if user meets condition", async function () { @@ -1288,7 +1294,7 @@ describe("IBosonExchangeHandler", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should emit a BuyerCommitted event if user meets condition", async function () { @@ -1364,7 +1370,7 @@ describe("IBosonExchangeHandler", function () { // Create Group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should emit a BuyerCommitted event", async function () { @@ -1414,7 +1420,7 @@ describe("IBosonExchangeHandler", function () { assert.equal(response, ExchangeState.Completed, "Exchange state is incorrect"); }); - it("should emit an ExchangeCompleted event if operator calls after dispute period", async function () { + it("should emit an ExchangeCompleted event if assistant calls after dispute period", async function () { // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -1430,9 +1436,9 @@ describe("IBosonExchangeHandler", function () { await setNextBlockTimestamp(newTime); // Complete exchange - await expect(exchangeHandler.connect(operator).completeExchange(exchange.id)) + await expect(exchangeHandler.connect(assistant).completeExchange(exchange.id)) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchange.id, operator.address); + .withArgs(offerId, buyerId, exchange.id, assistant.address); }); it("should emit an ExchangeCompleted event if anyone calls after dispute period", async function () { @@ -1462,7 +1468,7 @@ describe("IBosonExchangeHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Attempt to complete an exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchange(exchangeId)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchange(exchangeId)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1472,7 +1478,7 @@ describe("IBosonExchangeHandler", function () { exchangeId = "666"; // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchange(exchangeId)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchange(exchangeId)).to.revertedWith( RevertReasons.NO_SUCH_EXCHANGE ); }); @@ -1485,7 +1491,7 @@ describe("IBosonExchangeHandler", function () { assert.equal(response, ExchangeState.Committed, "Exchange state is incorrect"); // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchange(exchange.id)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchange(exchange.id)).to.revertedWith( RevertReasons.INVALID_STATE ); }); @@ -1495,7 +1501,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).cancelVoucher(exchange.id); // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchange(exchange.id)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchange(exchange.id)).to.revertedWith( RevertReasons.INVALID_STATE ); }); @@ -1513,7 +1519,7 @@ describe("IBosonExchangeHandler", function () { ); }); - it("caller is seller's operator and offer dispute period has not elapsed", async function () { + it("caller is seller's assistant and offer dispute period has not elapsed", async function () { // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -1521,7 +1527,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchange(exchange.id)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchange(exchange.id)).to.revertedWith( RevertReasons.DISPUTE_PERIOD_NOT_ELAPSED ); }); @@ -1581,7 +1587,7 @@ describe("IBosonExchangeHandler", function () { } }); - it("should emit an ExchangeCompleted event if operator calls after dispute period", async function () { + it("should emit an ExchangeCompleted event if assistant calls after dispute period", async function () { // Get the current block info blockNumber = await ethers.provider.getBlockNumber(); block = await ethers.provider.getBlock(blockNumber); @@ -1591,26 +1597,26 @@ describe("IBosonExchangeHandler", function () { await setNextBlockTimestamp(newTime); // Complete exchange - const tx = await exchangeHandler.connect(operator).completeExchangeBatch(exchangesToComplete); + const tx = await exchangeHandler.connect(assistant).completeExchangeBatch(exchangesToComplete); await expect(tx) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchangesToComplete[0], operator.address); + .withArgs(offerId, buyerId, exchangesToComplete[0], assistant.address); await expect(tx) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchangesToComplete[1], operator.address); + .withArgs(offerId, buyerId, exchangesToComplete[1], assistant.address); await expect(tx) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchangesToComplete[2], operator.address); + .withArgs(offerId, buyerId, exchangesToComplete[2], assistant.address); await expect(tx) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchangesToComplete[3], operator.address); + .withArgs(offerId, buyerId, exchangesToComplete[3], assistant.address); await expect(tx) .to.emit(exchangeHandler, "ExchangeCompleted") - .withArgs(offerId, buyerId, exchangesToComplete[4], operator.address); + .withArgs(offerId, buyerId, exchangesToComplete[4], assistant.address); }); it("should emit an ExchangeCompleted event if anyone calls after dispute period", async function () { @@ -1674,7 +1680,7 @@ describe("IBosonExchangeHandler", function () { exchangesToComplete = [exchangeId, ...exchangesToComplete]; // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchangeBatch(exchangesToComplete)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchangeBatch(exchangesToComplete)).to.revertedWith( RevertReasons.NO_SUCH_EXCHANGE ); }); @@ -1691,7 +1697,7 @@ describe("IBosonExchangeHandler", function () { exchangesToComplete = [exchangeId, ...exchangesToComplete]; // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchangeBatch(exchangesToComplete)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchangeBatch(exchangesToComplete)).to.revertedWith( RevertReasons.INVALID_STATE ); }); @@ -1714,7 +1720,7 @@ describe("IBosonExchangeHandler", function () { ); }); - it("caller is seller's operator and offer dispute period has not elapsed", async function () { + it("caller is seller's assistant and offer dispute period has not elapsed", async function () { // Create exchange with id 6 await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -1724,7 +1730,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); // Attempt to complete the exchange, expecting revert - await expect(exchangeHandler.connect(operator).completeExchangeBatch(exchangesToComplete)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).completeExchangeBatch(exchangesToComplete)).to.revertedWith( RevertReasons.DISPUTE_PERIOD_NOT_ELAPSED ); }); @@ -1737,16 +1743,16 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); }); - it("should emit an VoucherRevoked event when seller's operator calls", async function () { + it("should emit an VoucherRevoked event when seller's assistant calls", async function () { // Revoke the voucher, expecting event - await expect(exchangeHandler.connect(operator).revokeVoucher(exchange.id)) + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchange.id)) .to.emit(exchangeHandler, "VoucherRevoked") - .withArgs(offerId, exchange.id, operator.address); + .withArgs(offerId, exchange.id, assistant.address); }); it("should update state", async function () { // Revoke the voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Get the exchange state [, response] = await exchangeHandler.connect(rando).getExchangeState(exchange.id); @@ -1761,7 +1767,7 @@ describe("IBosonExchangeHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Attempt to complete an exchange, expecting revert - await expect(exchangeHandler.connect(operator).revokeVoucher(exchange.id)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchange.id)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1771,7 +1777,7 @@ describe("IBosonExchangeHandler", function () { exchangeId = "666"; // Attempt to revoke the voucher, expecting revert - await expect(exchangeHandler.connect(operator).revokeVoucher(exchangeId)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)).to.revertedWith( RevertReasons.NO_SUCH_EXCHANGE ); }); @@ -1781,15 +1787,15 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).cancelVoucher(exchange.id); // Attempt to revoke the voucher, expecting revert - await expect(exchangeHandler.connect(operator).revokeVoucher(exchange.id)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchange.id)).to.revertedWith( RevertReasons.INVALID_STATE ); }); - it("caller is not seller's operator", async function () { + it("caller is not seller's assistant", async function () { // Attempt to complete the exchange, expecting revert await expect(exchangeHandler.connect(rando).revokeVoucher(exchange.id)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); }); @@ -1873,7 +1879,7 @@ describe("IBosonExchangeHandler", function () { it("exchange is not in committed state", async function () { // Revoke the voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Attempt to cancel the voucher, expecting revert await expect(exchangeHandler.connect(buyer).cancelVoucher(exchange.id)).to.revertedWith( @@ -1997,7 +2003,7 @@ describe("IBosonExchangeHandler", function () { await setNextBlockTimestamp(Number(voucherRedeemableFrom) + Number(voucherValid) + Number(oneWeek)); // Revoke the voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Attempt to expire the voucher, expecting revert await expect(exchangeHandler.connect(buyer).expireVoucher(exchange.id)).to.revertedWith( @@ -2067,7 +2073,7 @@ describe("IBosonExchangeHandler", function () { it("exchange is not in committed state", async function () { // Revoke the voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Attempt to redeem the voucher, expecting revert await expect(exchangeHandler.connect(buyer).redeemVoucher(exchange.id)).to.revertedWith( @@ -2104,16 +2110,16 @@ describe("IBosonExchangeHandler", function () { context("👉 redeemVoucher() with bundle", async function () { beforeEach(async function () { // Mint some tokens to be bundled - await foreign20.connect(operator).mint(operator.address, "500"); + await foreign20.connect(assistant).mint(assistant.address, "500"); // Mint first two and last two tokens of range - await foreign721.connect(operator).mint("0", "2"); - await foreign721.connect(operator).mint("8", "2"); - await foreign1155.connect(operator).mint("1", "500"); + await foreign721.connect(assistant).mint("0", "2"); + await foreign721.connect(assistant).mint("8", "2"); + await foreign1155.connect(assistant).mint("1", "500"); // Approve the protocol diamond to transfer seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "30"); - await foreign721.connect(operator).setApprovalForAll(protocolDiamond.address, true); - await foreign1155.connect(operator).setApprovalForAll(protocolDiamond.address, true); + await foreign20.connect(assistant).approve(protocolDiamond.address, "30"); + await foreign721.connect(assistant).setApprovalForAll(protocolDiamond.address, true); + await foreign1155.connect(assistant).setApprovalForAll(protocolDiamond.address, true); // Create an ERC20 twin twin20 = mockTwin(foreign20.address); @@ -2141,9 +2147,9 @@ describe("IBosonExchangeHandler", function () { twinIds = [twin20.id, twin721.id, twin1155.id]; // Create twins - await twinHandler.connect(operator).createTwin(twin20.toStruct()); - await twinHandler.connect(operator).createTwin(twin721.toStruct()); - await twinHandler.connect(operator).createTwin(twin1155.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin1155.toStruct()); }); context("📦 Offer bundled with ERC20 twin", async function () { @@ -2151,7 +2157,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], [twin20.id]); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2180,7 +2186,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check twin supplyAvailable - const [, twin] = await twinHandler.connect(operator).getTwin(twin20.id); + const [, twin] = await twinHandler.connect(assistant).getTwin(twin20.id); expect(twin.supplyAvailable).to.equal(twin20.supplyAvailable - twin20.amount); }); @@ -2191,19 +2197,19 @@ describe("IBosonExchangeHandler", function () { twin20.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "2"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin20.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2216,7 +2222,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check the supplyAvailable of the twin - const [exists, twin] = await twinHandler.connect(operator).getTwin(twin20.id); + const [exists, twin] = await twinHandler.connect(assistant).getTwin(twin20.id); expect(exists).to.be.true; expect(twin.supplyAvailable).to.equal(twin20.supplyAvailable); }); @@ -2227,17 +2233,17 @@ describe("IBosonExchangeHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); twin20.supplyAvailable = "3"; twin20.id = "4"; - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin20.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2262,7 +2268,7 @@ describe("IBosonExchangeHandler", function () { context("Twin transfer fail", async function () { it("should revoke exchange when buyer is an EOA", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "0"); + await foreign20.connect(assistant).approve(protocolDiamond.address, "0"); const tx = await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); @@ -2284,21 +2290,21 @@ describe("IBosonExchangeHandler", function () { it("should revoke exchange when ERC20 contract transferFrom returns false", async function () { const [foreign20ReturnFalse] = await deployMockTokens(["Foreign20TransferFromReturnFalse"]); - await foreign20ReturnFalse.connect(operator).mint(operator.address, "500"); - await foreign20ReturnFalse.connect(operator).approve(protocolDiamond.address, "100"); + await foreign20ReturnFalse.connect(assistant).mint(assistant.address, "500"); + await foreign20ReturnFalse.connect(assistant).approve(protocolDiamond.address, "100"); // Create a new ERC20 twin twin20 = mockTwin(foreign20ReturnFalse.address, TokenType.FungibleToken); twin20.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); // Create a new offer const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set time forward to the offer's voucherRedeemableFrom @@ -2306,7 +2312,7 @@ describe("IBosonExchangeHandler", function () { await setNextBlockTimestamp(Number(voucherRedeemableFrom)); // Create a new bundle - await bundleHandler.connect(operator).createBundle(new Bundle("1", seller.id, [++offerId], [twin20.id])); + await bundleHandler.connect(assistant).createBundle(new Bundle("1", seller.id, [++offerId], [twin20.id])); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2324,7 +2330,7 @@ describe("IBosonExchangeHandler", function () { it("should raise a dispute when buyer account is a contract", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "0"); + await foreign20.connect(assistant).approve(protocolDiamond.address, "0"); // Deploy contract to test redeem called by another contract let TestProtocolFunctionsFactory = await ethers.getContractFactory("TestProtocolFunctions"); @@ -2366,7 +2372,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], [twin721.id]); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2378,9 +2384,9 @@ describe("IBosonExchangeHandler", function () { it("Should transfer the twin", async function () { let tokenId = "9"; - // Check the operator owns the last ERC721 of twin range + // Check the assistant owns the last ERC721 of twin range owner = await foreign721.ownerOf(tokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); [exists, response] = await exchangeHandler.connect(rando).getExchangeState(exchange.id); // Redeem the voucher @@ -2393,9 +2399,9 @@ describe("IBosonExchangeHandler", function () { expect(owner).to.equal(buyer.address); tokenId = "8"; - // Check the operator owns the last ERC721 of twin range + // Check the assistant owns the last ERC721 of twin range owner = await foreign721.ownerOf(tokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Commit to offer for the second time await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2415,20 +2421,20 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check twin supplyAvailable - const [, twin] = await twinHandler.connect(operator).getTwin(twin721.id); + const [, twin] = await twinHandler.connect(assistant).getTwin(twin721.id); expect(twin.supplyAvailable).to.equal(twin721.supplyAvailable - 1); }); it("Should transfer the twin even if supplyAvailable is equal to 1", async function () { - await foreign721.connect(operator).mint("10", "2"); + await foreign721.connect(assistant).mint("10", "2"); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "1"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); twin721.supplyAvailable = "1"; @@ -2436,11 +2442,11 @@ describe("IBosonExchangeHandler", function () { twin721.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin721.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2471,17 +2477,17 @@ describe("IBosonExchangeHandler", function () { other721 = await TokenContractFactory.connect(rando).deploy(); // Mint enough tokens to cover the offer - await other721.connect(operator).mint("0", "2"); + await other721.connect(assistant).mint("0", "2"); // Approve the protocol diamond to transfer seller's tokens - await other721.connect(operator).setApprovalForAll(protocolDiamond.address, true); + await other721.connect(assistant).setApprovalForAll(protocolDiamond.address, true); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "2"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Change twin supply to unlimited and token address to the new token @@ -2490,11 +2496,11 @@ describe("IBosonExchangeHandler", function () { twin721.id = "4"; // Create a new twin with the new token address - await twinHandler.connect(operator).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin721.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2509,7 +2515,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check the supplyAvailable of the twin - const [exists, twin] = await twinHandler.connect(operator).getTwin(twin721.id); + const [exists, twin] = await twinHandler.connect(assistant).getTwin(twin721.id); expect(exists).to.be.true; expect(twin.supplyAvailable).to.equal(twin721.supplyAvailable); }); @@ -2520,9 +2526,9 @@ describe("IBosonExchangeHandler", function () { // tokenId transferred to the buyer is 0 let expectedTokenId = "0"; - // Check the operator owns the first ERC721 of twin range + // Check the assistant owns the first ERC721 of twin range owner = await other721.ownerOf(expectedTokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Redeem the voucher await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)) @@ -2535,9 +2541,9 @@ describe("IBosonExchangeHandler", function () { ++expectedTokenId; - // Check the operator owns the second ERC721 of twin range + // Check the assistant owns the second ERC721 of twin range owner = await other721.ownerOf(expectedTokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Commit to offer for the second time await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2557,7 +2563,7 @@ describe("IBosonExchangeHandler", function () { context("Twin transfer fail", async function () { it("should revoke exchange when buyer is an EOA", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign721.connect(operator).setApprovalForAll(protocolDiamond.address, false); + await foreign721.connect(assistant).setApprovalForAll(protocolDiamond.address, false); const tx = await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); @@ -2609,7 +2615,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], [twin1155.id]); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2640,7 +2646,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check twin supplyAvailable - const [, twin] = await twinHandler.connect(operator).getTwin(twin1155.id); + const [, twin] = await twinHandler.connect(assistant).getTwin(twin1155.id); expect(twin.supplyAvailable).to.equal(twin1155.supplyAvailable - twin1155.amount); }); @@ -2651,19 +2657,19 @@ describe("IBosonExchangeHandler", function () { twin1155.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin1155.toStruct()); + await twinHandler.connect(assistant).createTwin(twin1155.toStruct()); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "2"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin1155.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2676,7 +2682,7 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); // Check the supplyAvailable of the twin - const [exists, twin] = await twinHandler.connect(operator).getTwin(twin1155.id); + const [exists, twin] = await twinHandler.connect(assistant).getTwin(twin1155.id); expect(exists).to.be.true; expect(twin.supplyAvailable).to.equal(twin1155.supplyAvailable); }); @@ -2687,18 +2693,18 @@ describe("IBosonExchangeHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); twin1155.supplyAvailable = "1"; twin1155.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin1155.toStruct()); + await twinHandler.connect(assistant).createTwin(twin1155.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin1155.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2730,7 +2736,7 @@ describe("IBosonExchangeHandler", function () { context("Twin transfer fail", async function () { it("should revoke exchange when buyer is an EOA", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign1155.connect(operator).setApprovalForAll(protocolDiamond.address, false); + await foreign1155.connect(assistant).setApprovalForAll(protocolDiamond.address, false); const tx = await exchangeHandler.connect(buyer).redeemVoucher(exchange.id); await expect(tx) @@ -2794,7 +2800,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], twinIds); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2811,9 +2817,9 @@ describe("IBosonExchangeHandler", function () { balance = await foreign20.balanceOf(buyer.address); expect(balance).to.equal(0); - // Check the operator owns the ERC721 + // Check the assistant owns the ERC721 owner = await foreign721.ownerOf(tokenIdNonFungible); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Check the buyer's balance of the ERC1155 balance = await foreign1155.balanceOf(buyer.address, tokenIdMultiToken); @@ -2857,36 +2863,36 @@ describe("IBosonExchangeHandler", function () { }); it("Should transfer the twin even if supplyAvailable is equal to amount", async function () { - await foreign721.connect(operator).mint("10", "1"); + await foreign721.connect(assistant).mint("10", "1"); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "1"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); twin1155.supplyAvailable = "1"; twin1155.id = "4"; // Create a new twin - await twinHandler.connect(operator).createTwin(twin1155.toStruct()); + await twinHandler.connect(assistant).createTwin(twin1155.toStruct()); twin20.supplyAvailable = "3"; twin20.id = "5"; - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); twin721.supplyAvailable = "1"; twin721.tokenId = "10"; twin721.id = "6"; - await twinHandler.connect(operator).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin1155.id, twin20.id, twin721.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -2946,17 +2952,17 @@ describe("IBosonExchangeHandler", function () { other721 = await TokenContractFactory.connect(rando).deploy(); // Mint enough tokens to cover the offer - await other721.connect(operator).mint("0", "2"); + await other721.connect(assistant).mint("0", "2"); // Approve the protocol diamond to transfer seller's tokens - await other721.connect(operator).setApprovalForAll(protocolDiamond.address, true); + await other721.connect(assistant).setApprovalForAll(protocolDiamond.address, true); const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); offer.quantityAvailable = "2"; // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Change twin supply to unlimited and token address to the new token @@ -2964,21 +2970,21 @@ describe("IBosonExchangeHandler", function () { twin721.tokenAddress = other721.address; twin721.id = "4"; // Create a new ERC721 twin with the new token address - await twinHandler.connect(operator).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); twin20.supplyAvailable = ethers.constants.MaxUint256.toString(); twin20.id = "5"; // Create a new ERC20 twin with the new token address - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); twin1155.supplyAvailable = ethers.constants.MaxUint256.toString(); twin1155.id = "6"; // Create a new ERC1155 twin with the new token address - await twinHandler.connect(operator).createTwin(twin1155.toStruct()); + await twinHandler.connect(assistant).createTwin(twin1155.toStruct()); // Create a new bundle bundle = new Bundle("1", seller.id, [++offerId], [twin721.id, twin20.id, twin1155.id]); - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -3014,13 +3020,13 @@ describe("IBosonExchangeHandler", function () { ); // Check the supplyAvailable of each twin - let [, twin] = await twinHandler.connect(operator).getTwin(twin721.id); + let [, twin] = await twinHandler.connect(assistant).getTwin(twin721.id); expect(twin.supplyAvailable).to.equal(twin721.supplyAvailable); - [, twin] = await twinHandler.connect(operator).getTwin(twin20.id); + [, twin] = await twinHandler.connect(assistant).getTwin(twin20.id); expect(twin.supplyAvailable).to.equal(twin20.supplyAvailable); - [, twin] = await twinHandler.connect(operator).getTwin(twin1155.id); + [, twin] = await twinHandler.connect(assistant).getTwin(twin1155.id); expect(twin.supplyAvailable).to.equal(twin1155.supplyAvailable); }); @@ -3029,9 +3035,9 @@ describe("IBosonExchangeHandler", function () { let expectedTokenId = "0"; let exchangeId = exchange.id; - // Check the operator owns the first ERC721 of twin range + // Check the assistant owns the first ERC721 of twin range owner = await other721.ownerOf(expectedTokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Redeem the voucher await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)) @@ -3044,9 +3050,9 @@ describe("IBosonExchangeHandler", function () { ++expectedTokenId; - // Check the operator owns the second ERC721 of twin range + // Check the assistant owns the second ERC721 of twin range owner = await other721.ownerOf(expectedTokenId); - expect(owner).to.equal(operator.address); + expect(owner).to.equal(assistant.address); // Commit to offer for the second time await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); @@ -3066,7 +3072,7 @@ describe("IBosonExchangeHandler", function () { context("Twin transfer fail", async function () { it("should revoke exchange when buyer is an EOA", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "0"); + await foreign20.connect(assistant).approve(protocolDiamond.address, "0"); let exchangeId = exchange.id; const tx = await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); @@ -3103,7 +3109,7 @@ describe("IBosonExchangeHandler", function () { it("should raise a dispute when buyer account is a contract", async function () { // Remove the approval for the protocal to transfer the seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "0"); + await foreign20.connect(assistant).approve(protocolDiamond.address, "0"); // Deploy contract to test redeem called by another contract let TestProtocolFunctionsFactory = await ethers.getContractFactory("TestProtocolFunctions"); @@ -3168,16 +3174,16 @@ describe("IBosonExchangeHandler", function () { validUntilDate = ethers.BigNumber.from(voucher.validUntilDate).add(oneMonth).toString(); }); - it("should emit an VoucherExtended event when seller's operator calls", async function () { + it("should emit an VoucherExtended event when seller's assistant calls", async function () { // Extend the voucher, expecting event - await expect(exchangeHandler.connect(operator).extendVoucher(exchange.id, validUntilDate)) + await expect(exchangeHandler.connect(assistant).extendVoucher(exchange.id, validUntilDate)) .to.emit(exchangeHandler, "VoucherExtended") - .withArgs(offerId, exchange.id, validUntilDate, operator.address); + .withArgs(offerId, exchange.id, validUntilDate, assistant.address); }); it("should update state", async function () { // Extend the voucher - await exchangeHandler.connect(operator).extendVoucher(exchange.id, validUntilDate); + await exchangeHandler.connect(assistant).extendVoucher(exchange.id, validUntilDate); // Get the voucher [, , response] = await exchangeHandler.connect(rando).getExchange(exchange.id); @@ -3193,7 +3199,7 @@ describe("IBosonExchangeHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Attempt to complete an exchange, expecting revert - await expect(exchangeHandler.connect(operator).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -3203,7 +3209,7 @@ describe("IBosonExchangeHandler", function () { exchangeId = "666"; // Attempt to extend voucher, expecting revert - await expect(exchangeHandler.connect(operator).extendVoucher(exchangeId, validUntilDate)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).extendVoucher(exchangeId, validUntilDate)).to.revertedWith( RevertReasons.NO_SUCH_EXCHANGE ); }); @@ -3213,15 +3219,15 @@ describe("IBosonExchangeHandler", function () { await exchangeHandler.connect(buyer).cancelVoucher(exchange.id); // Attempt to extend voucher, expecting revert - await expect(exchangeHandler.connect(operator).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( RevertReasons.INVALID_STATE ); }); - it("caller is not seller's operator", async function () { + it("caller is not seller's assistant", async function () { // Attempt to extend voucher, expecting revert await expect(exchangeHandler.connect(rando).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -3230,7 +3236,7 @@ describe("IBosonExchangeHandler", function () { validUntilDate = ethers.BigNumber.from(voucher.validUntilDate).sub(oneMonth).toString(); // Attempt to extend voucher, expecting revert - await expect(exchangeHandler.connect(operator).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( + await expect(exchangeHandler.connect(assistant).extendVoucher(exchange.id, validUntilDate)).to.revertedWith( RevertReasons.VOUCHER_EXTENSION_NOT_VALID ); }); @@ -3398,7 +3404,7 @@ describe("IBosonExchangeHandler", function () { it("exchange is not in committed state", async function () { // Revoke the voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Attempt to call onVoucherTransferred, expecting revert await expect( @@ -3473,7 +3479,7 @@ describe("IBosonExchangeHandler", function () { await setNextBlockTimestamp(newTime); // Complete exchange - await exchangeHandler.connect(operator).completeExchange(exchange.id); + await exchangeHandler.connect(assistant).completeExchange(exchange.id); // Now in Completed state, ask if exchange is finalized [exists, response] = await exchangeHandler.connect(rando).isExchangeFinalized(exchange.id); @@ -3484,7 +3490,7 @@ describe("IBosonExchangeHandler", function () { it("should return true if exchange is in Revoked state", async function () { // Revoke voucher - await exchangeHandler.connect(operator).revokeVoucher(exchange.id); + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); // Now in Revoked state, ask if exchange is finalized [exists, response] = await exchangeHandler.connect(rando).isExchangeFinalized(exchange.id); @@ -3556,7 +3562,7 @@ describe("IBosonExchangeHandler", function () { // Collect the signature components const { r, s, v } = await prepareDataSignatureParameters( - buyer, // Operator is the caller, seller should be the signer. + buyer, // Assistant is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -3564,7 +3570,7 @@ describe("IBosonExchangeHandler", function () { ); // Resolve Dispute - await disputeHandler.connect(operator).resolveDispute(exchange.id, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchange.id, buyerPercentBasisPoints, r, s, v); // Now in Resolved state, ask if exchange is finalized [exists, response] = await exchangeHandler.connect(rando).isExchangeFinalized(exchange.id); @@ -3589,7 +3595,7 @@ describe("IBosonExchangeHandler", function () { await disputeHandler.connect(buyer).escalateDispute(exchange.id); // Decide Dispute - await disputeHandler.connect(operatorDR).decideDispute(exchange.id, "1111"); + await disputeHandler.connect(assistantDR).decideDispute(exchange.id, "1111"); // Now in Decided state, ask if exchange is finalized [exists, response] = await exchangeHandler.connect(rando).isExchangeFinalized(exchange.id); @@ -3811,7 +3817,9 @@ describe("IBosonExchangeHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId); @@ -4026,19 +4034,19 @@ describe("IBosonExchangeHandler", function () { context("TwinReceipt tests", async function () { beforeEach(async function () { // Mint some tokens to be bundled - await foreign20.connect(operator).mint(operator.address, "500"); - await foreign721.connect(operator).mint("0", "10"); + await foreign20.connect(assistant).mint(assistant.address, "500"); + await foreign721.connect(assistant).mint("0", "10"); // Approve the protocol diamond to transfer seller's tokens - await foreign20.connect(operator).approve(protocolDiamond.address, "3"); - await foreign721.connect(operator).setApprovalForAll(protocolDiamond.address, true); + await foreign20.connect(assistant).approve(protocolDiamond.address, "3"); + await foreign721.connect(assistant).setApprovalForAll(protocolDiamond.address, true); // Create an ERC20 twin twin20 = mockTwin(foreign20.address); twin20.amount = "3"; expect(twin20.isValid()).is.true; - await twinHandler.connect(operator).createTwin(twin20.toStruct()); + await twinHandler.connect(assistant).createTwin(twin20.toStruct()); // Create an ERC721 twin twin721 = mockTwin(foreign721.address, TokenType.NonFungibleToken); @@ -4047,7 +4055,7 @@ describe("IBosonExchangeHandler", function () { twin721.id = "2"; expect(twin721.isValid()).is.true; - await twinHandler.connect(operator).createTwin(twin721.toStruct()); + await twinHandler.connect(assistant).createTwin(twin721.toStruct()); // Create a new offer const mo = await mockOffer(); @@ -4067,7 +4075,7 @@ describe("IBosonExchangeHandler", function () { // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); }); @@ -4075,7 +4083,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], [twin20.id]); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -4173,7 +4181,7 @@ describe("IBosonExchangeHandler", function () { // Create a new bundle bundle = new Bundle("1", seller.id, [offerId], [twin20.id, twin721.id]); expect(bundle.isValid()).is.true; - await bundleHandler.connect(operator).createBundle(bundle.toStruct()); + await bundleHandler.connect(assistant).createBundle(bundle.toStruct()); // Set time forward to the offer's voucherRedeemableFrom await setNextBlockTimestamp(Number(voucherRedeemableFrom)); @@ -4288,7 +4296,7 @@ describe("IBosonExchangeHandler", function () { // Create a new group group = new Group(groupId, seller.id, offerIds); expect(group.isValid()).is.true; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // Mint enough tokens for the buyer await foreign20.connect(buyer).mint(buyer.address, condition.threshold); @@ -4401,7 +4409,9 @@ describe("IBosonExchangeHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Commit to offer let tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 4e30a9d11..15bb0ed10 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -41,12 +41,12 @@ describe("IBosonFundsHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, feeCollector, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -113,8 +113,8 @@ describe("IBosonFundsHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -239,7 +239,7 @@ describe("IBosonFundsHandler", function () { context("📋 Funds Handler Methods", async function () { beforeEach(async function () { // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -252,11 +252,11 @@ describe("IBosonFundsHandler", function () { await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - // top up operators account - await mockToken.mint(operator.address, "1000000"); + // top up assistants account + await mockToken.mint(assistant.address, "1000000"); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, "1000000"); + await mockToken.connect(assistant).approve(protocolDiamond.address, "1000000"); // set the deposit amount depositAmount = "100"; @@ -274,9 +274,9 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsDeposited event", async function () { // Deposit funds, testing for the event // Deposit token - await expect(fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount)) + await expect(fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount)) .to.emit(fundsHandler, "FundsDeposited") - .withArgs(seller.id, operator.address, mockToken.address, depositAmount); + .withArgs(seller.id, assistant.address, mockToken.address, depositAmount); // Deposit native currency await expect( @@ -290,7 +290,7 @@ describe("IBosonFundsHandler", function () { it("should update state", async function () { // Deposit token - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); @@ -314,7 +314,7 @@ describe("IBosonFundsHandler", function () { it("should be possible to top up the account", async function () { // Deposit token - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); @@ -324,7 +324,7 @@ describe("IBosonFundsHandler", function () { expect(returnedAvailableFunds).to.eql(expectedAvailableFunds); // Deposit the same token again - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, 2 * depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, 2 * depositAmount); // Get new on chain state returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); @@ -341,7 +341,7 @@ describe("IBosonFundsHandler", function () { // Attempt to deposit funds, expecting revert await expect( - fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -400,7 +400,7 @@ describe("IBosonFundsHandler", function () { // not approved depositAmount = "10000000"; await expect( - fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount) ).to.revertedWith(RevertReasons.ERC20_INSUFFICIENT_ALLOWANCE); }); @@ -409,23 +409,23 @@ describe("IBosonFundsHandler", function () { const [Foreign20WithFee] = await deployMockTokens(["Foreign20WithFee"]); // mint tokens and approve - await Foreign20WithFee.mint(operator.address, depositAmount); - await Foreign20WithFee.connect(operator).approve(protocolDiamond.address, depositAmount); + await Foreign20WithFee.mint(assistant.address, depositAmount); + await Foreign20WithFee.connect(assistant).approve(protocolDiamond.address, depositAmount); // Attempt to deposit funds, expecting revert await expect( - fundsHandler.connect(operator).depositFunds(seller.id, Foreign20WithFee.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, Foreign20WithFee.address, depositAmount) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); it("ERC20 transferFrom returns false", async function () { const [foreign20ReturnFalse] = await deployMockTokens(["Foreign20TransferFromReturnFalse"]); - await foreign20ReturnFalse.connect(operator).mint(operator.address, depositAmount); - await foreign20ReturnFalse.connect(operator).approve(protocolDiamond.address, depositAmount); + await foreign20ReturnFalse.connect(assistant).mint(assistant.address, depositAmount); + await foreign20ReturnFalse.connect(assistant).approve(protocolDiamond.address, depositAmount); await expect( - fundsHandler.connect(operator).depositFunds(seller.id, foreign20ReturnFalse.address, depositAmount) + fundsHandler.connect(assistant).depositFunds(seller.id, foreign20ReturnFalse.address, depositAmount) ).to.revertedWith(RevertReasons.SAFE_ERC20_NOT_SUCCEEDED); }); }); @@ -438,7 +438,7 @@ describe("IBosonFundsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -482,10 +482,10 @@ describe("IBosonFundsHandler", function () { // Create both offers await Promise.all([ offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerNative, offerDates, offerDurations, disputeResolverId, agentId), offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId), ]); @@ -495,19 +495,19 @@ describe("IBosonFundsHandler", function () { offerTokenProtocolFee = offerNativeProtocolFee = offerFees.protocolFee; // top up seller's and buyer's account - await Promise.all([mockToken.mint(operator.address, sellerDeposit), mockToken.mint(buyer.address, price)]); + await Promise.all([mockToken.mint(assistant.address, sellerDeposit), mockToken.mint(buyer.address, price)]); // approve protocol to transfer the tokens await Promise.all([ - mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit), + mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit), mockToken.connect(buyer).approve(protocolDiamond.address, price), ]); // deposit to seller's pool await Promise.all([ - fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit), + fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit), fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit }), ]); @@ -812,7 +812,7 @@ describe("IBosonFundsHandler", function () { // Create offer with agent await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Set used variables @@ -821,15 +821,15 @@ describe("IBosonFundsHandler", function () { voucherRedeemableFrom = offerDates.voucherRedeemableFrom; // top up seller's and buyer's account - await mockToken.mint(operator.address, sellerDeposit); + await mockToken.mint(assistant.address, sellerDeposit); await mockToken.mint(buyer.address, price); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamond.address, price); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); // commit to agent offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); @@ -1024,7 +1024,7 @@ describe("IBosonFundsHandler", function () { const fallbackContractBuyerId = event.buyerId; // revoke the voucher so the contract gets credited some funds - await exchangeHandler.connect(operator).revokeVoucher(exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); // we call a fallbackContract which calls fundsHandler.withdraw, which should revert await expect( @@ -1051,7 +1051,7 @@ describe("IBosonFundsHandler", function () { const fallbackContractBuyerId = event.buyerId; // revoke the voucher so the contract gets credited some funds - await exchangeHandler.connect(operator).revokeVoucher(exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); // we call a fallbackContract which calls fundsHandler.withdraw, which should revert await expect( @@ -1085,10 +1085,10 @@ describe("IBosonFundsHandler", function () { it("Transfer of funds failed - ERC20 transfer returns false", async function () { const [foreign20ReturnFalse] = await deployMockTokens(["Foreign20TransferReturnFalse"]); - await foreign20ReturnFalse.connect(operator).mint(operator.address, sellerDeposit); - await foreign20ReturnFalse.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await foreign20ReturnFalse.connect(assistant).mint(assistant.address, sellerDeposit); + await foreign20ReturnFalse.connect(assistant).approve(protocolDiamond.address, sellerDeposit); - await fundsHandler.connect(operator).depositFunds(seller.id, foreign20ReturnFalse.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, foreign20ReturnFalse.address, sellerDeposit); await expect( fundsHandler.connect(clerk).withdrawFunds(seller.id, [foreign20ReturnFalse.address], [sellerDeposit]) @@ -1501,13 +1501,13 @@ describe("IBosonFundsHandler", function () { it("Returns info also for ERC20 tokens without the name", async function () { // Deploy the mock token with no name [mockToken] = await deployMockTokens(["Foreign20NoName"]); - // top up operators account - await mockToken.mint(operator.address, "1000000"); + // top up assistants account + await mockToken.mint(assistant.address, "1000000"); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, "1000000"); + await mockToken.connect(assistant).approve(protocolDiamond.address, "1000000"); // Deposit token - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, depositAmount); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount); // Read on chain state let returnedAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); @@ -1526,7 +1526,7 @@ describe("IBosonFundsHandler", function () { context("📋 FundsLib Methods", async function () { beforeEach(async function () { // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1541,7 +1541,7 @@ describe("IBosonFundsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1585,8 +1585,10 @@ describe("IBosonFundsHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create both offers await Promise.all([ - offerHandler.connect(operator).createOffer(offerNative, offerDates, offerDurations, disputeResolverId, agentId), - offerHandler.connect(operator).createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId), + offerHandler + .connect(assistant) + .createOffer(offerNative, offerDates, offerDurations, disputeResolverId, agentId), + offerHandler.connect(assistant).createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId), ]); // Set used variables @@ -1597,17 +1599,17 @@ describe("IBosonFundsHandler", function () { resolutionPeriod = offerDurations.resolutionPeriod; // top up seller's and buyer's account - await mockToken.mint(operator.address, `${2 * sellerDeposit}`); + await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); await mockToken.mint(buyer.address, `${2 * price}`); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, `${2 * sellerDeposit}`); + await mockToken.connect(assistant).approve(protocolDiamond.address, `${2 * sellerDeposit}`); await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, `${2 * sellerDeposit}`, { value: `${2 * sellerDeposit}`, }); @@ -1761,13 +1763,13 @@ describe("IBosonFundsHandler", function () { ]); // top up seller's and buyer's account - await otherToken.mint(operator.address, sellerDeposit); + await otherToken.mint(assistant.address, sellerDeposit); // approve protocol to transfer the tokens - await otherToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await otherToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, otherToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, otherToken.address, sellerDeposit); // seller's available funds let sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); @@ -1864,10 +1866,10 @@ describe("IBosonFundsHandler", function () { 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(operator.address, `${2 * price}`); - await mockToken.connect(operator).approve(protocolDiamond.address, `${2 * price}`); - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, `${2 * price}`); - await fundsHandler.connect(operator).depositFunds(seller.id, ethers.constants.AddressZero, `${2 * price}`, { + await mockToken.mint(assistant.address, `${2 * price}`); + await mockToken.connect(assistant).approve(protocolDiamond.address, `${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}`, { value: `${2 * price}`, }); @@ -1877,14 +1879,14 @@ describe("IBosonFundsHandler", function () { const sellersAvailableFundsBefore = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); // reserve a range and premint vouchers - await offerHandler.connect(operator).reserveRange(offerToken.id, offerToken.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offerToken.id, offerToken.quantityAvailable); const voucherCloneAddress = calculateContractAddress(accountHandler.address, "1"); const bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherCloneAddress); - await bosonVoucher.connect(operator).preMint(offerToken.id, offerToken.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerToken.id, offerToken.quantityAvailable); // commit to an offer via preminted voucher let tokenId = "1"; - tx = await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + tx = await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // it should emit FundsEncumbered event with amount equal to sellerDeposit + price let encumberedFunds = ethers.BigNumber.from(sellerDeposit).add(price); @@ -1918,11 +1920,11 @@ describe("IBosonFundsHandler", function () { // reserve a range and premint vouchers tokenId = await exchangeHandler.getNextExchangeId(); - await offerHandler.connect(operator).reserveRange(offerNative.id, offerNative.quantityAvailable); - await bosonVoucher.connect(operator).preMint(offerNative.id, offerNative.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offerNative.id, offerNative.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerNative.id, offerNative.quantityAvailable); // commit to an offer via preminted voucher - tx = await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + tx = await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // it should emit FundsEncumbered event with amount equal to sellerDeposit + price encumberedFunds = ethers.BigNumber.from(sellerDeposit).add(price); @@ -1983,7 +1985,7 @@ describe("IBosonFundsHandler", function () { new DisputeResolverFee(offerToken.exchangeToken, "BadContract", "0"), ]); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert @@ -2005,7 +2007,7 @@ describe("IBosonFundsHandler", function () { ]); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert @@ -2038,7 +2040,7 @@ describe("IBosonFundsHandler", function () { offerToken.sellerDeposit = ethers.BigNumber.from(offerToken.sellerDeposit).mul("4"); offerToken.id = "3"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert @@ -2050,7 +2052,7 @@ describe("IBosonFundsHandler", function () { offerNative.sellerDeposit = ethers.BigNumber.from(offerNative.sellerDeposit).mul("4"); offerNative.id = "4"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerNative, offerDates, offerDurations, disputeResolverId, agentId); // Attempt to commit to an offer, expecting revert @@ -2061,10 +2063,10 @@ describe("IBosonFundsHandler", function () { it("Seller'a availableFunds is less than the required sellerDeposit + price for preminted offer", async function () { // reserve a range and premint vouchers for offer in tokens - await offerHandler.connect(operator).reserveRange(offerToken.id, offerToken.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offerToken.id, offerToken.quantityAvailable); const voucherCloneAddress = calculateContractAddress(accountHandler.address, "1"); const bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherCloneAddress); - await bosonVoucher.connect(operator).preMint(offerToken.id, offerToken.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerToken.id, offerToken.quantityAvailable); // Seller's availableFunds is 2*sellerDeposit which is less than sellerDeposit + price. // Add the check in case if the sellerDeposit is changed in the future @@ -2072,17 +2074,17 @@ describe("IBosonFundsHandler", function () { // Attempt to commit to an offer via preminted voucher, expecting revert let tokenId = "1"; await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); // reserve a range and premint vouchers for offer in native currency tokenId = await exchangeHandler.getNextExchangeId(); - await offerHandler.connect(operator).reserveRange(offerNative.id, offerNative.quantityAvailable); - await bosonVoucher.connect(operator).preMint(offerNative.id, offerNative.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offerNative.id, offerNative.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerNative.id, offerNative.quantityAvailable); // Attempt to commit to an offer, expecting revert await expect( - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ).to.revertedWith(RevertReasons.INSUFFICIENT_AVAILABLE_FUNDS); }); @@ -2106,7 +2108,7 @@ describe("IBosonFundsHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolverId, agentId); // mint tokens and approve @@ -2236,7 +2238,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Commit to Offer @@ -2340,9 +2342,9 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Revoke the voucher, expecting event - await expect(exchangeHandler.connect(operator).revokeVoucher(exchangeId)) + await expect(exchangeHandler.connect(assistant).revokeVoucher(exchangeId)) .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, operator.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); }); it("should update state", async function () { @@ -2366,7 +2368,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Revoke the voucher so the funds are released - await exchangeHandler.connect(operator).revokeVoucher(exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); // Available funds should be increased for // buyer: sellerDeposit + price @@ -2388,7 +2390,7 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); // Revoke another voucher - await exchangeHandler.connect(operator).revokeVoucher(++exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); // Available funds should be increased for // buyer: sellerDeposit + price @@ -2417,19 +2419,19 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // top up seller's and buyer's account - await mockToken.mint(operator.address, `${2 * sellerDeposit}`); + await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); await mockToken.mint(buyer.address, `${2 * price}`); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, `${2 * sellerDeposit}`); + await mockToken.connect(assistant).approve(protocolDiamond.address, `${2 * sellerDeposit}`); await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); // Commit to Offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); @@ -2471,7 +2473,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Revoke the voucher so the funds are released - await exchangeHandler.connect(operator).revokeVoucher(exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); // Available funds should be increased for // buyer: sellerDeposit + price @@ -2493,7 +2495,7 @@ describe("IBosonFundsHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); // Revoke another voucher - await exchangeHandler.connect(operator).revokeVoucher(++exchangeId); + await exchangeHandler.connect(assistant).revokeVoucher(++exchangeId); // Available funds should be increased for // buyer: sellerDeposit + price @@ -2596,19 +2598,19 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // top up seller's and buyer's account - await mockToken.mint(operator.address, `${2 * sellerDeposit}`); + await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); await mockToken.mint(buyer.address, `${2 * price}`); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, `${2 * sellerDeposit}`); + await mockToken.connect(assistant).approve(protocolDiamond.address, `${2 * sellerDeposit}`); await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); // Commit to Offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); @@ -2816,7 +2818,7 @@ describe("IBosonFundsHandler", function () { // Exchange id exchangeId = "2"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); @@ -2977,7 +2979,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Commit to Offer @@ -3121,7 +3123,7 @@ describe("IBosonFundsHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Operator is the caller, seller should be the signer. + buyer, // Assistant is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -3132,15 +3134,15 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Resolve the dispute, expecting event const tx = await disputeHandler - .connect(operator) + .connect(assistant) .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, operator.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, operator.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3166,7 +3168,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Resolve the dispute, so the funds are released - await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage @@ -3194,7 +3196,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Commit to Offer @@ -3244,7 +3246,7 @@ describe("IBosonFundsHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Operator is the caller, seller should be the signer. + buyer, // Assistant is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -3272,7 +3274,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Resolve the dispute, so the funds are released - await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage @@ -3408,7 +3410,7 @@ describe("IBosonFundsHandler", function () { // Exchange id exchangeId = "2"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // approve protocol to transfer the tokens @@ -3512,7 +3514,7 @@ describe("IBosonFundsHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Operator is the caller, seller should be the signer. + buyer, // Assistant is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -3526,15 +3528,15 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Resolve the dispute, expecting event const tx = await disputeHandler - .connect(operator) + .connect(assistant) .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, operator.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, operator.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3560,7 +3562,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Resolve the dispute, so the funds are released - await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage @@ -3587,7 +3589,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // approve protocol to transfer the tokens @@ -3643,7 +3645,7 @@ describe("IBosonFundsHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Operator is the caller, seller should be the signer. + buyer, // Assistant is the caller, seller should be the signer. customSignatureType, "Resolution", message, @@ -3676,7 +3678,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Resolve the dispute, so the funds are released - await disputeHandler.connect(operator).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage @@ -3729,14 +3731,14 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Decide the dispute, expecting event - const tx = await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + const tx = await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, operatorDR.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, operatorDR.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); }); @@ -3762,7 +3764,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Decide the dispute, so the funds are released - await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage @@ -3789,7 +3791,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // approve protocol to transfer the tokens @@ -3860,7 +3862,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Decide the dispute, so the funds are released - await disputeHandler.connect(operatorDR).decideDispute(exchangeId, buyerPercentBasisPoints); + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); // Available funds should be increased for // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage @@ -3969,7 +3971,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // approve protocol to transfer the tokens @@ -4074,15 +4076,15 @@ describe("IBosonFundsHandler", function () { it("should emit a FundsReleased event", async function () { // Expire the dispute, expecting event - const tx = await disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId); + const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, operatorDR.address); + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); await expect(tx) .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, operatorDR.address); + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); @@ -4119,7 +4121,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId); + await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Available funds should be increased for // buyer: price + buyerEscalationDeposit @@ -4146,7 +4148,7 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Create Agent offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // approve protocol to transfer the tokens @@ -4200,7 +4202,7 @@ describe("IBosonFundsHandler", function () { expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(operatorDR).refuseEscalatedDispute(exchangeId); + await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Available funds should be increased for // buyer: price + buyerEscalationDeposit @@ -4320,7 +4322,7 @@ describe("IBosonFundsHandler", function () { // Create Agent Offer before setting new protocol fee as 3% await offerHandler - .connect(operator) + .connect(assistant) .createOffer(agentOffer, offerDates, offerDurations, disputeResolverId, agent.id); // Commit to Agent Offer @@ -4358,15 +4360,15 @@ describe("IBosonFundsHandler", 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(operator.address, sellerDeposit); + await mockToken.mint(assistant.address, sellerDeposit); await mockToken.mint(buyer.address, price); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamond.address, price); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit); + 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); diff --git a/test/protocol/GroupHandlerTest.js b/test/protocol/GroupHandlerTest.js index 3bf1e5cff..771c2000a 100644 --- a/test/protocol/GroupHandlerTest.js +++ b/test/protocol/GroupHandlerTest.js @@ -37,11 +37,11 @@ describe("IBosonGroupHandler", function () { deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -74,8 +74,8 @@ describe("IBosonGroupHandler", function () { accounts = await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -184,7 +184,7 @@ describe("IBosonGroupHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -199,7 +199,7 @@ describe("IBosonGroupHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -241,7 +241,9 @@ describe("IBosonGroupHandler", function () { expect(offerDurations.isValid()).is.true; // Create the offer - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); } // Required constructor params for Group @@ -273,7 +275,7 @@ describe("IBosonGroupHandler", function () { context("👉 createGroup()", async function () { it("should emit a GroupCreated event", async function () { // Create a group, testing for the event - const tx = await groupHandler.connect(operator).createGroup(group, condition); + const tx = await groupHandler.connect(assistant).createGroup(group, condition); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupCreated"); @@ -284,13 +286,13 @@ describe("IBosonGroupHandler", function () { assert.equal(event.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(event.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); it("should update state", async function () { // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // Get the group as a struct [, groupStruct] = await groupHandler.connect(rando).getGroup(groupId); @@ -308,7 +310,7 @@ describe("IBosonGroupHandler", function () { group.id = "444"; // Create a group, testing for the event - const tx = await groupHandler.connect(operator).createGroup(group, condition); + const tx = await groupHandler.connect(assistant).createGroup(group, condition); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupCreated"); @@ -319,7 +321,7 @@ describe("IBosonGroupHandler", function () { assert.equal(event.groupId.toString(), groupId, "Group Id is incorrect"); assert.equal(event.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toStruct().toString(), groupStruct.toString(), "Group struct is incorrect"); // wrong group id should not exist @@ -335,7 +337,7 @@ describe("IBosonGroupHandler", function () { group.offerIds = []; // Create a group, testing for the event - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // group should have no offers let returnedGroup; @@ -348,7 +350,7 @@ describe("IBosonGroupHandler", function () { offer.sellerId = "123"; // Create a group, testing for the event - const tx = await groupHandler.connect(operator).createGroup(group, condition); + const tx = await groupHandler.connect(assistant).createGroup(group, condition); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupCreated"); @@ -359,7 +361,7 @@ describe("IBosonGroupHandler", function () { assert.equal(event.groupId.toString(), groupId, "Group Id is incorrect"); assert.equal(event.sellerId.toString(), seller.id, "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toStruct().toString(), groupStruct.toString(), "Group struct is incorrect"); }); @@ -369,15 +371,15 @@ describe("IBosonGroupHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Groups]); // Attempt to create a group expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to Create a group, expecting revert await expect(groupHandler.connect(rando).createGroup(group, condition)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -392,8 +394,8 @@ describe("IBosonGroupHandler", function () { group.offerIds = ["2", "6"]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( - RevertReasons.NOT_OPERATOR + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( + RevertReasons.NOT_ASSISTANT ); }); @@ -402,7 +404,7 @@ describe("IBosonGroupHandler", function () { group.offerIds = ["1", "999"]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -410,20 +412,20 @@ describe("IBosonGroupHandler", function () { group.offerIds = ["0", "4"]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); it("Offer is already part of another group", async function () { // create first group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // Add offer that is already part of another group group.offerIds = ["1", "2", "4"]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.OFFER_MUST_BE_UNIQUE ); }); @@ -433,7 +435,7 @@ describe("IBosonGroupHandler", function () { group.offerIds = ["1", "1", "4"]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.OFFER_MUST_BE_UNIQUE ); }); @@ -443,7 +445,7 @@ describe("IBosonGroupHandler", function () { group.offerIds = [...Array(101).keys()]; // Attempt to create a group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.TOO_MANY_OFFERS ); }); @@ -457,7 +459,7 @@ describe("IBosonGroupHandler", function () { condition.tokenAddress = rando.address; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -466,7 +468,7 @@ describe("IBosonGroupHandler", function () { condition.tokenId = "20"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -475,7 +477,7 @@ describe("IBosonGroupHandler", function () { condition.threshold = "100"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -484,7 +486,7 @@ describe("IBosonGroupHandler", function () { condition.maxCommits = "5"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -504,7 +506,7 @@ describe("IBosonGroupHandler", function () { condition.tokenAddress = ethers.constants.AddressZero; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -513,7 +515,7 @@ describe("IBosonGroupHandler", function () { condition.maxCommits = "0"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -522,7 +524,7 @@ describe("IBosonGroupHandler", function () { condition.threshold = "0"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -542,7 +544,7 @@ describe("IBosonGroupHandler", function () { condition.tokenAddress = ethers.constants.AddressZero; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -551,7 +553,7 @@ describe("IBosonGroupHandler", function () { condition.threshold = "10"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -560,7 +562,7 @@ describe("IBosonGroupHandler", function () { condition.maxCommits = "0"; // Attempt to create the group, expecting revert - await expect(groupHandler.connect(operator).createGroup(group, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).createGroup(group, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -571,7 +573,7 @@ describe("IBosonGroupHandler", function () { context("👉 addOffersToGroup()", async function () { beforeEach(async function () { // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // set the new fields offerIdsToAdd = ["1", "4"]; @@ -582,7 +584,7 @@ describe("IBosonGroupHandler", function () { it("should emit a GroupUpdated event", async function () { // Add offers to a group, testing for the event - const tx = await groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd); + const tx = await groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupUpdated"); @@ -593,13 +595,13 @@ describe("IBosonGroupHandler", function () { assert.equal(event.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(event.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); it("should update state", async function () { // Add offers to a group, - await groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd); + await groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd); // Get the group as a struct [, groupStruct] = await groupHandler.connect(rando).getGroup(group.id); @@ -619,7 +621,7 @@ describe("IBosonGroupHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Groups]); // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -629,7 +631,7 @@ describe("IBosonGroupHandler", function () { group.id = "444"; // Attempt to add offers to the group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.NO_SUCH_GROUP ); @@ -637,7 +639,7 @@ describe("IBosonGroupHandler", function () { group.id = "0"; // Attempt to add offers to group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.NO_SUCH_GROUP ); }); @@ -645,7 +647,7 @@ describe("IBosonGroupHandler", function () { it("Caller is not the seller of the group", async function () { // Attempt to add offers to group, expecting revert await expect(groupHandler.connect(rando).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -660,18 +662,18 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = ["1", "6"]; // Attempt to add offers to group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( - RevertReasons.NOT_OPERATOR + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + RevertReasons.NOT_ASSISTANT ); }); it("Offer is already part of another group", async function () { // create another group group.offerIds = ["1"]; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.OFFER_MUST_BE_UNIQUE ); }); @@ -681,7 +683,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = ["1", "1", "4"]; // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.OFFER_MUST_BE_UNIQUE ); }); @@ -691,7 +693,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = [...Array(101).keys()]; // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.TOO_MANY_OFFERS ); }); @@ -701,7 +703,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = [...Array(98).keys()]; // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.TOO_MANY_OFFERS ); }); @@ -711,7 +713,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = []; // Attempt to add offers from the group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.NOTHING_UPDATED ); }); @@ -721,7 +723,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = ["1", "999"]; // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -729,7 +731,7 @@ describe("IBosonGroupHandler", function () { offerIdsToAdd = ["0", "2"]; // Attempt to add offers to a group, expecting revert - await expect(groupHandler.connect(operator).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( + await expect(groupHandler.connect(assistant).addOffersToGroup(group.id, offerIdsToAdd)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); @@ -740,7 +742,7 @@ describe("IBosonGroupHandler", function () { beforeEach(async function () { group.offerIds = ["1", "2", "3", "4", "5"]; // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // set the new fields offerIdsToRemove = ["1", "4"]; @@ -751,7 +753,7 @@ describe("IBosonGroupHandler", function () { it("should emit a GroupUpdated event", async function () { // Remove offers from a group, testing for the event - const tx = await groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove); + const tx = await groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupUpdated"); @@ -761,13 +763,13 @@ describe("IBosonGroupHandler", function () { expect(groupInstance.isValid()).to.be.true; assert.equal(event.groupId.toString(), group.id, "Group Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); it("should update state", async function () { // Remove offer from a group, - await groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove); + await groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove); // Get the group as a struct [, groupStruct] = await groupHandler.connect(rando).getGroup(group.id); @@ -786,7 +788,7 @@ describe("IBosonGroupHandler", function () { group.offerIds.push("4"); // Remove offer from a group, - await groupHandler.connect(operator).removeOffersFromGroup(group.id, ["1"]); + await groupHandler.connect(assistant).removeOffersFromGroup(group.id, ["1"]); // Get the group as a struct [, groupStruct] = await groupHandler.connect(rando).getGroup(group.id); @@ -808,7 +810,7 @@ describe("IBosonGroupHandler", function () { group.offerIds = ["3", "2"]; // Remove offer from a group - await groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove); + await groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove); // Get the group as a struct [, groupStruct] = await groupHandler.connect(rando).getGroup(group.id); @@ -829,7 +831,7 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers to a group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -839,7 +841,7 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers from the group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.NO_SUCH_GROUP); // Set invalid id @@ -847,14 +849,14 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers from group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.NO_SUCH_GROUP); }); it("Caller is not the seller of the group", async function () { // Attempt to remove offers from the group, expecting revert await expect(groupHandler.connect(rando).removeOffersFromGroup(group.id, offerIdsToRemove)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -864,19 +866,19 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers from the group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.OFFER_NOT_IN_GROUP); // create an offer and add it to another group await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); group.offerIds = ["6"]; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // Attempt to remove offers from a group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.OFFER_NOT_IN_GROUP); }); @@ -886,7 +888,7 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers from the group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); }); @@ -896,7 +898,7 @@ describe("IBosonGroupHandler", function () { // Attempt to remove offers from the group, expecting revert await expect( - groupHandler.connect(operator).removeOffersFromGroup(group.id, offerIdsToRemove) + groupHandler.connect(assistant).removeOffersFromGroup(group.id, offerIdsToRemove) ).to.revertedWith(RevertReasons.NOTHING_UPDATED); }); }); @@ -914,7 +916,7 @@ describe("IBosonGroupHandler", function () { expect(condition.isValid()).to.be.true; // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // id of the current group and increment groupId groupId++; @@ -924,7 +926,7 @@ describe("IBosonGroupHandler", function () { it("should emit a GroupUpdated event", async function () { // Update a group, testing for the event - const tx = await groupHandler.connect(operator).setGroupCondition(group.id, condition); + const tx = await groupHandler.connect(assistant).setGroupCondition(group.id, condition); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, groupHandlerFacet_Factory, "GroupUpdated"); @@ -935,13 +937,13 @@ describe("IBosonGroupHandler", function () { assert.equal(event.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(event.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(event.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(event.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); it("should update state", async function () { // Set a new condition - await groupHandler.connect(operator).setGroupCondition(group.id, condition); + await groupHandler.connect(assistant).setGroupCondition(group.id, condition); // Get the group as a struct [, groupStruct, conditionStruct] = await groupHandler.connect(rando).getGroup(group.id); @@ -956,7 +958,7 @@ describe("IBosonGroupHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Groups]); // Attempt to set group condition, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -966,7 +968,7 @@ describe("IBosonGroupHandler", function () { group.id = "444"; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.NO_SUCH_GROUP ); @@ -974,7 +976,7 @@ describe("IBosonGroupHandler", function () { group.id = "0"; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.NO_SUCH_GROUP ); }); @@ -982,7 +984,7 @@ describe("IBosonGroupHandler", function () { it("Caller is not the seller of the group", async function () { // Attempt to remove offers from the group, expecting revert await expect(groupHandler.connect(rando).setGroupCondition(group.id, condition)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -990,7 +992,7 @@ describe("IBosonGroupHandler", function () { condition.method = EvaluationMethod.None; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -1000,7 +1002,7 @@ describe("IBosonGroupHandler", function () { condition.tokenAddress = ethers.constants.AddressZero; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -1010,7 +1012,7 @@ describe("IBosonGroupHandler", function () { condition.maxCommits = "0"; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -1020,7 +1022,7 @@ describe("IBosonGroupHandler", function () { condition.tokenAddress = ethers.constants.AddressZero; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -1030,7 +1032,7 @@ describe("IBosonGroupHandler", function () { condition.maxCommits = "0"; // Attempt to update the group, expecting revert - await expect(groupHandler.connect(operator).setGroupCondition(group.id, condition)).to.revertedWith( + await expect(groupHandler.connect(assistant).setGroupCondition(group.id, condition)).to.revertedWith( RevertReasons.INVALID_CONDITION_PARAMETERS ); }); @@ -1040,7 +1042,7 @@ describe("IBosonGroupHandler", function () { context("👉 getGroup()", async function () { beforeEach(async function () { // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); }); it("should return true for exists if group is found", async function () { @@ -1095,7 +1097,7 @@ describe("IBosonGroupHandler", function () { context("👉 getNextGroupId()", async function () { beforeEach(async function () { // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // id of the current group and increment groupId groupId++; @@ -1115,7 +1117,7 @@ describe("IBosonGroupHandler", function () { it("should be incremented after a group is created", async function () { // Create another group group.offerIds = ["1", "4"]; - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // What we expect the next group id to be expected = ++groupId; diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index de26de59e..c843fd71d 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -45,12 +45,12 @@ describe("IBosonMetaTransactionsHandler", function () { let deployer, pauser, rando, - operator, + assistant, buyer, admin, clerk, treasury, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -118,8 +118,8 @@ describe("IBosonMetaTransactionsHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -300,20 +300,20 @@ describe("IBosonMetaTransactionsHandler", function () { it("should return false if nonce is not used", async function () { // Check if nonce is used before - result = await metaTransactionsHandler.connect(operator).isUsedNonce(rando.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(rando.address, nonce); // Verify the expectation assert.equal(result, expectedResult, "Nonce is used"); }); it("should be true after executing a meta transaction with nonce", async function () { - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); // Verify the expectation assert.equal(result, expectedResult, "Nonce is used"); // Create a valid seller for meta transaction - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -347,7 +347,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Prepare the message let message = {}; message.nonce = parseInt(nonce); - message.from = operator.address; + message.from = assistant.address; message.contractAddress = accountHandler.address; message.functionName = "createSeller((uint256,address,address,address,address,bool),(uint256,uint8),(string,uint256))"; @@ -355,7 +355,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -364,7 +364,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Send as meta transaction await metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -375,13 +375,13 @@ describe("IBosonMetaTransactionsHandler", function () { // We expect that the nonce is used now. Hence expecting to return true. expectedResult = true; - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is not used"); //Verify that another nonce value is unused. expectedResult = false; nonce = nonce + 1; - result = await metaTransactionsHandler.connect(rando).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(rando).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is used"); }); }); @@ -575,7 +575,7 @@ describe("IBosonMetaTransactionsHandler", function () { context("👉 AccountHandlerFacet 👉 createSeller()", async function () { beforeEach(async function () { // Create a valid seller for meta transaction - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -589,7 +589,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Prepare the message message = {}; message.nonce = parseInt(nonce); - message.from = operator.address; + message.from = assistant.address; message.contractAddress = accountHandler.address; message.functionName = "createSeller((uint256,address,address,address,address,bool),(uint256,uint8),(string,uint256))"; @@ -607,7 +607,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -618,14 +618,14 @@ describe("IBosonMetaTransactionsHandler", function () { await expect( metaTransactionsHandler .connect(deployer) - .executeMetaTransaction(operator.address, message.functionName, functionSignature, nonce, r, s, v) + .executeMetaTransaction(assistant.address, message.functionName, functionSignature, nonce, r, s, v) ) .to.emit(metaTransactionsHandler, "MetaTransactionExecuted") - .withArgs(operator.address, deployer.address, message.functionName, nonce); + .withArgs(assistant.address, deployer.address, message.functionName, nonce); // Verify that nonce is used. Expect true. let expectedResult = true; - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is unused"); }); @@ -646,7 +646,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -657,14 +657,14 @@ describe("IBosonMetaTransactionsHandler", function () { await expect( metaTransactionsHandler .connect(deployer) - .executeMetaTransaction(operator.address, message.functionName, functionSignature, nonce, r, s, v) + .executeMetaTransaction(assistant.address, message.functionName, functionSignature, nonce, r, s, v) ) .to.emit(metaTransactionsHandler, "MetaTransactionExecuted") - .withArgs(operator.address, deployer.address, message.functionName, nonce); + .withArgs(assistant.address, deployer.address, message.functionName, nonce); // Verify that nonce is used. Expect true. let expectedResult = true; - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is unused"); }); @@ -683,7 +683,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -693,7 +693,7 @@ describe("IBosonMetaTransactionsHandler", function () { // send a meta transaction, expecting revert await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -718,7 +718,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components ({ r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -729,18 +729,18 @@ describe("IBosonMetaTransactionsHandler", function () { await expect( metaTransactionsHandler .connect(deployer) - .executeMetaTransaction(operator.address, message.functionName, functionSignature, nonce, r, s, v) + .executeMetaTransaction(assistant.address, message.functionName, functionSignature, nonce, r, s, v) ) .to.emit(metaTransactionsHandler, "MetaTransactionExecuted") - .withArgs(operator.address, deployer.address, message.functionName, nonce); + .withArgs(assistant.address, deployer.address, message.functionName, nonce); // Verify that nonce is used. Expect true. let expectedResult = true; - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is unused"); // send a meta transaction again, check for event - seller.operator = operatorDR.address; + seller.assistant = assistantDR.address; seller.admin = adminDR.address; seller.clerk = clerkDR.address; seller.treasury = treasuryDR.address; @@ -775,7 +775,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Verify that nonce is used. Expect true. expectedResult = true; - result = await metaTransactionsHandler.connect(operatorDR).isUsedNonce(operatorDR.address, nonce); + result = await metaTransactionsHandler.connect(assistantDR).isUsedNonce(assistantDR.address, nonce); assert.equal(result, expectedResult, "Nonce is unused"); }); @@ -794,7 +794,7 @@ describe("IBosonMetaTransactionsHandler", function () { it("The meta transactions region of protocol is paused", async function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -808,7 +808,7 @@ describe("IBosonMetaTransactionsHandler", function () { await expect( metaTransactionsHandler .connect(deployer) - .executeMetaTransaction(operator.address, message.functionName, functionSignature, nonce, r, s, v) + .executeMetaTransaction(assistant.address, message.functionName, functionSignature, nonce, r, s, v) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -831,7 +831,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -841,7 +841,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -868,7 +868,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -878,7 +878,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -905,7 +905,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -915,7 +915,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -948,7 +948,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -958,7 +958,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -981,7 +981,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -990,7 +990,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute the meta transaction. await metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1002,7 +1002,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction again with the same nonce, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1027,7 +1027,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - rando, // Different user, not operator. + rando, // Different user, not assistant. customTransactionType, "MetaTransaction", message, @@ -1037,7 +1037,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1061,7 +1061,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -1071,7 +1071,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1084,7 +1084,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1097,7 +1097,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1110,7 +1110,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1126,7 +1126,7 @@ describe("IBosonMetaTransactionsHandler", function () { context("👉TwinHandler 👉 removeTwin()", async function () { beforeEach(async function () { // Create a valid seller for meta transaction - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1137,7 +1137,7 @@ describe("IBosonMetaTransactionsHandler", function () { emptyAuthToken = mockAuthToken(); expect(emptyAuthToken.isValid()).is.true; - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // Create a valid twin, then set fields in tests directly twin = mockTwin(bosonToken.address); @@ -1146,15 +1146,15 @@ describe("IBosonMetaTransactionsHandler", function () { expect(twin.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create a twin - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Prepare the message message = {}; message.nonce = parseInt(nonce); - message.from = operator.address; + message.from = assistant.address; message.contractAddress = twinHandler.address; }); @@ -1172,7 +1172,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -1181,7 +1181,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Remove the twin. Send as meta transaction. await metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1201,14 +1201,14 @@ describe("IBosonMetaTransactionsHandler", function () { // Prepare the message message = {}; message.nonce = parseInt(nonce); - message.from = operator.address; + message.from = assistant.address; message.contractAddress = metaTransactionsHandler.address; }); it("Should fail when try to call executeMetaTransaction method itself", async function () { // Function signature for executeMetaTransaction function. functionSignature = metaTransactionsHandler.interface.encodeFunctionData("executeMetaTransaction", [ - operator.address, + assistant.address, "executeMetaTransaction", ethers.constants.HashZero, // hash of zero nonce, @@ -1224,7 +1224,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -1234,7 +1234,7 @@ describe("IBosonMetaTransactionsHandler", function () { // send a meta transaction, expecting revert await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -1248,7 +1248,7 @@ describe("IBosonMetaTransactionsHandler", function () { context("Reentrancy guard", async function () { beforeEach(async function () { // Create a valid seller for meta transaction - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1260,7 +1260,7 @@ describe("IBosonMetaTransactionsHandler", function () { expect(emptyAuthToken.isValid()).is.true; // Create a valid seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); }); it("Should fail on reenter", async function () { @@ -1273,7 +1273,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1309,19 +1309,19 @@ describe("IBosonMetaTransactionsHandler", function () { // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolver.id, agentId); // top up seller's and buyer's account - await maliciousToken.mint(operator.address, sellerDeposit); + await maliciousToken.mint(assistant.address, sellerDeposit); await maliciousToken.mint(buyer.address, price); // Approve protocol to transfer the tokens - await maliciousToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await maliciousToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await maliciousToken.connect(buyer).approve(protocolDiamond.address, price); // Deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, maliciousToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, maliciousToken.address, sellerDeposit); // Commit to the offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerToken.id); @@ -1477,7 +1477,7 @@ describe("IBosonMetaTransactionsHandler", function () { offerId = "1"; // Create a valid seller - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1487,11 +1487,11 @@ describe("IBosonMetaTransactionsHandler", function () { // AuthToken emptyAuthToken = mockAuthToken(); expect(emptyAuthToken.isValid()).is.true; - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1561,19 +1561,19 @@ describe("IBosonMetaTransactionsHandler", function () { expect(offerDurations.isValid()).is.true; // top up seller's and buyer's account - await mockToken.mint(operator.address, sellerDeposit); + await mockToken.mint(assistant.address, sellerDeposit); await mockToken.mint(buyer.address, price); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamond.address, price); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Set the offer Type @@ -1772,7 +1772,7 @@ describe("IBosonMetaTransactionsHandler", function () { context("👉 MetaTxExchange", async function () { beforeEach(async function () { await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Required exchange constructor params @@ -2813,7 +2813,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components signatureSplits = await prepareDataSignatureParameters( - operator, // When buyer is the caller, seller should be the signer. + assistant, // When buyer is the caller, seller should be the signer. customSignatureType2, "Resolution", message2, @@ -3036,7 +3036,7 @@ describe("IBosonMetaTransactionsHandler", function () { offerId = "1"; // Create a valid seller - seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -3047,11 +3047,11 @@ describe("IBosonMetaTransactionsHandler", function () { emptyAuthToken = mockAuthToken(); expect(emptyAuthToken.isValid()).is.true; - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -3085,15 +3085,15 @@ describe("IBosonMetaTransactionsHandler", function () { voucherRedeemableFrom = offerDates.voucherRedeemableFrom; // top up seller's and buyer's account - await mockToken.mint(operator.address, sellerDeposit); + await mockToken.mint(assistant.address, sellerDeposit); await mockToken.mint(buyer.address, price); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamond.address, price); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); // Prepare the function signature for the facet function. functionSignature = offerHandler.interface.encodeFunctionData("createOffer", [ @@ -3120,7 +3120,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Prepare the message message = {}; message.nonce = parseInt(nonce); - message.from = operator.address; + message.from = assistant.address; message.contractAddress = offerHandler.address; message.functionName = "createOffer((uint256,uint256,uint256,uint256,uint256,uint256,address,string,string,bool),(uint256,uint256,uint256,uint256),(uint256,uint256,uint256),uint256,uint256)"; @@ -3135,7 +3135,7 @@ describe("IBosonMetaTransactionsHandler", function () { it("Should emit MetaTransactionExecuted event and update state", async () => { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -3145,7 +3145,7 @@ describe("IBosonMetaTransactionsHandler", function () { // send a meta transaction, check for event await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -3155,11 +3155,11 @@ describe("IBosonMetaTransactionsHandler", function () { ) ) .to.emit(metaTransactionsHandler, "MetaTransactionExecuted") - .withArgs(operator.address, deployer.address, message.functionName, nonce); + .withArgs(assistant.address, deployer.address, message.functionName, nonce); // Verify that nonce is used. Expect true. let expectedResult = true; - result = await metaTransactionsHandler.connect(operator).isUsedNonce(operator.address, nonce); + result = await metaTransactionsHandler.connect(assistant).isUsedNonce(assistant.address, nonce); assert.equal(result, expectedResult, "Nonce is unused"); }); @@ -3182,7 +3182,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -3192,7 +3192,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -3207,7 +3207,7 @@ describe("IBosonMetaTransactionsHandler", function () { it("Should fail when replay transaction", async function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - operator, + assistant, customTransactionType, "MetaTransaction", message, @@ -3216,7 +3216,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute the meta transaction. await metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -3228,7 +3228,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction again with the same nonce, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -3245,7 +3245,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Collect the signature components let { r, s, v } = await prepareDataSignatureParameters( - rando, // Different user, not seller's operator. + rando, // Different user, not seller's assistant. customTransactionType, "MetaTransaction", message, @@ -3255,7 +3255,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Execute meta transaction, expecting revert. await expect( metaTransactionsHandler.executeMetaTransaction( - operator.address, + assistant.address, message.functionName, functionSignature, nonce, @@ -3274,7 +3274,7 @@ describe("IBosonMetaTransactionsHandler", function () { exchangeId = "1"; // Create a valid seller - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -3288,7 +3288,7 @@ describe("IBosonMetaTransactionsHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -3330,24 +3330,24 @@ describe("IBosonMetaTransactionsHandler", function () { // Create both offers await Promise.all([ offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerNative, offerDates, offerDurations, disputeResolver.id, agentId), offerHandler - .connect(operator) + .connect(assistant) .createOffer(offerToken, offerDates, offerDurations, disputeResolver.id, agentId), ]); // top up seller's and buyer's account - await mockToken.mint(operator.address, sellerDeposit); + await mockToken.mint(assistant.address, sellerDeposit); await mockToken.mint(buyer.address, price); // approve protocol to transfer the tokens - await mockToken.connect(operator).approve(protocolDiamond.address, sellerDeposit); + await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); await mockToken.connect(buyer).approve(protocolDiamond.address, price); // deposit to seller's pool - await fundsHandler.connect(operator).depositFunds(seller.id, mockToken.address, sellerDeposit); - await fundsHandler.connect(operator).depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, ethers.constants.AddressZero, sellerDeposit, { value: sellerDeposit, }); diff --git a/test/protocol/OfferHandlerTest.js b/test/protocol/OfferHandlerTest.js index dfa245a24..720dfbbb3 100644 --- a/test/protocol/OfferHandlerTest.js +++ b/test/protocol/OfferHandlerTest.js @@ -37,11 +37,11 @@ describe("IBosonOfferHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -109,8 +109,8 @@ describe("IBosonOfferHandler", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -237,7 +237,7 @@ describe("IBosonOfferHandler", function () { id = nextAccountId = "1"; // argument sent to contract for createSeller will be ignored // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -251,7 +251,7 @@ describe("IBosonOfferHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -320,7 +320,7 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferCreated event", async function () { // Create an offer, testing for the event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -332,14 +332,14 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); it("should update state", async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Get the offer as a struct @@ -379,7 +379,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, testing for the event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -391,7 +391,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // wrong offer id should not exist @@ -409,7 +409,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, testing for the event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -421,7 +421,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -440,7 +440,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -452,7 +452,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -470,7 +470,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -482,7 +482,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTerms.toStruct(), offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -495,7 +495,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -507,7 +507,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -517,7 +517,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -529,14 +529,14 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); it("Should use the correct dispute resolver fee", async function () { // Create an offer in native currency await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -548,7 +548,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -565,7 +565,7 @@ describe("IBosonOfferHandler", function () { // Create an offer in boson token await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -577,7 +577,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -592,7 +592,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.SELLER_NOT_APPROVED); // add seller to allow list @@ -601,7 +601,7 @@ describe("IBosonOfferHandler", function () { // Create an offer testing for the event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.emit(offerHandler, "OfferCreated"); }); @@ -612,15 +612,15 @@ describe("IBosonOfferHandler", function () { // Attempt to create an offer expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to Create an offer, expecting revert await expect( offerHandler.connect(rando).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) - ).to.revertedWith(RevertReasons.NOT_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Valid from date is greater than valid until date", async function () { @@ -630,7 +630,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -647,7 +647,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -657,7 +657,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.OFFER_PENALTY_INVALID); }); @@ -667,7 +667,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.OFFER_MUST_BE_ACTIVE); }); @@ -678,7 +678,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.AMBIGUOUS_VOUCHER_EXPIRY); }); @@ -689,7 +689,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.AMBIGUOUS_VOUCHER_EXPIRY); }); @@ -700,7 +700,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.REDEMPTION_PERIOD_INVALID); }); @@ -712,7 +712,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.REDEMPTION_PERIOD_INVALID); }); @@ -722,7 +722,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_PERIOD); }); @@ -732,7 +732,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_RESOLUTION_PERIOD); }); @@ -742,7 +742,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_RESOLUTION_PERIOD); }); @@ -752,7 +752,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_QUANTITY_AVAILABLE); }); @@ -762,7 +762,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); }); @@ -776,7 +776,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); // after activation it should be possible to create the offer @@ -784,7 +784,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, test event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.emit(offerHandler, "OfferCreated"); }); @@ -795,7 +795,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); }); @@ -812,7 +812,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); // after activation it should be possible to create the offer @@ -820,7 +820,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, test event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.emit(offerHandler, "OfferCreated"); }); @@ -835,7 +835,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.SELLER_NOT_APPROVED); }); @@ -845,7 +845,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.DR_UNSUPPORTED_FEE); }); }); @@ -869,7 +869,7 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferCreated event with updated agent id", async function () { // Create an offer, testing for the event await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -881,7 +881,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Check that mapping between agent and offer is correct @@ -905,7 +905,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ) .to.emit(offerHandler, "OfferCreated") .withArgs( @@ -917,7 +917,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); //Check offer agent fee for New offer. @@ -934,7 +934,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // change agent fee percentage and create a new offer @@ -949,7 +949,7 @@ describe("IBosonOfferHandler", function () { // Create a new offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); //Check offer agent fee for New offer. @@ -968,13 +968,15 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( - offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) + offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId) ).to.revertedWith(RevertReasons.NO_SUCH_AGENT); }); it("Sum of agent fee amount and protocol fee amount should be <= than the offer fee limit", async function () { // Create a valid agent, then set fields in tests directly - agent = mockAgent(operator.address); + agent = mockAgent(assistant.address); agent.id = "4"; agent.feePercentage = "3000"; //30% expect(agent.isValid()).is.true; @@ -988,7 +990,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agent.id) ).to.revertedWith(RevertReasons.AGENT_FEE_AMOUNT_TOO_HIGH); }); @@ -1000,7 +1002,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1012,9 +1014,9 @@ describe("IBosonOfferHandler", function () { [, offerStruct] = await offerHandler.getOffer(id); // Void the offer, testing for the event - await expect(offerHandler.connect(operator).voidOffer(id)) + await expect(offerHandler.connect(assistant).voidOffer(id)) .to.emit(offerHandler, "OfferVoided") - .withArgs(id, offerStruct.sellerId, operator.address); + .withArgs(id, offerStruct.sellerId, assistant.address); }); it("should update state", async function () { @@ -1027,7 +1029,7 @@ describe("IBosonOfferHandler", function () { expect(voided).to.be.false; // Void the offer - await offerHandler.connect(operator).voidOffer(id); + await offerHandler.connect(assistant).voidOffer(id); // Voided field should be updated [, offerStruct] = await offerHandler.getOffer(id); @@ -1044,7 +1046,7 @@ describe("IBosonOfferHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Offers]); // Attempt to void an offer expecting revert - await expect(offerHandler.connect(operator).voidOffer(id)).to.revertedWith(RevertReasons.REGION_PAUSED); + await expect(offerHandler.connect(assistant).voidOffer(id)).to.revertedWith(RevertReasons.REGION_PAUSED); }); it("Offer does not exist", async function () { @@ -1052,21 +1054,21 @@ describe("IBosonOfferHandler", function () { id = "444"; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).voidOffer(id)).to.revertedWith(RevertReasons.NO_SUCH_OFFER); + await expect(offerHandler.connect(assistant).voidOffer(id)).to.revertedWith(RevertReasons.NO_SUCH_OFFER); // Set invalid id id = "0"; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).voidOffer(id)).to.revertedWith(RevertReasons.NO_SUCH_OFFER); + await expect(offerHandler.connect(assistant).voidOffer(id)).to.revertedWith(RevertReasons.NO_SUCH_OFFER); }); it("Caller is not seller", async function () { - // caller is not the operator of any seller + // caller is not the assistant of any seller // Attempt to update the offer, expecting revert - await expect(offerHandler.connect(rando).voidOffer(id)).to.revertedWith(RevertReasons.NOT_OPERATOR); + await expect(offerHandler.connect(rando).voidOffer(id)).to.revertedWith(RevertReasons.NOT_ASSISTANT); - // caller is an operator of another seller + // caller is an assistant of another seller // Create a valid seller, then set fields in tests directly seller = mockSeller(rando.address, rando.address, rando.address, rando.address); @@ -1076,15 +1078,15 @@ describe("IBosonOfferHandler", function () { await accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues); // Attempt to update the offer, expecting revert - await expect(offerHandler.connect(rando).voidOffer(id)).to.revertedWith(RevertReasons.NOT_OPERATOR); + await expect(offerHandler.connect(rando).voidOffer(id)).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Offer already voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer(id); + await offerHandler.connect(assistant).voidOffer(id); // Attempt to void the offer again, expecting revert - await expect(offerHandler.connect(operator).voidOffer(id)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOffer(id)).to.revertedWith( RevertReasons.OFFER_HAS_BEEN_VOIDED ); }); @@ -1096,7 +1098,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1109,14 +1111,14 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferExtended event", async function () { // Extend the valid until date, testing for the event - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)) + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)) .to.emit(offerHandler, "OfferExtended") - .withArgs(id, offer.sellerId, offerDates.validUntil, operator.address); + .withArgs(id, offer.sellerId, offerDates.validUntil, assistant.address); }); it("should update state", async function () { // Update an offer - await offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil); + await offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil); // Get the offer as a struct [, offerStruct, offerDatesStruct] = await offerHandler.connect(rando).getOffer(offer.id); @@ -1136,7 +1138,7 @@ describe("IBosonOfferHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Offers]); // Attempt to extend an offer expecting revert - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1146,7 +1148,7 @@ describe("IBosonOfferHandler", function () { id = "444"; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(id, offerDates.validUntil)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -1154,19 +1156,19 @@ describe("IBosonOfferHandler", function () { id = "0"; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(id, offerDates.validUntil)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); it("Caller is not seller", async function () { - // caller is not the operator of any seller + // caller is not the assistant of any seller // Attempt to update the offer, expecting revert await expect(offerHandler.connect(rando).extendOffer(id, offerDates.validUntil)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); - // caller is an operator of another seller + // caller is an assistant of another seller // Create a valid seller, then set fields in tests directly seller = mockSeller(rando.address, rando.address, rando.address, rando.address); @@ -1177,16 +1179,16 @@ describe("IBosonOfferHandler", function () { // Attempt to update the offer, expecting revert await expect(offerHandler.connect(rando).extendOffer(id, offerDates.validUntil)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); it("Offer is not extendable, since it's voided", async function () { // Void an offer - await offerHandler.connect(operator).voidOffer(id); + await offerHandler.connect(assistant).voidOffer(id); // Attempt to update an offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.OFFER_HAS_BEEN_VOIDED ); }); @@ -1195,7 +1197,7 @@ describe("IBosonOfferHandler", function () { // Make the valid until date the same as the existing offer offerDates.validUntil = ethers.BigNumber.from(offerDates.validUntil).sub("10000").toString(); - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.OFFER_PERIOD_INVALID ); @@ -1203,7 +1205,7 @@ describe("IBosonOfferHandler", function () { offerDates.validUntil = ethers.BigNumber.from(offerDates.validUntil).sub("1").toString(); // Attempt to update an offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.OFFER_PERIOD_INVALID ); }); @@ -1213,7 +1215,7 @@ describe("IBosonOfferHandler", function () { offerDates.validUntil = ethers.BigNumber.from(offerDates.validFrom - oneMonth * 6).toString(); // 6 months ago // Attempt to update an offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.OFFER_PERIOD_INVALID ); }); @@ -1227,7 +1229,7 @@ describe("IBosonOfferHandler", function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1240,14 +1242,14 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferExtended event", async function () { // Extend the valid until date, testing for the event - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)) + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)) .to.emit(offerHandler, "OfferExtended") - .withArgs(id, offer.sellerId, offerDates.validUntil, operator.address); + .withArgs(id, offer.sellerId, offerDates.validUntil, assistant.address); }); it("should update state", async function () { // Update an offer - await offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil); + await offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil); // Get the offer as a struct [, offerStruct, offerDatesStruct] = await offerHandler.connect(rando).getOffer(offer.id); @@ -1267,7 +1269,7 @@ describe("IBosonOfferHandler", function () { offerDates.validUntil = ethers.BigNumber.from(offerDates.voucherRedeemableUntil).add(oneWeek).toString(); // one week after voucherRedeemableUntil // Attempt to update an offer, expecting revert - await expect(offerHandler.connect(operator).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( + await expect(offerHandler.connect(assistant).extendOffer(offer.id, offerDates.validUntil)).to.revertedWith( RevertReasons.OFFER_PERIOD_INVALID ); }); @@ -1283,7 +1285,7 @@ describe("IBosonOfferHandler", function () { // Create an offer offer.quantityAvailable = "200"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1301,11 +1303,11 @@ describe("IBosonOfferHandler", function () { it("should emit an RangeReserved event", async function () { // Reserve a range, testing for the event - const tx = await offerHandler.connect(operator).reserveRange(id, length); + const tx = await offerHandler.connect(assistant).reserveRange(id, length); await expect(tx) .to.emit(offerHandler, "RangeReserved") - .withArgs(id, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(id, offer.sellerId, firstTokenId, lastTokenId, assistant.address); await expect(tx).to.emit(bosonVoucher, "RangeReserved").withArgs(id, range.toStruct()); }); @@ -1317,7 +1319,7 @@ describe("IBosonOfferHandler", function () { const nextExchangeIdBefore = await exchangeHandler.getNextExchangeId(); // Reserve a range - await offerHandler.connect(operator).reserveRange(id, length); + await offerHandler.connect(assistant).reserveRange(id, length); // Quantity available should be updated [, offerStruct] = await offerHandler.connect(rando).getOffer(id); @@ -1341,7 +1343,7 @@ describe("IBosonOfferHandler", function () { // Deposit seller funds so the commit will succeed const sellerPool = ethers.BigNumber.from(offer.sellerDeposit).mul(2); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); // Commit to the offer twice @@ -1349,21 +1351,21 @@ describe("IBosonOfferHandler", function () { await exchangeHandler.connect(rando).commitToOffer(rando.address, id, { value: price }); // Reserve a range, testing for the event - await expect(offerHandler.connect(operator).reserveRange(id, length)) + await expect(offerHandler.connect(assistant).reserveRange(id, length)) .to.emit(offerHandler, "RangeReserved") - .withArgs(id, offer.sellerId, firstTokenId + 2, lastTokenId + 2, operator.address); + .withArgs(id, offer.sellerId, firstTokenId + 2, lastTokenId + 2, assistant.address); }); it("It's possible to reserve a range with maximum allowed length", async function () { // Create an unlimited offer offer.quantityAvailable = ethers.constants.MaxUint256.toString(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Set maximum allowed length length = ethers.BigNumber.from(2).pow(128).sub(1); - await expect(offerHandler.connect(operator).reserveRange(nextOfferId, length)).to.emit( + await expect(offerHandler.connect(assistant).reserveRange(nextOfferId, length)).to.emit( offerHandler, "RangeReserved" ); @@ -1373,7 +1375,7 @@ describe("IBosonOfferHandler", function () { // Create an unlimited offer offer.quantityAvailable = ethers.constants.MaxUint256.toString(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Get the offer quantity available before reservation @@ -1381,7 +1383,7 @@ describe("IBosonOfferHandler", function () { const quantityAvailableBefore = offerStruct.quantityAvailable; // Reserve a range - await offerHandler.connect(operator).reserveRange(nextOfferId, length); + await offerHandler.connect(assistant).reserveRange(nextOfferId, length); // Quantity available should not change [, offerStruct] = await offerHandler.connect(rando).getOffer(nextOfferId); @@ -1399,7 +1401,7 @@ describe("IBosonOfferHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Offers]); // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1409,7 +1411,7 @@ describe("IBosonOfferHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -1419,7 +1421,7 @@ describe("IBosonOfferHandler", function () { id = "444"; // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -1427,29 +1429,29 @@ describe("IBosonOfferHandler", function () { id = "0"; // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); it("Offer already voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer(id); + await offerHandler.connect(assistant).voidOffer(id); // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.OFFER_HAS_BEEN_VOIDED ); }); it("Caller is not seller", async function () { - // caller is not the operator of any seller + // caller is not the assistant of any seller // Attempt to reserve a range, expecting revert await expect(offerHandler.connect(rando).reserveRange(id, length)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); - // caller is an operator of another seller + // caller is an assistant of another seller // Create a valid seller, then set fields in tests directly seller = mockSeller(rando.address, rando.address, rando.address, rando.address); @@ -1460,7 +1462,7 @@ describe("IBosonOfferHandler", function () { // Attempt to reserve a range, expecting revert await expect(offerHandler.connect(rando).reserveRange(id, length)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); @@ -1469,7 +1471,7 @@ describe("IBosonOfferHandler", function () { length = 0; // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.INVALID_RANGE_LENGTH ); }); @@ -1479,7 +1481,7 @@ describe("IBosonOfferHandler", function () { length = Number(offer.quantityAvailable) + 1; // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.INVALID_RANGE_LENGTH ); }); @@ -1488,24 +1490,24 @@ describe("IBosonOfferHandler", function () { // Create an unlimited offer offer.quantityAvailable = ethers.constants.MaxUint256.toString(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // Set length to more than maximum allowed range length length = ethers.BigNumber.from(2).pow(128); // Attempt to reserve a range, expecting revert - await expect(offerHandler.connect(operator).reserveRange(nextOfferId, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(nextOfferId, length)).to.revertedWith( RevertReasons.INVALID_RANGE_LENGTH ); }); it("Call to BosonVoucher.reserveRange() reverts", async function () { // Reserve a range - await offerHandler.connect(operator).reserveRange(id, length); + await offerHandler.connect(assistant).reserveRange(id, length); // Attempt to reserve the same range again, expecting revert - await expect(offerHandler.connect(operator).reserveRange(id, length)).to.revertedWith( + await expect(offerHandler.connect(assistant).reserveRange(id, length)).to.revertedWith( RevertReasons.OFFER_RANGE_ALREADY_RESERVED ); }); @@ -1516,7 +1518,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1561,7 +1563,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1582,7 +1584,7 @@ describe("IBosonOfferHandler", function () { it("should be incremented after an offer is created", async function () { // Create another offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // What we expect the next offer id to be @@ -1617,7 +1619,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); // id of the current offer and increment nextOfferId @@ -1632,7 +1634,7 @@ describe("IBosonOfferHandler", function () { expect(exists).to.be.true; // Void offer - await offerHandler.connect(operator).voidOffer(id); + await offerHandler.connect(assistant).voidOffer(id); // Get the exists flag [exists] = await offerHandler.connect(rando).isOfferVoided(id); @@ -1677,7 +1679,7 @@ describe("IBosonOfferHandler", function () { id = sellerId = nextAccountId = "1"; // argument sent to contract for createSeller will be ignored // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -1691,7 +1693,7 @@ describe("IBosonOfferHandler", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1827,7 +1829,7 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferCreated events for all offers", async function () { // Create an offer, testing for the event const tx = await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); await expect(tx) @@ -1841,7 +1843,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[0], offerFeesStructs[0], agentIds[0], - operator.address + assistant.address ); await expect(tx) @@ -1855,7 +1857,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[1], offerFeesStructs[1], agentIds[1], - operator.address + assistant.address ); await expect(tx) @@ -1869,7 +1871,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[2], offerFeesStructs[2], agentIds[2], - operator.address + assistant.address ); await expect(tx) @@ -1883,7 +1885,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[3], offerFeesStructs[3], agentIds[3], - operator.address + assistant.address ); await expect(tx) @@ -1897,14 +1899,14 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[4], offerFeesStructs[4], agentIds[4], - operator.address + assistant.address ); }); it("should update state", async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); for (let i = 0; i < 5; i++) { @@ -1947,7 +1949,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, testing for the event const tx = await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); await expect(tx) @@ -1961,7 +1963,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[0], offerFeesStructs[0], agentIds[0], - operator.address + assistant.address ); await expect(tx) @@ -1975,7 +1977,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[1], offerFeesStructs[1], agentIds[1], - operator.address + assistant.address ); await expect(tx) @@ -1989,7 +1991,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[2], offerFeesStructs[2], agentIds[2], - operator.address + assistant.address ); await expect(tx) @@ -2003,7 +2005,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[3], offerFeesStructs[3], agentIds[3], - operator.address + assistant.address ); await expect(tx) @@ -2017,7 +2019,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[4], offerFeesStructs[4], agentIds[4], - operator.address + assistant.address ); for (let i = 0; i < 5; i++) { @@ -2041,7 +2043,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, testing for the event const tx = await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); await expect(tx) @@ -2055,7 +2057,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[0], offerFeesStructs[0], agentIds[0], - operator.address + assistant.address ); await expect(tx) @@ -2069,7 +2071,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[1], offerFeesStructs[1], agentIds[1], - operator.address + assistant.address ); await expect(tx) @@ -2083,7 +2085,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[2], offerFeesStructs[2], agentIds[2], - operator.address + assistant.address ); await expect(tx) @@ -2097,7 +2099,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[3], offerFeesStructs[3], agentIds[3], - operator.address + assistant.address ); await expect(tx) @@ -2111,7 +2113,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[4], offerFeesStructs[4], agentIds[4], - operator.address + assistant.address ); }); @@ -2127,7 +2129,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.SELLER_NOT_APPROVED); @@ -2138,7 +2140,7 @@ describe("IBosonOfferHandler", function () { // Create an offer, testing for the event await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.emit(offerHandler, "OfferCreated"); }); @@ -2151,18 +2153,18 @@ describe("IBosonOfferHandler", function () { // Attempt to create offer batch, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to Create an offer, expecting revert await expect( offerHandler .connect(rando) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) - ).to.revertedWith(RevertReasons.NOT_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Valid from date is greater than valid until date in some offer", async function () { @@ -2173,7 +2175,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -2190,7 +2192,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -2202,7 +2204,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.OFFER_PENALTY_INVALID); }); @@ -2214,7 +2216,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.OFFER_MUST_BE_ACTIVE); }); @@ -2228,7 +2230,7 @@ describe("IBosonOfferHandler", function () { // Attempt to create the offers, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds, { gasLimit }) ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); }); @@ -2240,7 +2242,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_RESOLUTION_PERIOD); }); @@ -2255,7 +2257,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.AMBIGUOUS_VOUCHER_EXPIRY); }); @@ -2268,7 +2270,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.AMBIGUOUS_VOUCHER_EXPIRY); }); @@ -2283,7 +2285,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.REDEMPTION_PERIOD_INVALID); }); @@ -2297,7 +2299,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.REDEMPTION_PERIOD_INVALID); }); @@ -2309,7 +2311,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_PERIOD); }); @@ -2321,7 +2323,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_RESOLUTION_PERIOD); }); @@ -2333,7 +2335,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_QUANTITY_AVAILABLE); }); @@ -2345,7 +2347,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); }); @@ -2364,7 +2366,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create offers, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); @@ -2374,7 +2376,7 @@ describe("IBosonOfferHandler", function () { // Create offers, test event await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.emit(offerHandler, "OfferCreated"); }); @@ -2387,7 +2389,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create offers, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); }); @@ -2407,7 +2409,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create offers, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.INVALID_DISPUTE_RESOLVER); @@ -2417,7 +2419,7 @@ describe("IBosonOfferHandler", function () { // Create offers, test event await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.emit(offerHandler, "OfferCreated"); }); @@ -2434,7 +2436,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.SELLER_NOT_APPROVED); }); @@ -2446,7 +2448,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create offers, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.DR_UNSUPPORTED_FEE); }); @@ -2458,7 +2460,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); @@ -2468,7 +2470,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); }); @@ -2480,7 +2482,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); @@ -2490,7 +2492,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); }); @@ -2502,7 +2504,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); @@ -2512,7 +2514,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds) ).to.revertedWith(RevertReasons.ARRAY_LENGTH_MISMATCH); }); @@ -2554,7 +2556,7 @@ describe("IBosonOfferHandler", function () { it("should emit an OfferCreated events for all offers with updated agent ids", async function () { // Create an offer, testing for the event const tx = await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, nonZeroAgentIds); await expect(tx) @@ -2568,7 +2570,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[0], offerFeesStructs[0], nonZeroAgentIds[0], - operator.address + assistant.address ); await expect(tx) @@ -2582,7 +2584,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[1], offerFeesStructs[1], nonZeroAgentIds[1], - operator.address + assistant.address ); await expect(tx) @@ -2596,7 +2598,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[2], offerFeesStructs[2], nonZeroAgentIds[2], - operator.address + assistant.address ); await expect(tx) @@ -2610,7 +2612,7 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[3], offerFeesStructs[3], nonZeroAgentIds[3], - operator.address + assistant.address ); await expect(tx) @@ -2624,14 +2626,14 @@ describe("IBosonOfferHandler", function () { disputeResolutionTermsStructs[4], offerFeesStructs[4], nonZeroAgentIds[4], - operator.address + assistant.address ); }); it("all offer should have an agent assigned", async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, nonZeroAgentIds); for (let i = 1; i < 6; i++) { @@ -2650,7 +2652,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, nonZeroAgentIds) ).to.revertedWith(RevertReasons.NO_SUCH_AGENT); }); @@ -2660,7 +2662,7 @@ describe("IBosonOfferHandler", function () { let id = "4"; // argument sent to contract for createAgent will be ignored // Create a valid agent, then set fields in tests directly - agent = mockAgent(operator.address); + agent = mockAgent(assistant.address); agent.id = id; agent.feePercentage = "3000"; // 30% expect(agent.isValid()).is.true; @@ -2676,7 +2678,7 @@ describe("IBosonOfferHandler", function () { // Attempt to Create an offer, expecting revert await expect( offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, nonZeroAgentIds) ).to.revertedWith(RevertReasons.AGENT_FEE_AMOUNT_TOO_HIGH); }); @@ -2691,7 +2693,7 @@ describe("IBosonOfferHandler", function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); offersToVoid = ["1", "3", "5"]; @@ -2702,18 +2704,18 @@ describe("IBosonOfferHandler", function () { // call getOffer with offerId to check the seller id in the event // Void offers, testing for the event - const tx = await offerHandler.connect(operator).voidOfferBatch(offersToVoid); + const tx = await offerHandler.connect(assistant).voidOfferBatch(offersToVoid); await expect(tx) .to.emit(offerHandler, "OfferVoided") - .withArgs(offersToVoid[0], offerStruct.sellerId, operator.address); + .withArgs(offersToVoid[0], offerStruct.sellerId, assistant.address); await expect(tx) .to.emit(offerHandler, "OfferVoided") - .withArgs(offersToVoid[1], offerStruct.sellerId, operator.address); + .withArgs(offersToVoid[1], offerStruct.sellerId, assistant.address); await expect(tx) .to.emit(offerHandler, "OfferVoided") - .withArgs(offersToVoid[2], offerStruct.sellerId, operator.address); + .withArgs(offersToVoid[2], offerStruct.sellerId, assistant.address); }); it("should update state", async function () { @@ -2728,7 +2730,7 @@ describe("IBosonOfferHandler", function () { } // Void offers - await offerHandler.connect(operator).voidOfferBatch(offersToVoid); + await offerHandler.connect(assistant).voidOfferBatch(offersToVoid); for (const id of offersToVoid) { // Voided field should be updated @@ -2747,7 +2749,7 @@ describe("IBosonOfferHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Offers]); // Attempt to void offer batch, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.REGION_PAUSED ); }); @@ -2757,7 +2759,7 @@ describe("IBosonOfferHandler", function () { offersToVoid = ["1", "432", "2"]; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); @@ -2765,19 +2767,19 @@ describe("IBosonOfferHandler", function () { offersToVoid = ["1", "2", "0"]; // Attempt to void the offer, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.NO_SUCH_OFFER ); }); it("Caller is not seller", async function () { - // caller is not the operator of any seller + // caller is not the assistant of any seller // Attempt to update the offer, expecting revert await expect(offerHandler.connect(rando).voidOfferBatch(offersToVoid)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); - // caller is an operator of another seller + // caller is an assistant of another seller seller = mockSeller(rando.address, rando.address, rando.address, rando.address); // AuthToken @@ -2788,16 +2790,16 @@ describe("IBosonOfferHandler", function () { // Attempt to update the offer, expecting revert await expect(offerHandler.connect(rando).voidOfferBatch(offersToVoid)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); it("Offer already voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer("1"); + await offerHandler.connect(assistant).voidOffer("1"); // Attempt to void the offer again, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.OFFER_HAS_BEEN_VOIDED ); @@ -2805,7 +2807,7 @@ describe("IBosonOfferHandler", function () { offersToVoid = ["1", "4", "1"]; // Attempt to void the offer again, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.OFFER_HAS_BEEN_VOIDED ); }); @@ -2815,7 +2817,7 @@ describe("IBosonOfferHandler", function () { offersToVoid = [...Array(101).keys()]; // Attempt to void the offers, expecting revert - await expect(offerHandler.connect(operator).voidOfferBatch(offersToVoid)).to.revertedWith( + await expect(offerHandler.connect(assistant).voidOfferBatch(offersToVoid)).to.revertedWith( RevertReasons.TOO_MANY_OFFERS ); }); @@ -2827,7 +2829,7 @@ describe("IBosonOfferHandler", function () { beforeEach(async function () { // Create an offer await offerHandler - .connect(operator) + .connect(assistant) .createOfferBatch(offers, offerDatesList, offerDurationsList, disputeResolverIds, agentIds); offersToExtend = ["1", "3", "5"]; @@ -2841,18 +2843,18 @@ describe("IBosonOfferHandler", function () { it("should emit OfferExtended events", async function () { // Extend the valid until date, testing for the event - const tx = await offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate); + const tx = await offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate); await expect(tx) .to.emit(offerHandler, "OfferExtended") - .withArgs(offersToExtend[0], offer.sellerId, newValidUntilDate, operator.address); + .withArgs(offersToExtend[0], offer.sellerId, newValidUntilDate, assistant.address); await expect(tx) .to.emit(offerHandler, "OfferExtended") - .withArgs(offersToExtend[1], offer.sellerId, newValidUntilDate, operator.address); + .withArgs(offersToExtend[1], offer.sellerId, newValidUntilDate, assistant.address); await expect(tx) .to.emit(offerHandler, "OfferExtended") - .withArgs(offersToExtend[2], offer.sellerId, newValidUntilDate, operator.address); + .withArgs(offersToExtend[2], offer.sellerId, newValidUntilDate, assistant.address); }); it("should update state", async function () { @@ -2863,7 +2865,7 @@ describe("IBosonOfferHandler", function () { } // Extend offers - await offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate); + await offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate); for (const id of offersToExtend) { // validUntilDate field should be updated @@ -2879,7 +2881,7 @@ describe("IBosonOfferHandler", function () { // Attempt to void offer batch, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -2889,7 +2891,7 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.NO_SUCH_OFFER); // Set invalid id @@ -2897,18 +2899,18 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.NO_SUCH_OFFER); }); it("Caller is not seller", async function () { - // caller is not the operator of any seller + // caller is not the assistant of any seller // Attempt to extend the offers, expecting revert await expect(offerHandler.connect(rando).extendOfferBatch(offersToExtend, newValidUntilDate)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); - // caller is an operator of another seller + // caller is an assistant of another seller seller = mockSeller(rando.address, rando.address, rando.address, rando.address); // AuthToken @@ -2918,17 +2920,17 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect(offerHandler.connect(rando).extendOfferBatch(offersToExtend, newValidUntilDate)).to.revertedWith( - RevertReasons.NOT_OPERATOR + RevertReasons.NOT_ASSISTANT ); }); it("Offers are not extendable, since one of them it's voided", async function () { // Void the offer first - await offerHandler.connect(operator).voidOffer("3"); + await offerHandler.connect(assistant).voidOffer("3"); // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); }); @@ -2937,7 +2939,7 @@ describe("IBosonOfferHandler", function () { newValidUntilDate = ethers.BigNumber.from(offers[4].validUntilDate).sub("10000").toString(); // same as that validUntilDate of offer 5 await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); // Make new the valid until date less than existing one @@ -2945,7 +2947,7 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -2955,7 +2957,7 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -2965,7 +2967,7 @@ describe("IBosonOfferHandler", function () { offerDates.voucherRedeemableUntil = ethers.BigNumber.from(offerDates.validUntil).add(oneMonth).toString(); offerDurations.voucherValid = "0"; // only one of voucherRedeemableUntil and voucherValid can be non zero await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); offersToExtend.push(offer.id); @@ -2974,7 +2976,7 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.OFFER_PERIOD_INVALID); }); @@ -2984,7 +2986,7 @@ describe("IBosonOfferHandler", function () { // Attempt to extend the offers, expecting revert await expect( - offerHandler.connect(operator).extendOfferBatch(offersToExtend, newValidUntilDate) + offerHandler.connect(assistant).extendOfferBatch(offersToExtend, newValidUntilDate) ).to.revertedWith(RevertReasons.TOO_MANY_OFFERS); }); }); diff --git a/test/protocol/OrchestrationHandlerTest.js b/test/protocol/OrchestrationHandlerTest.js index d97a1627d..7a7b75860 100644 --- a/test/protocol/OrchestrationHandlerTest.js +++ b/test/protocol/OrchestrationHandlerTest.js @@ -54,12 +54,12 @@ describe("IBosonOrchestrationHandler", function () { buyer, admin, rando, - operator, + assistant, clerk, treasury, other1, other2, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -135,8 +135,8 @@ describe("IBosonOrchestrationHandler", function () { ] = await ethers.getSigners(); // make all account the same - clerk = operator = admin; - operatorDR = clerkDR = adminDR; + clerk = assistant = admin; + assistantDR = clerkDR = adminDR; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -283,7 +283,7 @@ describe("IBosonOrchestrationHandler", function () { beforeEach(async function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -309,7 +309,7 @@ describe("IBosonOrchestrationHandler", function () { .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, operator.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, assistant.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // How that seller looks as a returned struct @@ -329,10 +329,10 @@ describe("IBosonOrchestrationHandler", function () { expect(authToken.isValid()).is.true; authTokenStruct = authToken.toStruct(); - // deploy mock auth token and mint one to operator + // deploy mock auth token and mint one to assistant const [mockAuthERC721Contract] = await deployMockTokens(["Foreign721"]); await configHandler.connect(deployer).setAuthTokenContract(AuthTokenType.Lens, mockAuthERC721Contract.address); - await mockAuthERC721Contract.connect(operator).mint(authToken.tokenId, 1); + await mockAuthERC721Contract.connect(assistant).mint(authToken.tokenId, 1); // The first offer id nextOfferId = "1"; @@ -392,7 +392,9 @@ describe("IBosonOrchestrationHandler", function () { offer.id++; // create an offer with erc20 exchange token - await offerHandler.connect(operator).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // mint tokens to buyer and approve the protocol buyerEscalationDepositToken = applyPercentage(DRFeeToken, buyerEscalationDepositPercentage); @@ -409,7 +411,7 @@ describe("IBosonOrchestrationHandler", function () { beforeEach(async function () { // Create the seller and offer await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -435,7 +437,7 @@ describe("IBosonOrchestrationHandler", function () { // Deposit seller funds so the commit will succeed const fundsToDeposit = ethers.BigNumber.from(sellerDeposit).mul(quantityAvailable); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(seller.id, ethers.constants.AddressZero, fundsToDeposit, { value: fundsToDeposit }); buyerId = accountId.next().value; @@ -604,7 +606,7 @@ describe("IBosonOrchestrationHandler", function () { await setNextBlockTimestamp(newTime); // Complete exchange - await exchangeHandler.connect(operator).completeExchange(exchangeId); + await exchangeHandler.connect(assistant).completeExchange(exchangeId); // Attempt to raise a dispute, expecting revert await expect( @@ -648,7 +650,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated and OfferCreated events with empty auth token", async function () { // Create a seller and an offer, testing for the event tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -664,7 +666,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") .withArgs( @@ -676,7 +678,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Voucher clone contract @@ -691,7 +693,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should emit a SellerCreated and OfferCreated events with auth token", async function () { @@ -700,7 +702,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -716,7 +718,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -729,7 +731,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Voucher clone contract @@ -744,7 +746,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { @@ -753,7 +755,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -811,7 +813,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -828,7 +830,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -857,7 +859,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -876,7 +878,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -905,7 +907,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -922,7 +924,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -941,7 +943,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -955,7 +957,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // wrong seller id should not exist @@ -982,7 +984,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1004,7 +1006,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -1015,7 +1017,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1037,7 +1039,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -1056,7 +1058,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1078,7 +1080,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -1092,7 +1094,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1114,7 +1116,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -1125,7 +1127,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer, testing for the event await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1147,7 +1149,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -1155,7 +1157,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1177,7 +1179,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -1241,7 +1243,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and a preminted offer, testing for the event tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -1258,7 +1260,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -1271,12 +1273,12 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Voucher clone contract bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); @@ -1292,7 +1294,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { @@ -1301,7 +1303,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and a preminted offer await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -1363,7 +1365,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -1384,7 +1386,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1405,7 +1407,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a offer expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1426,7 +1428,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a offer expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1448,7 +1450,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -1469,7 +1471,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1485,13 +1487,13 @@ describe("IBosonOrchestrationHandler", function () { it("addresses are not unique to this seller Id", async function () { // Create a seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); - // Attempt to create a seller with non-unique admin, operator and clerk, expecting revert - // N.B. operator and admin are tested together, since they must be the same + // Attempt to create a seller with non-unique admin, assistant and clerk, expecting revert + // N.B. assistant and admin are tested together, since they must be the same await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1506,7 +1508,7 @@ describe("IBosonOrchestrationHandler", function () { }); it("Caller is not the supplied admin", async function () { - seller.operator = rando.address; + seller.assistant = rando.address; seller.clerk = rando.address; // Attempt to create a seller and an offer, expecting revert @@ -1528,7 +1530,7 @@ describe("IBosonOrchestrationHandler", function () { it("Caller does not own supplied auth token", async function () { seller.admin = ethers.constants.AddressZero; - seller.operator = rando.address; + seller.assistant = rando.address; seller.clerk = rando.address; // Attempt to create a seller and an offer, expecting revert @@ -1548,7 +1550,7 @@ describe("IBosonOrchestrationHandler", function () { ).to.revertedWith(RevertReasons.NOT_ADMIN); }); - it("Caller is not the supplied operator", async function () { + it("Caller is not the supplied assistant", async function () { seller.admin = rando.address; seller.clerk = rando.address; @@ -1566,12 +1568,12 @@ describe("IBosonOrchestrationHandler", function () { voucherInitValues, agentId ) - ).to.revertedWith(RevertReasons.NOT_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT_AND_CLERK); }); it("Caller is not the supplied clerk", async function () { seller.admin = rando.address; - seller.operator = rando.address; + seller.assistant = rando.address; // Attempt to create a seller and an offer, expecting revert await expect( @@ -1587,14 +1589,14 @@ describe("IBosonOrchestrationHandler", function () { voucherInitValues, agentId ) - ).to.revertedWith(RevertReasons.NOT_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT_AND_CLERK); }); it("admin address is NOT zero address and AuthTokenType is NOT None", async function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1614,7 +1616,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1633,12 +1635,12 @@ describe("IBosonOrchestrationHandler", function () { seller.admin = ethers.constants.AddressZero; // Create a seller - await accountHandler.connect(operator).createSeller(seller, authToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, authToken, voucherInitValues); // Attempt to create a seller with non-unique authToken and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1660,7 +1662,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1687,7 +1689,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1708,7 +1710,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1729,7 +1731,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1751,7 +1753,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1773,7 +1775,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1795,7 +1797,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1818,7 +1820,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1839,7 +1841,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1860,7 +1862,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1881,7 +1883,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1902,7 +1904,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1923,7 +1925,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1950,7 +1952,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -1972,7 +1974,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2003,7 +2005,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2029,7 +2031,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2050,7 +2052,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2071,7 +2073,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -2093,7 +2095,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -2115,7 +2117,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller and an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOffer( seller, offer, @@ -2157,7 +2159,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated and OfferCreated events", async function () { // Create a seller and an offer, testing for the event const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2176,7 +2178,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -2190,7 +2192,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -2205,7 +2207,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to Create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2224,7 +2226,7 @@ describe("IBosonOrchestrationHandler", function () { let id = "3"; // argument sent to contract for createAgent will be ignored // Create a valid agent, then set fields in tests directly - agent = mockAgent(operator.address); + agent = mockAgent(assistant.address); agent.id = id; agent.feePercentage = "3000"; // 30% expect(agent.isValid()).is.true; @@ -2238,7 +2240,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to Create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2277,13 +2279,13 @@ describe("IBosonOrchestrationHandler", function () { groupStruct = group.toStruct(); // create a seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); }); it("should emit an OfferCreated and GroupCreated events", async function () { // Create an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // OfferCreated event @@ -2298,7 +2300,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -2312,14 +2314,14 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventGroupCreated.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(eventGroupCreated.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(eventGroupCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventGroupCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); it("should update state", async function () { // Create an offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // Get the offer as a struct @@ -2372,7 +2374,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // OfferCreated event @@ -2387,7 +2389,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -2410,7 +2412,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // OfferCreated event @@ -2425,7 +2427,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -2457,7 +2459,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -2470,7 +2472,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -2484,7 +2486,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -2497,7 +2499,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -2508,7 +2510,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -2521,7 +2523,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -2529,7 +2531,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -2542,7 +2544,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -2560,7 +2562,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition in boson token await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -2573,7 +2575,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -2585,7 +2587,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.emit(orchestrationHandler, "OfferCreated"); }); @@ -2611,7 +2613,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated and GroupCreated events", async function () { // Create an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId); // OfferCreated event @@ -2626,7 +2628,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -2640,7 +2642,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventGroupCreated.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(eventGroupCreated.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(eventGroupCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventGroupCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); }); }); @@ -2664,7 +2666,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a preminted offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithCondition( offer, offerDates, @@ -2687,13 +2689,13 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // RangeReserved event (on protocol contract) await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -2706,7 +2708,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventGroupCreated.groupId.toString(), group.id, "Group Id is incorrect"); assert.equal(eventGroupCreated.sellerId.toString(), group.sellerId, "Seller Id is incorrect"); - assert.equal(eventGroupCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventGroupCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(groupInstance.toString(), group.toString(), "Group struct is incorrect"); // RangeReserved event (on voucher contract) @@ -2716,7 +2718,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create a preminted offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithCondition( offer, offerDates, @@ -2790,7 +2792,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOffer( seller, offer, @@ -2811,7 +2813,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -2824,7 +2826,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithCondition( offer, offerDates, @@ -2844,18 +2846,18 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to create an offer with condition, expecting revert await expect( orchestrationHandler .connect(rando) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) - ).to.revertedWith(RevertReasons.NOT_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Condition 'None' has some values in other fields", async function () { @@ -2864,7 +2866,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer with condition, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.revertedWith(RevertReasons.INVALID_CONDITION_PARAMETERS); }); @@ -2876,7 +2878,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer with condition, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.revertedWith(RevertReasons.INVALID_CONDITION_PARAMETERS); }); @@ -2888,7 +2890,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer with condition, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithCondition(offer, offerDates, offerDurations, disputeResolver.id, condition, agentId) ).to.revertedWith(RevertReasons.INVALID_CONDITION_PARAMETERS); }); @@ -2898,7 +2900,7 @@ describe("IBosonOrchestrationHandler", function () { context("👉 createOfferAddToGroup()", async function () { beforeEach(async function () { // create a seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // The first group id nextGroupId = "1"; @@ -2927,7 +2929,7 @@ describe("IBosonOrchestrationHandler", function () { // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolver.id, agentId); nextOfferId++; @@ -2951,7 +2953,7 @@ describe("IBosonOrchestrationHandler", function () { expect(group.isValid()).is.true; // Create a group - await groupHandler.connect(operator).createGroup(group, condition); + await groupHandler.connect(assistant).createGroup(group, condition); // after another offer is added offer.id = nextOfferId.toString(); // not necessary as input parameter @@ -2969,7 +2971,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated and GroupUpdated events", async function () { // Create an offer, add it to the group, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId); // OfferCreated event @@ -2984,7 +2986,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3004,7 +3006,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create an offer, add it to the group await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId); // Get the offer as a struct @@ -3057,7 +3059,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, add it to the group, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId); // OfferCreated event @@ -3072,7 +3074,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3095,7 +3097,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, add it to the group, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId); // OfferCreated event @@ -3110,7 +3112,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3142,7 +3144,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, add it to the group, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3155,7 +3157,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3169,7 +3171,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, add it to the group, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3182,7 +3184,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3193,7 +3195,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, add it to the group, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3206,7 +3208,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3214,7 +3216,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3227,7 +3229,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -3245,7 +3247,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in boson token await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3258,7 +3260,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3270,7 +3272,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ).to.emit(orchestrationHandler, "OfferCreated"); }); @@ -3296,7 +3298,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated and GroupUpdated events", async function () { // Create an offer, add it to the group, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId); // OfferCreated event @@ -3311,7 +3313,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3347,7 +3349,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated, a GroupUpdated and a RangeReserved events", async function () { // Create a preminted offer, add it to the group, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAddToGroup( offer, offerDates, @@ -3370,13 +3372,13 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // RangeReserved event (on protocol contract) await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -3398,7 +3400,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create a preminted offer, add it to the group await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAddToGroup( offer, offerDates, @@ -3472,7 +3474,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -3484,7 +3486,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a offer expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -3497,7 +3499,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAddToGroup( offer, offerDates, @@ -3517,7 +3519,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a group expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -3529,7 +3531,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer and add it to the group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, invalidGroupId, agentId) ).to.revertedWith(RevertReasons.NO_SUCH_GROUP); @@ -3539,7 +3541,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer and add it to the group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, invalidGroupId, agentId) ).to.revertedWith(RevertReasons.NO_SUCH_GROUP); }); @@ -3550,7 +3552,7 @@ describe("IBosonOrchestrationHandler", function () { orchestrationHandler .connect(rando) .createOfferAddToGroup(offer, offerDates, offerDurations, disputeResolver.id, nextGroupId, agentId) - ).to.revertedWith(RevertReasons.NOT_OPERATOR); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); }); }); @@ -3582,16 +3584,16 @@ describe("IBosonOrchestrationHandler", function () { twinStruct = twin.toStruct(); // create a seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler }); it("should emit an OfferCreated, a TwinCreated and a BundleCreated events", async function () { // Create an offer, a twin and a bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId); // OfferCreated event @@ -3606,7 +3608,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3636,7 +3638,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create an offer, a twin and a bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId); // Get the offer as a struct @@ -3693,7 +3695,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId); // OfferCreated event @@ -3708,7 +3710,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3746,7 +3748,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId); // OfferCreated event @@ -3761,7 +3763,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -3807,7 +3809,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3820,7 +3822,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3834,7 +3836,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3847,7 +3849,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3860,7 +3862,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3873,7 +3875,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3881,7 +3883,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3894,7 +3896,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -3912,7 +3914,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in boson token await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ) .to.emit(orchestrationHandler, "OfferCreated") @@ -3925,7 +3927,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -3937,7 +3939,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer, a twin and a bundle, testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.emit(orchestrationHandler, "OfferCreated"); }); @@ -3963,7 +3965,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated, a TwinCreated and a BundleCreated events", async function () { // Create an offer, a twin and a bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId); // OfferCreated event @@ -3978,7 +3980,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -4024,7 +4026,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated, a TwinCreated, a BundleCreated and a RangeReserved events", async function () { // Create a preminted offer, a twin and a bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAndTwinWithBundle( offer, offerDates, @@ -4047,13 +4049,13 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // RangeReserved event (on protocol contract) await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -4085,7 +4087,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create a preminted offer, a twin and a bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAndTwinWithBundle( offer, offerDates, @@ -4162,7 +4164,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -4174,7 +4176,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -4186,7 +4188,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a bundle, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -4198,7 +4200,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a twin, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -4211,7 +4213,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferAndTwinWithBundle( offer, offerDates, @@ -4226,14 +4228,14 @@ describe("IBosonOrchestrationHandler", function () { it("should revert if protocol is not approved to transfer the ERC20 token", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 0); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 0); // approving the twin handler //ERC20 token address twin.tokenAddress = bosonToken.address; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.NO_TRANSFER_APPROVED); }); @@ -4244,7 +4246,7 @@ describe("IBosonOrchestrationHandler", function () { await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.NO_TRANSFER_APPROVED); }); @@ -4255,7 +4257,7 @@ describe("IBosonOrchestrationHandler", function () { await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.revertedWith(RevertReasons.NO_TRANSFER_APPROVED); }); @@ -4266,7 +4268,7 @@ describe("IBosonOrchestrationHandler", function () { await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.be.revertedWith(RevertReasons.UNSUPPORTED_TOKEN); }); @@ -4276,7 +4278,7 @@ describe("IBosonOrchestrationHandler", function () { await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.be.revertedWith(RevertReasons.UNSUPPORTED_TOKEN); }); @@ -4286,7 +4288,7 @@ describe("IBosonOrchestrationHandler", function () { await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferAndTwinWithBundle(offer, offerDates, offerDurations, disputeResolver.id, twin, agentId) ).to.be.revertedWith(RevertReasons.UNSUPPORTED_TOKEN); }); @@ -4337,16 +4339,16 @@ describe("IBosonOrchestrationHandler", function () { twinStruct = twin.toStruct(); // create a seller - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler }); it("should emit an OfferCreated, a GroupCreated, a TwinCreated and a BundleCreated events", async function () { // Create an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4369,7 +4371,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -4409,7 +4411,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create an offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4493,7 +4495,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4516,7 +4518,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -4564,7 +4566,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4587,7 +4589,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -4643,7 +4645,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4664,7 +4666,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -4678,7 +4680,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4699,7 +4701,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -4712,7 +4714,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4733,7 +4735,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -4741,7 +4743,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in native currency await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4762,7 +4764,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // create another offer, now with bosonToken as exchange token @@ -4780,7 +4782,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer in boson token await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4801,7 +4803,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); }); @@ -4813,7 +4815,7 @@ describe("IBosonOrchestrationHandler", function () { // Create an offer with condition, twin and bundle testing for the events await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4848,7 +4850,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated, a GroupCreated, a TwinCreated and a BundleCreated events", async function () { // Create an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4871,7 +4873,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -4927,7 +4929,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit an OfferCreated, a GroupCreated, a TwinCreated, a BundleCreated and a RangeReserved events", async function () { // Create a preminted offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -4951,13 +4953,13 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // RangeReserved event (on protocol contract) await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -4999,7 +5001,7 @@ describe("IBosonOrchestrationHandler", function () { it("should update state", async function () { // Create a preminted offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5096,7 +5098,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5116,7 +5118,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5136,7 +5138,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5156,7 +5158,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a bundle, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5176,7 +5178,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a twin, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5197,7 +5199,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createPremintedOfferWithConditionAndTwinAndBundle( offer, offerDates, @@ -5237,7 +5239,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, and a GroupCreated event", async function () { // Create a seller and an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5255,7 +5257,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated and OfferCreated events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -5268,7 +5270,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -5296,13 +5298,13 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Create a seller and an offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5380,7 +5382,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -5395,7 +5397,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5426,7 +5428,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -5443,7 +5445,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5474,7 +5476,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -5491,7 +5493,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller and an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5512,7 +5514,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -5526,7 +5528,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -5570,7 +5572,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, and a GroupCreated event", async function () { // Create a seller and an offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5591,7 +5593,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -5605,7 +5607,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -5637,7 +5639,7 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a GroupCreated and a RangeReserved event", async function () { // Create a seller and a preminted offer with condition, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithCondition( seller, offer, @@ -5656,7 +5658,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated and OfferCreated RangeReserved events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -5669,12 +5671,12 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -5703,13 +5705,13 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Create a seller and an offer with condition await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithCondition( seller, offer, @@ -5791,7 +5793,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -5812,7 +5814,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5834,7 +5836,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5856,7 +5858,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5878,7 +5880,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithCondition( seller, offer, @@ -5901,7 +5903,7 @@ describe("IBosonOrchestrationHandler", function () { const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithCondition( seller, offer, @@ -5949,11 +5951,11 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a TwinCreated and a BundleCreated event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition and a twin with bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -5971,7 +5973,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated and OfferCreated events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -5984,7 +5986,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -5998,7 +6000,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventTwinCreated.twinId.toString(), twin.id, "Twin Id is incorrect"); assert.equal(eventTwinCreated.sellerId.toString(), twin.sellerId, "Seller Id is incorrect"); - assert.equal(eventTwinCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventTwinCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(twinInstance.toString(), twin.toString(), "Twin struct is incorrect"); // BundleCreated event @@ -6009,7 +6011,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventBundleCreated.bundleId.toString(), bundle.id, "Bundle Id is incorrect"); assert.equal(eventBundleCreated.sellerId.toString(), bundle.sellerId, "Seller Id is incorrect"); - assert.equal(eventBundleCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventBundleCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(bundleInstance.toString(), bundle.toString(), "Bundle struct is incorrect"); // Voucher clone contract @@ -6024,16 +6026,16 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition and a twin with bundle, testing for the events await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6114,7 +6116,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -6128,11 +6130,11 @@ describe("IBosonOrchestrationHandler", function () { expect(voucherInitValues.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition and a twin with bundle, testing for the events await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6163,7 +6165,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -6179,11 +6181,11 @@ describe("IBosonOrchestrationHandler", function () { expect(voucherInitValues.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition and a twin with bundle, testing for the events await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6214,7 +6216,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -6226,7 +6228,7 @@ describe("IBosonOrchestrationHandler", function () { it("should ignore any provided ids and assign the next available", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler const sellerId = seller.id; seller.id = "333"; @@ -6235,7 +6237,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller, an offer with condition and a twin with bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6256,7 +6258,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -6270,7 +6272,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -6328,11 +6330,11 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a TwinCreated and a BundleCreated event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition and a twin with bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6353,7 +6355,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -6367,7 +6369,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -6381,7 +6383,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventTwinCreated.twinId.toString(), twin.id, "Twin Id is incorrect"); assert.equal(eventTwinCreated.sellerId.toString(), twin.sellerId, "Seller Id is incorrect"); - assert.equal(eventTwinCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventTwinCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(twinInstance.toString(), twin.toString(), "Twin struct is incorrect"); // BundleCreated event @@ -6392,7 +6394,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventBundleCreated.bundleId.toString(), bundle.id, "Bundle Id is incorrect"); assert.equal(eventBundleCreated.sellerId.toString(), bundle.sellerId, "Seller Id is incorrect"); - assert.equal(eventBundleCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventBundleCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(bundleInstance.toString(), bundle.toString(), "Bundle struct is incorrect"); }); }); @@ -6410,11 +6412,11 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a TwinCreated, a BundleCreated and RangeReserved event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, a preminted offer with condition and a twin with bundle, testing for the events const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferAndTwinWithBundle( seller, offer, @@ -6433,7 +6435,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated, OfferCreated and RangeReserved events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -6446,12 +6448,12 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -6464,7 +6466,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventTwinCreated.twinId.toString(), twin.id, "Twin Id is incorrect"); assert.equal(eventTwinCreated.sellerId.toString(), twin.sellerId, "Seller Id is incorrect"); - assert.equal(eventTwinCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventTwinCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(twinInstance.toString(), twin.toString(), "Twin struct is incorrect"); // BundleCreated event @@ -6475,7 +6477,7 @@ describe("IBosonOrchestrationHandler", function () { assert.equal(eventBundleCreated.bundleId.toString(), bundle.id, "Bundle Id is incorrect"); assert.equal(eventBundleCreated.sellerId.toString(), bundle.sellerId, "Seller Id is incorrect"); - assert.equal(eventBundleCreated.executedBy.toString(), operator.address, "Executed by is incorrect"); + assert.equal(eventBundleCreated.executedBy.toString(), assistant.address, "Executed by is incorrect"); assert.equal(bundleInstance.toString(), bundle.toString(), "Bundle struct is incorrect"); // Voucher clone contract @@ -6492,16 +6494,16 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, a preminted offer with condition and a twin with bundle, testing for the events await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferAndTwinWithBundle( seller, offer, @@ -6586,7 +6588,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -6607,7 +6609,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6629,7 +6631,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6651,7 +6653,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6673,7 +6675,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a bundle, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6695,7 +6697,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a twin expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferAndTwinWithBundle( seller, offer, @@ -6715,13 +6717,13 @@ describe("IBosonOrchestrationHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Approve twin transfer - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Attempt to create a twin expecting revert const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferAndTwinWithBundle( seller, offer, @@ -6784,11 +6786,11 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a GroupCreated, a TwinCreated and a BundleCreated event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -6807,7 +6809,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated and OfferCreated events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -6820,7 +6822,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -6868,16 +6870,16 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -6978,7 +6980,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -6992,11 +6994,11 @@ describe("IBosonOrchestrationHandler", function () { expect(voucherInitValues.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7028,7 +7030,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -7044,11 +7046,11 @@ describe("IBosonOrchestrationHandler", function () { expect(voucherInitValues.isValid()).is.true; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, an offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7080,7 +7082,7 @@ describe("IBosonOrchestrationHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -7092,7 +7094,7 @@ describe("IBosonOrchestrationHandler", function () { it("should ignore any provided ids and assign the next available", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler const sellerId = seller.id; seller.id = "333"; @@ -7101,7 +7103,7 @@ describe("IBosonOrchestrationHandler", function () { // Create a seller, an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7123,7 +7125,7 @@ describe("IBosonOrchestrationHandler", function () { sellerStruct, calculateContractAddress(orchestrationHandler.address, "1"), emptyAuthTokenStruct, - operator.address + assistant.address ); await expect(tx) @@ -7137,7 +7139,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -7206,13 +7208,13 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a GroupCreated, a TwinCreated and a BundleCreated event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); // Create a seller, an offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7229,7 +7231,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated and OfferCreated events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -7242,7 +7244,7 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); // Events with structs that contain arrays must be tested differently @@ -7290,7 +7292,7 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); }); @@ -7307,11 +7309,11 @@ describe("IBosonOrchestrationHandler", function () { it("should emit a SellerCreated, an OfferCreated, a GroupCreated, a TwinCreated, a BundleCreated and a RangeReserved event", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, a preminted offer with condition, twin and bundle const tx = await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7331,7 +7333,7 @@ describe("IBosonOrchestrationHandler", function () { // SellerCreated, OfferCreated and RangeReserved events await expect(tx) .to.emit(orchestrationHandler, "SellerCreated") - .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, operator.address); + .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, assistant.address); await expect(tx) .to.emit(orchestrationHandler, "OfferCreated") @@ -7344,12 +7346,12 @@ describe("IBosonOrchestrationHandler", function () { disputeResolutionTermsStruct, offerFeesStruct, agentId, - operator.address + assistant.address ); await expect(tx) .to.emit(orchestrationHandler, "RangeReserved") - .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, operator.address); + .withArgs(nextOfferId, offer.sellerId, firstTokenId, lastTokenId, assistant.address); // Events with structs that contain arrays must be tested differently const txReceipt = await tx.wait(); @@ -7398,16 +7400,16 @@ describe("IBosonOrchestrationHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); // approving the twin handler + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // approving the twin handler // Create a seller, a preminted offer with condition, twin and bundle await orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7512,7 +7514,7 @@ describe("IBosonOrchestrationHandler", function () { expectedCloneAddress = calculateContractAddress(orchestrationHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -7533,7 +7535,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to orchestrate expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7556,7 +7558,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a seller, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7579,7 +7581,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create an offer, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7602,7 +7604,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7625,7 +7627,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a twin expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7648,7 +7650,7 @@ describe("IBosonOrchestrationHandler", function () { // Attempt to create a group, expecting revert await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndOfferWithConditionAndTwinAndBundle( seller, offer, @@ -7669,13 +7671,13 @@ describe("IBosonOrchestrationHandler", function () { await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); // Approve twin transfer - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Attempt to create a group, expecting revert const reservedRangeLength = offer.quantityAvailable; await expect( orchestrationHandler - .connect(operator) + .connect(assistant) .createSellerAndPremintedOfferWithConditionAndTwinAndBundle( seller, offer, diff --git a/test/protocol/SellerHandlerTest.js b/test/protocol/SellerHandlerTest.js index 9e9a32dde..e49bd6151 100644 --- a/test/protocol/SellerHandlerTest.js +++ b/test/protocol/SellerHandlerTest.js @@ -24,7 +24,7 @@ describe("SellerHandler", function () { let deployer, pauser, rando, - operator, + assistant, admin, clerk, treasury, @@ -79,7 +79,7 @@ describe("SellerHandler", function () { ] = await ethers.getSigners(); // make all account the same - authTokenOwner = operator = clerk = admin; + authTokenOwner = assistant = clerk = admin; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -188,7 +188,7 @@ describe("SellerHandler", function () { context("📋 Seller Methods", async function () { beforeEach(async function () { // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // How that seller looks as a returned struct @@ -243,7 +243,7 @@ describe("SellerHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should emit a SellerCreated event when auth token is not empty", async function () { @@ -273,7 +273,7 @@ describe("SellerHandler", function () { await expect(tx) .to.emit(bosonVoucher, "OwnershipTransferred") - .withArgs(ethers.constants.AddressZero, operator.address); + .withArgs(ethers.constants.AddressZero, assistant.address); }); it("should update state when authToken is empty", async function () { @@ -300,7 +300,7 @@ describe("SellerHandler", function () { // Voucher clone contract bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -332,7 +332,7 @@ describe("SellerHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -366,7 +366,7 @@ describe("SellerHandler", function () { // Get Royalty Information for Exchange id i.e. Voucher token id let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = ethers.constants.AddressZero; //expect zero address when exchange id does not exist @@ -402,7 +402,7 @@ describe("SellerHandler", function () { // Voucher clone contract bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", expectedCloneAddress); - expect(await bosonVoucher.owner()).to.equal(operator.address, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(assistant.address, "Wrong voucher clone owner"); bosonVoucher = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); expect(await bosonVoucher.contractURI()).to.equal(contractURI, "Wrong contract URI"); @@ -428,8 +428,8 @@ describe("SellerHandler", function () { expect(exists).to.be.true; }); - it("should be possible to use the same address for operator, admin, clerk, and treasury", async function () { - seller.operator = other1.address; + it("should be possible to use the same address for assistant, admin, clerk, and treasury", async function () { + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; seller.treasury = other1.address; @@ -450,7 +450,7 @@ describe("SellerHandler", function () { .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, admin.address); seller.id = accountId.next().value; - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; @@ -494,10 +494,10 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, authTokenOwner.address); - seller.operator = other1.address; + seller.assistant = other1.address; seller.clerk = other1.address; - // Update operator and clerk addresses so we can create a seller with the same auth token id but different type + // Update assistant and clerk addresses so we can create a seller with the same auth token id but different type const tx = await accountHandler.connect(authTokenOwner).updateSeller(seller, authToken); pendingSellerUpdate = seller.clone(); @@ -519,15 +519,15 @@ describe("SellerHandler", function () { sellerStruct = seller.toStruct(); // Nothing pending left - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdate.clerk = ethers.constants.AddressZero; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); - // Operator and clerk addresses owner must approve the update + // Assistant and clerk addresses owner must approve the update await expect( await accountHandler .connect(other1) - .optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator, SellerUpdateFields.Clerk]) + .optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant, SellerUpdateFields.Clerk]) ) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -540,7 +540,7 @@ describe("SellerHandler", function () { ); seller.id = accountId.next().value; - seller.operator = authTokenOwner.address; + seller.assistant = authTokenOwner.address; seller.clerk = authTokenOwner.address; //Create struct again with new addresses @@ -574,7 +574,7 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, sellerStruct, expectedCloneAddress, authTokenStruct, authTokenOwner.address); - seller.operator = other1.address; + seller.assistant = other1.address; seller.clerk = other1.address; pendingSellerUpdate = seller.clone(); @@ -589,7 +589,7 @@ describe("SellerHandler", function () { pendingAuthToken.tokenType = 0; pendingAuthTokenStruct = pendingAuthToken.toStruct(); - // Update operator and clerk addresses so we can create a seller with the same auth token id but different type + // Update assistant and clerk addresses so we can create a seller with the same auth token id but different type const tx = await accountHandler.connect(authTokenOwner).updateSeller(seller, authToken); await expect(tx) @@ -597,17 +597,17 @@ describe("SellerHandler", function () { .withArgs(seller.id, pendingSellerUpdateStruct, pendingAuthTokenStruct, authTokenOwner.address); // Nothing pending left - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdate.clerk = ethers.constants.AddressZero; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); sellerStruct = seller.toStruct(); - // Operator and clerk addresses owner must approve the update + // Assistant and clerk addresses owner must approve the update await expect( accountHandler .connect(other1) - .optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator, SellerUpdateFields.Clerk]) + .optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant, SellerUpdateFields.Clerk]) ) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -621,7 +621,7 @@ describe("SellerHandler", function () { authTokenOwner = rando; seller.id = accountId.next().value; - seller.operator = authTokenOwner.address; + seller.assistant = authTokenOwner.address; seller.clerk = authTokenOwner.address; //Create struct again with new addresses @@ -667,23 +667,23 @@ describe("SellerHandler", function () { // Create a seller await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - // Update seller operator - seller.operator = other1.address; + // Update seller assistant + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); // Approve the update - await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); seller.admin = other1.address; seller.clerk = other1.address; - // Attempt to Create a seller with non-unique operator, expecting revert + // Attempt to Create a seller with non-unique assistant, expecting revert await expect( accountHandler.connect(other1).createSeller(seller, emptyAuthToken, voucherInitValues) ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); seller.admin = admin.address; - seller.operator = operator.address; + seller.assistant = assistant.address; seller.clerk = clerk.address; // Attempt to Create a seller with non-unique admin, expecting revert @@ -699,7 +699,7 @@ describe("SellerHandler", function () { await accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk]); seller.admin = other2.address; - seller.operator = other2.address; + seller.assistant = other2.address; // Attempt to Create a seller with non-unique clerk, expecting revert await expect( @@ -711,17 +711,17 @@ describe("SellerHandler", function () { // Create a seller await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - // Update seller operator - seller.operator = other1.address; + // Update seller assistant + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); // Approve the update - await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); seller.admin = other1.address; seller.clerk = other1.address; - // Attempt to Create a seller with non-unique operator, expecting revert + // Attempt to Create a seller with non-unique assistant, expecting revert await expect( accountHandler.connect(other1).createSeller(seller, emptyAuthToken, voucherInitValues) ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); @@ -729,19 +729,19 @@ describe("SellerHandler", function () { // Update seller clerk seller.clerk = other2.address; seller.admin = admin.address; - seller.operator = operator.address; + seller.assistant = assistant.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); // Approve the update await accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk]); - // Admin and operator are the same address + // Admin and assistant are the same address await accountHandler - .connect(operator) - .optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator, SellerUpdateFields.Admin]); + .connect(assistant) + .optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant, SellerUpdateFields.Admin]); seller.admin = other2.address; - seller.operator = other2.address; + seller.assistant = other2.address; // Attempt to Create a seller with non-unique clerk, expecting revert await expect( @@ -751,18 +751,18 @@ describe("SellerHandler", function () { // Update seller admin seller.clerk = clerk.address; seller.admin = other3.address; - seller.operator = operator.address; + seller.assistant = assistant.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); // Approve the update await accountHandler.connect(other3).optInToSellerUpdate(seller.id, [SellerUpdateFields.Admin]); - // Operator and clerk are the same + // Assistant and clerk are the same await accountHandler .connect(clerk) - .optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk, SellerUpdateFields.Operator]); + .optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk, SellerUpdateFields.Assistant]); - seller.operator = other3.address; + seller.assistant = other3.address; seller.clerk = other3.address; // Attempt to Create a seller with non-unique admin, expecting revert @@ -774,7 +774,7 @@ describe("SellerHandler", function () { it("addresses are not unique to this seller Id when address used for same role and the seller is created with auth token", async function () { // Create a seller seller.admin = rando.address; - seller.operator = rando.address; + seller.assistant = rando.address; seller.clerk = rando.address; seller2 = mockSeller( @@ -786,26 +786,26 @@ describe("SellerHandler", function () { await accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues); - // Update the seller, so operator matches authTokenOwner - seller.operator = authTokenOwner.address; + // Update the seller, so assistant matches authTokenOwner + seller.assistant = authTokenOwner.address; await accountHandler.connect(rando).updateSeller(seller, emptyAuthToken); // Approve the update - await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); - // Attempt to Create a seller with non-unique operator, expecting revert + // Attempt to Create a seller with non-unique assistant, expecting revert await expect( accountHandler.connect(authTokenOwner).createSeller(seller2, authToken, voucherInitValues) ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); - // Update seller operator and clerk, so clerk matches authTokenOwner + // Update seller assistant and clerk, so clerk matches authTokenOwner seller.clerk = authTokenOwner.address; - seller.operator = rando.address; + seller.assistant = rando.address; await accountHandler.connect(rando).updateSeller(seller, emptyAuthToken); // Approve the update await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk]); - await accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); // Attempt to Create a seller with non-unique clerk, expecting revert await expect( @@ -821,7 +821,7 @@ describe("SellerHandler", function () { await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller.id, [SellerUpdateFields.Admin]); await accountHandler.connect(rando).optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk]); - // Attempt to Create a seller with non-unique operator, expecting revert + // Attempt to Create a seller with non-unique assistant, expecting revert await expect( accountHandler.connect(authTokenOwner).createSeller(seller2, authToken, voucherInitValues) ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); @@ -846,7 +846,7 @@ describe("SellerHandler", function () { it("authToken is not unique to this seller", async function () { // Set admin == zero address because seller will be created with auth token seller.admin = ethers.constants.AddressZero; - seller.operator = authTokenOwner.address; + seller.assistant = authTokenOwner.address; seller.clerk = authTokenOwner.address; // Create a seller @@ -871,7 +871,7 @@ describe("SellerHandler", function () { }); it("Caller is not the supplied admin", async function () { - seller.operator = rando.address; + seller.assistant = rando.address; seller.clerk = rando.address; // Attempt to Create a seller with admin not the same to caller address @@ -883,7 +883,7 @@ describe("SellerHandler", function () { it("Caller does not own supplied auth token", async function () { // Set admin == zero address because seller will be created with auth token seller.admin = ethers.constants.AddressZero; - seller.operator = rando.address; + seller.assistant = rando.address; seller.clerk = rando.address; // Attempt to Create a seller without owning the auth token @@ -892,28 +892,28 @@ describe("SellerHandler", function () { ).to.revertedWith(RevertReasons.NOT_ADMIN); }); - it("Caller is not the supplied operator", async function () { + it("Caller is not the supplied assistant", async function () { seller.admin = rando.address; seller.clerk = rando.address; - // Attempt to Create a seller with operator not the same to caller address + // Attempt to Create a seller with assistant not the same to caller address await expect( accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues) - ).to.revertedWith(RevertReasons.NOT_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT_AND_CLERK); }); it("Caller is not the supplied clerk", async function () { seller.admin = rando.address; - seller.operator = rando.address; + seller.assistant = rando.address; // Attempt to Create a seller with clerk not the same to caller address await expect( accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues) - ).to.revertedWith(RevertReasons.NOT_OPERATOR_AND_CLERK); + ).to.revertedWith(RevertReasons.NOT_ASSISTANT_AND_CLERK); }); it("addresses are the zero address", async function () { - seller.operator = ethers.constants.AddressZero; + seller.assistant = ethers.constants.AddressZero; seller.treasury = ethers.constants.AddressZero; seller.clerk = ethers.constants.AddressZero; @@ -923,10 +923,10 @@ describe("SellerHandler", function () { ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); - it("Operator address is zero address", async function () { - seller.operator = ethers.constants.AddressZero; + it("Assistant address is zero address", async function () { + seller.assistant = ethers.constants.AddressZero; - // Attempt to Create a seller with operator == zero address + // Attempt to Create a seller with assistant == zero address await expect( accountHandler.connect(authTokenOwner).createSeller(seller, emptyAuthToken, voucherInitValues) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); @@ -1058,10 +1058,10 @@ describe("SellerHandler", function () { accountId.next(true); }); - it("should return the correct seller when searching on operator address", async function () { + it("should return the correct seller when searching on assistant address", async function () { [exists, sellerStruct, authTokenStruct] = await accountHandler .connect(rando) - .getSellerByAddress(operator.address); + .getSellerByAddress(assistant.address); expect(exists).is.true; @@ -1444,7 +1444,7 @@ describe("SellerHandler", function () { pendingSellerUpdate.treasury = ethers.constants.AddressZero; pendingSellerUpdate.clerk = ethers.constants.AddressZero; pendingSellerUpdate.admin = ethers.constants.AddressZero; - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdate.active = false; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); @@ -1458,7 +1458,7 @@ describe("SellerHandler", function () { sellerStruct = seller.toStruct(); seller.admin = ethers.constants.AddressZero; - seller.operator = other1.address; + seller.assistant = other1.address; seller.clerk = other3.address; expect(seller.isValid()).is.true; @@ -1492,22 +1492,22 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, pendingSellerUpdateStruct, pendingAuthTokenStruct, admin.address); - // Update seller operator - tx = await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + // Update seller assistant + tx = await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); // Voucher clone contract const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - await expect(tx).to.emit(bosonVoucher, "OwnershipTransferred").withArgs(operator.address, other1.address); + await expect(tx).to.emit(bosonVoucher, "OwnershipTransferred").withArgs(assistant.address, other1.address); - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); seller.clerk = clerk.address; seller.admin = admin.address; sellerStruct = seller.toStruct(); - // Check operator update + // Check assistant update await expect(tx) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -1572,12 +1572,12 @@ describe("SellerHandler", function () { const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - // Since operator stayed the same, clone contract ownership should not be transferred + // Since assistant stayed the same, clone contract ownership should not be transferred await expect(tx).to.not.emit(bosonVoucher, "OwnershipTransferred"); }); it("should update state of all fields except Id and active flag", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = ethers.constants.AddressZero; seller.clerk = other3.address; seller.treasury = other4.address; @@ -1591,8 +1591,8 @@ describe("SellerHandler", function () { // Update a seller await accountHandler.connect(admin).updateSeller(seller, authToken); - // Approve operator update - await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + // Approve assistant update + await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); // Approve clerk update await accountHandler.connect(other3).optInToSellerUpdate(seller.id, [SellerUpdateFields.Clerk]); @@ -1618,7 +1618,7 @@ describe("SellerHandler", function () { } //Check that old addresses are no longer mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getSellerByAddress(operator.address); + [exists] = await accountHandler.connect(rando).getSellerByAddress(assistant.address); expect(exists).to.be.false; [exists] = await accountHandler.connect(rando).getSellerByAddress(admin.address); @@ -1628,7 +1628,7 @@ describe("SellerHandler", function () { expect(exists).to.be.false; //Check that new addresses are mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getSellerByAddress(seller.operator); + [exists] = await accountHandler.connect(rando).getSellerByAddress(seller.assistant); expect(exists).to.be.true; //Zero address -- should return false @@ -1642,21 +1642,21 @@ describe("SellerHandler", function () { const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - expect(await bosonVoucher.owner()).to.equal(seller.operator, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(seller.assistant, "Wrong voucher clone owner"); }); it("should update state from auth token to empty auth token", async function () { seller2 = mockSeller(other1.address, ethers.constants.AddressZero, other1.address, other1.address); expect(seller2.isValid()).is.true; - // msg.sender must be equal to seller's operator and clerk + // msg.sender must be equal to seller's assistant and clerk await mockAuthERC721Contract.connect(authTokenOwner).transferFrom(authTokenOwner.address, other1.address, 8400); authTokenOwner = other1; // Create a seller with auth token await accountHandler.connect(authTokenOwner).createSeller(seller2, authToken, voucherInitValues); - seller2.operator = other5.address; + seller2.assistant = other5.address; seller2.admin = other6.address; seller2.clerk = other7.address; seller2.treasury = other8.address; @@ -1670,8 +1670,8 @@ describe("SellerHandler", function () { // Update seller await accountHandler.connect(authTokenOwner).updateSeller(seller2, emptyAuthToken); - // Approve operator update - await accountHandler.connect(other5).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Operator]); + // Approve assistant update + await accountHandler.connect(other5).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Assistant]); // Approve admin update await accountHandler.connect(other6).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Admin]); @@ -1707,7 +1707,7 @@ describe("SellerHandler", function () { expect(exists).to.be.false; //Check that new addresses are mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.operator); + [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.assistant); expect(exists).to.be.true; [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.admin); @@ -1720,21 +1720,21 @@ describe("SellerHandler", function () { const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - expect(await bosonVoucher.owner()).to.equal(seller.operator, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(seller.assistant, "Wrong voucher clone owner"); }); it("should update state from auth token to new auth token", async function () { seller2 = mockSeller(other1.address, ethers.constants.AddressZero, other1.address, other1.address); expect(seller2.isValid()).is.true; - // msg.sender must be equal to seller's operator and clerk + // msg.sender must be equal to seller's assistant and clerk await mockAuthERC721Contract.connect(authTokenOwner).transferFrom(authTokenOwner.address, other1.address, 8400); authTokenOwner = other1; // Create a seller with auth token await accountHandler.connect(authTokenOwner).createSeller(seller2, authToken, voucherInitValues); - seller2.operator = other5.address; + seller2.assistant = other5.address; seller2.admin = ethers.constants.AddressZero; seller2.clerk = other7.address; seller2.treasury = other8.address; @@ -1753,7 +1753,7 @@ describe("SellerHandler", function () { // Update seller await accountHandler.connect(authTokenOwner).updateSeller(seller2, authToken2); - await accountHandler.connect(other5).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(other5).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Assistant]); await accountHandler.connect(other7).optInToSellerUpdate(seller2.id, [SellerUpdateFields.Clerk]); await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller2.id, [SellerUpdateFields.AuthToken]); @@ -1785,7 +1785,7 @@ describe("SellerHandler", function () { expect(exists).to.be.false; //Check that new addresses are mapped. We don't map the treasury address. - [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.operator); + [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.assistant); expect(exists).to.be.true; [exists] = await accountHandler.connect(rando).getSellerByAddress(seller2.admin); @@ -1798,7 +1798,7 @@ describe("SellerHandler", function () { const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - expect(await bosonVoucher.owner()).to.equal(seller.operator, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(seller.assistant, "Wrong voucher clone owner"); }); it("should update state correctly if values are the same", async function () { @@ -1826,11 +1826,11 @@ describe("SellerHandler", function () { const bosonVoucherCloneAddress = calculateContractAddress(exchangeHandler.address, "1"); bosonVoucher = await ethers.getContractAt("OwnableUpgradeable", bosonVoucherCloneAddress); - expect(await bosonVoucher.owner()).to.equal(seller.operator, "Wrong voucher clone owner"); + expect(await bosonVoucher.owner()).to.equal(seller.assistant, "Wrong voucher clone owner"); }); it("should update only one address", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; sellerStruct = seller.toStruct(); @@ -1838,7 +1838,7 @@ describe("SellerHandler", function () { await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); // Approve update - await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); // Get the seller as a struct [, sellerStruct, authTokenStruct] = await accountHandler.connect(rando).getSeller(seller.id); @@ -1865,7 +1865,7 @@ describe("SellerHandler", function () { contractURI = `https://ipfs.io/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ`; - // msg.sender must be equal to seller's operator and clerk + // msg.sender must be equal to seller's assistant and clerk authTokenOwner = other1; await mockAuthERC721Contract.connect(authTokenOwner).mint(8500, 1); @@ -1876,7 +1876,7 @@ describe("SellerHandler", function () { await accountHandler.connect(authTokenOwner).createSeller(seller2, authToken2, voucherInitValues); //Update seller2 - seller2.operator = rando.address; + seller2.assistant = rando.address; seller2.admin = ethers.constants.AddressZero; seller2.clerk = rando.address; seller2.treasury = rando.address; @@ -1896,10 +1896,10 @@ describe("SellerHandler", function () { // Approve update await accountHandler .connect(rando) - .optInToSellerUpdate(seller2.id, [SellerUpdateFields.Operator, SellerUpdateFields.Clerk]); + .optInToSellerUpdate(seller2.id, [SellerUpdateFields.Assistant, SellerUpdateFields.Clerk]); // Approve auth token update - authTokenOwner = operator; + authTokenOwner = assistant; await accountHandler.connect(authTokenOwner).optInToSellerUpdate(seller2.id, [SellerUpdateFields.AuthToken]); // Check first seller hasn't changed @@ -2023,8 +2023,8 @@ describe("SellerHandler", function () { authTokenOwner.address ); - seller.operator = other3.address; - pendingSellerUpdate.operator = other3.address; + seller.assistant = other3.address; + pendingSellerUpdate.assistant = other3.address; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); // Transfer ownership of auth token because owner must be different from old admin @@ -2040,11 +2040,11 @@ describe("SellerHandler", function () { .withArgs(seller.id, pendingSellerUpdateStruct, emptyAuthTokenStruct, authTokenOwner.address); sellerStruct = seller.toStruct(); - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); // Approve update - await expect(accountHandler.connect(other3).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(other3).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -2066,7 +2066,7 @@ describe("SellerHandler", function () { seller2.id = accountId.next().value; seller2.treasury = other2.address; - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; @@ -2091,7 +2091,7 @@ describe("SellerHandler", function () { await accountHandler .connect(other1) .optInToSellerUpdate(seller.id, [ - SellerUpdateFields.Operator, + SellerUpdateFields.Assistant, SellerUpdateFields.Admin, SellerUpdateFields.Clerk, ]); @@ -2114,19 +2114,19 @@ describe("SellerHandler", function () { expect(returnedSeller2.treasury).to.equal(treasury.address); }); - it("should be possible to use the same address for operator, admin, clerk, and treasury", async function () { + it("should be possible to use the same address for assistant, admin, clerk, and treasury", async function () { // Only treasury doesn't need owner approval and will be updated immediately seller.treasury = other1.address; sellerStruct = seller.toStruct(); - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; // Update seller const tx = await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); - // Pending seller is filled with only admin, clerk, and operator addresses + // Pending seller is filled with only admin, clerk, and assistant addresses pendingSellerUpdate = seller.clone(); pendingSellerUpdate.id = "0"; pendingSellerUpdate.active = false; @@ -2154,7 +2154,7 @@ describe("SellerHandler", function () { // Nothing pending left pendingSellerUpdate.admin = ethers.constants.AddressZero; pendingSellerUpdate.clerk = ethers.constants.AddressZero; - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); // Approve update @@ -2162,7 +2162,7 @@ describe("SellerHandler", function () { await accountHandler .connect(other1) .optInToSellerUpdate(seller.id, [ - SellerUpdateFields.Operator, + SellerUpdateFields.Assistant, SellerUpdateFields.Admin, SellerUpdateFields.Clerk, ]) @@ -2215,7 +2215,7 @@ describe("SellerHandler", function () { }); it("addresses are the zero address", async function () { - seller.operator = ethers.constants.AddressZero; + seller.assistant = ethers.constants.AddressZero; seller.treasury = ethers.constants.AddressZero; seller.clerk = ethers.constants.AddressZero; @@ -2225,8 +2225,8 @@ describe("SellerHandler", function () { ); }); - it("Operator is the zero address", async function () { - seller.operator = ethers.constants.AddressZero; + it("Assistant is the zero address", async function () { + seller.assistant = ethers.constants.AddressZero; // Attempt to update a seller, expecting revert await expect(accountHandler.connect(authTokenOwner).updateSeller(seller, emptyAuthToken)).to.revertedWith( @@ -2254,7 +2254,7 @@ describe("SellerHandler", function () { it("addresses are not unique to this seller Id when addresses used for same role", async function () { seller.id = accountId.next().value; - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; seller.treasury = other1.address; @@ -2267,16 +2267,16 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, other1.address); - //Set operator address value to be same as first seller created in Seller Methods beforeEach - seller.operator = operator.address; //already being used by seller 1 + //Set assistant address value to be same as first seller created in Seller Methods beforeEach + seller.assistant = assistant.address; //already being used by seller 1 - // Attempt to update seller 2 with non-unique operator, expecting revert + // Attempt to update seller 2 with non-unique assistant, expecting revert await expect(accountHandler.connect(other1).updateSeller(seller, emptyAuthToken)).to.revertedWith( RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE ); seller.admin = admin.address; //already being used by seller 1 - seller.operator = other1.address; + seller.assistant = other1.address; // Attempt to update a seller with non-unique admin, expecting revert await expect(accountHandler.connect(other1).updateSeller(seller, emptyAuthToken)).to.revertedWith( @@ -2294,7 +2294,7 @@ describe("SellerHandler", function () { it("addresses are not unique to this seller Id when address used for different role", async function () { seller.id = accountId.next().value; - seller.operator = other1.address; + seller.assistant = other1.address; seller.admin = other1.address; seller.clerk = other1.address; seller.treasury = other1.address; @@ -2307,17 +2307,17 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerCreated") .withArgs(seller.id, sellerStruct, expectedCloneAddress, emptyAuthTokenStruct, other1.address); - //Set seller 2's admin address to seller 1's operator address - seller.admin = operator.address; + //Set seller 2's admin address to seller 1's assistant address + seller.admin = assistant.address; - // Attempt to update seller 2 with non-unique operator, expecting revert + // Attempt to update seller 2 with non-unique assistant, expecting revert await expect(accountHandler.connect(other1).updateSeller(seller, emptyAuthToken)).to.revertedWith( RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE ); - //Set seller 2's operator address to seller 1's clerk address + //Set seller 2's assistant address to seller 1's clerk address seller.admin = other1.address; - seller.operator = clerk.address; + seller.assistant = clerk.address; // Attempt to update a seller with non-unique admin, expecting revert await expect(accountHandler.connect(other1).updateSeller(seller, emptyAuthToken)).to.revertedWith( @@ -2325,7 +2325,7 @@ describe("SellerHandler", function () { ); //Set seller 2's clerk address to seller 1's admin address - seller.operator = other1.address; + seller.assistant = other1.address; seller.clerk = admin.address; // Attempt to Update a seller with non-unique clerk, expecting revert @@ -2436,20 +2436,20 @@ describe("SellerHandler", function () { pendingSellerUpdate.treasury = ethers.constants.AddressZero; pendingSellerUpdate.clerk = ethers.constants.AddressZero; pendingSellerUpdate.admin = ethers.constants.AddressZero; - pendingSellerUpdate.operator = ethers.constants.AddressZero; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; pendingSellerUpdate.active = false; pendingSellerUpdateStruct = pendingSellerUpdate.toStruct(); await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); }); - it("New operator should opt-in to update seller", async function () { - seller.operator = other1.address; + it("New assistant should opt-in to update seller", async function () { + seller.assistant = other1.address; sellerStruct = seller.toStruct(); await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); - await expect(accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -2497,10 +2497,10 @@ describe("SellerHandler", function () { ); }); - it("Should update admin, clerk and operator in a single call ", async function () { + it("Should update admin, clerk and assistant in a single call ", async function () { seller.clerk = other1.address; seller.admin = other1.address; - seller.operator = other1.address; + seller.assistant = other1.address; sellerStruct = seller.toStruct(); await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); @@ -2511,7 +2511,7 @@ describe("SellerHandler", function () { .optInToSellerUpdate(seller.id, [ SellerUpdateFields.Clerk, SellerUpdateFields.Admin, - SellerUpdateFields.Operator, + SellerUpdateFields.Assistant, ]) ) .to.emit(accountHandler, "SellerUpdateApplied") @@ -2525,9 +2525,9 @@ describe("SellerHandler", function () { ); }); - it("Should update operator, clerk and auth token in a single call when addresses are the same ", async function () { + it("Should update assistant, clerk and auth token in a single call when addresses are the same ", async function () { seller.admin = ethers.constants.AddressZero; - seller.operator = authTokenOwner.address; + seller.assistant = authTokenOwner.address; seller.clerk = authTokenOwner.address; sellerStruct = seller.toStruct(); @@ -2537,7 +2537,7 @@ describe("SellerHandler", function () { accountHandler .connect(authTokenOwner) .optInToSellerUpdate(seller.id, [ - SellerUpdateFields.Operator, + SellerUpdateFields.Assistant, SellerUpdateFields.Clerk, SellerUpdateFields.AuthToken, ]) @@ -2574,7 +2574,7 @@ describe("SellerHandler", function () { }); it("If updateSeller is called twice with no optIn in between, pendingSellerUpdate is populated with the data from second call", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; pendingSellerUpdate = seller.clone(); pendingSellerUpdate.id = "0"; @@ -2588,20 +2588,20 @@ describe("SellerHandler", function () { .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, pendingSellerUpdateStruct, emptyAuthTokenStruct, admin.address); - seller.operator = other2.address; + seller.assistant = other2.address; sellerStruct = seller.toStruct(); const pendingSellerUpdate2 = pendingSellerUpdate.clone(); - pendingSellerUpdate2.operator = ethers.constants.AddressZero; + pendingSellerUpdate2.assistant = ethers.constants.AddressZero; const pendingSellerUpdate2Struct = pendingSellerUpdate2.toStruct(); await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); await expect( - accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]) + accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); - await expect(accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator])) + await expect(accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant])) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( seller.id, @@ -2649,7 +2649,7 @@ describe("SellerHandler", function () { }); it("Should not emit 'SellerUpdateApplied' event if caller doesn't specify any field", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); await expect(accountHandler.connect(other1).optInToSellerUpdate(seller.id, [])).to.not.emit( @@ -2659,7 +2659,7 @@ describe("SellerHandler", function () { }); it("Should not emit 'SellerUpdateApplied'event if there is no pending update for specified field", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); await expect( @@ -2671,7 +2671,7 @@ describe("SellerHandler", function () { it("There are no pending updates", async function () { seller.clerk = other1.address; seller.admin = other1.address; - seller.operator = other1.address; + seller.assistant = other1.address; sellerStruct = seller.toStruct(); // No pending update auth token @@ -2683,7 +2683,7 @@ describe("SellerHandler", function () { .optInToSellerUpdate(seller.id, [ SellerUpdateFields.Clerk, SellerUpdateFields.Admin, - SellerUpdateFields.Operator, + SellerUpdateFields.Assistant, ]) ) .to.emit(accountHandler, "SellerUpdateApplied") @@ -2752,14 +2752,14 @@ describe("SellerHandler", function () { ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); }); - it("Caller is not the new operator", async function () { - seller.operator = other1.address; + it("Caller is not the new assistant", async function () { + seller.assistant = other1.address; sellerStruct = seller.toStruct(); await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); await expect( - accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]) + accountHandler.connect(other2).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.UNAUTHORIZED_CALLER_UPDATE); }); @@ -2775,7 +2775,7 @@ describe("SellerHandler", function () { }); it("The sellers region of protocol is paused", async function () { - seller.operator = other1.address; + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); @@ -2821,20 +2821,20 @@ describe("SellerHandler", function () { ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); }); - it("Operator is not unique to this seller", async function () { - // Update seller operator - seller.operator = other1.address; + it("Assistant is not unique to this seller", async function () { + // Update seller assistant + seller.assistant = other1.address; await accountHandler.connect(admin).updateSeller(seller, emptyAuthToken); - // Create seller with same operator + // Create seller with same assistant seller2 = mockSeller(other1.address, other1.address, other1.address, other1.address); expect(seller2.isValid()).is.true; await accountHandler.connect(other1).createSeller(seller2, emptyAuthToken, voucherInitValues); - // Attemp to approve the update with non-unique operator, expecting revert + // Attemp to approve the update with non-unique assistant, expecting revert await expect( - accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]) + accountHandler.connect(other1).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]) ).to.revertedWith(RevertReasons.SELLER_ADDRESS_MUST_BE_UNIQUE); }); }); diff --git a/test/protocol/TwinHandlerTest.js b/test/protocol/TwinHandlerTest.js index 99337cab5..1bc819694 100644 --- a/test/protocol/TwinHandlerTest.js +++ b/test/protocol/TwinHandlerTest.js @@ -24,7 +24,7 @@ const { getStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); describe("IBosonTwinHandler", function () { // Common vars let InterfaceIds; - let deployer, pauser, rando, operator, admin, clerk, treasury, protocolTreasury; + let deployer, pauser, rando, assistant, admin, clerk, treasury, protocolTreasury; let seller; let erc165, protocolDiamond, @@ -62,7 +62,7 @@ describe("IBosonTwinHandler", function () { [deployer, pauser, admin, treasury, rando, protocolTreasury] = await ethers.getSigners(); // make all account the same - operator = clerk = admin; + assistant = clerk = admin; // Deploy the Protocol Diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -178,7 +178,7 @@ describe("IBosonTwinHandler", function () { id = "1"; // argument sent to contract for createSeller will be ignored // Create a valid seller, then set fields in tests directly - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); expect(seller.isValid()).is.true; // VoucherInitValues @@ -212,10 +212,10 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = bosonToken.address; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create a twin, testing for the event - const tx = await twinHandler.connect(operator).createTwin(twin); + const tx = await twinHandler.connect(assistant).createTwin(twin); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, twinHandler, "TwinCreated"); @@ -233,10 +233,10 @@ describe("IBosonTwinHandler", function () { twin.id = "444"; // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create a twin, testing for the event - const tx = await twinHandler.connect(operator).createTwin(twin); + const tx = await twinHandler.connect(assistant).createTwin(twin); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, twinHandler, "TwinCreated"); @@ -271,11 +271,11 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = foreign721.address; // Mint a token and approve twinHandler contract to transfer it - await foreign721.connect(operator).mint(twin.tokenId, "1"); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, "1"); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create a twin, testing for the event - const tx = await twinHandler.connect(operator).createTwin(twin); + const tx = await twinHandler.connect(assistant).createTwin(twin); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, twinHandler, "TwinCreated"); @@ -293,11 +293,11 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = foreign1155.address; // Mint a token and approve twinHandler contract to transfer it - await foreign1155.connect(operator).mint(twin.tokenId, twin.amount); - await foreign1155.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign1155.connect(assistant).mint(twin.tokenId, twin.amount); + await foreign1155.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create a twin, testing for the event - const tx = await twinHandler.connect(operator).createTwin(twin); + const tx = await twinHandler.connect(assistant).createTwin(twin); const txReceipt = await tx.wait(); const event = getEvent(txReceipt, twinHandler, "TwinCreated"); @@ -319,16 +319,16 @@ describe("IBosonTwinHandler", function () { twin.tokenType = TokenType.NonFungibleToken; // Mint a token and approve twinHandler contract to transfer it - await foreign721.connect(operator).mint(twin.tokenId, twin.supplyAvailable); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, twin.supplyAvailable); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create first twin with ids range: ["5"..."14"] - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Create an twin with ids range: ["18" ... "23"] twin.tokenId = "18"; twin.supplyAvailable = "6"; - await expect(twinHandler.connect(operator).createTwin(twin)).not.to.be.reverted; + await expect(twinHandler.connect(assistant).createTwin(twin)).not.to.be.reverted; }); it("It is possible to add an ERC721 with unlimited supply if token is not used yet", async function () { @@ -346,14 +346,14 @@ describe("IBosonTwinHandler", function () { twin2.tokenAddress = foreign721_2.address; // Approve twinHandler contract to transfer it - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); - await foreign721_2.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); + await foreign721_2.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create a twin with limited supply - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Create another twin with unlimited supply - await expect(twinHandler.connect(operator).createTwin(twin2)).not.to.be.reverted; + await expect(twinHandler.connect(assistant).createTwin(twin2)).not.to.be.reverted; }); it("It is possible to add ERC721 even if another ERC721 with unlimited supply exists", async function () { @@ -371,14 +371,14 @@ describe("IBosonTwinHandler", function () { twin2.tokenAddress = foreign721_2.address; // Approve twinHandler contract to transfer it - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); - await foreign721_2.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); + await foreign721_2.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create a twin with unlimited supply - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Create another twin with limited supply - await expect(twinHandler.connect(operator).createTwin(twin2)).not.to.be.reverted; + await expect(twinHandler.connect(assistant).createTwin(twin2)).not.to.be.reverted; }); it("Should ignore twin id set by seller and use nextAccountId on twins entity", async function () { @@ -386,9 +386,9 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = bosonToken.address; // Approve twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); let [exists, storedTwin] = await twinHandler.getTwin("666"); expect(exists).to.be.false; @@ -409,10 +409,10 @@ describe("IBosonTwinHandler", function () { twin.supplyAvailable = "10"; // Approving the twinHandler contract to transfer seller's tokens - await foreign721.connect(operator).mint(twin.tokenId, twin.supplyAvailable); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, twin.supplyAvailable); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); - const tx = await twinHandler.connect(operator).createTwin(twin); + const tx = await twinHandler.connect(assistant).createTwin(twin); const txReceipt = await tx.wait(); const [id] = getEvent(txReceipt, twinHandler, "TwinCreated"); @@ -454,19 +454,19 @@ describe("IBosonTwinHandler", function () { .pause([PausableRegion.Offers, PausableRegion.Twins, PausableRegion.Bundles]); // Attempt to Remove a twin, expecting revert - await expect(twinHandler.connect(operator).removeTwin(twin.id)).to.revertedWith(RevertReasons.REGION_PAUSED); + await expect(twinHandler.connect(assistant).removeTwin(twin.id)).to.revertedWith(RevertReasons.REGION_PAUSED); }); - it("Caller not operator of any seller", async function () { + it("Caller not assistant of any seller", async function () { // Attempt to Create a twin, expecting revert - await expect(twinHandler.connect(rando).createTwin(twin)).to.revertedWith(RevertReasons.NOT_OPERATOR); + await expect(twinHandler.connect(rando).createTwin(twin)).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("protocol is not approved to transfer the ERC20 token", async function () { //ERC20 token address twin.tokenAddress = bosonToken.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.revertedWith( RevertReasons.NO_TRANSFER_APPROVED ); }); @@ -475,7 +475,7 @@ describe("IBosonTwinHandler", function () { //ERC721 token address twin.tokenAddress = foreign721.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.revertedWith( RevertReasons.NO_TRANSFER_APPROVED ); }); @@ -484,15 +484,15 @@ describe("IBosonTwinHandler", function () { //ERC1155 token address twin.tokenAddress = foreign1155.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.revertedWith( RevertReasons.NO_TRANSFER_APPROVED ); }); it("supplyAvailable is zero", async function () { // Mint a token and approve twinHandler contract to transfer it - await foreign721.connect(operator).mint(twin.tokenId, "1"); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, "1"); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); twin.supplyAvailable = "0"; twin.amount = "0"; @@ -500,57 +500,65 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = foreign721.address; twin.tokenType = TokenType.NonFungibleToken; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_SUPPLY_AVAILABLE ); }); it("Amount is greater than supply available and token type is FungibleToken", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); twin.supplyAvailable = "10"; twin.amount = "20"; twin.tokenAddress = bosonToken.address; twin.tokenType = TokenType.FungibleToken; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith(RevertReasons.INVALID_AMOUNT); + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( + RevertReasons.INVALID_AMOUNT + ); }); it("Amount is greater than supply available and token type is MultiToken", async function () { // Mint a token and approve twinHandler contract to transfer it - await foreign1155.connect(operator).mint(twin.tokenId, "1"); - await foreign1155.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign1155.connect(assistant).mint(twin.tokenId, "1"); + await foreign1155.connect(assistant).setApprovalForAll(twinHandler.address, true); twin.supplyAvailable = "10"; twin.amount = "20"; twin.tokenAddress = foreign1155.address; twin.tokenType = TokenType.MultiToken; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith(RevertReasons.INVALID_AMOUNT); + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( + RevertReasons.INVALID_AMOUNT + ); }); it("Amount is zero and token type is FungibleToken", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); twin.amount = "0"; twin.tokenAddress = bosonToken.address; twin.tokenType = TokenType.FungibleToken; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith(RevertReasons.INVALID_AMOUNT); + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( + RevertReasons.INVALID_AMOUNT + ); }); it("Amount is zero and token type is MultiToken", async function () { // Mint a token and approve twinHandler contract to transfer it - await foreign1155.connect(operator).mint(twin.tokenId, "1"); - await foreign1155.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign1155.connect(assistant).mint(twin.tokenId, "1"); + await foreign1155.connect(assistant).setApprovalForAll(twinHandler.address, true); twin.amount = "0"; twin.tokenAddress = foreign1155.address; twin.tokenType = TokenType.MultiToken; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith(RevertReasons.INVALID_AMOUNT); + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( + RevertReasons.INVALID_AMOUNT + ); }); it("Amount is zero and token type is NonFungibleToken", async function () { @@ -560,10 +568,10 @@ describe("IBosonTwinHandler", function () { twin.tokenId = "1"; // Mint a token and approve twinHandler contract to transfer it - await foreign721.connect(operator).mint(twin.tokenId, "1"); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, "1"); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_PROPERTY ); }); @@ -576,35 +584,35 @@ describe("IBosonTwinHandler", function () { twin.tokenType = TokenType.NonFungibleToken; // Mint a token and approve twinHandler contract to transfer it - await foreign721.connect(operator).mint(twin.tokenId, twin.supplyAvailable); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).mint(twin.tokenId, twin.supplyAvailable); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create first twin with ids range: ["5"..."14"] - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Create another twin with exact same range - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); // Create an twin with ids range: ["0" ... "5"] twin.tokenId = "0"; twin.supplyAvailable = "6"; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); // Create an twin with ids range: ["14" ... "18"] twin.tokenId = "14"; twin.supplyAvailable = "5"; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); // Create an twin with ids range: ["6" ... "9"] twin.tokenId = "6"; twin.supplyAvailable = "4"; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); }); @@ -615,13 +623,13 @@ describe("IBosonTwinHandler", function () { twin.tokenAddress = foreign721.address; twin.amount = "0"; - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create twin with unlimited supply - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Create new twin with same token address - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); }); @@ -633,10 +641,10 @@ describe("IBosonTwinHandler", function () { twin.amount = "0"; twin.tokenId = ethers.constants.MaxUint256.sub(twin.supplyAvailable).add(1).toString(); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create new twin with same token address - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); }); @@ -648,10 +656,10 @@ describe("IBosonTwinHandler", function () { twin.amount = "0"; twin.tokenId = ethers.constants.MaxUint256.add(1).div(2).add(1).toString(); - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create new twin with same token address - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); }); @@ -660,7 +668,7 @@ describe("IBosonTwinHandler", function () { it("Token address is a zero address", async function () { twin.tokenAddress = ethers.constants.AddressZero; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.UNSUPPORTED_TOKEN ); }); @@ -668,7 +676,7 @@ describe("IBosonTwinHandler", function () { it("Token address is a contract address that does not support the isApprovedForAll", async function () { twin.tokenAddress = twinHandler.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.UNSUPPORTED_TOKEN ); }); @@ -676,27 +684,27 @@ describe("IBosonTwinHandler", function () { it("Token address is a contract that reverts from a fallback method", async function () { twin.tokenAddress = fallbackError.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.UNSUPPORTED_TOKEN ); }); it("Token address is a contract that doesn't implement IERC721 interface when selected token type is NonFungible", async function () { - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); twin.tokenType = TokenType.NonFungibleToken; twin.tokenAddress = bosonToken.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TOKEN_ADDRESS ); }); it("Token address is a contract that doesn't implement IERC1155 interface when selected token type is MultiToken", async function () { - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); twin.tokenType = TokenType.MultiToken; twin.tokenAddress = bosonToken.address; - await expect(twinHandler.connect(operator).createTwin(twin)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin)).to.be.revertedWith( RevertReasons.INVALID_TOKEN_ADDRESS ); }); @@ -707,10 +715,10 @@ describe("IBosonTwinHandler", function () { context("👉 removeTwin()", async function () { beforeEach(async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create a twin - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); }); it("should emit a TwinDeleted event", async function () { @@ -719,9 +727,9 @@ describe("IBosonTwinHandler", function () { expect(success).to.be.true; // Remove the twin, testing for the event. - await expect(twinHandler.connect(operator).removeTwin(twin.id)) + await expect(twinHandler.connect(assistant).removeTwin(twin.id)) .to.emit(twinHandler, "TwinDeleted") - .withArgs(twin.id, twin.sellerId, operator.address); + .withArgs(twin.id, twin.sellerId, assistant.address); // Expect twin to be not found. [success] = await twinHandler.connect(rando).getTwin(twin.id); @@ -734,16 +742,16 @@ describe("IBosonTwinHandler", function () { twin.amount = "0"; const expectedNewTwinId = "2"; - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); // Create a twin with range: [0,1499] - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // Remove twin - await twinHandler.connect(operator).removeTwin(expectedNewTwinId); + await twinHandler.connect(assistant).removeTwin(expectedNewTwinId); // Twin range must be available and createTwin transaction with same range should succeed - await expect(twinHandler.connect(operator).createTwin(twin)).to.not.reverted; + await expect(twinHandler.connect(assistant).createTwin(twin)).to.not.reverted; }); it("If there is NonFungible twin with multiple ranges, the correct one is removed", async function () { @@ -765,25 +773,25 @@ describe("IBosonTwinHandler", function () { twin3.tokenId = "5000"; twin3.id = "3"; - await foreign721.connect(operator).setApprovalForAll(twinHandler.address, true); + await foreign721.connect(assistant).setApprovalForAll(twinHandler.address, true); - await twinHandler.connect(operator).createTwin(twin1); - await twinHandler.connect(operator).createTwin(twin2); - await twinHandler.connect(operator).createTwin(twin3); + await twinHandler.connect(assistant).createTwin(twin1); + await twinHandler.connect(assistant).createTwin(twin2); + await twinHandler.connect(assistant).createTwin(twin3); // Remove twin - await twinHandler.connect(operator).removeTwin(twin2.id); + await twinHandler.connect(assistant).removeTwin(twin2.id); // We don't have getters, so we implicitly test that correct change was done // Twin2 should still exists, therefore it should not be possible to create it again - await expect(twinHandler.connect(operator).createTwin(twin1)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin1)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); - await expect(twinHandler.connect(operator).createTwin(twin3)).to.be.revertedWith( + await expect(twinHandler.connect(assistant).createTwin(twin3)).to.be.revertedWith( RevertReasons.INVALID_TWIN_TOKEN_RANGE ); // Twin2 was removed, therefore it should be possible to be added again - await expect(twinHandler.connect(operator).createTwin(twin2)).to.not.reverted; + await expect(twinHandler.connect(assistant).createTwin(twin2)).to.not.reverted; }); context("💔 Revert Reasons", async function () { @@ -794,21 +802,21 @@ describe("IBosonTwinHandler", function () { .pause([PausableRegion.Offers, PausableRegion.Twins, PausableRegion.Bundles]); // Attempt to Remove a twin, expecting revert - await expect(twinHandler.connect(operator).removeTwin(twin.id)).to.revertedWith(RevertReasons.REGION_PAUSED); + await expect(twinHandler.connect(assistant).removeTwin(twin.id)).to.revertedWith(RevertReasons.REGION_PAUSED); }); it("Twin does not exist", async function () { let nonExistantTwinId = "999"; // Attempt to Remove a twin, expecting revert - await expect(twinHandler.connect(operator).removeTwin(nonExistantTwinId)).to.revertedWith( + await expect(twinHandler.connect(assistant).removeTwin(nonExistantTwinId)).to.revertedWith( RevertReasons.NO_SUCH_TWIN ); }); it("Caller is not the seller", async function () { // Attempt to Remove a twin, expecting revert - await expect(twinHandler.connect(rando).removeTwin(twin.id)).to.revertedWith(RevertReasons.NOT_OPERATOR); + await expect(twinHandler.connect(rando).removeTwin(twin.id)).to.revertedWith(RevertReasons.NOT_ASSISTANT); }); it("Bundle for twin exists", async function () { @@ -827,7 +835,7 @@ describe("IBosonTwinHandler", function () { // Create the offer await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Bundle: Required constructor params @@ -837,10 +845,10 @@ describe("IBosonTwinHandler", function () { // Create a new bundle bundle = new Bundle(bundleId, seller.id, offerIds, twinIds); - await bundleHandler.connect(operator).createBundle(bundle); + await bundleHandler.connect(assistant).createBundle(bundle); // Attempt to Remove a twin, expecting revert - await expect(twinHandler.connect(operator).removeTwin(twin.id)).to.revertedWith( + await expect(twinHandler.connect(assistant).removeTwin(twin.id)).to.revertedWith( RevertReasons.BUNDLE_FOR_TWIN_EXISTS ); }); @@ -850,10 +858,10 @@ describe("IBosonTwinHandler", function () { context("👉 getTwin()", async function () { beforeEach(async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create a twin - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // id of the current twin and increment nextTwinId id = nextTwinId++; @@ -926,10 +934,10 @@ describe("IBosonTwinHandler", function () { it("should be incremented after a twin is created", async function () { // Approving the twinHandler contract to transfer seller's tokens - await bosonToken.connect(operator).approve(twinHandler.address, 1); + await bosonToken.connect(assistant).approve(twinHandler.address, 1); // Create another twin - await twinHandler.connect(operator).createTwin(twin); + await twinHandler.connect(assistant).createTwin(twin); // What we expect the next twin id to be expected = ++nextTwinId; diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index 69b2c09f2..1cd24463c 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -44,11 +44,11 @@ describe("IBosonVoucher", function () { buyer, rando, rando2, - operator, + assistant, admin, clerk, treasury, - operatorDR, + assistantDR, adminDR, clerkDR, treasuryDR, @@ -74,8 +74,8 @@ describe("IBosonVoucher", function () { await ethers.getSigners(); // make all account the same - operator = clerk = admin; - operatorDR = clerkDR = adminDR; + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; // Deploy diamond [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); @@ -169,7 +169,7 @@ describe("IBosonVoucher", function () { voucherInitValues = mockVoucherInitValues(); const bosonVoucherInit = await ethers.getContractAt("BosonVoucher", bosonVoucher.address); - await bosonVoucherInit.initializeVoucher(sellerId, operator.address, voucherInitValues); + await bosonVoucherInit.initializeVoucher(sellerId, assistant.address, voucherInitValues); }); // Interface support @@ -397,27 +397,27 @@ describe("IBosonVoucher", function () { it("Should emit Transfer events", async function () { // Premint tokens, test for event - const tx = await bosonVoucher.connect(operator).preMint(offerId, amount); + const tx = await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect an event for every mint for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") - .withArgs(ethers.constants.AddressZero, operator.address, i + Number(start)); + .withArgs(ethers.constants.AddressZero, assistant.address, i + Number(start)); } }); it("Should update state", async function () { - let sellerBalanceBefore = await bosonVoucher.balanceOf(operator.address); + let sellerBalanceBefore = await bosonVoucher.balanceOf(assistant.address); // Premint tokens - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); // Expect a correct owner for all preminted tokens for (let i = 0; i < Number(amount); i++) { let tokenId = i + Number(start); let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, operator.address, `Wrong token owner for token ${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 @@ -426,7 +426,7 @@ describe("IBosonVoucher", function () { ); // Seller's balance should be updated for the total mint amount - let sellerBalanceAfter = await bosonVoucher.balanceOf(operator.address); + let sellerBalanceAfter = await bosonVoucher.balanceOf(assistant.address); assert.equal(sellerBalanceAfter.toNumber(), sellerBalanceBefore.add(amount).toNumber(), "Balance mismatch"); // Get available premints from contract @@ -435,7 +435,7 @@ describe("IBosonVoucher", function () { }); it("MetaTx: forwarder can execute preMint on behalf of seller", async function () { - const nonce = Number(await forwarder.getNonce(operator.address)); + const nonce = Number(await forwarder.getNonce(assistant.address)); const types = { ForwardRequest: [ @@ -449,14 +449,14 @@ describe("IBosonVoucher", function () { const functionSignature = bosonVoucher.interface.encodeFunctionData("preMint", [offerId, amount]); const message = { - from: operator.address, + from: assistant.address, to: bosonVoucher.address, nonce: nonce, data: functionSignature, }; const { signature } = await prepareDataSignatureParameters( - operator, + assistant, types, "ForwardRequest", message, @@ -472,7 +472,7 @@ describe("IBosonVoucher", function () { for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") - .withArgs(ethers.constants.AddressZero, operator.address, i + Number(start)); + .withArgs(ethers.constants.AddressZero, assistant.address, i + Number(start)); } }); @@ -488,20 +488,20 @@ describe("IBosonVoucher", function () { offerId = 15; // Try to premint, it should fail - await expect(bosonVoucher.connect(operator).preMint(offerId, amount)).to.be.revertedWith( + 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(operator).preMint(offerId, amount); + 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(operator).preMint(offerId, amount)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.INVALID_AMOUNT_TO_MINT ); }); @@ -513,7 +513,7 @@ describe("IBosonVoucher", function () { amount = "101"; // Try to premint, it should fail - await expect(bosonVoucher.connect(operator).preMint(offerId, amount)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.TOO_MANY_TO_MINT ); }); @@ -523,7 +523,7 @@ describe("IBosonVoucher", function () { await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); // Try to premint, it should fail - await expect(bosonVoucher.connect(operator).preMint(offerId, amount)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_EXPIRED_OR_VOIDED ); }); @@ -541,7 +541,7 @@ describe("IBosonVoucher", function () { ); // Try to premint, it should fail - await expect(bosonVoucher.connect(operator).preMint(offerId, amount)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.be.revertedWith( RevertReasons.OFFER_EXPIRED_OR_VOIDED ); }); @@ -573,7 +573,7 @@ describe("IBosonVoucher", function () { // amount to mint amount = "5"; - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); // "void" the offer offer.voided = true; @@ -584,7 +584,7 @@ describe("IBosonVoucher", function () { it("Should emit Transfer events", async function () { // Burn tokens, test for event - const tx = await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + const 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), "Wrong number of events emitted"); @@ -593,15 +593,15 @@ describe("IBosonVoucher", function () { for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, ethers.constants.AddressZero, i + Number(start)); + .withArgs(assistant.address, ethers.constants.AddressZero, i + Number(start)); } }); it("Should update state", async function () { - let sellerBalanceBefore = await bosonVoucher.balanceOf(operator.address); + let sellerBalanceBefore = await bosonVoucher.balanceOf(assistant.address); // Burn tokens - await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // All burned tokens should not have an owner for (let i = 0; i < Number(amount); i++) { @@ -610,7 +610,7 @@ describe("IBosonVoucher", function () { } // Seller's balance should be decreased for the total burn amount - let sellerBalanceAfter = await bosonVoucher.balanceOf(operator.address); + let sellerBalanceAfter = await bosonVoucher.balanceOf(assistant.address); assert.equal(sellerBalanceAfter.toNumber(), sellerBalanceBefore.sub(amount).toNumber(), "Balance mismatch"); // Get available premints from contract @@ -625,7 +625,7 @@ describe("IBosonVoucher", function () { 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(operator).burnPremintedVouchers(offerId); + let 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), "Wrong number of events emitted"); @@ -636,7 +636,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(operator).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( RevertReasons.NOTHING_TO_BURN ); }); @@ -649,7 +649,7 @@ describe("IBosonVoucher", function () { .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); // Mint another 10 vouchers, so that there are 15 in total - await bosonVoucher.connect(operator).preMint(offerId, 10); + await bosonVoucher.connect(assistant).preMint(offerId, 10); amount = `${Number(amount) + 10}`; // "void" the offer @@ -659,7 +659,7 @@ describe("IBosonVoucher", function () { .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); // Burn tokens, test for event - let tx = await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + 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"); @@ -670,7 +670,7 @@ describe("IBosonVoucher", function () { assert.equal(returnedRange.toString(), range.toString(), "Range mismatch"); // Second call should burn the difference - tx = await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // Number of events emitted should be equal to amount assert.equal( @@ -691,7 +691,7 @@ describe("IBosonVoucher", function () { } // Second call should revert since there's nothing to burn - await expect(bosonVoucher.connect(operator).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( RevertReasons.NOTHING_TO_BURN ); }); @@ -703,12 +703,12 @@ describe("IBosonVoucher", function () { await mockProtocol.mock.commitToPreMintedOffer.returns(); await Promise.all( commitedVouchers.map((tokenId) => - bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId) + bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId) ) ); // Burn tokens, test for event - let tx = await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + let tx = await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // Number of events emitted should be equal to amount of preminted vouchers decreased by length of commited vouchers // We test this to inderectly verify that no events were emitted for commited vouchers @@ -728,7 +728,7 @@ describe("IBosonVoucher", function () { // Check that Transfer event was emitted and owner does not exist anymore await expect(tx) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, ethers.constants.AddressZero, i + Number(start)); + .withArgs(assistant.address, ethers.constants.AddressZero, i + Number(start)); await expect(bosonVoucher.ownerOf(tokenId)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); } } @@ -749,7 +749,7 @@ describe("IBosonVoucher", function () { await setNextBlockTimestamp(ethers.BigNumber.from(offerDates.validUntil).add(1).toHexString()); // Burn tokens, test for event - const tx = await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + const 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), "Wrong number of events emitted"); @@ -758,7 +758,7 @@ describe("IBosonVoucher", function () { for (let i = 0; i < Number(amount); i++) { await expect(tx) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, ethers.constants.AddressZero, i + Number(start)); + .withArgs(assistant.address, ethers.constants.AddressZero, i + Number(start)); } }); @@ -774,7 +774,7 @@ describe("IBosonVoucher", function () { offerId = 15; // Try to burn, it should fail - await expect(bosonVoucher.connect(operator).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( RevertReasons.NO_RESERVED_RANGE_FOR_OFFER ); }); @@ -787,17 +787,17 @@ describe("IBosonVoucher", function () { .returns(true, offer, offerDates, offerDurations, disputeResolutionTerms, offerFees); // Try to burn, it should fail - await expect(bosonVoucher.connect(operator).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( RevertReasons.OFFER_STILL_VALID ); }); it("Nothing to burn", async function () { // Burn tokens - await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // Try to burn, it should fail - await expect(bosonVoucher.connect(operator).burnPremintedVouchers(offerId)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.be.revertedWith( RevertReasons.NOTHING_TO_BURN ); }); @@ -842,7 +842,7 @@ describe("IBosonVoucher", function () { it("Part of range is preminted", async function () { // Premint tokens - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); // Get available premints from contract let newAmount = Number(length) - Number(amount); @@ -850,7 +850,7 @@ describe("IBosonVoucher", function () { assert.equal(availablePremints.toNumber(), newAmount, "Available Premints mismatch"); // Premint again - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); newAmount -= Number(amount); availablePremints = await bosonVoucher.getAvailablePreMints(offerId); assert.equal(availablePremints.toNumber(), newAmount, "Available Premints mismatch"); @@ -861,7 +861,7 @@ describe("IBosonVoucher", function () { await configHandler.connect(deployer).setMaxPremintedVouchers(length); // Premint tokens - await bosonVoucher.connect(operator).preMint(offerId, length); + await bosonVoucher.connect(assistant).preMint(offerId, length); // Get available premints from contract let availablePremints = await bosonVoucher.getAvailablePreMints(offerId); @@ -934,7 +934,7 @@ describe("IBosonVoucher", function () { // amount to premint amount = "50"; range.minted = amount; - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); }); it("Get range object for offer with reserved range", async function () { @@ -1003,7 +1003,7 @@ describe("IBosonVoucher", function () { // amount to premint amount = 50; - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); }); it("Returns true owner if token exists - via issue voucher", async function () { @@ -1029,7 +1029,7 @@ describe("IBosonVoucher", function () { await mockProtocol.mock.commitToPreMintedOffer.returns(); // Transfer preminted token - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // Token owner should be the buyer let tokenOwner = await bosonVoucher.ownerOf(tokenId); @@ -1042,7 +1042,7 @@ describe("IBosonVoucher", function () { let endTokenId = startTokenId + Number(amount); for (let i = startTokenId; i < endTokenId; i++) { let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, operator.address, `Token owner mismatch ${i}`); + assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i}`); } }); @@ -1066,7 +1066,7 @@ describe("IBosonVoucher", function () { // amount to premint amount = length - i * 30; - await bosonVoucher.connect(operator).preMint(offerId, amount); + await bosonVoucher.connect(assistant).preMint(offerId, amount); ranges.push(new Range(start, length, amount, "0")); previousStartId = start; @@ -1086,7 +1086,7 @@ describe("IBosonVoucher", function () { } else if (i <= currentRangeMintEndId) { // tokenId in range and minted. Seller should be the owner let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, operator.address, `Token owner mismatch ${i}`); + assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i}`); } else if (i <= currentRangeEndId) { // tokenId still in range, but not minted yet await expect(bosonVoucher.connect(rando).ownerOf(i)).to.be.revertedWith(RevertReasons.ERC721_NON_EXISTENT); @@ -1117,14 +1117,14 @@ describe("IBosonVoucher", function () { await bosonVoucher.connect(protocol).reserveRange(nextOfferId, nextStartId, nextLength); // amount to premint - await bosonVoucher.connect(operator).preMint(nextOfferId, nextAmount); + await bosonVoucher.connect(assistant).preMint(nextOfferId, nextAmount); // First range - preminted tokens let startTokenId = Number(start); let endTokenId = startTokenId + Number(amount); for (let i = startTokenId; i < endTokenId; i++) { let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, operator.address, `Token owner mismatch ${i}`); + assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i}`); } // First range - not preminted tokens @@ -1139,7 +1139,7 @@ describe("IBosonVoucher", function () { endTokenId = startTokenId + Number(nextAmount); for (let i = startTokenId; i < endTokenId; i++) { let tokenOwner = await bosonVoucher.ownerOf(i); - assert.equal(tokenOwner, operator.address, `Token owner mismatch ${i}`); + assert.equal(tokenOwner, assistant.address, `Token owner mismatch ${i}`); } // First range - not preminted tokens @@ -1179,10 +1179,10 @@ describe("IBosonVoucher", function () { // Token owner should be the seller let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, operator.address, "Token owner mismatch"); + assert.equal(tokenOwner, assistant.address, "Token owner mismatch"); // Transfer preminted token - await bosonVoucher.connect(operator).transferFrom(operator.address, buyer.address, tokenId); + await bosonVoucher.connect(assistant).transferFrom(assistant.address, buyer.address, tokenId); // Token owner should be the buyer tokenOwner = await bosonVoucher.ownerOf(tokenId); @@ -1202,7 +1202,7 @@ describe("IBosonVoucher", function () { // Token owner should be the seller let tokenOwner = await bosonVoucher.ownerOf(tokenId); - assert.equal(tokenOwner, operator.address, "Token owner mismatch"); + assert.equal(tokenOwner, assistant.address, "Token owner mismatch"); // Void the offer offer.voided = true; @@ -1216,7 +1216,7 @@ describe("IBosonVoucher", function () { ); // Burn preminted voucher - await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // Token should have no owner await expect(bosonVoucher.connect(rando).ownerOf(tokenId)).to.be.revertedWith( @@ -1247,7 +1247,7 @@ describe("IBosonVoucher", function () { }; beforeEach(async function () { - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); // Prepare the AuthToken and VoucherInitValues emptyAuthToken = mockAuthToken(); @@ -1258,7 +1258,7 @@ describe("IBosonVoucher", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1286,7 +1286,7 @@ describe("IBosonVoucher", function () { // Create an offer const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer( offer.toStruct(), offerDates.toStruct(), @@ -1351,13 +1351,13 @@ describe("IBosonVoucher", function () { 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, operator.address, tokenId, ...additionalArgs); + await bosonVoucher.connect(buyer)[selector](buyer.address, assistant.address, tokenId, ...additionalArgs); const tx = await bosonVoucher - .connect(operator) - [selector](operator.address, rando.address, tokenId, ...additionalArgs); + .connect(assistant) + [selector](assistant.address, rando.address, tokenId, ...additionalArgs); - await expect(tx).to.emit(bosonVoucher, "Transfer").withArgs(operator.address, rando.address, tokenId); + await expect(tx).to.emit(bosonVoucher, "Transfer").withArgs(assistant.address, rando.address, tokenId); const randoBuyer = mockBuyer(); @@ -1369,7 +1369,7 @@ describe("IBosonVoucher", function () { context("💔 Revert Reasons", async function () { it("From does not own the voucher", async function () { await expect( - bosonVoucher.connect(rando)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(rando)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); }); @@ -1381,7 +1381,7 @@ describe("IBosonVoucher", function () { // Create preminted offer const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer( offer.toStruct(), offerDates.toStruct(), @@ -1389,7 +1389,7 @@ describe("IBosonVoucher", function () { disputeResolverId, agentId ); - await offerHandler.connect(operator).reserveRange(offer.id, offer.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable); // 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, { @@ -1407,23 +1407,25 @@ describe("IBosonVoucher", function () { bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); // amount to premint - await bosonVoucher.connect(operator).preMint(offerId, offer.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerId, offer.quantityAvailable); }); it("Should emit a Transfer event", async function () { await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, rando.address, tokenId); + .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, operator.address, "Seller is not the owner"); + assert.equal(tokenOwner, assistant.address, "Seller is not the owner"); - await bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs); + await bosonVoucher + .connect(assistant) + [selector](assistant.address, rando.address, tokenId, ...additionalArgs); // After transfer, rando should be the owner tokenOwner = await bosonVoucher.ownerOf(tokenId); @@ -1433,8 +1435,8 @@ describe("IBosonVoucher", function () { it("Should call commitToPreMintedOffer", async function () { const randoBuyer = mockBuyer(); const tx = await bosonVoucher - .connect(operator) - [selector](operator.address, rando.address, tokenId, ...additionalArgs); + .connect(assistant) + [selector](assistant.address, rando.address, tokenId, ...additionalArgs); // Get the block timestamp of the confirmed tx const blockNumber = tx.blockNumber; @@ -1457,35 +1459,35 @@ describe("IBosonVoucher", function () { it("Second transfer should behave as normal voucher transfer", async function () { // First transfer should call commitToPreMintedOffer, and not onVoucherTransferred let tx = await bosonVoucher - .connect(operator) - [selector](operator.address, rando.address, tokenId, ...additionalArgs); + .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, operator.address, tokenId, ...additionalArgs); + [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(operator) - [selector](operator.address, rando.address, tokenId, ...additionalArgs); + .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(operator).setApprovalForAll(rando2.address, true); + await bosonVoucher.connect(assistant).setApprovalForAll(rando2.address, true); await expect( - bosonVoucher.connect(rando2)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(rando2)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, rando.address, tokenId); + .withArgs(assistant.address, rando.address, tokenId); }); context("💔 Revert Reasons", async function () { @@ -1498,26 +1500,26 @@ describe("IBosonVoucher", function () { it("Cannot transfer preminted voucher twice", async function () { // Make first transfer await bosonVoucher - .connect(operator) - [selector](operator.address, buyer.address, tokenId, ...additionalArgs); + .connect(assistant) + [selector](assistant.address, buyer.address, tokenId, ...additionalArgs); // Second transfer should fail, since voucher has an owner await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); // It should also fail if transfer done with transferPremintedFrom await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.NOT_COMMITTABLE); }); it("Transfer preminted voucher, which was committed and burned already", async function () { await bosonVoucher - .connect(operator) - [selector](operator.address, buyer.address, tokenId, ...additionalArgs); + .connect(assistant) + [selector](assistant.address, buyer.address, tokenId, ...additionalArgs); // Redeem voucher, effectively burning it await setNextBlockTimestamp(ethers.BigNumber.from(voucherRedeemableFrom).toHexString()); @@ -1525,30 +1527,30 @@ describe("IBosonVoucher", function () { // Transfer should fail, since voucher has been burned await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + 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(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted vouchers - await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // None of reserved but not preminted tokens should have an owner await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + 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(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Transfer should fail, since protocol reverts await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); }); @@ -1558,7 +1560,7 @@ describe("IBosonVoucher", function () { // Transfer should fail, since protocol reverts await expect( - bosonVoucher.connect(operator)[selector](operator.address, rando.address, tokenId, ...additionalArgs) + bosonVoucher.connect(assistant)[selector](assistant.address, rando.address, tokenId, ...additionalArgs) ).to.be.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); }); }); @@ -1573,9 +1575,9 @@ describe("IBosonVoucher", function () { // Create preminted offer const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); - await offerHandler.connect(operator).reserveRange(offer.id, offer.quantityAvailable); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable); // 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, { @@ -1593,25 +1595,27 @@ describe("IBosonVoucher", function () { bosonVoucher = await ethers.getContractAt("BosonVoucher", voucherAddress); // amount to premint - await bosonVoucher.connect(operator).preMint(offerId, offer.quantityAvailable); + await bosonVoucher.connect(assistant).preMint(offerId, offer.quantityAvailable); }); it("Should emit a Transfer event", async function () { await expect( - bosonVoucher.connect(operator).transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + bosonVoucher + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, rando.address, tokenId); + .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, operator.address, "Seller is not the owner"); + assert.equal(tokenOwner, assistant.address, "Seller is not the owner"); await bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x"); + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x"); // After transfer, rando should be the owner tokenOwner = await bosonVoucher.ownerOf(tokenId); @@ -1621,8 +1625,8 @@ describe("IBosonVoucher", function () { it("Should call commitToPreMintedOffer", async function () { const randoBuyer = mockBuyer(); const tx = await bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x"); + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x"); // Get the block timestamp of the confirmed tx const blockNumber = tx.blockNumber; @@ -1645,28 +1649,28 @@ describe("IBosonVoucher", function () { it("Second transfer should behave as normal voucher transfer", async function () { // First transfer should call commitToPreMintedOffer, and not onVoucherTransferred let tx = await bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x"); + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x"); 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) - ["safeTransferFrom(address,address,uint256,bytes)"](rando.address, operator.address, tokenId, "0x"); + ["safeTransferFrom(address,address,uint256,bytes)"](rando.address, assistant.address, tokenId, "0x"); 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(operator).setApprovalForAll(rando2.address, true); + await bosonVoucher.connect(assistant).setApprovalForAll(rando2.address, true); await expect( - bosonVoucher.connect(rando2).transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + bosonVoucher.connect(rando2).transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ) .to.emit(bosonVoucher, "Transfer") - .withArgs(operator.address, rando.address, tokenId); + .withArgs(assistant.address, rando.address, tokenId); }); context("💔 Revert Reasons", async function () { @@ -1679,28 +1683,28 @@ describe("IBosonVoucher", function () { it("Cannot transfer preminted voucher twice", async function () { // Make first transfer await bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, buyer.address, offerId, tokenId, "0x"); + .connect(assistant) + .transferPremintedFrom(assistant.address, buyer.address, offerId, tokenId, "0x"); // Second transfer should fail, since voucher has an owner await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.NOT_COMMITTABLE); // It should also fail if transfer done with standard safeTransferFrom await expect( bosonVoucher - .connect(operator) - ["safeTransferFrom(address,address,uint256,bytes)"](operator.address, rando.address, tokenId, "0x") + .connect(assistant) + ["safeTransferFrom(address,address,uint256,bytes)"](assistant.address, rando.address, tokenId, "0x") ).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(operator) - .transferPremintedFrom(operator.address, buyer.address, offerId, tokenId, "0x"); + .connect(assistant) + .transferPremintedFrom(assistant.address, buyer.address, offerId, tokenId, "0x"); // Redeem voucher, effectively burning it await setNextBlockTimestamp(ethers.BigNumber.from(voucherRedeemableFrom).toHexString()); @@ -1709,35 +1713,35 @@ describe("IBosonVoucher", function () { // Transfer should fail, since voucher has been burned await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.NOT_COMMITTABLE); }); it("Transfer preminted voucher, which was not committed but burned already", async function () { // Void offer - await offerHandler.connect(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted vouchers - await bosonVoucher.connect(operator).burnPremintedVouchers(offerId); + await bosonVoucher.connect(assistant).burnPremintedVouchers(offerId); // None of reserved but not preminted tokens should have an owner await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.NOT_COMMITTABLE); }); it("Transfer preminted voucher, where offer was voided", async function () { // Void offer - await offerHandler.connect(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Transfer should fail, since protocol reverts await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); }); @@ -1748,8 +1752,8 @@ describe("IBosonVoucher", function () { // Transfer should fail, since protocol reverts await expect( bosonVoucher - .connect(operator) - .transferPremintedFrom(operator.address, rando.address, offerId, tokenId, "0x") + .connect(assistant) + .transferPremintedFrom(assistant.address, rando.address, offerId, tokenId, "0x") ).to.be.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); }); }); @@ -1801,7 +1805,7 @@ describe("IBosonVoucher", function () { let metadataUri; beforeEach(async function () { - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); // prepare the VoucherInitValues voucherInitValues = mockVoucherInitValues(); @@ -1817,7 +1821,7 @@ describe("IBosonVoucher", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1838,7 +1842,7 @@ describe("IBosonVoucher", function () { const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); await fundsHandler .connect(admin) @@ -1864,31 +1868,31 @@ describe("IBosonVoucher", function () { const ownable = await ethers.getContractAt("OwnableUpgradeable", bosonVoucher.address); await expect(bosonVoucher.connect(protocol).transferOwnership(rando.address)) .to.emit(ownable, "OwnershipTransferred") - .withArgs(operator.address, rando.address); + .withArgs(assistant.address, rando.address); }); it("should transfer ownership with success", async function () { - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + await bosonVoucher.connect(protocol).transferOwnership(assistant.address); const ownable = await ethers.getContractAt("OwnableUpgradeable", bosonVoucher.address); const owner = await ownable.owner(); - expect(owner).eq(operator.address, "Wrong 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(operator.address)).to.be.revertedWith( + 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 () { - // succesfully transfer to operator - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + // succesfully transfer to assistant + await bosonVoucher.connect(protocol).transferOwnership(assistant.address); // owner tries to transfer, it should fail - await expect(bosonVoucher.connect(operator).transferOwnership(rando.address)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).transferOwnership(rando.address)).to.be.revertedWith( RevertReasons.ACCESS_DENIED ); }); @@ -1904,20 +1908,20 @@ describe("IBosonVoucher", function () { context("setContractURI()", function () { beforeEach(async function () { - // give ownership to operator - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + // give ownership to assistant + await bosonVoucher.connect(protocol).transferOwnership(assistant.address); contractURI = "newContractURI"; }); it("should emit ContractURIChanged event", async function () { - await expect(bosonVoucher.connect(operator).setContractURI(contractURI)) + 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(operator).setContractURI(contractURI); + await bosonVoucher.connect(assistant).setContractURI(contractURI); const returnedContractURI = await bosonVoucher.contractURI(); @@ -1939,7 +1943,7 @@ describe("IBosonVoucher", function () { context("ERC2981 NFT Royalty fee", function () { beforeEach(async function () { - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); // prepare the VoucherInitValues voucherInitValues = mockVoucherInitValues(); @@ -1955,7 +1959,7 @@ describe("IBosonVoucher", function () { // Create a valid dispute resolver disputeResolver = mockDisputeResolver( - operatorDR.address, + assistantDR.address, adminDR.address, clerkDR.address, treasuryDR.address, @@ -1976,7 +1980,7 @@ describe("IBosonVoucher", function () { const { offer, offerDates, offerDurations, disputeResolverId } = await mockOffer(); await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); await fundsHandler .connect(admin) @@ -1995,13 +1999,13 @@ describe("IBosonVoucher", function () { context("setRoyaltyPercentage()", function () { beforeEach(async function () { - // give ownership to operator - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + // 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(operator).setRoyaltyPercentage(royaltyPercentage)) + await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)) .to.emit(bosonVoucher, "RoyaltyPercentageChanged") .withArgs(royaltyPercentage); }); @@ -2009,7 +2013,7 @@ describe("IBosonVoucher", function () { it("should set a royalty fee percentage", async function () { // First, set royalty fee as 0 royaltyPercentage = "0"; //0% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); let receiver, royaltyAmount; [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); @@ -2023,7 +2027,7 @@ describe("IBosonVoucher", function () { // Now, set royalty fee as 10% royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); @@ -2053,7 +2057,7 @@ describe("IBosonVoucher", function () { royaltyPercentage = "1500"; //15% // royalty percentage too high, expectig revert - await expect(bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( + await expect(bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage)).to.be.revertedWith( RevertReasons.ROYALTY_FEE_INVALID ); }); @@ -2062,11 +2066,11 @@ describe("IBosonVoucher", function () { context("getRoyaltyPercentage()", function () { it("should return the royalty fee percentage", async function () { - // give ownership to operator - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + // give ownership to assistant + await bosonVoucher.connect(protocol).transferOwnership(assistant.address); royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); expect(await bosonVoucher.connect(rando).getRoyaltyPercentage()).to.equal( royaltyPercentage, @@ -2077,17 +2081,17 @@ describe("IBosonVoucher", function () { context("royaltyInfo()", function () { beforeEach(async function () { - // give ownership to operator - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + // 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(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); let receiver, royaltyAmount; - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations let expectedRecipient = seller.treasury; @@ -2098,9 +2102,9 @@ describe("IBosonVoucher", function () { // Now, set royalty fee as 10% royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); - [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + [receiver, royaltyAmount] = await bosonVoucher.connect(assistant).royaltyInfo(exchangeId, offerPrice); // Expectations expectedRecipient = seller.treasury; @@ -2112,7 +2116,7 @@ describe("IBosonVoucher", function () { // Any random address can check the royalty info // Now, set royalty fee as 8% royaltyPercentage = "800"; //8% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); [receiver, royaltyAmount] = await bosonVoucher.connect(rando).royaltyInfo(exchangeId, offerPrice); @@ -2127,11 +2131,11 @@ describe("IBosonVoucher", function () { it("if exhanfe doesn't exist it should return 0 values", async function () { // Set royalty fee as 10% royaltyPercentage = "1000"; //10% - await bosonVoucher.connect(operator).setRoyaltyPercentage(royaltyPercentage); + await bosonVoucher.connect(assistant).setRoyaltyPercentage(royaltyPercentage); // Set inexistentexchangeId exchangeId = "100000"; - const [receiver, royaltyAmount] = await bosonVoucher.connect(operator).royaltyInfo(exchangeId, offerPrice); + 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"); @@ -2167,11 +2171,11 @@ describe("IBosonVoucher", function () { emptyAuthToken = mockAuthToken(); expect(emptyAuthToken.isValid()).is.true; - seller = mockSeller(operator.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - await bosonVoucher.connect(protocol).transferOwnership(operator.address); + await bosonVoucher.connect(protocol).transferOwnership(assistant.address); expect(await bosonVoucher.connect(rando).getSellerId()).to.equal(seller.id, "Invalid seller id returned"); diff --git a/test/upgrade/2.0.0-2.1.0.js b/test/upgrade/2.0.0-2.1.0.js index d5e0b635e..80bd9f3ea 100644 --- a/test/upgrade/2.0.0-2.1.0.js +++ b/test/upgrade/2.0.0-2.1.0.js @@ -20,7 +20,7 @@ const newVersion = "v2.1.0"; */ describe("[@skip-on-coverage] After facet upgrade, everything is still operational", function () { // Common vars - let deployer, rando, admin, operator, clerk, treasury; + let deployer, rando, admin, assistant, clerk, treasury; let accountHandler, oldHandlers; let ERC165Facet; let snapshot; @@ -32,7 +32,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation before(async function () { // Make accounts available - [deployer, rando, admin, operator, clerk, treasury] = await ethers.getSigners(); + [deployer, rando, admin, assistant, clerk, treasury] = await ethers.getSigners(); ({ protocolDiamondAddress, protocolContracts, mockContracts } = await deploySuite(deployer, oldVersion)); @@ -105,7 +105,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation const seller = oldSeller.seller.clone(); seller.admin = admin.address; - seller.operator = operator.address; + seller.assistant = assistant.address; seller.clerk = clerk.address; seller.treasury = treasury.address; @@ -148,7 +148,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation disputeResolver.escalationResponsePeriod = Number( Number(disputeResolver.escalationResponsePeriod) - 100 ).toString(); - disputeResolver.operator = operator.address; + disputeResolver.assistant = assistant.address; disputeResolver.admin = admin.address; disputeResolver.clerk = clerk.address; disputeResolver.treasury = treasury.address; @@ -231,7 +231,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation const seller = oldSeller.seller.clone(); seller.treasury = treasury.address; seller.admin = admin.address; - seller.operator = operator.address; + seller.assistant = assistant.address; seller.clerk = clerk.address; const pendingSellerUpdate = seller.clone(); @@ -268,13 +268,13 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation .to.emit(accountHandler, "SellerUpdatePending") .withArgs(seller.id, pendingSellerUpdate.toStruct(), pendingAuthTokenStruct, oldSeller.wallet.address); - // Update seller operator - tx = await accountHandler.connect(operator).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + // Update seller assistant + tx = await accountHandler.connect(assistant).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); - pendingSellerUpdate.operator = ethers.constants.AddressZero; - expectedSeller.operator = seller.operator; + pendingSellerUpdate.assistant = ethers.constants.AddressZero; + expectedSeller.assistant = seller.assistant; - // Check operator update + // Check assistant update await expect(tx) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -283,7 +283,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation pendingSellerUpdate.toStruct(), oldSellerAuthToken, pendingAuthTokenStruct, - operator.address + assistant.address ); // Update seller clerk @@ -292,7 +292,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation pendingSellerUpdate.clerk = ethers.constants.AddressZero; expectedSeller.clerk = seller.clerk; - // Check operator update + // Check assistant update await expect(tx) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -310,7 +310,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation pendingSellerUpdate.admin = ethers.constants.AddressZero; expectedSeller.admin = seller.admin; - // Check operator update + // Check assistant update await expect(tx) .to.emit(accountHandler, "SellerUpdateApplied") .withArgs( @@ -332,7 +332,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation disputeResolver.escalationResponsePeriod = Number( Number(disputeResolver.escalationResponsePeriod) - 100 ).toString(); - disputeResolver.operator = operator.address; + disputeResolver.assistant = assistant.address; disputeResolver.admin = admin.address; disputeResolver.clerk = clerk.address; disputeResolver.treasury = treasury.address; @@ -355,21 +355,21 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation .to.emit(accountHandler, "DisputeResolverUpdatePending") .withArgs(disputeResolver.id, disputeResolverPendingUpdate.toStruct(), oldDisputeResolver.wallet.address); - // Approve operator update - expectedDisputeResolver.operator = disputeResolver.operator; - disputeResolverPendingUpdate.operator = ethers.constants.AddressZero; + // Approve assistant update + expectedDisputeResolver.assistant = disputeResolver.assistant; + disputeResolverPendingUpdate.assistant = ethers.constants.AddressZero; await expect( accountHandler - .connect(operator) - .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Operator]) + .connect(assistant) + .optInToDisputeResolverUpdate(disputeResolver.id, [DisputeResolverUpdateFields.Assistant]) ) .to.emit(accountHandler, "DisputeResolverUpdateApplied") .withArgs( disputeResolver.id, expectedDisputeResolver.toStruct(), disputeResolverPendingUpdate.toStruct(), - operator.address + assistant.address ); // Approve admin update diff --git a/test/upgrade/clients/BosonVoucher-2.1.0-2.2.0.js b/test/upgrade/clients/BosonVoucher-2.1.0-2.2.0.js index 1cfd22ba0..145cb3c84 100644 --- a/test/upgrade/clients/BosonVoucher-2.1.0-2.2.0.js +++ b/test/upgrade/clients/BosonVoucher-2.1.0-2.2.0.js @@ -36,7 +36,7 @@ let snapshot; */ describe("[@skip-on-coverage] After client upgrade, everything is still operational", function () { // Common vars - let deployer, operator; + let deployer, assistant; // reference protocol state let voucherContractState; @@ -51,7 +51,7 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio before(async function () { // Make accounts available - [deployer, operator] = await ethers.getSigners(); + [deployer, assistant] = await ethers.getSigners(); // temporary update config, so compiler outputs storage layout for (const compiler of hre.config.solidity.compilers) { @@ -128,26 +128,26 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio beforeEach(async function () { // Create a seller sellerId = await accountHandler.getNextAccountId(); - const seller = mockSeller(operator.address, operator.address, operator.address, operator.address); + const seller = mockSeller(assistant.address, assistant.address, assistant.address, assistant.address); const voucherInitValues = mockVoucherInitValues(); const emptyAuthToken = mockAuthToken(); - await accountHandler.connect(operator).createSeller(seller, emptyAuthToken, voucherInitValues); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); const agentId = "0"; // agent id is optional while creating an offer // Create a valid dispute resolver disputeResolverId = await accountHandler.getNextAccountId(); const disputeResolver = mockDisputeResolver( - operator.address, - operator.address, - operator.address, - operator.address, + assistant.address, + assistant.address, + assistant.address, + assistant.address, true ); const disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; const sellerAllowList = []; await accountHandler - .connect(operator) + .connect(assistant) .createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); // Create an offer @@ -155,11 +155,11 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio const { offer, offerDates, offerDurations } = await mockOffer(); offer.quantityAvailable = "100"; await offerHandler - .connect(operator) + .connect(assistant) .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, agentId); await fundsHandler - .connect(operator) + .connect(assistant) .depositFunds(sellerId, ethers.constants.AddressZero, offer.sellerDeposit, { value: offer.sellerDeposit }); start = await exchangeHandler.getNextExchangeId(); @@ -177,16 +177,19 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio it("reserveRange()", async function () { // Reserve range, test for event - await expect(offerHandler.connect(operator).reserveRange(offerId, length)).to.emit(bosonVoucher, "RangeReserved"); + await expect(offerHandler.connect(assistant).reserveRange(offerId, length)).to.emit( + bosonVoucher, + "RangeReserved" + ); }); context("preMint()", async function () { it("seller can pre mint vouchers", async function () { // Reserve range - await offerHandler.connect(operator).reserveRange(offerId, length); + await offerHandler.connect(assistant).reserveRange(offerId, length); // Premint tokens, test for event - await expect(bosonVoucher.connect(operator).preMint(offerId, amount)).to.emit(bosonVoucher, "Transfer"); + await expect(bosonVoucher.connect(assistant).preMint(offerId, amount)).to.emit(bosonVoucher, "Transfer"); }); it("MetaTx: forwarder can pre mint on behalf of seller on old vouchers", async function () { @@ -200,14 +203,14 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio wallet, } = preUpgradeEntities.sellers[sellersLength - 1]; - // reassign operator because signer must be on provider default accounts in order to call eth_signTypedData_v4 - operator = (await ethers.getSigners())[2]; - seller.operator = operator.address; + // reassign assistant because signer must be on provider default accounts in order to call eth_signTypedData_v4 + assistant = (await ethers.getSigners())[2]; + seller.assistant = assistant.address; await accountHandler.connect(wallet).updateSeller(seller, authToken); - await accountHandler.connect(operator).optInToSellerUpdate(seller.id, [SellerUpdateFields.Operator]); + await accountHandler.connect(assistant).optInToSellerUpdate(seller.id, [SellerUpdateFields.Assistant]); // Reserve range - await offerHandler.connect(operator).reserveRange(offerId, length); + await offerHandler.connect(assistant).reserveRange(offerId, length); // Get last seller voucher bosonVoucher = await ethers.getContractAt( @@ -215,7 +218,7 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio calculateContractAddress(exchangeHandler.address, sellersLength) ); - const nonce = Number(await forwarder.getNonce(operator.address)); + const nonce = Number(await forwarder.getNonce(assistant.address)); const types = { ForwardRequest: [ @@ -229,14 +232,14 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio const functionSignature = bosonVoucher.interface.encodeFunctionData("preMint", [offerId, amount]); const message = { - from: operator.address, + from: assistant.address, to: bosonVoucher.address, nonce: nonce, data: functionSignature, }; const { signature } = await prepareDataSignatureParameters( - operator, + assistant, types, "ForwardRequest", message, @@ -253,19 +256,19 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio it("burnPremintedVouchers()", async function () { // Reserve range and premint tokens - await offerHandler.connect(operator).reserveRange(offerId, length); - await bosonVoucher.connect(operator).preMint(offerId, amount); + await offerHandler.connect(assistant).reserveRange(offerId, length); + await bosonVoucher.connect(assistant).preMint(offerId, amount); // void the offer - await offerHandler.connect(operator).voidOffer(offerId); + await offerHandler.connect(assistant).voidOffer(offerId); // Burn preminted vouchers, test for event - await expect(bosonVoucher.connect(operator).burnPremintedVouchers(offerId)).to.emit(bosonVoucher, "Transfer"); + await expect(bosonVoucher.connect(assistant).burnPremintedVouchers(offerId)).to.emit(bosonVoucher, "Transfer"); }); it("getRange()", async function () { // Reserve range - await offerHandler.connect(operator).reserveRange(offerId, length); + await offerHandler.connect(assistant).reserveRange(offerId, length); const range = new Range(start.toString(), length, "0", "0"); @@ -276,7 +279,7 @@ describe("[@skip-on-coverage] After client upgrade, everything is still operatio it("getAvailablePreMints()", async function () { // Reserve range - await offerHandler.connect(operator).reserveRange(offerId, length); + await offerHandler.connect(assistant).reserveRange(offerId, length); // Get available premints from contract const availablePremints = await bosonVoucher.getAvailablePreMints(offerId); diff --git a/test/util/mock.js b/test/util/mock.js index 0dac0aad3..c8f97cba6 100644 --- a/test/util/mock.js +++ b/test/util/mock.js @@ -110,12 +110,12 @@ function mockTwin(tokenAddress, tokenType) { return new Twin(id, sellerId, amount, supplyAvailable, tokenId, tokenAddress, tokenType); } -function mockDisputeResolver(operatorAddress, adminAddress, clerkAddress, treasuryAddress, active) { +function mockDisputeResolver(assistantAddress, adminAddress, clerkAddress, treasuryAddress, active) { const metadataUriDR = `https://ipfs.io/ipfs/disputeResolver1`; return new DisputeResolver( accountId.next().value, oneMonth.toString(), - operatorAddress, + assistantAddress, adminAddress, clerkAddress, treasuryAddress, @@ -124,8 +124,8 @@ function mockDisputeResolver(operatorAddress, adminAddress, clerkAddress, treasu ); } -function mockSeller(operatorAddress, adminAddress, clerkAddress, treasuryAddress) { - return new Seller(accountId.next().value, operatorAddress, adminAddress, clerkAddress, treasuryAddress, true); +function mockSeller(assistantAddress, adminAddress, clerkAddress, treasuryAddress) { + return new Seller(accountId.next().value, assistantAddress, adminAddress, clerkAddress, treasuryAddress, true); } function mockBuyer(wallet) { diff --git a/test/util/upgrade.js b/test/util/upgrade.js index e8e950211..1f4b64ed9 100644 --- a/test/util/upgrade.js +++ b/test/util/upgrade.js @@ -990,11 +990,11 @@ async function getProtocolLookupsPrivateContractState( #2 [X] // placeholder for bundleIdByTwin #3 [ ] // placeholder for groupIdByOffer #4 [X] // placeholder for agentIdByOffer - #5 [X] // placeholder for sellerIdByOperator + #5 [X] // placeholder for sellerIdByAssistant #6 [X] // placeholder for sellerIdByAdmin #7 [X] // placeholder for sellerIdByClerk #8 [ ] // placeholder for buyerIdByWallet - #9 [X] // placeholder for disputeResolverIdByOperator + #9 [X] // placeholder for disputeResolverIdByAssistant #10 [X] // placeholder for disputeResolverIdByAdmin #11 [X] // placeholder for disputeResolverIdByClerk #12 [ ] // placeholder for disputeResolverFeeTokenIndex @@ -1572,7 +1572,7 @@ async function getVoucherContractState({ bosonVouchers, exchanges, sellers, buye ); // balanceOf(address owner) - // isApprovedForAll(address owner, address operator) + // isApprovedForAll(address owner, address assistant) const addresses = [...sellers, ...buyers].map((acc) => acc.wallet.address); const balanceOf = await Promise.all(addresses.map((address) => bosonVoucher.balanceOf(address))); const isApprovedForAll = await Promise.all( From 45705bae6350d9144fab9ac64c4e1432ce9a400d Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 14 Feb 2023 12:06:26 +0100 Subject: [PATCH 10/47] PriceDIscovery domain test --- contracts/mock/PriceDiscovery.sol | 58 +++++ .../protocol/facets/ExchangeHandlerFacet.sol | 35 ++- scripts/domain/Direction.js | 12 + scripts/domain/PriceDiscovery.js | 138 +++++++++++ scripts/util/validations.js | 9 + test/domain/PriceDiscoveryTest.js | 226 ++++++++++++++++++ 6 files changed, 460 insertions(+), 18 deletions(-) create mode 100644 contracts/mock/PriceDiscovery.sol create mode 100644 scripts/domain/Direction.js create mode 100644 scripts/domain/PriceDiscovery.js create mode 100644 test/domain/PriceDiscoveryTest.js diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol new file mode 100644 index 000000000..3361eb9ac --- /dev/null +++ b/contracts/mock/PriceDiscovery.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (metatx/MockForwarder.sol) +pragma solidity ^0.8.9; + +import "../interfaces/IERC20.sol"; +import "../interfaces/IERC721.sol"; + +/** + * @dev Simple price discovery contract used in tests + * + * This contract simluates external price discovery mechanism. + * When user commits to an offer, protocol talks to this contract to validate the exchange. + */ +contract PriceDiscovery { + struct Order { + address seller; + address buyer; + address voucherContract; // sold by seller + uint256 tokenId; // is exchange id + address exchangeToken; + uint256 price; + } + + /** + * @dev simple fulfillOrder that does not perform any checks + * It just transfers the voucher and exchange token to the buyer + * If any of the transfers fail, the whole transaction will revert + */ + function fulfilOrder(Order calldata _order) external { + // transfer voucher + try IERC721(_order.voucherContract).transferFrom(_order.seller, _order.buyer, _order.tokenId) {} catch ( + bytes memory reason + ) { + if (reason.length == 0) { + revert("Voucher transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + + // transfer exchange token + try IERC20(_order.exchangeToken).transferFrom(_order.buyer, _order.seller, _order.price) {} catch ( + bytes memory reason + ) { + if (reason.length == 0) { + revert("Voucher transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } +} diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 5885ec6cc..e05c900ae 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -105,8 +105,8 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // temporary struct for development purposes struct PriceDiscovery { uint256 price; - address validator; - bytes proof; + address priceDiscoveryContract; + bytes priceDiscoveryData; Direction direction; } @@ -144,9 +144,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Fetch offer (, Offer storage offer) = fetchOffer(exchange.offerId); - // Authorize the buyer to commit if original offer is in a conditional group - require(authorizeCommit(_buyer, offer, _exchangeId), CANNOT_COMMIT); - // Get token address address tokenAddress = offer.exchangeToken; @@ -218,6 +215,8 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { ); } + // since exchange and voucher are passed by reference, they are updated + emit BuyerCommitted(exchange.offerId, exchange.buyerId, _exchangeId, exchange, voucher, msgSender()); // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred } @@ -260,16 +259,16 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // At this point, protocol temporary holds buyer's payment uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // If token is ERC20, approve validator to transfer funds + // If token is ERC20, approve price discovery contract to transfer funds if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.validator), _priceDiscovery.price); + IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); } { - // Call the validator - (bool success, bytes memory returnData) = address(_priceDiscovery.validator).call{ value: msg.value }( - _priceDiscovery.proof - ); + // Call the price discovery contract + (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ + value: msg.value + }(_priceDiscovery.priceDiscoveryData); // If error, return error message string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); @@ -277,7 +276,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } // If token is ERC20, reset approval if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.validator), 0); + IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); } // Check the escrow amount @@ -327,14 +326,14 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get protocol balance before the exchange uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // Approve validator to transfer voucher - bosonVoucher.approve(_priceDiscovery.validator, _exchangeId); + // Approve price discovery contract to transfer voucher + bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, _exchangeId); { - // Call the validator - (bool success, bytes memory returnData) = address(_priceDiscovery.validator).call{ value: msg.value }( - _priceDiscovery.proof - ); + // Call the price discovery contract + (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ + value: msg.value + }(_priceDiscovery.priceDiscoveryData); // If error, return error message string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); diff --git a/scripts/domain/Direction.js b/scripts/domain/Direction.js new file mode 100644 index 000000000..197c41a61 --- /dev/null +++ b/scripts/domain/Direction.js @@ -0,0 +1,12 @@ +/** + * Boson Protocol Domain Enum: Direction + */ +class Direction {} + +Direction.Buy = 0; +Direction.Sell = 1; + +Direction.Types = [Direction.Buy, Direction.Sell]; + +// Export +module.exports = Direction; diff --git a/scripts/domain/PriceDiscovery.js b/scripts/domain/PriceDiscovery.js new file mode 100644 index 000000000..9e45cbbc5 --- /dev/null +++ b/scripts/domain/PriceDiscovery.js @@ -0,0 +1,138 @@ +const Direction = require("./Direction"); +const { bigNumberIsValid, addressIsValid, bytesIsValid, enumIsValid } = require("../util/validations.js"); + +/** + * Boson Client Entity: PriceDiscovery + * + * See: {BosonVoucher.PriceDiscovery} + */ +class PriceDiscovery { + /* + struct PriceDiscovery { + uint256 price; + address priceDiscoveryContract; + bytes priceDiscoveryData; + Direction direction; + } + */ + + constructor(price, priceDiscoveryContract, priceDiscoveryData, direction) { + this.price = price; + this.priceDiscoveryContract = priceDiscoveryContract; + this.priceDiscoveryData = priceDiscoveryData; + this.direction = direction; + } + + /** + * Get a new PriceDiscovery instance from a pojo representation + * @param o + * @returns {PriceDiscovery} + */ + static fromObject(o) { + const { price, priceDiscoveryContract, priceDiscoveryData, direction } = o; + return new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + } + + /** + * Get a new PriceDiscovery instance from a returned struct representation + * @param struct + * @returns {*} + */ + static fromStruct(struct) { + let price, priceDiscoveryContract, priceDiscoveryData, direction; + + // destructure struct + [price, priceDiscoveryContract, priceDiscoveryData, direction] = struct; + + return PriceDiscovery.fromObject({ + price: price.toString(), + priceDiscoveryContract: priceDiscoveryContract, + priceDiscoveryData: priceDiscoveryData, + direction: direction, + }); + } + + /** + * Get a database representation of this PriceDiscovery instance + * @returns {object} + */ + toObject() { + return JSON.parse(this.toString()); + } + + /** + * Get a string representation of this PriceDiscovery instance + * @returns {string} + */ + toString() { + return JSON.stringify(this); + } + + /** + * Get a struct representation of this PriceDiscovery instance + * @returns {string} + */ + toStruct() { + return [this.price, this.priceDiscoveryContract, this.priceDiscoveryData, this.direction]; + } + + /** + * Clone this PriceDiscovery + * @returns {PriceDiscovery} + */ + clone() { + return PriceDiscovery.fromObject(this.toObject()); + } + + /** + * Is this PriceDiscovery instance's price field valid? + * Must be a string representation of a big number + * @returns {boolean} + */ + priceIsValid() { + return bigNumberIsValid(this.price); + } + + /** + * Is this PriceDiscovery instance's priceDiscoveryContract field valid? + * Must be a eip55 compliant Ethereum address + * @returns {boolean} + */ + priceDiscoveryContractIsValid() { + return addressIsValid(this.priceDiscoveryContract); + } + + /** + * Is this PriceDiscovery instance's priceDiscoveryData field valid? + * If present, must be a string representation of bytes + * @returns {boolean} + */ + priceDiscoveryDataIsValid() { + return bytesIsValid(this.priceDiscoveryData); + } + + /** + * Is this PriceDiscovery instance's direction field valid? + * Must be a number belonging to the Direction enum + * @returns {boolean} + */ + directionIsValid() { + return enumIsValid(this.direction, Direction.Types); + } + + /** + * Is this PriceDiscovery instance valid? + * @returns {boolean} + */ + isValid() { + return ( + this.priceIsValid() && + this.priceDiscoveryContractIsValid() && + this.priceDiscoveryDataIsValid() && + this.directionIsValid() + ); + } +} + +// Export +module.exports = PriceDiscovery; diff --git a/scripts/util/validations.js b/scripts/util/validations.js index 07b6db318..11904cae9 100644 --- a/scripts/util/validations.js +++ b/scripts/util/validations.js @@ -117,6 +117,14 @@ function bytes4ArrayIsValid(bytes4Array) { return valid; } +function bytesIsValid(bytes) { + let valid = false; + try { + valid = typeof bytes === "string" && bytes.startsWith("0x") && bytes.length % 2 === 0; + } catch (e) {} + return valid; +} + exports.bigNumberIsValid = bigNumberIsValid; exports.enumIsValid = enumIsValid; exports.addressIsValid = addressIsValid; @@ -124,3 +132,4 @@ exports.booleanIsValid = booleanIsValid; exports.bigNumberArrayIsValid = bigNumberArrayIsValid; exports.stringIsValid = stringIsValid; exports.bytes4ArrayIsValid = bytes4ArrayIsValid; +exports.bytesIsValid = bytesIsValid; diff --git a/test/domain/PriceDiscoveryTest.js b/test/domain/PriceDiscoveryTest.js new file mode 100644 index 000000000..19e97098b --- /dev/null +++ b/test/domain/PriceDiscoveryTest.js @@ -0,0 +1,226 @@ +const hre = require("hardhat"); +const ethers = hre.ethers; +const { expect } = require("chai"); +const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); +const Direction = require("../../scripts/domain/Direction"); + +/** + * Test the PriceDiscovery domain entity + */ +describe("PriceDiscovery", function () { + // Suite-wide scope + let priceDiscovery, object, promoted, clone, dehydrated, rehydrated, key, value, struct; + let accounts, price, priceDiscoveryContract, priceDiscoveryData, direction; + + beforeEach(async function () { + // Get a list of accounts + accounts = await ethers.getSigners(); + + // Required constructor params + price = "150"; + priceDiscoveryContract = accounts[1].address; + priceDiscoveryData = "0xdeadbeef"; + direction = Direction.Buy; + }); + + context("📋 Constructor", async function () { + it("Should allow creation of valid, fully populated PriceDiscovery instance", async function () { + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + expect(priceDiscovery.priceIsValid()).is.true; + expect(priceDiscovery.priceDiscoveryContractIsValid()).is.true; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; + expect(priceDiscovery.directionIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + }); + + context("📋 Field validations", async function () { + beforeEach(async function () { + // Create a valid priceDiscovery, then set fields in tests directly + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + expect(priceDiscovery.isValid()).is.true; + }); + + it("Always present, price must be the string representation of a BigNumber", async function () { + // Invalid field value + priceDiscovery.price = "zedzdeadbaby"; + expect(priceDiscovery.priceIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.price = new Date(); + expect(priceDiscovery.priceIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.price = 12; + expect(priceDiscovery.priceIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.price = "0"; + expect(priceDiscovery.priceIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + + // Valid field value + priceDiscovery.price = "126"; + expect(priceDiscovery.priceIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + + it("Always present, priceDiscoveryContract must be a string representation of an EIP-55 compliant address", async function () { + // Invalid field value + priceDiscovery.priceDiscoveryContract = "0xASFADF"; + expect(priceDiscovery.priceDiscoveryContractIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.priceDiscoveryContract = "zedzdeadbaby"; + expect(priceDiscovery.priceDiscoveryContractIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.priceDiscoveryContract = accounts[0].address; + expect(priceDiscovery.priceDiscoveryContractIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + + // Valid field value + priceDiscovery.priceDiscoveryContract = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; + expect(priceDiscovery.priceDiscoveryContractIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + + it("If present, priceDiscoveryData must be the string representation of bytes", async function () { + // Invalid field value + priceDiscovery.priceDiscoveryData = "zedzdeadbaby"; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.priceDiscoveryData = new Date(); + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.priceDiscoveryData = 12; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.priceDiscoveryData = "0x1"; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.priceDiscoveryData = "0x"; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + + // Valid field value + priceDiscovery.priceDiscoveryData = "0x1234567890abcdef"; + expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + + it("If present, direction must be a Direction enum", async function () { + // Invalid field value + priceDiscovery.direction = "zedzdeadbaby"; + expect(priceDiscovery.directionIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.direction = new Date(); + expect(priceDiscovery.directionIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.direction = 12; + expect(priceDiscovery.directionIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.direction = Direction.Sell; + expect(priceDiscovery.directionIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + }); + + context("📋 Utility functions", async function () { + beforeEach(async function () { + // Create a valid priceDiscovery, then set fields in tests directly + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + expect(priceDiscovery.isValid()).is.true; + + // Get plain object + object = { + price, + priceDiscoveryContract, + priceDiscoveryData, + direction, + }; + + // Struct representation + struct = [price, priceDiscoveryContract, priceDiscoveryData, direction]; + }); + + context("👉 Static", async function () { + it("PriceDiscovery.fromObject() should return a PriceDiscovery instance with the same values as the given plain object", async function () { + // Promote to instance + promoted = PriceDiscovery.fromObject(object); + + // Is a PriceDiscovery instance + expect(promoted instanceof PriceDiscovery).is.true; + + // Key values all match + for ([key, value] of Object.entries(priceDiscovery)) { + expect(JSON.stringify(promoted[key]) === JSON.stringify(value)).is.true; + } + }); + + it("PriceDiscovery.fromStruct() should return an PriceDiscovery instance from a struct representation", async function () { + // Get instance from struct + priceDiscovery = PriceDiscovery.fromStruct(struct); + + // Ensure it marshals back to a valid priceDiscovery + expect(priceDiscovery.isValid()).to.be.true; + }); + }); + + context("👉 Instance", async function () { + it("instance.toString() should return a JSON string representation of the PriceDiscovery instance", async function () { + dehydrated = priceDiscovery.toString(); + rehydrated = JSON.parse(dehydrated); + + for ([key, value] of Object.entries(priceDiscovery)) { + expect(JSON.stringify(rehydrated[key]) === JSON.stringify(value)).is.true; + } + }); + + it("instance.clone() should return another PriceDiscovery instance with the same property values", async function () { + // Get plain object + clone = priceDiscovery.clone(); + + // Is an PriceDiscovery instance + expect(clone instanceof PriceDiscovery).is.true; + + // Key values all match + for ([key, value] of Object.entries(priceDiscovery)) { + expect(JSON.stringify(clone[key]) === JSON.stringify(value)).is.true; + } + }); + + it("instance.toObject() should return a plain object representation of the PriceDiscovery instance", async function () { + // Get plain object + object = priceDiscovery.toObject(); + + // Not an PriceDiscovery instance + expect(object instanceof PriceDiscovery).is.false; + + // Key values all match + for ([key, value] of Object.entries(priceDiscovery)) { + expect(JSON.stringify(object[key]) === JSON.stringify(value)).is.true; + } + }); + }); + }); +}); From b87bd1baee6c4ea70b1073e862188dcb28eadea2 Mon Sep 17 00:00:00 2001 From: zajck Date: Wed, 15 Feb 2023 13:25:15 +0100 Subject: [PATCH 11/47] sequential commits test, first batch --- contracts/domain/BosonTypes.sol | 13 + .../handlers/IBosonExchangeHandler.sol | 6 + contracts/mock/PriceDiscovery.sol | 29 +- contracts/mock/WETH9.sol | 73 ++++ .../protocol/facets/ExchangeHandlerFacet.sol | 19 - hardhat.config.js | 23 +- .../util/deploy-protocol-handler-facets.js | 17 +- test/protocol/ExchangeHandlerTest.js | 395 ++++++++++++++++++ test/util/utils.js | 2 +- 9 files changed, 527 insertions(+), 50 deletions(-) create mode 100644 contracts/mock/WETH9.sol diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 9f85f203c..53d06742e 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -291,4 +291,17 @@ contract BosonTypes { string contractURI; uint256 royaltyPercentage; } + + // temporary struct for development purposes + struct PriceDiscovery { + uint256 price; + address priceDiscoveryContract; + bytes priceDiscoveryData; + Direction direction; + } + + enum Direction { + Buy, + Sell + } } diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index 2f2863ad7..1bb17270a 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -258,4 +258,10 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * @return receipt - the receipt for the exchange. See {BosonTypes.Receipt} */ function getReceipt(uint256 _exchangeId) external view returns (BosonTypes.Receipt memory receipt); + + function sequentialCommitToOffer( + address payable _buyer, + uint256 _exchangeId, + BosonTypes.PriceDiscovery calldata _priceDiscovery + ) external payable; } diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index 3361eb9ac..4e581115c 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -26,9 +26,10 @@ contract PriceDiscovery { * It just transfers the voucher and exchange token to the buyer * If any of the transfers fail, the whole transaction will revert */ - function fulfilOrder(Order calldata _order) external { + function fulfilOrder(Order calldata _order) external payable { // transfer voucher - try IERC721(_order.voucherContract).transferFrom(_order.seller, _order.buyer, _order.tokenId) {} catch ( + // TODO: try safe transfer from! + try IERC721(_order.voucherContract).transferFrom(_order.seller, msg.sender, _order.tokenId) {} catch ( bytes memory reason ) { if (reason.length == 0) { @@ -42,17 +43,21 @@ contract PriceDiscovery { } // transfer exchange token - try IERC20(_order.exchangeToken).transferFrom(_order.buyer, _order.seller, _order.price) {} catch ( - bytes memory reason - ) { - if (reason.length == 0) { - revert("Voucher transfer failed"); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) + if (_order.exchangeToken == address(0)) { + (bool success, ) = payable(_order.buyer).call{ value: _order.price }(""); + require(success, "Token transfer failed"); + } else + try IERC20(_order.exchangeToken).transferFrom(msg.sender, _order.seller, _order.price) {} catch ( + bytes memory reason + ) { + if (reason.length == 0) { + revert("Token transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } } } - } } } diff --git a/contracts/mock/WETH9.sol b/contracts/mock/WETH9.sol new file mode 100644 index 000000000..6ff8672c8 --- /dev/null +++ b/contracts/mock/WETH9.sol @@ -0,0 +1,73 @@ +/** + * @title WETH + * + * @notice Mock WETH used for testing + * source: https://github.com/gnosis/canonical-weth/blob/master/contracts/WETH9.sol + */ + +// solhint-disable-next-line compiler-version +pragma solidity >=0.4.22 <0.6; + +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + function() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + msg.sender.transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom( + address src, + address dst, + uint256 wad + ) public returns (bool) { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index e05c900ae..ef4953009 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -102,25 +102,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { commitToOfferInternal(_buyer, offer, 0, false); } - // temporary struct for development purposes - struct PriceDiscovery { - uint256 price; - address priceDiscoveryContract; - bytes priceDiscoveryData; - Direction direction; - } - - // enum ValidatorType { - // None, - // Simple, - // Advanced - // } - - enum Direction { - Buy, - Sell - } - function sequentialCommitToOffer( address payable _buyer, uint256 _exchangeId, diff --git a/hardhat.config.js b/hardhat.config.js index be1bdde10..0c23b8bc0 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -135,16 +135,23 @@ module.exports = { }, }, solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200, - details: { - yul: true, + compilers: [ + { + version: "0.8.9", + settings: { + optimizer: { + enabled: true, + runs: 200, + details: { + yul: true, + }, + }, }, }, - }, + { + version: "0.5.17", // Mock weth contract + }, + ], }, gasReporter: { currency: "USD", diff --git a/scripts/util/deploy-protocol-handler-facets.js b/scripts/util/deploy-protocol-handler-facets.js index fde60f250..6b59a727d 100644 --- a/scripts/util/deploy-protocol-handler-facets.js +++ b/scripts/util/deploy-protocol-handler-facets.js @@ -71,24 +71,21 @@ async function deployAndCutFacets( * Reused between deployment script and unit tests for consistency. * * @param facetNames - array of facet names to deploy - * @param facetsToInit - object with facet names and corresponding initialization arguments {facetName1: initializerArguments1, facetName2: initializerArguments2, ...} - * provide only for facets that should be initialized + * @param facetsToInit - object with facet names and corresponding constructor and/or initialization arguments + * {facetName1: {constructorArgs: constructorArguments1, init: initializerArguments1}, facetName2: {init: initializerArguments2}, ...} + * provide only for facets that have constructor or should be initialized * @param maxPriorityFeePerGas - maxPriorityFeePerGas for transactions * @returns {Promise<(*|*|*)[]>} */ async function deployProtocolFacets(facetNames, facetsToInit, maxPriorityFeePerGas) { let deployedFacets = []; - // TODO: get constructorArguments from a config file - let constructorArguments = { - ExchangeHandlerFacet: ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], - }; - // Deploy all handler facets for (const facetName of facetNames) { let FacetContractFactory = await ethers.getContractFactory(facetName); + const constructorArguments = (facetsToInit[facetName] && facetsToInit[facetName].constructorArgs) || []; const facetContract = await FacetContractFactory.deploy( - ...(constructorArguments[facetName] || []), + ...constructorArguments, await getFees(maxPriorityFeePerGas) ); await facetContract.deployTransaction.wait(confirmations); @@ -99,10 +96,10 @@ async function deployProtocolFacets(facetNames, facetsToInit, maxPriorityFeePerG cut: [], }; - if (facetsToInit[facetName] && facetName !== "ProtocolInitializationHandlerFacet") { + if (facetsToInit[facetName] && facetsToInit[facetName].init && facetName !== "ProtocolInitializationHandlerFacet") { const calldata = facetContract.interface.encodeFunctionData( "initialize", - facetsToInit[facetName].length && facetsToInit[facetName] + facetsToInit[facetName].init.length && facetsToInit[facetName].init ); deployedFacet.initialize = calldata; diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index cf0a54976..967069b68 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -14,6 +14,8 @@ const ExchangeState = require("../../scripts/domain/ExchangeState"); const DisputeState = require("../../scripts/domain/DisputeState"); const Group = require("../../scripts/domain/Group"); const EvaluationMethod = require("../../scripts/domain/EvaluationMethod"); +const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); +const Direction = require("../../scripts/domain/Direction"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); @@ -63,6 +65,7 @@ describe("IBosonExchangeHandler", function () { treasury, rando, buyer, + buyer2, newOwner, fauxClient, assistantDR, @@ -107,6 +110,7 @@ describe("IBosonExchangeHandler", function () { let exchangesToComplete, exchangeId; let offer, offerFees; let offerDates, offerDurations; + let weth; before(async function () { // get interface Ids @@ -121,6 +125,7 @@ describe("IBosonExchangeHandler", function () { admin, treasury, buyer, + buyer2, rando, newOwner, fauxClient, @@ -220,6 +225,13 @@ describe("IBosonExchangeHandler", function () { const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); + const wethFactory = await ethers.getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.deployed(); + + // Add WETH + facetsToDeploy["ExchangeHandlerFacet"].constructorArgs = [weth.address]; + // Cut the protocol handler facets into the Diamond await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); @@ -363,6 +375,8 @@ describe("IBosonExchangeHandler", function () { offer.quantityAvailable = "10"; disputeResolverId = mo.disputeResolverId; + offerDurations.voucherValid = (oneMonth * 12).toString(); + // Check if domains are valid expect(offer.isValid()).is.true; expect(offerDates.isValid()).is.true; @@ -1384,6 +1398,387 @@ describe("IBosonExchangeHandler", function () { }); }); + context("👉 sequentialCommitToOffer()", async function () { + let priceDiscoveryContract, priceDiscovery, price2; + let newBuyer; + + // TODO: + // * ERC20 as exchange token + // * sell order + + before(async function () { + // Deploy PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + }); + + beforeEach(async function () { + // Commit to offer with first buyer + tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + txReceipt = await tx.wait(); + event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); + + // Get the block timestamp of the confirmed tx + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + + // 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); + + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Direction.Buy); + + // deposit weth - needed only for Buy direction + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); + + it("should transfer the voucher", async function () { + // buyer is owner of voucher + expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); + + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); + + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler.connect(rando).getOffer(offerId); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit on someone else's behalf", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(rando) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + + // buyer2 is owner of voucher, not rando + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + // TESTS BELOW NOT UPDATED YET + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to create an exchange, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to create a buyer, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to commit, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(ethers.constants.AddressZero, offerId, { value: price }) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("offer id is invalid", async function () { + // An invalid offer id + offerId = "666"; + + // Attempt to commit, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.NO_SUCH_OFFER); + }); + + it("offer is voided", async function () { + // Void the offer first + await offerHandler.connect(assistant).voidOffer(offerId); + + // Attempt to commit to the voided offer, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + }); + + it("offer is not yet available for commits", async function () { + // Create an offer with staring date in the future + // get current block timestamp + const block = await ethers.provider.getBlock("latest"); + const now = block.timestamp.toString(); + + // set validFrom date in the past + offerDates.validFrom = ethers.BigNumber.from(now) + .add(oneMonth * 6) + .toString(); // 6 months in the future + offerDates.validUntil = ethers.BigNumber.from(offerDates.validFrom).add(10).toString(); // just after the valid from so it succeeds. + + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + + // Attempt to commit to the not availabe offer, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); + }); + + it("offer has expired", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Attempt to commit to the expired offer, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + }); + + it("offer sold", async function () { + // Create an offer with only 1 item + offer.quantityAvailable = "1"; + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + // Commit to offer, so it's not availble anymore + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }); + + // Attempt to commit to the sold out offer, expecting revert + await expect( + exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + }); + }); + }); + context("👉 completeExchange()", async function () { beforeEach(async function () { // Commit to offer diff --git a/test/util/utils.js b/test/util/utils.js index b885d0a03..2e60486e4 100644 --- a/test/util/utils.js +++ b/test/util/utils.js @@ -225,7 +225,7 @@ async function getFacetsWithArgs(facetNames, config) { const facets = await getFacets(config); const keys = Object.keys(facets).filter((key) => facetNames.includes(key)); return keys.reduce((obj, key) => { - obj[key] = facets[key]; + obj[key] = { init: facets[key] }; return obj; }, {}); } From 47d40ad83f15510d32be6ff480b4831caced6563 Mon Sep 17 00:00:00 2001 From: zajck Date: Wed, 15 Feb 2023 14:57:46 +0100 Subject: [PATCH 12/47] revert reasons --- contracts/domain/BosonConstants.sol | 2 + .../handlers/IBosonExchangeHandler.sol | 5 +- contracts/mock/PriceDiscovery.sol | 3 +- .../protocol/facets/ExchangeHandlerFacet.sol | 41 ++++++-- contracts/protocol/libs/ProtocolLib.sol | 4 + scripts/config/revert-reasons.js | 2 + test/protocol/ExchangeHandlerTest.js | 95 +++++++------------ 7 files changed, 81 insertions(+), 71 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 9981c0bcc..5c5fad598 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -111,6 +111,8 @@ string constant TOO_MANY_EXCHANGES = "Exceeded maximum exchanges in a single tra 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"; +string constant UNEXPECTED_ERC721_RECEIVED = "Unexpected ERC721 received"; +string constant FEE_AMOUNT_TOO_HIGH = "Fee amount is too high"; // Revert Reasons: Twin related string constant NO_SUCH_TWIN = "No such twin"; diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index 1bb17270a..e3af94542 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -5,15 +5,16 @@ import { BosonTypes } from "../../domain/BosonTypes.sol"; import { IBosonExchangeEvents } from "../events/IBosonExchangeEvents.sol"; import { IBosonTwinEvents } from "../events/IBosonTwinEvents.sol"; import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /** * @title IBosonExchangeHandler * * @notice Handles exchanges associated with offers within the protocol. * - * The ERC-165 identifier for this interface is: 0xe300dfc1 + * The ERC-165 identifier for this interface is: 0xf666ec8b */ -interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents { +interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents, IERC721Receiver { /** * @notice Commits to an offer (first step of an exchange). * diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index 4e581115c..f5bfd6536 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -28,8 +28,7 @@ contract PriceDiscovery { */ function fulfilOrder(Order calldata _order) external payable { // transfer voucher - // TODO: try safe transfer from! - try IERC721(_order.voucherContract).transferFrom(_order.seller, msg.sender, _order.tokenId) {} catch ( + try IERC721(_order.voucherContract).safeTransferFrom(_order.seller, msg.sender, _order.tokenId) {} catch ( bytes memory reason ) { if (reason.length == 0) { diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index ef4953009..d55c57114 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -113,6 +113,9 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { price */ + // Make sure buyer address is not zero address + require(_buyer != address(0), INVALID_ADDRESS); + // verify that order can be fulfilled [// pass forward to 0x or seaport] // offer must be done in a way that boson protocol receives minimal necesarry funds to go in escrow @@ -120,7 +123,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); // Make sure the voucher is still valid - require(block.timestamp <= voucher.validUntilDate, "VOUCHER_INVALID"); + require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); // Fetch offer (, Offer storage offer) = fetchOffer(exchange.offerId); @@ -147,7 +150,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { ); // Verify that fees and royalties are not higher than the price. - require((protocolFeeAmount + royaltyAmount) <= _priceDiscovery.price, "FEE_AMOUNT_TOO_HIGH"); + require((protocolFeeAmount + royaltyAmount) <= _priceDiscovery.price, FEE_AMOUNT_TOO_HIGH); // Get price paid by current buyer uint256 len = sequentialCommits.length; @@ -245,6 +248,12 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); } + // Store the information about incoming voucher + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + address cloneAddress = protocolLookups().cloneAddress[_initialSellerId]; + ps.incomingVoucherId = _exchangeId; + ps.incomingVoucherCloneAddress = cloneAddress; + { // Call the price discovery contract (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ @@ -260,6 +269,10 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); } + // Clear the storage + delete ps.incomingVoucherId; + delete ps.incomingVoucherCloneAddress; + // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); @@ -283,11 +296,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } // Transfer voucher to buyer - IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]).transferFrom( - address(this), - _buyer, - _exchangeId - ); + IBosonVoucher(cloneAddress).transferFrom(address(this), _buyer, _exchangeId); } function fulfilSellOrder( @@ -352,6 +361,24 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); } + // during sequential commit to offer, we expect to receive the boson voucher, therefore we need to implement onERC721Received + // alternative option, where vouchers are modified to not invoke onERC721Received when to is protocol is unsafe, since one can abuse it to send vouchers to protocol + // this should return true value only when protocol expects to receive the voucher + // should revert if called from any other address + function onERC721Received( + address, + address, + uint256 _tokenId, + bytes calldata + ) external view override returns (bytes4) { + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + require( + ps.incomingVoucherId == _tokenId && ps.incomingVoucherCloneAddress == msg.sender, + UNEXPECTED_ERC721_RECEIVED + ); + return this.onERC721Received.selector; + } + /** * @notice Commits to a preminted offer (first step of an exchange). * diff --git a/contracts/protocol/libs/ProtocolLib.sol b/contracts/protocol/libs/ProtocolLib.sol index d99259446..d957f543e 100644 --- a/contracts/protocol/libs/ProtocolLib.sol +++ b/contracts/protocol/libs/ProtocolLib.sol @@ -234,6 +234,10 @@ library ProtocolLib { mapping(bytes32 => bool) initializedVersions; // Current protocol version bytes32 version; + // Incoming voucher id + uint256 incomingVoucherId; + // Incoming voucher clone address + address incomingVoucherCloneAddress; } /** diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index be698efa9..02d8217a6 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -127,6 +127,8 @@ exports.RevertReasons = { 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", + UNEXPECTED_ERC721_RECEIVED: "Unexpected ERC721 received", + FEE_AMOUNT_TOO_HIGH: "Fee amount is too high", // Voucher related EXCHANGE_ID_IN_RESERVED_RANGE: "Exchange id falls within a pre-minted offer's range", diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 967069b68..917752927 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -1680,16 +1680,16 @@ describe("IBosonExchangeHandler", function () { .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - // TESTS BELOW NOT UPDATED YET - context("💔 Revert Reasons", async function () { it("The exchanges region of protocol is paused", async function () { // Pause the exchanges region of the protocol await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); - // Attempt to create an exchange, expecting revert + // Attempt to sequentially commit, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -1697,84 +1697,59 @@ describe("IBosonExchangeHandler", function () { // Pause the buyers region of the protocol await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); - // Attempt to create a buyer, expecting revert + // Attempt to sequentially commit, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); it("buyer address is the zero address", async function () { - // Attempt to commit, expecting revert + // Attempt to sequentially commit, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(ethers.constants.AddressZero, offerId, { value: price }) + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); - it("offer id is invalid", async function () { + it("exchange id is invalid", async function () { // An invalid offer id - offerId = "666"; - - // Attempt to commit, expecting revert - await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.NO_SUCH_OFFER); - }); - - it("offer is voided", async function () { - // Void the offer first - await offerHandler.connect(assistant).voidOffer(offerId); - - // Attempt to commit to the voided offer, expecting revert - await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); - }); - - it("offer is not yet available for commits", async function () { - // Create an offer with staring date in the future - // get current block timestamp - const block = await ethers.provider.getBlock("latest"); - const now = block.timestamp.toString(); - - // set validFrom date in the past - offerDates.validFrom = ethers.BigNumber.from(now) - .add(oneMonth * 6) - .toString(); // 6 months in the future - offerDates.validUntil = ethers.BigNumber.from(offerDates.validFrom).add(10).toString(); // just after the valid from so it succeeds. - - await offerHandler - .connect(assistant) - .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + exchangeId = "666"; - // Attempt to commit to the not availabe offer, expecting revert + // Attempt to sequentially commit, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); - it("offer has expired", async function () { + it("voucher not valid anymore", async function () { // Go past offer expiration date - await setNextBlockTimestamp(Number(offerDates.validUntil)); + await setNextBlockTimestamp(Number(voucher.validUntilDate)); - // Attempt to commit to the expired offer, expecting revert + // Attempt to sequentially commit to the expired voucher, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); }); - it("offer sold", async function () { - // Create an offer with only 1 item - offer.quantityAvailable = "1"; - await offerHandler - .connect(assistant) - .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); - // Commit to offer, so it's not availble anymore - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, ++offerId, { value: price }); + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); // Attempt to commit to the sold out offer, expecting revert await expect( - exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); }); }); }); From b13ce9cc30f928e75ff8356978f87868b82f545e Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 16 Feb 2023 13:08:21 +0100 Subject: [PATCH 13/47] escrow amount --- contracts/mock/PriceDiscovery.sol | 8 +- .../protocol/facets/ExchangeHandlerFacet.sol | 3 +- test/protocol/ExchangeHandlerTest.js | 696 +++++++++++------- 3 files changed, 450 insertions(+), 257 deletions(-) diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index f5bfd6536..55e88d3a3 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -43,8 +43,14 @@ contract PriceDiscovery { // transfer exchange token if (_order.exchangeToken == address(0)) { - (bool success, ) = payable(_order.buyer).call{ value: _order.price }(""); + (bool success, ) = payable(_order.seller).call{ value: _order.price }(""); require(success, "Token transfer failed"); + + // return any extra ETH to the buyer + if (msg.value > _order.price) { + (success, ) = payable(msg.sender).call{ value: msg.value - _order.price }(""); + require(success, "ETH return failed"); + } } else try IERC20(_order.exchangeToken).transferFrom(msg.sender, _order.seller, _order.price) {} catch ( bytes memory reason diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index d55c57114..b7ca8185c 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -277,11 +277,12 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 protocolBalanceAfter = getBalance(_exchangeToken); uint256 expectedBalanceAfter = protocolBalanceBefore - _priceDiscovery.price + _escrowAmount; + // console.log("expectedBalanceAfter",expectedBalanceAfter); if (protocolBalanceAfter > expectedBalanceAfter) { // Escrowed too much, return the difference to buyer FundsLib.transferFundsFromProtocol( _exchangeToken, - payable(_buyer), + payable(_seller), protocolBalanceAfter - expectedBalanceAfter ); } else if (protocolBalanceAfter < expectedBalanceAfter) { diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 917752927..aafd87e8c 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -1428,328 +1428,514 @@ describe("IBosonExchangeHandler", function () { // Update the validUntilDate date in the expected exchange struct voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); - - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: buyer.address, - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: offer.exchangeToken, - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); - - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Direction.Buy); - - // deposit weth - needed only for Buy direction - await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow - await weth.connect(buyer).approve(protocolDiamond.address, price2); - - // Approve transfers - // Buyer does not approve, since its in ETH. - // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); - - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; }); - it("should emit a BuyerCommitted event", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("should update state", async function () { - // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // buyer2 is owner of voucher - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - - // buyer2 is exchange.buyerId - // Get the exchange as a struct - const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - - // Parse into entity - let returnedExchange = Exchange.fromStruct(exchangeStruct); - expect(returnedExchange.buyerId).to.equal(newBuyer.id); - - // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); - }); + context("General actions", async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; - it("should transfer the voucher", async function () { - // buyer is owner of voucher - expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); - // buyer2 is owner of voucher - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); + // deposit weth - needed only for Buy direction + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); - it("voucher should remain unchanged", async function () { - // Voucher before - let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - let returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Voucher after - [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - }); + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); - it("only new buyer can redeem voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherRedeemed") - .withArgs(offerId, exchangeId, buyer2.address); - }); + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - it("only new buyer can cancel voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherCanceled") - .withArgs(offerId, exchangeId, buyer2.address); - }); + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); - it("should not increment the next exchange id counter", async function () { - const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); - // Get the next exchange id and ensure it was incremented by the creation of the offer - const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); - expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); - }); + it("should transfer the voucher", async function () { + // buyer is owner of voucher + expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); - it("Should not decrement quantityAvailable", async function () { - // Get quantityAvailable before - const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler.connect(rando).getOffer(offerId); + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); - // Get quantityAvailable after - const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); - expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); - }); + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit on someone else's behalf", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(rando) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); - // buyer2 is owner of voucher, not rando - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if offer is voided", async function () { - // Void the offer - await offerHandler.connect(assistant).voidOffer(offerId); + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if redemption period has not started yet", async function () { - // Redemption not yet possible - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( - RevertReasons.VOUCHER_NOT_REDEEMABLE - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); - it("It is possible to commit even if offer has expired", async function () { - // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if is sold out", async function () { - // Commit to all remaining quantity - for (let i = 1; i < offer.quantityAvailable; i++) { - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - } + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - context("💔 Revert Reasons", async function () { - it("The exchanges region of protocol is paused", async function () { - // Pause the exchanges region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); - // Attempt to sequentially commit, expecting revert + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit on someone else's behalf", async function () { + // Sequential commit to offer, retrieving the event await expect( exchangeHandler - .connect(buyer2) + .connect(rando) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + + // buyer2 is owner of voucher, not rando + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); }); - it("The buyers region of protocol is paused", async function () { - // Pause the buyers region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); - // Attempt to sequentially commit, expecting revert + // Committing directly is not possible await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); - it("buyer address is the zero address", async function () { - // Attempt to sequentially commit, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("exchange id is invalid", async function () { - // An invalid offer id - exchangeId = "666"; + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); - // Attempt to sequentially commit, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("voucher not valid anymore", async function () { - // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); - // Attempt to sequentially commit to the expired voucher, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("protocol fees to high", async function () { - // Set protocol fees to 95% - await configHandler.setProtocolFeePercentage(9500); - // Set royalty fees to 6% - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } - // Attempt to commit to the sold out offer, expecting revert + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("exchange id is invalid", async function () { + // An invalid offer id + exchangeId = "666"; + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); + + it("voucher not valid anymore", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(voucher.validUntilDate)); + + // Attempt to sequentially commit to the expired voucher, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to commit to the sold out offer, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("insufficient values sent", async function () { + // Attempt to commit to the sold out offer, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + }); + }); + + context("Escrow amount", async function () { + let scenarios = [ + { case: "Increasing price", multiplier: 11 }, + { case: "Constant price", multiplier: 10 }, + { case: "Decreasing price", multiplier: 9 }, + ]; + // let scenarios=[{case: "Decreasing price", multiplier: 9}]; + + async function getBalances() { + const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ + ethers.provider.getBalance(exchangeHandler.address), + ethers.provider.getBalance(buyer.address), + weth.balanceOf(buyer.address), + ethers.provider.getBalance(buyer2.address), + ethers.provider.getBalance(treasury.address), + ]); + + return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; + } + + scenarios.forEach((scenario) => { + context(scenario.case, async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // deposit weth - needed only for Buy direction + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 500, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 500, + royalties: 700, // more than profit + }, + ]; + + // const fees = [{ + // protocol: 0, royalties: 0 + // }] + + fees.forEach((fee) => { + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2, gasPrice: 0 }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer. Buyer pays more than needed + priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); + + console.log("original price", price); + console.log("sent it", priceDiscovery.price); + console.log("expected total escrow", priceDiscovery.price); + console.log("expected escrow change", ethers.BigNumber.from(priceDiscovery.price).sub(price)); + + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + value: priceDiscovery.price, + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = priceDiscovery.price; + const reducedSecondaryPrice = ethers.BigNumber.from(priceDiscovery.price) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + // let esc2 = price2; + const expectedProtocolChange = ethers.BigNumber.from(priceDiscovery.price).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it.skip("no protocol fee and royalties - underpaid", async function () { + // price discovery contract reverts + }); + }); + }); }); }); }); From 6ad5f1154d3847df2a09f06cc2892bc36c6d47b5 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 17 Feb 2023 09:29:12 +0100 Subject: [PATCH 14/47] Return overpaid to buyer --- .../handlers/IBosonExchangeHandler.sol | 2 +- .../protocol/facets/ExchangeHandlerFacet.sol | 126 ++- hardhat.config.js | 2 + test/protocol/ExchangeHandlerTest.js | 834 +++++++++--------- 4 files changed, 468 insertions(+), 496 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index e3af94542..30de53e84 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -12,7 +12,7 @@ import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; * * @notice Handles exchanges associated with offers within the protocol. * - * The ERC-165 identifier for this interface is: 0xf666ec8b + * The ERC-165 identifier for this interface is: 0xe36d9689 */ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents, IERC721Receiver { /** diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index b7ca8185c..27ecd8c17 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -107,18 +107,9 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, PriceDiscovery calldata _priceDiscovery ) external payable exchangesNotPaused buyersNotPaused nonReentrant { - /* need to know - seller (== current owner of voucher) - buyer (== new owner) - price - */ - // Make sure buyer address is not zero address require(_buyer != address(0), INVALID_ADDRESS); - // verify that order can be fulfilled [// pass forward to 0x or seaport] - // offer must be done in a way that boson protocol receives minimal necesarry funds to go in escrow - // Exchange must exist (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); @@ -131,7 +122,18 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get token address address tokenAddress = offer.exchangeToken; - // Calculate the amount to be immediately released to current voucher owner + // Get current buyer address. This is actually the seller in sequential commit + address _seller; + { + (, Buyer storage currentBuyer) = fetchBuyer(exchange.buyerId); + _seller = currentBuyer.wallet; + } + + // First call price discovery and get actual price + // It might be lower tha submitted for buy orders and higher for sell orders + uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _seller, _buyer, offer.sellerId); + + // Calculate the amount to be kept in escrow uint256 escrowAmount; { // Get sequential commits for this exchange @@ -141,16 +143,16 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Calculate fees uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token ? protocolFees().flatBoson - : (protocolFees().percentage * _priceDiscovery.price) / 10000; + : (protocolFees().percentage * actualPrice) / 10000; // Calculate royalties (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( _exchangeId, - _priceDiscovery.price + actualPrice ); // Verify that fees and royalties are not higher than the price. - require((protocolFeeAmount + royaltyAmount) <= _priceDiscovery.price, FEE_AMOUNT_TOO_HIGH); + require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); // Get price paid by current buyer uint256 len = sequentialCommits.length; @@ -158,45 +160,37 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Calculate the amount to be immediately released to current voucher owner // escrowAmount = - // _priceDiscovery.price - - // Math.min(currentPrice, _priceDiscovery.price - protocolFeeAmount - royaltyAmount); + // actualPrice - + // Math.min(currentPrice, actualPrice - protocolFeeAmount - royaltyAmount); // escrowAmount = - // Math.max(_priceDiscovery.price-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow - escrowAmount = - Math.max(_priceDiscovery.price, protocolFeeAmount + royaltyAmount + currentPrice) - - currentPrice; + // Math.max(actualPrice-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow + escrowAmount = Math.max(actualPrice, protocolFeeAmount + royaltyAmount + currentPrice) - currentPrice; // Update sequential commit sequentialCommits.push( SequentialCommit({ resellerId: exchange.buyerId, - price: _priceDiscovery.price, + price: actualPrice, protocolFeeAmount: protocolFeeAmount, royaltyAmount: royaltyAmount }) ); } - } - - // Release funds to current buyer - // FundsLib.increaseAvailableFunds(exchange.buyerId, tokenAddress, currentBuyerAmount); // must be called before transfer is done - - { - // Amount of funds to go in escrow - // uint256 escrowAmount = ; - // Get current buyer address. This is actually the seller in sequential commit - (, Buyer storage currentBuyer) = fetchBuyer(exchange.buyerId); - - fulFilOrder( - _exchangeId, - tokenAddress, - _priceDiscovery, - escrowAmount, - currentBuyer.wallet, - _buyer, - offer.sellerId - ); + // Make sure enough get escrowed + if (_priceDiscovery.direction == Direction.Buy && escrowAmount > 0) { + // Price discovery should send funds to the seller + // Nothing in escrow, need to pull everything from seller + if (tokenAddress == address(0)) { + FundsLib.transferFundsToProtocol(address(weth), _seller, escrowAmount); + weth.withdraw(escrowAmount); + } else { + FundsLib.transferFundsToProtocol(tokenAddress, _seller, escrowAmount); + } + } else { + // when sell direction, we have full proceeds in escrow. Keep minimal in, return the difference + FundsLib.transferFundsFromProtocol(tokenAddress, payable(_seller), actualPrice - escrowAmount); + } } // since exchange and voucher are passed by reference, they are updated @@ -208,23 +202,14 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - uint256 _escrowAmount, address _seller, address _buyer, uint256 _initialSellerId - ) internal { + ) internal returns (uint256 actualPrice) { if (_priceDiscovery.direction == Direction.Buy) { - fulfilBuyOrder( - _exchangeId, - _exchangeToken, - _priceDiscovery, - _escrowAmount, - _seller, - _buyer, - _initialSellerId - ); + return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); } else { - fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _escrowAmount, _seller, _initialSellerId); + return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _seller, _initialSellerId); } } @@ -232,11 +217,9 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - uint256 _escrowAmount, - address _seller, address _buyer, uint256 _initialSellerId - ) internal { + ) internal returns (uint256 actualPrice) { // Transfer buyers funds to protocol FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); @@ -275,25 +258,13 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); + actualPrice = protocolBalanceBefore - protocolBalanceAfter; - uint256 expectedBalanceAfter = protocolBalanceBefore - _priceDiscovery.price + _escrowAmount; - // console.log("expectedBalanceAfter",expectedBalanceAfter); - if (protocolBalanceAfter > expectedBalanceAfter) { - // Escrowed too much, return the difference to buyer - FundsLib.transferFundsFromProtocol( - _exchangeToken, - payable(_seller), - protocolBalanceAfter - expectedBalanceAfter - ); - } else if (protocolBalanceAfter < expectedBalanceAfter) { - uint256 diff = expectedBalanceAfter - protocolBalanceAfter; - // Not enough in the escrow, pull it form seller - if (_exchangeToken == address(0)) { - FundsLib.transferFundsToProtocol(address(weth), _seller, diff); - weth.withdraw(diff); - } else { - FundsLib.transferFundsToProtocol(_exchangeToken, _seller, diff); - } + uint256 overchargedAmount = _priceDiscovery.price - actualPrice; + + if (overchargedAmount > 0) { + // Return the surplus to buyer + FundsLib.transferFundsFromProtocol(_exchangeToken, payable(_buyer), overchargedAmount); } // Transfer voucher to buyer @@ -304,10 +275,9 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - uint256 _escrowAmount, address _seller, uint256 _initialSellerId - ) internal { + ) internal returns (uint256 actualPrice) { // what about non-zero msg.value? IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); @@ -337,6 +307,11 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); + actualPrice = protocolBalanceAfter - protocolBalanceBefore; + + require(actualPrice >= _priceDiscovery.price, "Price discovery contract returned less than expected"); + + /** uint256 expectedBalanceAfter = protocolBalanceBefore + _escrowAmount; // what about potential non zero msg.value? if (protocolBalanceAfter > expectedBalanceAfter) { @@ -356,6 +331,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { FundsLib.transferFundsToProtocol(_exchangeToken, _seller, diff); } } + */ } function getBalance(address _tokenAddress) internal view returns (uint256) { diff --git a/hardhat.config.js b/hardhat.config.js index 0c23b8bc0..38056f855 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -103,6 +103,8 @@ module.exports = { networks: { hardhat: { accounts: { mnemonic: environments.hardhat.mnemonic }, + gasPrice: 0, + initialBaseFeePerGas: 0, }, localhost: { url: environments.localhost.txNode || "http://127.0.0.1:8545", diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index aafd87e8c..9684a5384 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -1430,509 +1430,503 @@ describe("IBosonExchangeHandler", function () { voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); }); - context("General actions", async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: buyer.address, - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: offer.exchangeToken, - price: price2, - }; + context("Buy order", async function () { + context("General actions", async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + // deposit weth - needed only for Buy direction + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); - // deposit weth - needed only for Buy direction - await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow - await weth.connect(buyer).approve(protocolDiamond.address, price2); + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); - // Approve transfers - // Buyer does not approve, since its in ETH. - // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; - }); + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - it("should emit a BuyerCommitted event", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + // Sequential commit to offer + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("should update state", async function () { - // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - // buyer2 is exchange.buyerId - // Get the exchange as a struct - const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - // Parse into entity - let returnedExchange = Exchange.fromStruct(exchangeStruct); - expect(returnedExchange.buyerId).to.equal(newBuyer.id); + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); - // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); - }); - - it("should transfer the voucher", async function () { - // buyer is owner of voucher - expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // buyer2 is owner of voucher - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); - - it("voucher should remain unchanged", async function () { - // Voucher before - let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - let returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Voucher after - [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - }); - - it("only new buyer can redeem voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherRedeemed") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("only new buyer can cancel voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherCanceled") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("should not increment the next exchange id counter", async function () { - const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); - // Get the next exchange id and ensure it was incremented by the creation of the offer - const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); - expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); - }); + it("should transfer the voucher", async function () { + // buyer is owner of voucher + expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); - it("Should not decrement quantityAvailable", async function () { - // Get quantityAvailable before - const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler - .connect(rando) - .getOffer(offerId); + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); - // Get quantityAvailable after - const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); - expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); - }); + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit on someone else's behalf", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(rando) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); - // buyer2 is owner of voucher, not rando - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if offer is voided", async function () { - // Void the offer - await offerHandler.connect(assistant).voidOffer(offerId); + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if redemption period has not started yet", async function () { - // Redemption not yet possible - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( - RevertReasons.VOUCHER_NOT_REDEEMABLE - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); - it("It is possible to commit even if offer has expired", async function () { - // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - it("It is possible to commit even if is sold out", async function () { - // Commit to all remaining quantity - for (let i = 1; i < offer.quantityAvailable; i++) { - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - } + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler + // Sequential commit to offer, creating a new exchange + await exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - context("💔 Revert Reasons", async function () { - it("The exchanges region of protocol is paused", async function () { - // Pause the exchanges region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler + .connect(rando) + .getOffer(offerId); - // Attempt to sequentially commit, expecting revert + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit on someone else's behalf", async function () { + // Sequential commit to offer, retrieving the event await expect( exchangeHandler - .connect(buyer2) + .connect(rando) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + + // buyer2 is owner of voucher, not rando + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); }); - it("The buyers region of protocol is paused", async function () { - // Pause the buyers region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); - // Attempt to sequentially commit, expecting revert + // Committing directly is not possible await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); - it("buyer address is the zero address", async function () { - // Attempt to sequentially commit, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("exchange id is invalid", async function () { - // An invalid offer id - exchangeId = "666"; + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); - // Attempt to sequentially commit, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("voucher not valid anymore", async function () { - // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); - // Attempt to sequentially commit to the expired voucher, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - it("protocol fees to high", async function () { - // Set protocol fees to 95% - await configHandler.setProtocolFeePercentage(9500); - // Set royalty fees to 6% - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } - // Attempt to commit to the sold out offer, expecting revert + // Committing directly is not possible await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); - }); + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); - it("insufficient values sent", async function () { - // Attempt to commit to the sold out offer, expecting revert + // Sequential commit to offer, retrieving the event await expect( exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) - ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); - }); - }); - context("Escrow amount", async function () { - let scenarios = [ - { case: "Increasing price", multiplier: 11 }, - { case: "Constant price", multiplier: 10 }, - { case: "Decreasing price", multiplier: 9 }, - ]; - // let scenarios=[{case: "Decreasing price", multiplier: 9}]; - - async function getBalances() { - const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ - ethers.provider.getBalance(exchangeHandler.address), - ethers.provider.getBalance(buyer.address), - weth.balanceOf(buyer.address), - ethers.provider.getBalance(buyer2.address), - ethers.provider.getBalance(treasury.address), - ]); + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); - return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; - } - - scenarios.forEach((scenario) => { - context(scenario.case, async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: buyer.address, - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: offer.exchangeToken, - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); - - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); - // deposit weth - needed only for Buy direction - await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow - await weth.connect(buyer).approve(protocolDiamond.address, price2); + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); - // Approve transfers - // Buyer does not approve, since its in ETH. - // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; + it("buyer address is the zero address", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); - const fees = [ - { - protocol: 0, - royalties: 0, - }, - { - protocol: 500, - royalties: 0, - }, - { - protocol: 0, - royalties: 600, - }, - { - protocol: 300, - royalties: 400, // less than profit - }, - { - protocol: 500, - royalties: 700, // more than profit - }, - ]; - - // const fees = [{ - // protocol: 0, royalties: 0 - // }] - - fees.forEach((fee) => { - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - - const balancesBefore = await getBalances(); - - // Sequential commit to offer - await exchangeHandler + it("exchange id is invalid", async function () { + // An invalid offer id + exchangeId = "666"; + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2, gasPrice: 0 }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) - ); - }); + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + it("voucher not valid anymore", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(voucher.validUntilDate)); - const balancesBefore = await getBalances(); + // Attempt to sequentially commit to the expired voucher, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); - // Sequential commit to offer. Buyer pays more than needed - priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); - console.log("original price", price); - console.log("sent it", priceDiscovery.price); - console.log("expected total escrow", priceDiscovery.price); - console.log("expected escrow change", ethers.BigNumber.from(priceDiscovery.price).sub(price)); + // Attempt to commit to the sold out offer, expecting revert + await expect( + exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); - await exchangeHandler + it("insufficient values sent", async function () { + // Attempt to commit to the sold out offer, expecting revert + await expect( + exchangeHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { - value: priceDiscovery.price, - gasPrice: 0, - }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = priceDiscovery.price; - const reducedSecondaryPrice = ethers.BigNumber.from(priceDiscovery.price) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - // let esc2 = price2; - const expectedProtocolChange = ethers.BigNumber.from(priceDiscovery.price).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + }); + }); + + context("Escrow amount", async function () { + let scenarios = [ + { case: "Increasing price", multiplier: 11 }, + { case: "Constant price", multiplier: 10 }, + { case: "Decreasing price", multiplier: 9 }, + ]; + + async function getBalances() { + const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ + ethers.provider.getBalance(exchangeHandler.address), + ethers.provider.getBalance(buyer.address), + weth.balanceOf(buyer.address), + ethers.provider.getBalance(buyer2.address), + ethers.provider.getBalance(treasury.address), + ]); + + return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; + } + + scenarios.forEach((scenario) => { + context(scenario.case, async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy ); + + // deposit weth - needed only for Buy direction + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; }); - it.skip("no protocol fee and royalties - underpaid", async function () { - // price discovery contract reverts + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 500, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 500, + royalties: 700, // more than profit + }, + ]; + + fees.forEach((fee) => { + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + value: price2, + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ + fee.royalties / 100 + }% - overpaid`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer. Buyer pays more than needed + priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); + + await exchangeHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + value: priceDiscovery.price, + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); }); }); }); From 701720d635a0f9f28505fdeff3b60f6baf62f2d1 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 17 Feb 2023 12:14:38 +0100 Subject: [PATCH 15/47] sell order --- contracts/mock/PriceDiscovery.sol | 32 +- .../protocol/facets/ExchangeHandlerFacet.sol | 70 ++- test/protocol/ExchangeHandlerTest.js | 484 +++++++++++++++++- 3 files changed, 536 insertions(+), 50 deletions(-) diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index 55e88d3a3..5e8749f78 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -26,7 +26,7 @@ contract PriceDiscovery { * It just transfers the voucher and exchange token to the buyer * If any of the transfers fail, the whole transaction will revert */ - function fulfilOrder(Order calldata _order) external payable { + function fulfilBuyOrder(Order calldata _order) external payable { // transfer voucher try IERC721(_order.voucherContract).safeTransferFrom(_order.seller, msg.sender, _order.tokenId) {} catch ( bytes memory reason @@ -65,4 +65,34 @@ contract PriceDiscovery { } } } + + function fulfilSellOrder(Order calldata _order) external payable { + // transfer voucher + try IERC721(_order.voucherContract).safeTransferFrom(msg.sender, _order.buyer, _order.tokenId) {} catch ( + bytes memory reason + ) { + if (reason.length == 0) { + revert("Voucher transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + + // transfer exchange token + try IERC20(_order.exchangeToken).transferFrom(_order.buyer, msg.sender, _order.price) {} catch ( + bytes memory reason + ) { + if (reason.length == 0) { + revert("Token transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } } diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 27ecd8c17..25735a51d 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -16,6 +16,7 @@ import { IERC1155 } from "../../interfaces/IERC1155.sol"; import { IERC721 } from "../../interfaces/IERC721.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { Math } from "../../ext_libs/Math.sol"; +import "hardhat/console.sol"; interface WETH9Like { function withdraw(uint256) external; @@ -122,16 +123,20 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get token address address tokenAddress = offer.exchangeToken; - // Get current buyer address. This is actually the seller in sequential commit + // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred address _seller; { (, Buyer storage currentBuyer) = fetchBuyer(exchange.buyerId); _seller = currentBuyer.wallet; } + if (_priceDiscovery.direction == Direction.Sell) { + require(_seller == msgSender(), NOT_VOUCHER_HOLDER); + } + // First call price discovery and get actual price // It might be lower tha submitted for buy orders and higher for sell orders - uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _seller, _buyer, offer.sellerId); + uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer.sellerId); // Calculate the amount to be kept in escrow uint256 escrowAmount; @@ -178,18 +183,26 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } // Make sure enough get escrowed - if (_priceDiscovery.direction == Direction.Buy && escrowAmount > 0) { - // Price discovery should send funds to the seller - // Nothing in escrow, need to pull everything from seller - if (tokenAddress == address(0)) { - FundsLib.transferFundsToProtocol(address(weth), _seller, escrowAmount); - weth.withdraw(escrowAmount); - } else { - FundsLib.transferFundsToProtocol(tokenAddress, _seller, escrowAmount); + if (_priceDiscovery.direction == Direction.Buy) { + if (escrowAmount > 0) { + // Price discovery should send funds to the seller + // Nothing in escrow, need to pull everything from seller + if (tokenAddress == address(0)) { + FundsLib.transferFundsToProtocol(address(weth), _seller, escrowAmount); + weth.withdraw(escrowAmount); + } else { + FundsLib.transferFundsToProtocol(tokenAddress, _seller, escrowAmount); + } } } else { // when sell direction, we have full proceeds in escrow. Keep minimal in, return the difference - FundsLib.transferFundsFromProtocol(tokenAddress, payable(_seller), actualPrice - escrowAmount); + if (tokenAddress == address(0)) { + tokenAddress = address(weth); + if (escrowAmount > 0) weth.withdraw(escrowAmount); + } + + uint256 payout = actualPrice - escrowAmount; + if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(_seller), payout); } } @@ -202,14 +215,13 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - address _seller, address _buyer, uint256 _initialSellerId ) internal returns (uint256 actualPrice) { if (_priceDiscovery.direction == Direction.Buy) { return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); } else { - return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _seller, _initialSellerId); + return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); } } @@ -275,14 +287,16 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - address _seller, uint256 _initialSellerId ) internal returns (uint256 actualPrice) { // what about non-zero msg.value? + // No need to reset approval IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); // Transfer seller's voucher to protocol - bosonVoucher.transferFrom(_seller, address(this), _exchangeId); + bosonVoucher.transferFrom(msgSender(), address(this), _exchangeId); + + if (_exchangeToken == address(0)) _exchangeToken = address(weth); // Get protocol balance before the exchange uint256 protocolBalanceBefore = getBalance(_exchangeToken); @@ -301,37 +315,11 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { require(success, errorMessage); } - // Reset approval - bosonVoucher.approve(address(0), _exchangeId); - // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); actualPrice = protocolBalanceAfter - protocolBalanceBefore; - require(actualPrice >= _priceDiscovery.price, "Price discovery contract returned less than expected"); - - /** - uint256 expectedBalanceAfter = protocolBalanceBefore + _escrowAmount; // what about potential non zero msg.value? - - if (protocolBalanceAfter > expectedBalanceAfter) { - // Return the difference to the seller - FundsLib.transferFundsFromProtocol( - _exchangeToken, - payable(_seller), - protocolBalanceAfter - expectedBalanceAfter - ); - } else if (protocolBalanceAfter < expectedBalanceAfter) { - uint256 diff = expectedBalanceAfter - protocolBalanceAfter; - // Not enough in the escrow, pull it form seller - if (_exchangeToken == address(0)) { - FundsLib.transferFundsToProtocol(address(weth), _seller, diff); - weth.withdraw(diff); - } else { - FundsLib.transferFundsToProtocol(_exchangeToken, _seller, diff); - } - } - */ } function getBalance(address _tokenAddress) internal view returns (uint256) { diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 9684a5384..392f2f8d8 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -1401,10 +1401,10 @@ describe("IBosonExchangeHandler", function () { context("👉 sequentialCommitToOffer()", async function () { let priceDiscoveryContract, priceDiscovery, price2; let newBuyer; + let reseller; // for clarity in tests // TODO: // * ERC20 as exchange token - // * sell order before(async function () { // Deploy PriceDiscovery contract @@ -1428,6 +1428,8 @@ describe("IBosonExchangeHandler", function () { // Update the validUntilDate date in the expected exchange struct voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); + + reseller = buyer; }); context("Buy order", async function () { @@ -1446,7 +1448,7 @@ describe("IBosonExchangeHandler", function () { price: price2, }; - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); priceDiscovery = new PriceDiscovery( price2, @@ -1455,8 +1457,9 @@ describe("IBosonExchangeHandler", function () { Direction.Buy ); - // deposit weth - needed only for Buy direction - await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + // Seller needs to deposit weth in order to fill the escrow at the last step + // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) + await weth.connect(buyer).deposit({ value: price2 }); await weth.connect(buyer).approve(protocolDiamond.address, price2); // Approve transfers @@ -1754,7 +1757,7 @@ describe("IBosonExchangeHandler", function () { // Set royalty fees to 6% await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); - // Attempt to commit to the sold out offer, expecting revert + // Attempt to sequentially commit, expecting revert await expect( exchangeHandler .connect(buyer2) @@ -1763,7 +1766,7 @@ describe("IBosonExchangeHandler", function () { }); it("insufficient values sent", async function () { - // Attempt to commit to the sold out offer, expecting revert + // Attempt to sequentially commit, expecting revert await expect( exchangeHandler .connect(buyer2) @@ -1808,7 +1811,9 @@ describe("IBosonExchangeHandler", function () { price: price2, }; - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilOrder", [order]); + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ + order, + ]); priceDiscovery = new PriceDiscovery( price2, @@ -1817,7 +1822,8 @@ describe("IBosonExchangeHandler", function () { Direction.Buy ); - // deposit weth - needed only for Buy direction + // Seller needs to deposit weth in order to fill the escrow at the last step + // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow await weth.connect(buyer).approve(protocolDiamond.address, price2); @@ -1932,6 +1938,468 @@ describe("IBosonExchangeHandler", function () { }); }); }); + + context("Sell order", async function () { + context("General actions", async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Sell + ); + + // Approve transfers + // Buyer2 needs to approve price discovery to transfer the ETH + await weth.connect(buyer2).deposit({ value: price2 }); + await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + + // Seller approves protocol to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + + mockBuyer(reseller.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + + // Sequential commit to offer + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); + + it("should transfer the voucher", async function () { + // reseller is owner of voucher + expect(await bosonVoucherClone.connect(reseller).ownerOf(exchangeId)).to.equal(reseller.address); + + // Sequential commit to offer + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); + + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + // Sequential commit to offer, creating a new exchange + await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); + + // Committing directly is not possible + await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( + RevertReasons.OFFER_HAS_BEEN_VOIDED + ); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( + RevertReasons.OFFER_HAS_EXPIRED + ); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler + .connect(reseller) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("exchange id is invalid", async function () { + // An invalid offer id + exchangeId = "666"; + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); + + it("voucher not valid anymore", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(voucher.validUntilDate)); + + // Attempt to sequentially commit to the expired voucher, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to sequentially commit, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("voucher transfer not approved", async function () { + // revoke approval + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, false); + + // Attempt to sequentially commit to, expecting revert + await expect( + exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); + }); + + it("Only seller can call, if direction is Sell", async function () { + // Sequential commit to offer, retrieving the event + await expect( + exchangeHandler.connect(rando).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.NOT_VOUCHER_HOLDER); + }); + }); + }); + + context("Escrow amount", async function () { + let scenarios = [ + { case: "Increasing price", multiplier: 11 }, + { case: "Constant price", multiplier: 10 }, + { case: "Decreasing price", multiplier: 9 }, + ]; + + async function getBalances() { + const [protocol, seller, sellerWeth, newBuyer, newBuyerWeth, originalSeller] = await Promise.all([ + ethers.provider.getBalance(exchangeHandler.address), + ethers.provider.getBalance(reseller.address), + weth.balanceOf(reseller.address), + ethers.provider.getBalance(buyer2.address), + weth.balanceOf(buyer2.address), + ethers.provider.getBalance(treasury.address), + ]); + + return { protocol, seller: seller.add(sellerWeth), newBuyer: newBuyer.add(newBuyerWeth), originalSeller }; + } + + scenarios.forEach((scenario) => { + context(scenario.case, async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [ + order, + ]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Sell + ); + + // Approve transfers + // Buyer2 needs to approve price discovery to transfer the ETH + await weth.connect(buyer2).deposit({ value: price2 }); + await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + + // Seller approves protocol to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 500, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 500, + royalties: 700, // more than profit + }, + ]; + + fees.forEach((fee) => { + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer + await exchangeHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ + fee.royalties / 100 + }% - underpriced`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer. Buyer pays more than needed + priceDiscovery.price = ethers.BigNumber.from(price2).div(2).toString(); + + await exchangeHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + }); + }); + }); + }); + }); }); context("👉 completeExchange()", async function () { From 3d9fb9f7db8897202fadb7cd18d1006a3017a469 Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 20 Feb 2023 16:55:21 +0100 Subject: [PATCH 16/47] releaseFunds - COMPLETED --- .../protocol/facets/ExchangeHandlerFacet.sol | 19 +- contracts/protocol/libs/FundsLib.sol | 6 +- test/protocol/FundsHandlerTest.js | 2406 ++++++++++++++++- 3 files changed, 2417 insertions(+), 14 deletions(-) diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 25735a51d..b26b86e06 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -124,14 +124,15 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { address tokenAddress = offer.exchangeToken; // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred - address _seller; + address seller; + uint256 buyerId = exchange.buyerId; { - (, Buyer storage currentBuyer) = fetchBuyer(exchange.buyerId); - _seller = currentBuyer.wallet; + (, Buyer storage currentBuyer) = fetchBuyer(buyerId); + seller = currentBuyer.wallet; } if (_priceDiscovery.direction == Direction.Sell) { - require(_seller == msgSender(), NOT_VOUCHER_HOLDER); + require(seller == msgSender(), NOT_VOUCHER_HOLDER); } // First call price discovery and get actual price @@ -174,7 +175,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Update sequential commit sequentialCommits.push( SequentialCommit({ - resellerId: exchange.buyerId, + resellerId: buyerId, price: actualPrice, protocolFeeAmount: protocolFeeAmount, royaltyAmount: royaltyAmount @@ -188,10 +189,10 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Price discovery should send funds to the seller // Nothing in escrow, need to pull everything from seller if (tokenAddress == address(0)) { - FundsLib.transferFundsToProtocol(address(weth), _seller, escrowAmount); + FundsLib.transferFundsToProtocol(address(weth), seller, escrowAmount); weth.withdraw(escrowAmount); } else { - FundsLib.transferFundsToProtocol(tokenAddress, _seller, escrowAmount); + FundsLib.transferFundsToProtocol(tokenAddress, seller, escrowAmount); } } } else { @@ -202,7 +203,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } uint256 payout = actualPrice - escrowAmount; - if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(_seller), payout); + if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(seller), payout); } } @@ -221,7 +222,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { if (_priceDiscovery.direction == Direction.Buy) { return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); } else { - return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); + // return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); } } diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 10738f649..fad6bd8d7 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -8,6 +8,7 @@ import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { SafeERC20 } from "../../ext_libs/SafeERC20.sol"; import { Math } from "../../ext_libs/Math.sol"; +import "hardhat/console.sol"; /** * @title FundsLib @@ -299,11 +300,12 @@ library FundsLib { // escrowed for exchange between buyer i and i+1 { - uint256 escrowAmount = Math.max(sc.price, sc.protocolFeeAmount + sc.royaltyAmount + resellerBuyPrice) - + uint256 escrowAmount = Math.max(sc.price - sc.protocolFeeAmount - sc.royaltyAmount, resellerBuyPrice) - resellerBuyPrice; currentResellerAmount = (escrowAmount * effectivePriceMultiplier) / 10000 + nextResellerAmount; - nextResellerAmount = escrowAmount + nextResellerAmount - currentResellerAmount; + nextResellerAmount = escrowAmount - currentResellerAmount; + resellerBuyPrice = sc.price; // for next iteration // uint256 nextResellerAmountTemp = escrowAmount - currentResellerAmount; // TODO: is it cheaper to make another memory variable and save one subtraction? // currentResellerAmount += nextResellerAmount; // nextResellerAmount = nextResellerAmountTemp; diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 15bb0ed10..23043adb1 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -5,6 +5,8 @@ const Role = require("../../scripts/domain/Role"); const { Funds, FundsList } = require("../../scripts/domain/Funds"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); +const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); +const Direction = require("../../scripts/domain/Direction"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); @@ -98,6 +100,7 @@ describe("IBosonFundsHandler", function () { agentAvailableFunds; let DRFee, buyerEscalationDeposit; let protocolInitializationFacet; + let buyer1, buyer2, buyer3; before(async function () { // get interface Ids @@ -109,8 +112,22 @@ describe("IBosonFundsHandler", function () { beforeEach(async function () { // Make accounts available - [deployer, pauser, admin, treasury, rando, buyer, feeCollector, adminDR, treasuryDR, other, protocolTreasury] = - await ethers.getSigners(); + [ + deployer, + pauser, + admin, + treasury, + rando, + buyer, + feeCollector, + adminDR, + treasuryDR, + other, + protocolTreasury, + buyer1, + buyer2, + buyer3, + ] = await ethers.getSigners(); // make all account the same assistant = clerk = admin; @@ -163,7 +180,7 @@ describe("IBosonFundsHandler", function () { maxEscalationResponsePeriod: oneMonth, maxDisputesPerBatch: 100, maxAllowedSellers: 100, - maxTotalOfferFeePercentage: 4000, //40% + maxTotalOfferFeePercentage: 10000, //100% maxRoyaltyPecentage: 1000, //10% maxResolutionPeriod: oneMonth, minDisputePeriod: oneWeek, @@ -193,6 +210,13 @@ describe("IBosonFundsHandler", function () { const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); + const wethFactory = await ethers.getContractFactory("WETH9"); + const weth = await wethFactory.deploy(); + await weth.deployed(); + + // Add WETH + facetsToDeploy["ExchangeHandlerFacet"].constructorArgs = [weth.address]; + // Cut the protocol handler facets into the Diamond const { deployedFacets } = await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); protocolInitializationFacet = deployedFacets.find((f) => f.name === "ProtocolInitializationHandlerFacet").contract; @@ -4401,5 +4425,2381 @@ describe("IBosonFundsHandler", function () { }); }); }); + + context("👉 releaseFunds() - Sequential commit", async function () { + let priceDiscoveryContract; + let resellersAvailableFunds, expectedResellersAvailableFunds; + + before(async function () { + // Deploy PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + }); + + // const directions = ["increasing", "constant", "decreasing", "mixed"]; + const directions = ["increasing"]; + + let buyerChains; + beforeEach(async function () { + buyerChains = { + increasing: [ + { buyer: buyer1, price: "150" }, + { buyer: buyer2, price: "160" }, + { buyer: buyer3, price: "400" }, + ], + constant: [ + { buyer: buyer1, price: "100" }, + { buyer: buyer2, price: "100" }, + { buyer: buyer3, price: "100" }, + ], + decreasing: [ + { buyer: buyer1, price: "90" }, + { buyer: buyer2, price: "85" }, + { buyer: buyer3, price: "50" }, + ], + mixed: [ + { buyer: buyer1, price: "130" }, + { buyer: buyer2, price: "130" }, + { buyer: buyer3, price: "120" }, + ], + }; + }); + + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 1000, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 8500, // ridiculously high + royalties: 700, + }, + ]; + + directions.forEach((direction) => { + let bosonVoucherClone; + let offer; + + context(`Direction: ${direction}`, async function () { + fees.forEach((fee) => { + context(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + let expectedCloneAddress; + let voucherOwner, previousPrice; + let payoutInformation = []; + let totalRoyalties, totalProtocolFee; + + beforeEach(async function () { + expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + + // set fees + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + offer = offerToken.clone(); + offer.id = "3"; + offer.price = "100"; + offer.sellerDeposit = "10"; + offer.buyerCancelPenalty = "30"; + + // approve protocol to transfer the tokens + + // deposit to seller's pool + await fundsHandler.connect(clerk).withdrawFunds(seller.id, [], []); // withdraw all, so it's easier to test + await mockToken.connect(assistant).mint(assistant.address, offer.sellerDeposit); + await mockToken.connect(assistant).approve(fundsHandler.address, offer.sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, offer.sellerDeposit); + + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, 0); + + // ids + exchangeId = "1"; + protocolId = "0"; + + // Create buyer with protocol address to not mess up ids in tests + await accountHandler.createBuyer(mockBuyer(exchangeHandler.address)); + + // commit to offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id); + + // ids + exchangeId = "1"; + agentId = "3"; + buyerId = 5; + + voucherOwner = buyer; // voucherOwner is the first buyer + previousPrice = offer.price; + totalRoyalties = new ethers.BigNumber.from(0); + totalProtocolFee = new ethers.BigNumber.from(0); + for (const trade of buyerChains[direction]) { + // Prepare calldata for PriceDiscovery contract + let order = { + seller: voucherOwner.address, + buyer: trade.buyer.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: ethers.BigNumber.from(offer.price).mul(trade.price).div(100), + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ + order, + ]); + + const priceDiscovery = new PriceDiscovery( + order.price, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // voucher owner approves protocol to transfer the tokens + await mockToken.mint(voucherOwner.address, order.price); + await mockToken.connect(voucherOwner).approve(protocolDiamond.address, order.price); + + // Voucher owner approves PriceDiscovery contract to transfer the tokens + await bosonVoucherClone.connect(voucherOwner).setApprovalForAll(priceDiscoveryContract.address, true); + + // Buyer approves protocol to transfer the tokens + await mockToken.mint(trade.buyer.address, order.price); + await mockToken.connect(trade.buyer).approve(protocolDiamond.address, order.price); + + // commit to offer + await exchangeHandler + .connect(trade.buyer) + .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); + + // Fees, royalites and immediate payout + const royalties = order.price.mul(fee.royalties).div(10000); + const protocolFee = order.price.mul(fee.protocol).div(10000); + const reducedSecondaryPrice = order.price.sub(royalties).sub(protocolFee); + const immediatePayout = reducedSecondaryPrice.lte(previousPrice) + ? reducedSecondaryPrice + : previousPrice; + payoutInformation.push({ buyerId: buyerId++, immediatePayout, reducedSecondaryPrice }); + + // Total royalties and fees + totalRoyalties = totalRoyalties.add(royalties); + totalProtocolFee = totalProtocolFee.add(protocolFee); + + voucherOwner = trade.buyer; // last buyer is voucherOwner in next iteration + previousPrice = order.price; + } + }); + + context("Final state COMPLETED", async function () { + let resellerPayoffs; + beforeEach(async function () { + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + + // succesfully redeem exchange + await exchangeHandler.connect(voucherOwner).redeemVoucher(exchangeId); + + // expected payoffs + // last buyer: 0 + + // resellers + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; + }); + + // seller: sellerDeposit + price - protocolFee + royalties + const initialFee = applyPercentage(offer.price, fee.protocol); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .add(totalRoyalties) + .sub(initialFee) + .toString(); + + // protocol: protocolFee + protocolPayoff = totalProtocolFee.add(initialFee).toString(); + }); + + it("should emit a FundsReleased event", async function () { + // Complete the exchange, expecting event + const tx = await exchangeHandler.connect(voucherOwner).completeExchange(exchangeId); + + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); + + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + voucherOwner.address + ); + } + } + + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, voucherOwner.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); + + it("should update state", async function () { + // // commit again, so seller has nothing in available funds + // await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // Complete the exchange so the funds are released + await exchangeHandler.connect(voucherOwner).completeExchange(exchangeId); + + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocolFee - agentFee + royalties + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList(r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : []); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); + }); + }); + }); + }); + + 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; + }); + + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.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)); + 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(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)); + 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(protocolDiamond.address, `${2 * sellerDeposit}`); + await mockToken.connect(buyer).approve(protocolDiamond.address, `${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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.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)); + 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); + + // 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + 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(); + + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.buyerCancelPenalty).toString(); + + // protocol: 0 + protocolPayoff = 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); + + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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); + + // 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.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)); + 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(protocolDiamond.address, `${2 * sellerDeposit}`); + await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); + + // deposit to seller's pool + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); + + // Commit to Offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + + // expected payoffs + // buyer: price - buyerCancelPenalty + buyerPayoff = ethers.BigNumber.from(agentOffer.price).sub(agentOffer.buyerCancelPenalty).toString(); + + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) + .add(agentOffer.buyerCancelPenalty) + .toString(); + + // 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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); + + // 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.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)); + 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 () { + // ProtocolInitializationHandlerFacet has to be passed to deploy function works + const facetsToDeploy = await getFacetsWithArgs(["DisputeHandlerFacet"]); + + await deployAndCutFacets( + protocolDiamond.address, + facetsToDeploy, + maxPriorityFeePerGas, + "2.1.0", + protocolInitializationFacet + ); + + // Cast Diamond to IBosonDisputeHandler + disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); + + // 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("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(); + + // protocol: 0 + protocolPayoff = offerTokenProtocolFee; + }); + + 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); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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( + mockToken.address, + "Foreign20", + 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)); + 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 = 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: 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); + }); + + 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); + + await expect(tx) + .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); + }); + + 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)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.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)); + 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)); + 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 = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(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(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)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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; 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.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)); + 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); + + // 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) + .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(); + + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + new Funds(mockToken.address, "Foreign20", sellerPayoff), + ]); + + 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)); + 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(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(); + + // 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 + )); + }); + + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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( + mockToken.address, + "Foreign20", + 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)); + + 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); + + 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 = 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(); + + // 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 + )); + }); + + 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)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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); + // 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)]); + 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)); + + 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; + + // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit + sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) + .add(offerToken.price) + .sub(offerTokenProtocolFee) + .add(buyerEscalationDeposit) + .toString(); + + // protocol: 0 + protocolPayoff = offerTokenProtocolFee; + + // Escalate the dispute + await disputeHandler.connect(buyer).escalateDispute(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, buyer.address); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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 + 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.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)); + 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 = 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) + .add(buyerEscalationDeposit) + .toString(); + + // protocol: 0 + protocolPayoff = agentOfferProtocolFee; + + // 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(protocolDiamond.address, agentOffer.price); + await mockToken.mint(buyer.address, agentOffer.price); + 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); + + // escalate the dispute + await mockToken.mint(buyer.address, buyerEscalationDeposit); + await mockToken.connect(buyer).approve(protocolDiamond.address, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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 + 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)); + 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)); + 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); + }); + + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + 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)); + 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(protocolDiamond.address, 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); + + 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; + + // 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(protocolDiamond.address, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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); + // 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)]); + 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)); + + 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); + }); + + 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); + + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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); 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() + ); + 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)); + 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(protocolDiamond.address, 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); + + // 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(protocolDiamond.address, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.push( + 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)); + 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); + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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; 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() + ); + 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)); + 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(protocolDiamond.address, 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(protocolDiamond.address, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.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)); + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // 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}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + 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)); + 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(protocolDiamond.address, 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(protocolDiamond.address, 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([ + new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), + ]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + 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.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)); + 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", protocolDiamond.address); + + // 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 () { + exchangeId = "2"; + + // Cast Diamond to IBosonConfigHandler + configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + + // 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(protocolDiamond.address, sellerDeposit); + await mockToken.connect(buyer).approve(protocolDiamond.address, 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 eba75f7451a669b95b30985a6f12a4630f71f4db Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 10:21:54 +0100 Subject: [PATCH 17/47] releaseFunds - REVOKED --- .../protocol/facets/FundsHandlerFacet.sol | 32 +- contracts/protocol/libs/FundsLib.sol | 64 +++- test/protocol/FundsHandlerTest.js | 300 +++++++----------- 3 files changed, 176 insertions(+), 220 deletions(-) diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 4dbb0ceb1..40babb89e 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -48,25 +48,27 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { address _tokenAddress, uint256 _amount ) external payable override fundsNotPaused nonReentrant { - // Check seller exists in sellers mapping - (bool exists, , ) = fetchSeller(_sellerId); + if (_amount > 0) { + // Check seller exists in sellers mapping + (bool exists, , ) = fetchSeller(_sellerId); - // Seller must exist - require(exists, NO_SUCH_SELLER); + // Seller must exist + require(exists, NO_SUCH_SELLER); - if (msg.value != 0) { - // Receiving native currency - require(_tokenAddress == address(0), NATIVE_WRONG_ADDRESS); - require(_amount == msg.value, NATIVE_WRONG_AMOUNT); - } else { - // Transfer tokens from the caller - FundsLib.transferFundsToProtocol(_tokenAddress, _amount); - } + if (msg.value != 0) { + // Receiving native currency + require(_tokenAddress == address(0), NATIVE_WRONG_ADDRESS); + require(_amount == msg.value, NATIVE_WRONG_AMOUNT); + } else { + // Transfer tokens from the caller + FundsLib.transferFundsToProtocol(_tokenAddress, _amount); + } - // Increase available funds - FundsLib.increaseAvailableFunds(_sellerId, _tokenAddress, _amount); + // Increase available funds + FundsLib.increaseAvailableFunds(_sellerId, _tokenAddress, _amount); - emit FundsDeposited(_sellerId, msgSender(), _tokenAddress, _amount); + emit FundsDeposited(_sellerId, msgSender(), _tokenAddress, _amount); + } } /** diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index fad6bd8d7..f154947ee 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -258,16 +258,45 @@ library FundsLib { // calculate effective price multiplier uint256 effectivePriceMultiplier; + // { + // if (_exchangeState == BosonTypes.ExchangeState.Completed) { + // // COMPLETED, buyer pays full price + // effectivePriceMultiplier = 10000; + // } else if ( + // _exchangeState == BosonTypes.ExchangeState.Revoked || + // _exchangeState == BosonTypes.ExchangeState.Canceled + // ) { + // // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) + // effectivePriceMultiplier = 0; + // } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { + // // DISPUTED + // // get the information about the dispute, which must exist + // BosonTypes.Dispute storage dispute = ProtocolLib.protocolEntities().disputes[_exchangeId]; + // BosonTypes.DisputeState disputeState = dispute.state; + + // if (disputeState == BosonTypes.DisputeState.Retracted) { + // // RETRACTED - same as "COMPLETED" + // effectivePriceMultiplier = 10000; + // } else if (disputeState == BosonTypes.DisputeState.Refused) { + // // REFUSED, buyer pays nothing + // effectivePriceMultiplier = 0; + // } else { + // // RESOLVED or DECIDED + // effectivePriceMultiplier = 10000 - dispute.buyerPercent; + // } + // } + // } + { if (_exchangeState == BosonTypes.ExchangeState.Completed) { // COMPLETED, buyer pays full price - effectivePriceMultiplier = 10000; + effectivePriceMultiplier = 0; } else if ( _exchangeState == BosonTypes.ExchangeState.Revoked || _exchangeState == BosonTypes.ExchangeState.Canceled ) { // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) - effectivePriceMultiplier = 0; + effectivePriceMultiplier = 10000; } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { // DISPUTED // get the information about the dispute, which must exist @@ -276,20 +305,20 @@ library FundsLib { if (disputeState == BosonTypes.DisputeState.Retracted) { // RETRACTED - same as "COMPLETED" - effectivePriceMultiplier = 10000; + effectivePriceMultiplier = 0; } else if (disputeState == BosonTypes.DisputeState.Refused) { // REFUSED, buyer pays nothing - effectivePriceMultiplier = 0; + effectivePriceMultiplier = 10000; } else { // RESOLVED or DECIDED - effectivePriceMultiplier = 10000 - dispute.buyerPercent; + effectivePriceMultiplier = dispute.buyerPercent; } } } uint256 resellerBuyPrice = _initialPrice; address msgSender = EIP712Lib.msgSender(); - uint256 nextResellerAmount; + // uint256 nextResellerAmount; for (uint256 i = 0; i < sequentialCommits.length; i++) { BosonTypes.SequentialCommit memory sc = sequentialCommits[i]; // we need all members of the struct @@ -297,18 +326,27 @@ library FundsLib { royalties += sc.royaltyAmount; uint256 currentResellerAmount; + uint256 reducedSecondaryPrice = sc.price - sc.protocolFeeAmount - sc.royaltyAmount; // escrowed for exchange between buyer i and i+1 { - uint256 escrowAmount = Math.max(sc.price - sc.protocolFeeAmount - sc.royaltyAmount, resellerBuyPrice) - - resellerBuyPrice; + // uint256 escrowAmount = Math.max(sc.price - sc.protocolFeeAmount - sc.royaltyAmount, resellerBuyPrice) - + // resellerBuyPrice; - currentResellerAmount = (escrowAmount * effectivePriceMultiplier) / 10000 + nextResellerAmount; - nextResellerAmount = escrowAmount - currentResellerAmount; - resellerBuyPrice = sc.price; // for next iteration + // currentResellerAmount = ((escrowAmount) * effectivePriceMultiplier) / 10000 + nextResellerAmount; + // nextResellerAmount = escrowAmount + nextResellerAmount - currentResellerAmount; + // resellerBuyPrice = sc.price; // for next iteration // uint256 nextResellerAmountTemp = escrowAmount - currentResellerAmount; // TODO: is it cheaper to make another memory variable and save one subtraction? // currentResellerAmount += nextResellerAmount; // nextResellerAmount = nextResellerAmountTemp; + currentResellerAmount = + (effectivePriceMultiplier * + resellerBuyPrice + + (10000 - effectivePriceMultiplier) * + reducedSecondaryPrice) / + 10000 - + Math.min(resellerBuyPrice, reducedSecondaryPrice); + resellerBuyPrice = sc.price; } if (currentResellerAmount > 0) { increaseAvailableFundsAndEmitEvent( @@ -321,8 +359,8 @@ library FundsLib { } } - protocolFee = (protocolFee * effectivePriceMultiplier) / 10000; - royalties = (royalties * effectivePriceMultiplier) / 10000; + protocolFee = (protocolFee * (10000 - effectivePriceMultiplier)) / 10000; + royalties = (royalties * (10000 - effectivePriceMultiplier)) / 10000; // ? do we need to return nextResellerAmount and add it to buyerPayoff? } diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 23043adb1..b05468694 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4437,8 +4437,8 @@ describe("IBosonFundsHandler", function () { await priceDiscoveryContract.deployed(); }); - // const directions = ["increasing", "constant", "decreasing", "mixed"]; - const directions = ["increasing"]; + const directions = ["increasing", "constant", "decreasing", "mixed"]; + // const directions = ["increasing"]; let buyerChains; beforeEach(async function () { @@ -4512,8 +4512,8 @@ describe("IBosonFundsHandler", function () { offer = offerToken.clone(); offer.id = "3"; offer.price = "100"; - offer.sellerDeposit = "10"; - offer.buyerCancelPenalty = "30"; + offer.sellerDeposit = "0"; + offer.buyerCancelPenalty = "00"; // approve protocol to transfer the tokens @@ -4543,7 +4543,7 @@ describe("IBosonFundsHandler", function () { buyerId = 5; voucherOwner = buyer; // voucherOwner is the first buyer - previousPrice = offer.price; + previousPrice = ethers.BigNumber.from(offer.price); totalRoyalties = new ethers.BigNumber.from(0); totalProtocolFee = new ethers.BigNumber.from(0); for (const trade of buyerChains[direction]) { @@ -4593,7 +4593,7 @@ describe("IBosonFundsHandler", function () { const immediatePayout = reducedSecondaryPrice.lte(previousPrice) ? reducedSecondaryPrice : previousPrice; - payoutInformation.push({ buyerId: buyerId++, immediatePayout, reducedSecondaryPrice }); + payoutInformation.push({ buyerId: buyerId++, immediatePayout, previousPrice, reducedSecondaryPrice }); // Total royalties and fees totalRoyalties = totalRoyalties.add(royalties); @@ -4616,7 +4616,7 @@ describe("IBosonFundsHandler", function () { // expected payoffs // last buyer: 0 - // resellers + // resellers: difference between the secondary price and immediate payout resellerPayoffs = payoutInformation.map((pi) => { return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; }); @@ -4637,12 +4637,16 @@ describe("IBosonFundsHandler", function () { // Complete the exchange, expecting event const tx = await exchangeHandler.connect(voucherOwner).completeExchange(exchangeId); + // seller await expect(tx) .to.emit(exchangeHandler, "FundsReleased") .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); + // resellers + let expectedEventCount = 1; // 1 for seller for (const resellerPayoff of resellerPayoffs) { if (resellerPayoff.payoff != "0") { + expectedEventCount++; await expect(tx) .to.emit(exchangeHandler, "FundsReleased") .withArgs( @@ -4655,6 +4659,11 @@ describe("IBosonFundsHandler", function () { } } + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); + + // protocol if (protocolPayoff != "0") { await expect(tx) .to.emit(exchangeHandler, "ProtocolFeeCollected") @@ -4665,9 +4674,6 @@ describe("IBosonFundsHandler", function () { }); it("should update state", async function () { - // // commit again, so seller has nothing in available funds - // await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id); - // Read on chain state sellersAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(seller.id)); buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); @@ -4695,6 +4701,7 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: 0 // seller: sellerDeposit + price - protocolFee - agentFee + royalties + // resellers: difference between the secondary price and immediate payout // protocol: protocolFee // agent: 0 expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); @@ -4722,203 +4729,112 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); - }); - }); - }); - }); - - 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; - }); - - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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.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)); - 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(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)); - 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(protocolDiamond.address, `${2 * sellerDeposit}`); - await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); + context("Final state REVOKED", async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: sellerDeposit + price + buyerPayoff = ethers.BigNumber.from(offer.sellerDeposit).add(offer.price).toString(); - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${2 * sellerDeposit}`); + // resellers: difference between original price and immediate payoff + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.previousPrice.sub(pi.immediatePayout).toString() }; + }); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // seller: 0 + sellerPayoff = 0; - // expected payoffs - // buyer: sellerDeposit + price - buyerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit).add(agentOffer.price).toString(); + // protocol: 0 + protocolPayoff = 0; + }); - // seller: 0 - sellerPayoff = 0; + it("should emit a FundsReleased event", async function () { + // Revoke the voucher, expecting event + const tx = await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); - // protocol: 0 - protocolPayoff = 0; + // Buyer + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); - // agent: 0 - agentPayoff = 0; + // Resellers + let expectedEventCount = 1; // 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + assistant.address + ); + } + } - exchangeId = "2"; - }); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - 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)); + // Expect no protocol fee + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + }); - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // Revoke the voucher so the funds are released - await exchangeHandler.connect(assistant).revokeVoucher(exchangeId); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); - // Available funds should be increased for - // buyer: sellerDeposit + price - // seller: 0 - // 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)); - 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); - // 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); + // Available funds should be increased for + // buyer: sellerDeposit + price + // seller: 0 + // resellers: difference between original price and immediate payoff + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList(r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : []); + }); - // Revoke another voucher - await exchangeHandler.connect(assistant).revokeVoucher(++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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - 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); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); + }); }); }); }); From c113d2ac6e2be0f890b983b9e2cf7d4bbd14c084 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 10:34:01 +0100 Subject: [PATCH 18/47] releaseFunds - CANCELED --- test/protocol/FundsHandlerTest.js | 115 +++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index b05468694..5c9bbcb67 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4512,8 +4512,8 @@ describe("IBosonFundsHandler", function () { offer = offerToken.clone(); offer.id = "3"; offer.price = "100"; - offer.sellerDeposit = "0"; - offer.buyerCancelPenalty = "00"; + offer.sellerDeposit = "10"; + offer.buyerCancelPenalty = "30"; // approve protocol to transfer the tokens @@ -4834,6 +4834,117 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); + + context("Final state CANCELED", async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: price - buyerCancelPenalty + buyerPayoff = ethers.BigNumber.from(offer.price).sub(offer.buyerCancelPenalty).toString(); + + // resellers: difference between original price and immediate payoff + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.previousPrice.sub(pi.immediatePayout).toString() }; + }); + + // seller: sellerDeposit + buyerCancelPenalty + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit).add(offer.buyerCancelPenalty).toString(); + + // protocol: 0 + protocolPayoff = 0; + }); + + it("should emit a FundsReleased event", async function () { + // Cancel the voucher, expecting event + const tx = await exchangeHandler.connect(voucherOwner).cancelVoucher(exchangeId); + + // Buyer + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, voucherOwner.address); + + // Seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); + + // Resellers + let expectedEventCount = 2; // 1 for buyer, 1 for seller + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + voucherOwner.address + ); + } + } + + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); + + // Expect no protocol fee + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // Cancel the voucher, so the funds are released + await exchangeHandler.connect(voucherOwner).cancelVoucher(exchangeId); + + // Available funds should be increased for + // buyer: price - buyerCancelPenalty + // seller: sellerDeposit + buyerCancelPenalty + // resellers: difference between original price and immediate payoff + // protocol: 0 + // agent: 0 + expectedSellerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", sellerPayoff); + expectedBuyerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", buyerPayoff)); + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList(r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : []); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); }); }); }); From 091154ef377d8bbf8fdf43d1553981dc04f7bee8 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 10:44:41 +0100 Subject: [PATCH 19/47] releaseFunds - RETRACTED --- test/protocol/FundsHandlerTest.js | 570 ++++++++++++++---------------- 1 file changed, 258 insertions(+), 312 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 5c9bbcb67..1b52714da 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4945,163 +4945,288 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); - }); - }); - }); - }); - 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("Final state DISPUTED", async function () { + beforeEach(async function () { + // ProtocolInitializationHandlerFacet has to be passed to deploy function works + const facetsToDeploy = await getFacetsWithArgs(["DisputeHandlerFacet"]); + + await deployAndCutFacets( + protocolDiamond.address, + facetsToDeploy, + maxPriorityFeePerGas, + "2.1.0", + protocolInitializationFacet + ); - // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit).add(offerToken.buyerCancelPenalty).toString(); + // Cast Diamond to IBosonDisputeHandler + disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); - // protocol: 0 - protocolPayoff = 0; - }); + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - 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); + // succesfully redeem exchange + await exchangeHandler.connect(voucherOwner).redeemVoucher(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, buyer.address); + // raise the dispute + tx = await disputeHandler.connect(voucherOwner).raiseDispute(exchangeId); - await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); - }); + // 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(); + }); - 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)); + context("Final state DISPUTED - RETRACTED", async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: 0 + buyerPayoff = 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; + }); - // Cancel the voucher, so the funds are released - await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); + // seller: sellerDeposit + price - protocolFee + royalties + const initialFee = applyPercentage(offer.price, fee.protocol); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .add(totalRoyalties) + .sub(initialFee) + .toString(); - // 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.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)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // protocol: protocolFee + protocolPayoff = totalProtocolFee.add(initialFee).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); + it("should emit a FundsReleased event", async function () { + // Retract from the dispute, expecting event + const tx = await disputeHandler.connect(voucherOwner).retractDispute(exchangeId); - // top up seller's and buyer's account - await mockToken.mint(assistant.address, `${2 * sellerDeposit}`); - await mockToken.mint(buyer.address, `${2 * price}`); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); + + // resellers + let expectedEventCount = 1; // 1 for seller + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + voucherOwner.address + ); + } + } - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamond.address, `${2 * sellerDeposit}`); - await mockToken.connect(buyer).approve(protocolDiamond.address, `${2 * price}`); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, `${sellerDeposit}`); + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, voucherOwner.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); - // 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // Retract from the dispute, so the funds are released + await disputeHandler.connect(voucherOwner).retractDispute(exchangeId); + + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocolFee - agentFee + royalties + // resellers: difference between the secondary price and immediate payout + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); - // expected payoffs - // buyer: price - buyerCancelPenalty - buyerPayoff = ethers.BigNumber.from(agentOffer.price).sub(agentOffer.buyerCancelPenalty).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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); - // seller: sellerDeposit + buyerCancelPenalty - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.buyerCancelPenalty) - .toString(); + context("Final state DISPUTED - RETRACTED via expireDispute", async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: 0 + buyerPayoff = 0; - // protocol: 0 - protocolPayoff = 0; + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; + }); - // agent: 0 - agentPayoff = 0; + // seller: sellerDeposit + price - protocolFee + royalties + const initialFee = applyPercentage(offer.price, fee.protocol); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .add(totalRoyalties) + .sub(initialFee) + .toString(); - exchangeId = "2"; - }); + // protocol: protocolFee + protocolPayoff = totalProtocolFee.add(initialFee).toString(); - 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)); + await setNextBlockTimestamp(Number(timeout)); + }); - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireDispute(exchangeId); - // Cancel the voucher, so the funds are released - await exchangeHandler.connect(buyer).cancelVoucher(exchangeId); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); + + // resellers + let expectedEventCount = 1; // 1 for seller + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + rando.address + ); + } + } - // 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.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)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); + + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, rando.address); + } else { + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // 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 - protocolFee - agentFee + royalties + // resellers: difference between the secondary price and immediate payout + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); + }); + }); }); }); }); @@ -5138,185 +5263,6 @@ describe("IBosonFundsHandler", function () { timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); }); - 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(); - - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; - }); - - 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); - - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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( - mockToken.address, - "Foreign20", - 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)); - 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 = 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: 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); - }); - - 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); - - await expect(tx) - .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); - }); - - 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)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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.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)); - 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)); - 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 From 31e16fac49b7cc9148ab84a603068d2779e0e932 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 11:39:57 +0100 Subject: [PATCH 20/47] releaseFunds - DISPUTED - RESOLVED --- test/protocol/FundsHandlerTest.js | 577 +++++++++--------------------- 1 file changed, 169 insertions(+), 408 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 1b52714da..359d9c7eb 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -5225,6 +5225,175 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); + + context("Final state DISPUTED - RESOLVED", async function () { + let resellerPayoffs; + beforeEach(async function () { + buyerPercentBasisPoints = "5000"; // 55.66% + const sellerPercentBasisPoints = 10000 - parseInt(buyerPercentBasisPoints); // 44.34% + + // expected payoffs + // last buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = applyPercentage( + ethers.BigNumber.from(offer.price).add(offer.sellerDeposit), + buyerPercentBasisPoints + ); + + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + const diff = pi.reducedSecondaryPrice.sub(pi.previousPrice); + const payoff = diff.gt(0) + ? applyPercentage(diff, sellerPercentBasisPoints) + : applyPercentage(diff.mul(-1), buyerPercentBasisPoints); + return { id: pi.buyerId, payoff }; + }); + + // seller: sellerDeposit + price + royalties + const initialFee = applyPercentage(offer.price, "0"); + sellerPayoff = applyPercentage( + ethers.BigNumber.from(offer.sellerDeposit).add(offer.price).add(totalRoyalties).sub(initialFee), + sellerPercentBasisPoints + ); + + // protocol: protocolFee (only secondary market) + protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); + + // 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( + voucherOwner, // Assistant is the caller, seller should be the signer. + customSignatureType, + "Resolution", + message, + disputeHandler.address + )); + }); + + it("should emit a FundsReleased event", async function () { + // Retract from the dispute, expecting event + // Resolve the dispute, expecting event + const tx = await disputeHandler + .connect(assistant) + .resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); + + // buyer + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); + + // resellers + let expectedEventCount = 2; // 1 for seller, 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + assistant.address + ); + } + } + + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); + + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, assistant.address); + } else { + 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)); + buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); + protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); + agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // 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) + // resellers: (difference between the secondary price and immediate payout)*(1-buyerPercentage) + // protocol: protocolFee (secondary market only) + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + ]); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); }); }); }); @@ -5263,414 +5432,6 @@ describe("IBosonFundsHandler", function () { timeout = ethers.BigNumber.from(disputedDate).add(resolutionPeriod).toString(); }); - context("Final state DISPUTED - RETRACTED via expireDispute", 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(); - - // 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(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)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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; 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.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)); - 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); - - // 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) - .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(); - - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - new Funds(mockToken.address, "Foreign20", sellerPayoff), - ]); - - 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)); - 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(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(); - - // 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 - )); - }); - - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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( - mockToken.address, - "Foreign20", - 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)); - - 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); - - 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 = 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(); - - // 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 - )); - }); - - 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)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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); - // 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)]); - 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)); - - 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 From 8a71b3a8b9ce24b6a30a4ebb1d6506b60d08a43b Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 12:05:02 +0100 Subject: [PATCH 21/47] releaseFunds - ESCALATED --- test/protocol/FundsHandlerTest.js | 989 +++++++++++++----------------- 1 file changed, 423 insertions(+), 566 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 359d9c7eb..5f252a7da 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -5229,7 +5229,7 @@ describe("IBosonFundsHandler", function () { context("Final state DISPUTED - RESOLVED", async function () { let resellerPayoffs; beforeEach(async function () { - buyerPercentBasisPoints = "5000"; // 55.66% + buyerPercentBasisPoints = "5566"; // 55.66% const sellerPercentBasisPoints = 10000 - parseInt(buyerPercentBasisPoints); // 44.34% // expected payoffs @@ -5250,10 +5250,11 @@ describe("IBosonFundsHandler", function () { // seller: sellerDeposit + price + royalties const initialFee = applyPercentage(offer.price, "0"); - sellerPayoff = applyPercentage( - ethers.BigNumber.from(offer.sellerDeposit).add(offer.price).add(totalRoyalties).sub(initialFee), - sellerPercentBasisPoints - ); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .sub(buyerPayoff) + .add(applyPercentage(totalRoyalties, sellerPercentBasisPoints)) + .toString(); // protocol: protocolFee (only secondary market) protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); @@ -5284,7 +5285,6 @@ describe("IBosonFundsHandler", function () { }); it("should emit a FundsReleased event", async function () { - // Retract from the dispute, expecting event // Resolve the dispute, expecting event const tx = await disputeHandler .connect(assistant) @@ -5394,629 +5394,486 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); - }); - }); - }); - }); - }); - context("Final state DISPUTED", async function () { - beforeEach(async function () { - // ProtocolInitializationHandlerFacet has to be passed to deploy function works - const facetsToDeploy = await getFacetsWithArgs(["DisputeHandlerFacet"]); + context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: 0 + buyerPayoff = 0; - await deployAndCutFacets( - protocolDiamond.address, - facetsToDeploy, - maxPriorityFeePerGas, - "2.1.0", - protocolInitializationFacet - ); + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; + }); - // Cast Diamond to IBosonDisputeHandler - disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); + // seller: sellerDeposit + price - protocolFee + royalties + const initialFee = applyPercentage(offer.price, fee.protocol); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .add(totalRoyalties) + .sub(initialFee) + .toString(); - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + // protocol: protocolFee + protocolPayoff = totalProtocolFee.add(initialFee).toString(); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // Escalate the dispute + await disputeHandler.connect(voucherOwner).escalateDispute(exchangeId); + }); - // raise the dispute - tx = 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(voucherOwner).retractDispute(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(); - }); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); - context("Final state DISPUTED - ESCALATED - RETRACTED", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // resellers + let expectedEventCount = 1; // 1 for seller + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + voucherOwner.address + ); + } + } - // seller: sellerDeposit + price - protocolFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(offerToken.sellerDeposit) - .add(offerToken.price) - .sub(offerTokenProtocolFee) - .add(buyerEscalationDeposit) - .toString(); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // protocol: 0 - protocolPayoff = offerTokenProtocolFee; + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, voucherOwner.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); - // Escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - 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([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); - await expect(tx) - .to.emit(disputeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, protocolPayoff, buyer.address); + // Retract from the dispute, so the funds are released + await disputeHandler.connect(voucherOwner).retractDispute(exchangeId); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, buyer.address); + // Available funds should be increased for + // buyer: 0 + // seller: sellerDeposit + price - protocolFee - agentFee + royalties + // resellers: difference between the secondary price and immediate payout + // protocol: protocolFee + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); - //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; - }); + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - 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)); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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 () { + let resellerPayoffs; + beforeEach(async function () { + buyerPercentBasisPoints = "5566"; // 55.66% + const sellerPercentBasisPoints = 10000 - parseInt(buyerPercentBasisPoints); // 44.34% - // Retract from the dispute, so the funds are released - await disputeHandler.connect(buyer).retractDispute(exchangeId); + // expected payoffs + // last buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = applyPercentage( + ethers.BigNumber.from(offer.price).add(offer.sellerDeposit), + buyerPercentBasisPoints + ); - // 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.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)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + const diff = pi.reducedSecondaryPrice.sub(pi.previousPrice); + const payoff = diff.gt(0) + ? applyPercentage(diff, sellerPercentBasisPoints) + : applyPercentage(diff.mul(-1), buyerPercentBasisPoints); + return { id: pi.buyerId, payoff }; + }); - context("Offer has an agent", async function () { - beforeEach(async function () { - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // seller: sellerDeposit + price + royalties + const initialFee = applyPercentage(offer.price, "0"); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .sub(buyerPayoff) + .add(applyPercentage(totalRoyalties, sellerPercentBasisPoints)) + .toString(); - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + // protocol: protocolFee (only secondary market) + protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); - // seller: sellerDeposit + price - protocolFee - agentFee + buyerEscalationDeposit - sellerPayoff = ethers.BigNumber.from(agentOffer.sellerDeposit) - .add(agentOffer.price) - .sub(agentOfferProtocolFee) - .sub(agentFee) - .add(buyerEscalationDeposit) - .toString(); - - // protocol: 0 - protocolPayoff = agentOfferProtocolFee; - - // 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(protocolDiamond.address, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - 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); - - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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 + 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)); - 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)); - 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); - }); - - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - 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)); - 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(protocolDiamond.address, 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); - - buyerPercentBasisPoints = "5566"; // 55.66% + // Set the message Type, needed for signature + resolutionType = [ + { name: "exchangeId", type: "uint256" }, + { name: "buyerPercentBasisPoints", type: "uint256" }, + ]; - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); + customSignatureType = { + Resolution: resolutionType, + }; - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + message = { + exchangeId: exchangeId, + buyerPercentBasisPoints, + }; - // protocol: 0 - protocolPayoff = 0; + // Collect the signature components + ({ r, s, v } = await prepareDataSignatureParameters( + voucherOwner, // 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" }, - ]; + // Escalate the dispute + await disputeHandler.connect(voucherOwner).escalateDispute(exchangeId); + }); - customSignatureType = { - Resolution: resolutionType, - }; + 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); - message = { - exchangeId: exchangeId, - buyerPercentBasisPoints, - }; + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistant.address); - // Collect the signature components - ({ r, s, v } = await prepareDataSignatureParameters( - buyer, // Assistant is the caller, seller should be the signer. - customSignatureType, - "Resolution", - message, - disputeHandler.address - )); + // buyer + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistant.address); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // resellers + let expectedEventCount = 2; // 1 for seller, 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + assistant.address + ); + } + } - 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)); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, assistant.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); - // Resolve the dispute, so the funds are released - await disputeHandler.connect(assistant).resolveDispute(exchangeId, buyerPercentBasisPoints, r, s, v); + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // Available funds should be increased for - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - // 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)]); - 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)); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); - 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("Final state DISPUTED - ESCALATED - DECIDED", async function () { - beforeEach(async function () { - buyerPercentBasisPoints = "5566"; // 55.66% + // Available funds should be increased for + // buyer: (price + sellerDeposit)*buyerPercentage + // seller: (price + sellerDeposit)*(1-buyerPercentage) + // resellers: (difference between the secondary price and immediate payout)*(1-buyerPercentage) + // protocol: protocolFee (secondary market only) + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + ]); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(offerToken.price) - .add(offerToken.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); - // protocol: 0 - protocolPayoff = 0; + context("Final state DISPUTED - ESCALATED - DECIDED", async function () { + let resellerPayoffs; + beforeEach(async function () { + buyerPercentBasisPoints = "4321"; // 43.21% + const sellerPercentBasisPoints = 10000 - parseInt(buyerPercentBasisPoints); // 44.34% - // escalate the dispute - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // expected payoffs + // last buyer: (price + sellerDeposit)*buyerPercentage + buyerPayoff = applyPercentage( + ethers.BigNumber.from(offer.price).add(offer.sellerDeposit), + buyerPercentBasisPoints + ); - 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); + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + const diff = pi.reducedSecondaryPrice.sub(pi.previousPrice); + const payoff = diff.gt(0) + ? applyPercentage(diff, sellerPercentBasisPoints) + : applyPercentage(diff.mul(-1), buyerPercentBasisPoints); + return { id: pi.buyerId, payoff }; + }); - await expect(tx) - .to.emit(disputeHandler, "FundsReleased") - .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); + // seller: sellerDeposit + price + royalties + const initialFee = applyPercentage(offer.price, "0"); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .sub(buyerPayoff) + .add(applyPercentage(totalRoyalties, sellerPercentBasisPoints)) + .toString(); - await expect(tx).to.not.emit(disputeHandler, "ProtocolFeeCollected"); - }); + // protocol: protocolFee (only secondary market) + protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); - 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)); + // Escalate the dispute + await disputeHandler.connect(voucherOwner).escalateDispute(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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + it("should emit a FundsReleased event", async function () { + // Decide the dispute, expecting event + const tx = await disputeHandler + .connect(assistantDR) + .decideDispute(exchangeId, buyerPercentBasisPoints); - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); - // 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.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - 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)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); + // buyer + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.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); + // resellers + let expectedEventCount = 2; // 1 for seller, 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + assistantDR.address + ); + } + } - // approve protocol to transfer the tokens - await mockToken.connect(buyer).approve(protocolDiamond.address, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, assistantDR.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); - exchangeId = "2"; + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // Decide the dispute, so the funds are released + await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); - // 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(); + // Available funds should be increased for + // buyer: (price + sellerDeposit)*buyerPercentage + // seller: (price + sellerDeposit)*(1-buyerPercentage) + // resellers: (difference between the secondary price and immediate payout)*(1-buyerPercentage) + // protocol: protocolFee (secondary market only) + // agent: 0 + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + expectedBuyerAvailableFunds = new FundsList([ + new Funds(mockToken.address, "Foreign20", buyerPayoff), + ]); + if (protocolPayoff != "0") { + expectedProtocolAvailableFunds.funds.push( + new Funds(mockToken.address, "Foreign20", protocolPayoff) + ); + } + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); - buyerPercentBasisPoints = "5566"; // 55.66% + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); - // expected payoffs - // buyer: (price + sellerDeposit + buyerEscalationDeposit)*buyerPercentage - buyerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .mul(buyerPercentBasisPoints) - .div("10000") - .toString(); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + }); + }); + }); + }); + }); + }); - // seller: (price + sellerDeposit + buyerEscalationDeposit)*(1-buyerPercentage) - sellerPayoff = ethers.BigNumber.from(agentOffer.price) - .add(agentOffer.sellerDeposit) - .add(buyerEscalationDeposit) - .sub(buyerPayoff) - .toString(); + context("Final state DISPUTED", async function () { + beforeEach(async function () { + // ProtocolInitializationHandlerFacet has to be passed to deploy function works + const facetsToDeploy = await getFacetsWithArgs(["DisputeHandlerFacet"]); - // protocol: 0 - protocolPayoff = 0; + await deployAndCutFacets( + protocolDiamond.address, + facetsToDeploy, + maxPriorityFeePerGas, + "2.1.0", + protocolInitializationFacet + ); - // escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // Cast Diamond to IBosonDisputeHandler + disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); - 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)); + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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); - // Decide the dispute, so the funds are released - await disputeHandler.connect(assistantDR).decideDispute(exchangeId, buyerPercentBasisPoints); + // 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.funds.push( - 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)); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - }); - }); + // 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( From c71f3b55ec4723227c10114f063d7bfde093baa2 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 12:26:24 +0100 Subject: [PATCH 22/47] releaseFunds - REFUSED --- test/protocol/FundsHandlerTest.js | 595 ++++++++++++------------------ 1 file changed, 234 insertions(+), 361 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 5f252a7da..e5b1ada14 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4099,7 +4099,7 @@ describe("IBosonFundsHandler", function () { }); it("should emit a FundsReleased event", async function () { - // Expire the dispute, expecting event + // Refuse the dispute, expecting event const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); await expect(tx) @@ -4144,7 +4144,7 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Expire the escalated dispute, so the funds are released + // Refuse the escalated dispute, so the funds are released await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Available funds should be increased for @@ -4225,7 +4225,7 @@ describe("IBosonFundsHandler", function () { expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); - // Expire the escalated dispute, so the funds are released + // Refuse the escalated dispute, so the funds are released await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); // Available funds should be increased for @@ -5543,7 +5543,7 @@ describe("IBosonFundsHandler", function () { return { id: pi.buyerId, payoff }; }); - // seller: sellerDeposit + price + royalties + // seller: (sellerDeposit + price + royalties)*(1-buyerPercentage) const initialFee = applyPercentage(offer.price, "0"); sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) .add(offer.price) @@ -5551,7 +5551,7 @@ describe("IBosonFundsHandler", function () { .add(applyPercentage(totalRoyalties, sellerPercentBasisPoints)) .toString(); - // protocol: protocolFee (only secondary market) + // protocol: protocolFee *(1-buyerPercentage) protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); // Set the message Type, needed for signature @@ -5658,9 +5658,9 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage) + // seller: (price + sellerDeposit + royalties)*(1-buyerPercentage) // resellers: (difference between the secondary price and immediate payout)*(1-buyerPercentage) - // protocol: protocolFee (secondary market only) + // protocol: protocolFee *(1-buyerPercentage) // agent: 0 expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); expectedBuyerAvailableFunds = new FundsList([ @@ -5715,7 +5715,7 @@ describe("IBosonFundsHandler", function () { return { id: pi.buyerId, payoff }; }); - // seller: sellerDeposit + price + royalties + // seller: (sellerDeposit + price + royalties)*(1-buyerPercentage) const initialFee = applyPercentage(offer.price, "0"); sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) .add(offer.price) @@ -5723,7 +5723,7 @@ describe("IBosonFundsHandler", function () { .add(applyPercentage(totalRoyalties, sellerPercentBasisPoints)) .toString(); - // protocol: protocolFee (only secondary market) + // protocol: protocolFee*(1-buyerPercentage) protocolPayoff = applyPercentage(totalProtocolFee.add(initialFee), sellerPercentBasisPoints); // Escalate the dispute @@ -5804,9 +5804,9 @@ describe("IBosonFundsHandler", function () { // Available funds should be increased for // buyer: (price + sellerDeposit)*buyerPercentage - // seller: (price + sellerDeposit)*(1-buyerPercentage) + // seller: (price + sellerDeposit + royalties)*(1-buyerPercentage) // resellers: (difference between the secondary price and immediate payout)*(1-buyerPercentage) - // protocol: protocolFee (secondary market only) + // protocol: protocolFee *(1-buyerPercentage) // agent: 0 expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); expectedBuyerAvailableFunds = new FundsList([ @@ -5838,384 +5838,257 @@ describe("IBosonFundsHandler", function () { expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); }); }); - }); - }); - }); - }); - }); - - context("Final state DISPUTED", async function () { - beforeEach(async function () { - // ProtocolInitializationHandlerFacet has to be passed to deploy function works - const facetsToDeploy = await getFacetsWithArgs(["DisputeHandlerFacet"]); - - await deployAndCutFacets( - protocolDiamond.address, - facetsToDeploy, - maxPriorityFeePerGas, - "2.1.0", - protocolInitializationFacet - ); - - // Cast Diamond to IBosonDisputeHandler - disputeHandler = await ethers.getContractAt("IBosonDisputeHandler", protocolDiamond.address); - - // 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( - "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); - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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; 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() - ); - 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)); - 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(protocolDiamond.address, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - exchangeId = "2"; + context( + "Final state DISPUTED - ESCALATED - REFUSED via expireEscalatedDispute (fail to resolve)", + async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: price + buyerEscalationDeposit + buyerPayoff = ethers.BigNumber.from(offer.price).add(buyerEscalationDeposit).toString(); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // resellers: difference between original price and immediate payoff + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.previousPrice.sub(pi.immediatePayout).toString() }; + }); - // raise the dispute - tx = await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // seller: sellerDeposit + sellerPayoff = offer.sellerDeposit; - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // protocol: 0 + protocolPayoff = 0; - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // Escalate the dispute + tx = await disputeHandler.connect(voucherOwner).escalateDispute(exchangeId); - // protocol: 0 - protocolPayoff = 0; + // Get the block timestamp of the confirmed tx and set escalatedDate + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + escalatedDate = block.timestamp.toString(); - // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, buyerEscalationDeposit); - tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + await setNextBlockTimestamp( + Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod) + ); + }); - // Get the block timestamp of the confirmed tx and set escalatedDate - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - escalatedDate = block.timestamp.toString(); + it("should emit a FundsReleased event", async function () { + // Expire the dispute, expecting event + const tx = await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); - await setNextBlockTimestamp(Number(escalatedDate) + Number(disputeResolver.escalationResponsePeriod)); - }); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, rando.address); - 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)); + // buyer + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, rando.address); + + // resellers + let expectedEventCount = 2; // 1 for seller, 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + rando.address + ); + } + } - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(rando).expireEscalatedDispute(exchangeId); + // protocol + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + }); - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // 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()) + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // 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 + // resellers: difference between the secondary price and immediate payout + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + } ); - 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)); - 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)); - buyerAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(buyerId)); - protocolAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(protocolId)); - agentAvailableFunds = FundsList.fromStruct(await fundsHandler.getAvailableFunds(agentId)); - - // 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}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - 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.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); - 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)); - 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(protocolDiamond.address, agentOffer.price); - await mockToken.mint(buyer.address, agentOffer.price); - - // Commit to Offer - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, agentOffer.id); - exchangeId = "2"; + context( + "Final state DISPUTED - ESCALATED - REFUSED via refuseEscalatedDispute (explicit refusal)", + async function () { + let resellerPayoffs; + beforeEach(async function () { + // expected payoffs + // last buyer: price + buyerEscalationDeposit + buyerPayoff = ethers.BigNumber.from(offer.price).add(buyerEscalationDeposit).toString(); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // resellers: difference between original price and immediate payoff + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.previousPrice.sub(pi.immediatePayout).toString() }; + }); - // raise the dispute - await disputeHandler.connect(buyer).raiseDispute(exchangeId); + // seller: sellerDeposit + sellerPayoff = offer.sellerDeposit; - // expected payoffs - // buyer: price + buyerEscalationDeposit - buyerPayoff = ethers.BigNumber.from(offerToken.price).add(buyerEscalationDeposit).toString(); + // protocol: 0 + protocolPayoff = 0; - // seller: sellerDeposit - sellerPayoff = offerToken.sellerDeposit; + // Escalate the dispute + await disputeHandler.connect(voucherOwner).escalateDispute(exchangeId); + }); - // protocol: 0 - protocolPayoff = 0; + it("should emit a FundsReleased event", async function () { + // Refuse the dispute, expecting event + const tx = await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); - // Escalate the dispute - await mockToken.mint(buyer.address, buyerEscalationDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, buyerEscalationDeposit); - await disputeHandler.connect(buyer).escalateDispute(exchangeId); - }); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, assistantDR.address); - 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)); + // buyer + await expect(tx) + .to.emit(disputeHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, offerToken.exchangeToken, buyerPayoff, assistantDR.address); + + // resellers + let expectedEventCount = 2; // 1 for seller, 1 for buyer + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + assistantDR.address + ); + } + } - // Chain state should match the expected available funds - expectedSellerAvailableFunds = new FundsList([ - new Funds(ethers.constants.AddressZero, "Native currency", `${2 * sellerDeposit}`), - ]); - expectedBuyerAvailableFunds = new FundsList([]); - expectedProtocolAvailableFunds = new FundsList([]); - expectedAgentAvailableFunds = new FundsList([]); - expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); - expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); - expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); - expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); - // Expire the escalated dispute, so the funds are released - await disputeHandler.connect(assistantDR).refuseEscalatedDispute(exchangeId); + // protocol + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + }); - // Available funds should be increased for - // buyer: price + buyerEscalationDeposit - // 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()) + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + // Chain state should match the expected available funds + expectedSellerAvailableFunds = new FundsList([]); + expectedBuyerAvailableFunds = new FundsList([]); + expectedProtocolAvailableFunds = new FundsList([]); + expectedAgentAvailableFunds = new FundsList([]); + expectedResellersAvailableFunds = new Array(resellerPayoffs.length).fill(new FundsList([])); + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + + // Refuse 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 + // resellers: difference between the secondary price and immediate payout + // protocol: 0 + // agent: 0 + expectedBuyerAvailableFunds.funds[0] = new Funds(mockToken.address, "Foreign20", buyerPayoff); + expectedSellerAvailableFunds.funds.push(new Funds(mockToken.address, "Foreign20", sellerPayoff)); + expectedResellersAvailableFunds = resellerPayoffs.map((r) => { + return new FundsList( + r.payoff != "0" ? [new Funds(mockToken.address, "Foreign20", r.payoff)] : [] + ); + }); + + 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)); + resellersAvailableFunds = ( + await Promise.all(resellerPayoffs.map((r) => fundsHandler.getAvailableFunds(r.id))) + ).map((returnedValue) => FundsList.fromStruct(returnedValue)); + + expect(sellersAvailableFunds).to.eql(expectedSellerAvailableFunds); + expect(buyerAvailableFunds).to.eql(expectedBuyerAvailableFunds); + expect(protocolAvailableFunds).to.eql(expectedProtocolAvailableFunds); + expect(agentAvailableFunds).to.eql(expectedAgentAvailableFunds); + expect(resellersAvailableFunds).to.eql(expectedResellersAvailableFunds); + }); + } ); - 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)); - 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 () { From bbc3e19d211bfcd68c1ece6da4a85e9a20e4d261 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 12:59:22 +0100 Subject: [PATCH 23/47] changing fee + royalties --- test/protocol/FundsHandlerTest.js | 316 +++++++++++++++--------------- 1 file changed, 161 insertions(+), 155 deletions(-) diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index e5b1ada14..b8349021e 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4438,7 +4438,6 @@ describe("IBosonFundsHandler", function () { }); const directions = ["increasing", "constant", "decreasing", "mixed"]; - // const directions = ["increasing"]; let buyerChains; beforeEach(async function () { @@ -4496,13 +4495,12 @@ describe("IBosonFundsHandler", function () { context(`Direction: ${direction}`, async function () { fees.forEach((fee) => { context(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { - let expectedCloneAddress; let voucherOwner, previousPrice; let payoutInformation = []; let totalRoyalties, totalProtocolFee; beforeEach(async function () { - expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); + const expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); // set fees @@ -4515,8 +4513,6 @@ describe("IBosonFundsHandler", function () { offer.sellerDeposit = "10"; offer.buyerCancelPenalty = "30"; - // approve protocol to transfer the tokens - // deposit to seller's pool await fundsHandler.connect(clerk).withdrawFunds(seller.id, [], []); // withdraw all, so it's easier to test await mockToken.connect(assistant).mint(assistant.address, offer.sellerDeposit); @@ -4529,7 +4525,8 @@ describe("IBosonFundsHandler", function () { // ids exchangeId = "1"; - protocolId = "0"; + agentId = "3"; + buyerId = 5; // Create buyer with protocol address to not mess up ids in tests await accountHandler.createBuyer(mockBuyer(exchangeHandler.address)); @@ -4537,11 +4534,6 @@ describe("IBosonFundsHandler", function () { // commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id); - // ids - exchangeId = "1"; - agentId = "3"; - buyerId = 5; - voucherOwner = buyer; // voucherOwner is the first buyer previousPrice = ethers.BigNumber.from(offer.price); totalRoyalties = new ethers.BigNumber.from(0); @@ -4586,7 +4578,7 @@ describe("IBosonFundsHandler", function () { gasPrice: 0, }); - // Fees, royalites and immediate payout + // Fees, royalties and immediate payout const royalties = order.price.mul(fee.royalties).div(10000); const protocolFee = order.price.mul(fee.protocol).div(10000); const reducedSecondaryPrice = order.price.sub(royalties).sub(protocolFee); @@ -6088,179 +6080,193 @@ describe("IBosonFundsHandler", function () { }); }); }); - }); - }); - context("Changing the protocol fee", async function () { - beforeEach(async function () { - // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + context("Changing the protocol fee and royalties", async function () { + let voucherOwner, previousPrice; + let payoutInformation = []; + let totalRoyalties, totalProtocolFee; + let resellerPayoffs; - // 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 + beforeEach(async function () { + const fees = [ + { protocol: 100, royalties: 50 }, + { protocol: 400, royalties: 200 }, + { protocol: 300, royalties: 300 }, + { protocol: 700, royalties: 100 }, + ]; - // 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(); + let feeIndex = 0; + let fee = fees[feeIndex]; - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + // set fees + const expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); + const bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // create a new offer + offer = offerToken.clone(); + offer.id = "3"; + offer.price = "100"; + offer.sellerDeposit = "10"; + offer.buyerCancelPenalty = "30"; - // 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); + // deposit to seller's pool + await fundsHandler.connect(clerk).withdrawFunds(seller.id, [], []); // withdraw all, so it's easier to test + await mockToken.connect(assistant).mint(assistant.address, offer.sellerDeposit); + await mockToken.connect(assistant).approve(fundsHandler.address, offer.sellerDeposit); + await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, offer.sellerDeposit); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, offerToken.exchangeToken, offerTokenProtocolFee, buyer.address); - }); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, 0); - context("Offer has an agent", async function () { - beforeEach(async function () { - exchangeId = "2"; + // ids + exchangeId = "1"; + agentId = "3"; + buyerId = 5; - // Cast Diamond to IBosonConfigHandler - configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + // Create buyer with protocol address to not mess up ids in tests + await accountHandler.createBuyer(mockBuyer(exchangeHandler.address)); - // expected payoffs - // buyer: 0 - buyerPayoff = 0; + // commit to offer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id); - // agentPayoff: agentFee - agentFee = ethers.BigNumber.from(agentOffer.price).mul(agentFeePercentage).div("10000").toString(); - agentPayoff = agentFee; + voucherOwner = buyer; // voucherOwner is the first buyer + previousPrice = ethers.BigNumber.from(offer.price); + totalRoyalties = new ethers.BigNumber.from(0); + totalProtocolFee = new ethers.BigNumber.from(0); + for (const trade of buyerChains[direction]) { + feeIndex++; + fee = fees[feeIndex]; - // 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 new fee + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - // set the new procol fee - protocolFeePercentage = "300"; // 3% - await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - }); + // Prepare calldata for PriceDiscovery contract + let order = { + seller: voucherOwner.address, + buyer: trade.buyer.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: ethers.BigNumber.from(offer.price).mul(trade.price).div(100), + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ + order, + ]); - 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)); + const priceDiscovery = new PriceDiscovery( + order.price, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + // voucher owner approves protocol to transfer the tokens + await mockToken.mint(voucherOwner.address, order.price); + await mockToken.connect(voucherOwner).approve(protocolDiamond.address, order.price); - // Complete the exchange, expecting event - const tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + // Voucher owner approves PriceDiscovery contract to transfer the tokens + await bosonVoucherClone.connect(voucherOwner).setApprovalForAll(priceDiscoveryContract.address, true); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + // Buyer approves protocol to transfer the tokens + await mockToken.mint(trade.buyer.address, order.price); + await mockToken.connect(trade.buyer).approve(protocolDiamond.address, order.price); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + // commit to offer + await exchangeHandler + .connect(trade.buyer) + .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); - }); + // Fees, royalties and immediate payout + const royalties = order.price.mul(fee.royalties).div(10000); + const protocolFee = order.price.mul(fee.protocol).div(10000); + const reducedSecondaryPrice = order.price.sub(royalties).sub(protocolFee); + const immediatePayout = reducedSecondaryPrice.lte(previousPrice) + ? reducedSecondaryPrice + : previousPrice; + payoutInformation.push({ buyerId: buyerId++, immediatePayout, previousPrice, reducedSecondaryPrice }); - 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 + // Total royalties and fees + totalRoyalties = totalRoyalties.add(royalties); + totalProtocolFee = totalProtocolFee.add(protocolFee); - // top up seller's and buyer's account - await mockToken.mint(assistant.address, sellerDeposit); - await mockToken.mint(buyer.address, price); + voucherOwner = trade.buyer; // last buyer is voucherOwner in next iteration + previousPrice = order.price; + } - // approve protocol to transfer the tokens - await mockToken.connect(assistant).approve(protocolDiamond.address, sellerDeposit); - await mockToken.connect(buyer).approve(protocolDiamond.address, price); + // expected payoffs + // buyer: 0 + buyerPayoff = 0; - // deposit to seller's pool - await fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, sellerDeposit); + // resellers: difference between the secondary price and immediate payout + resellerPayoffs = payoutInformation.map((pi) => { + return { id: pi.buyerId, payoff: pi.reducedSecondaryPrice.sub(pi.immediatePayout).toString() }; + }); - // 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(); + // seller: sellerDeposit + price - protocolFee + royalties + const initialFee = applyPercentage(offer.price, fees[0].protocol); + sellerPayoff = ethers.BigNumber.from(offer.sellerDeposit) + .add(offer.price) + .add(totalRoyalties) + .sub(initialFee) + .toString(); - // Set time forward to the offer's voucherRedeemableFrom - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + // protocol: protocolFee + protocolPayoff = totalProtocolFee.add(initialFee).toString(); + }); - // succesfully redeem exchange - await exchangeHandler.connect(buyer).redeemVoucher(exchangeId); + it("Fees and royalties should be the same as at the commit time", async function () { + // set the new protocol fee + protocolFeePercentage = "300"; // 3% + await configHandler.connect(deployer).setProtocolFeePercentage(protocolFeePercentage); - // Complete the exchange, expecting event - tx = await exchangeHandler.connect(buyer).completeExchange(exchangeId); + // Set time forward to the offer's voucherRedeemableFrom + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - // Complete the exchange, expecting event - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, seller.id, agentOffer.exchangeToken, sellerPayoff, buyer.address); + // succesfully redeem exchange + await exchangeHandler.connect(voucherOwner).redeemVoucher(exchangeId); - await expect(tx) - .to.emit(exchangeHandler, "ProtocolFeeCollected") - .withArgs(exchangeId, agentOffer.exchangeToken, protocolPayoff, buyer.address); + // seller + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs(exchangeId, seller.id, offerToken.exchangeToken, sellerPayoff, voucherOwner.address); - await expect(tx) - .to.emit(exchangeHandler, "FundsReleased") - .withArgs(exchangeId, agentId, agentOffer.exchangeToken, agentPayoff, buyer.address); + // resellers + let expectedEventCount = 1; // 1 for seller + for (const resellerPayoff of resellerPayoffs) { + if (resellerPayoff.payoff != "0") { + expectedEventCount++; + await expect(tx) + .to.emit(exchangeHandler, "FundsReleased") + .withArgs( + exchangeId, + resellerPayoff.id, + offer.exchangeToken, + resellerPayoff.payoff, + voucherOwner.address + ); + } + } + + // Make sure exact number of FundsReleased events was emitted + const eventCount = (await tx.wait()).events.filter((e) => e.event == "FundsReleased").length; + expect(eventCount).to.equal(expectedEventCount); + + // protocol + if (protocolPayoff != "0") { + await expect(tx) + .to.emit(exchangeHandler, "ProtocolFeeCollected") + .withArgs(exchangeId, offer.exchangeToken, protocolPayoff, voucherOwner.address); + } else { + await expect(tx).to.not.emit(exchangeHandler, "ProtocolFeeCollected"); + } + }); }); }); }); From c992cc9f6f749b294e91fa7f734998dcb2259029 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 21 Feb 2023 15:34:06 +0100 Subject: [PATCH 24/47] Refactor --- contracts/protocol/libs/FundsLib.sol | 132 +++++++++------------------ test/protocol/FundsHandlerTest.js | 12 ++- 2 files changed, 55 insertions(+), 89 deletions(-) diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index f154947ee..735dcb8f9 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -245,109 +245,68 @@ library FundsLib { uint256 _initialPrice, address _exchangeToken ) internal returns (uint256 protocolFee, uint256 royalties) { - // ProtocolLib.protocolEntities() and sequentialCommits.length are not stored to memory due to stack too deep errors - // Revisit when update to newest compiler version - - BosonTypes.SequentialCommit[] storage sequentialCommits = ProtocolLib.protocolEntities().sequentialCommits[ - _exchangeId - ]; - - if (sequentialCommits.length == 0) { - return (0, 0); - } + BosonTypes.SequentialCommit[] storage sequentialCommits; // calculate effective price multiplier uint256 effectivePriceMultiplier; - // { - // if (_exchangeState == BosonTypes.ExchangeState.Completed) { - // // COMPLETED, buyer pays full price - // effectivePriceMultiplier = 10000; - // } else if ( - // _exchangeState == BosonTypes.ExchangeState.Revoked || - // _exchangeState == BosonTypes.ExchangeState.Canceled - // ) { - // // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) - // effectivePriceMultiplier = 0; - // } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { - // // DISPUTED - // // get the information about the dispute, which must exist - // BosonTypes.Dispute storage dispute = ProtocolLib.protocolEntities().disputes[_exchangeId]; - // BosonTypes.DisputeState disputeState = dispute.state; - - // if (disputeState == BosonTypes.DisputeState.Retracted) { - // // RETRACTED - same as "COMPLETED" - // effectivePriceMultiplier = 10000; - // } else if (disputeState == BosonTypes.DisputeState.Refused) { - // // REFUSED, buyer pays nothing - // effectivePriceMultiplier = 0; - // } else { - // // RESOLVED or DECIDED - // effectivePriceMultiplier = 10000 - dispute.buyerPercent; - // } - // } - // } - { - if (_exchangeState == BosonTypes.ExchangeState.Completed) { - // COMPLETED, buyer pays full price - effectivePriceMultiplier = 0; - } else if ( - _exchangeState == BosonTypes.ExchangeState.Revoked || - _exchangeState == BosonTypes.ExchangeState.Canceled - ) { - // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) - effectivePriceMultiplier = 10000; - } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { - // DISPUTED - // get the information about the dispute, which must exist - BosonTypes.Dispute storage dispute = ProtocolLib.protocolEntities().disputes[_exchangeId]; - BosonTypes.DisputeState disputeState = dispute.state; + ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities(); - if (disputeState == BosonTypes.DisputeState.Retracted) { - // RETRACTED - same as "COMPLETED" - effectivePriceMultiplier = 0; - } else if (disputeState == BosonTypes.DisputeState.Refused) { - // REFUSED, buyer pays nothing + sequentialCommits = pe.sequentialCommits[_exchangeId]; + + if (sequentialCommits.length == 0) { + return (0, 0); + } + + { + if (_exchangeState == BosonTypes.ExchangeState.Completed) { + // COMPLETED, buyer pays full price effectivePriceMultiplier = 10000; - } else { - // RESOLVED or DECIDED - effectivePriceMultiplier = dispute.buyerPercent; + } else if ( + _exchangeState == BosonTypes.ExchangeState.Revoked || + _exchangeState == BosonTypes.ExchangeState.Canceled + ) { + // REVOKED or CANCELED, buyer pays nothing (buyerCancelationPenalty is not considered payment) + effectivePriceMultiplier = 0; + } else if (_exchangeState == BosonTypes.ExchangeState.Disputed) { + // DISPUTED + // get the information about the dispute, which must exist + BosonTypes.Dispute storage dispute = pe.disputes[_exchangeId]; + BosonTypes.DisputeState disputeState = dispute.state; + + if (disputeState == BosonTypes.DisputeState.Retracted) { + // RETRACTED - same as "COMPLETED" + effectivePriceMultiplier = 10000; + } else if (disputeState == BosonTypes.DisputeState.Refused) { + // REFUSED, buyer pays nothing + effectivePriceMultiplier = 0; + } else { + // RESOLVED or DECIDED + effectivePriceMultiplier = 10000 - dispute.buyerPercent; + } } } } uint256 resellerBuyPrice = _initialPrice; address msgSender = EIP712Lib.msgSender(); - // uint256 nextResellerAmount; - for (uint256 i = 0; i < sequentialCommits.length; i++) { + uint256 len = sequentialCommits.length; + for (uint256 i = 0; i < len; i++) { BosonTypes.SequentialCommit memory sc = sequentialCommits[i]; // we need all members of the struct protocolFee += sc.protocolFeeAmount; royalties += sc.royaltyAmount; - uint256 currentResellerAmount; uint256 reducedSecondaryPrice = sc.price - sc.protocolFeeAmount - sc.royaltyAmount; - // escrowed for exchange between buyer i and i+1 - { - // uint256 escrowAmount = Math.max(sc.price - sc.protocolFeeAmount - sc.royaltyAmount, resellerBuyPrice) - - // resellerBuyPrice; - - // currentResellerAmount = ((escrowAmount) * effectivePriceMultiplier) / 10000 + nextResellerAmount; - // nextResellerAmount = escrowAmount + nextResellerAmount - currentResellerAmount; - // resellerBuyPrice = sc.price; // for next iteration - // uint256 nextResellerAmountTemp = escrowAmount - currentResellerAmount; // TODO: is it cheaper to make another memory variable and save one subtraction? - // currentResellerAmount += nextResellerAmount; - // nextResellerAmount = nextResellerAmountTemp; - currentResellerAmount = - (effectivePriceMultiplier * - resellerBuyPrice + - (10000 - effectivePriceMultiplier) * - reducedSecondaryPrice) / - 10000 - - Math.min(resellerBuyPrice, reducedSecondaryPrice); - resellerBuyPrice = sc.price; - } + uint256 currentResellerAmount = ( + reducedSecondaryPrice > resellerBuyPrice + ? effectivePriceMultiplier * (reducedSecondaryPrice - resellerBuyPrice) + : (10000 - effectivePriceMultiplier) * (resellerBuyPrice - reducedSecondaryPrice) + ) / 10000; + + resellerBuyPrice = sc.price; + if (currentResellerAmount > 0) { increaseAvailableFundsAndEmitEvent( _exchangeId, @@ -359,9 +318,8 @@ library FundsLib { } } - protocolFee = (protocolFee * (10000 - effectivePriceMultiplier)) / 10000; - royalties = (royalties * (10000 - effectivePriceMultiplier)) / 10000; - // ? do we need to return nextResellerAmount and add it to buyerPayoff? + protocolFee = (protocolFee * effectivePriceMultiplier) / 10000; + royalties = (royalties * effectivePriceMultiplier) / 10000; } function increaseAvailableFundsAndEmitEvent( diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index b8349021e..73b9c7732 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4496,10 +4496,12 @@ describe("IBosonFundsHandler", function () { fees.forEach((fee) => { context(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { let voucherOwner, previousPrice; - let payoutInformation = []; + let payoutInformation; let totalRoyalties, totalProtocolFee; beforeEach(async function () { + payoutInformation = []; + const expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); @@ -4527,6 +4529,7 @@ describe("IBosonFundsHandler", function () { exchangeId = "1"; agentId = "3"; buyerId = 5; + protocolId = 0; // Create buyer with protocol address to not mess up ids in tests await accountHandler.createBuyer(mockBuyer(exchangeHandler.address)); @@ -6083,11 +6086,13 @@ describe("IBosonFundsHandler", function () { context("Changing the protocol fee and royalties", async function () { let voucherOwner, previousPrice; - let payoutInformation = []; + let payoutInformation; let totalRoyalties, totalProtocolFee; let resellerPayoffs; beforeEach(async function () { + payoutInformation = []; + const fees = [ { protocol: 100, royalties: 50 }, { protocol: 400, royalties: 200 }, @@ -6232,6 +6237,9 @@ describe("IBosonFundsHandler", function () { // succesfully redeem exchange await exchangeHandler.connect(voucherOwner).redeemVoucher(exchangeId); + // complete exchange + tx = await exchangeHandler.connect(voucherOwner).completeExchange(exchangeId); + // seller await expect(tx) .to.emit(exchangeHandler, "FundsReleased") From 628ed83b9fc0886b05aa75fd6fbe382e7ece6855 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 28 Feb 2023 10:48:47 +0100 Subject: [PATCH 25/47] refactor into separate facet --- .../example/SnapshotGate/support/ERC721.sol | 2 +- .../IERC721Receiver.sol | 0 contracts/interfaces/IWETH9Like.sol | 21 + .../handlers/IBosonExchangeHandler.sol | 11 +- .../IBosonSequentialCommitHandler.sol | 54 + .../protocol/bases/PriceDiscoveryBase.sol | 188 +++ .../protocol/facets/ExchangeHandlerFacet.sol | 265 ---- .../facets/SequenatialCommitHandlerFacet.sol | 210 +++ contracts/protocol/libs/FundsLib.sol | 2 - scripts/config/facet-deploy.js | 1 + scripts/config/supported-interfaces.js | 9 +- test/protocol/ExchangeHandlerTest.js | 1016 ------------ test/protocol/SequentialCommitHandlerTest.js | 1361 +++++++++++++++++ 13 files changed, 1844 insertions(+), 1296 deletions(-) rename contracts/{example/SnapshotGate/support => interfaces}/IERC721Receiver.sol (100%) create mode 100644 contracts/interfaces/IWETH9Like.sol create mode 100644 contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol create mode 100644 contracts/protocol/bases/PriceDiscoveryBase.sol create mode 100644 contracts/protocol/facets/SequenatialCommitHandlerFacet.sol create mode 100644 test/protocol/SequentialCommitHandlerTest.js diff --git a/contracts/example/SnapshotGate/support/ERC721.sol b/contracts/example/SnapshotGate/support/ERC721.sol index d20200149..d418b1c12 100644 --- a/contracts/example/SnapshotGate/support/ERC721.sol +++ b/contracts/example/SnapshotGate/support/ERC721.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.9; import "../../../interfaces/IERC721.sol"; +import "../../../interfaces/IERC721Receiver.sol"; import "../../../ext_libs/Address.sol"; import "../../../ext_libs/Strings.sol"; -import "./IERC721Receiver.sol"; import "./IERC721Metadata.sol"; /** diff --git a/contracts/example/SnapshotGate/support/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol similarity index 100% rename from contracts/example/SnapshotGate/support/IERC721Receiver.sol rename to contracts/interfaces/IERC721Receiver.sol diff --git a/contracts/interfaces/IWETH9Like.sol b/contracts/interfaces/IWETH9Like.sol new file mode 100644 index 000000000..6354f14e3 --- /dev/null +++ b/contracts/interfaces/IWETH9Like.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +/** + * @title IWETH9Like + * + * @notice Provides the minimum interface for native token wrapper + */ +interface IWETH9Like { + function withdraw(uint256) external; + + function deposit() external payable; + + function transfer(address, uint256) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); +} diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index 30de53e84..2f2863ad7 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -5,16 +5,15 @@ import { BosonTypes } from "../../domain/BosonTypes.sol"; import { IBosonExchangeEvents } from "../events/IBosonExchangeEvents.sol"; import { IBosonTwinEvents } from "../events/IBosonTwinEvents.sol"; import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /** * @title IBosonExchangeHandler * * @notice Handles exchanges associated with offers within the protocol. * - * The ERC-165 identifier for this interface is: 0xe36d9689 + * The ERC-165 identifier for this interface is: 0xe300dfc1 */ -interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents, IERC721Receiver { +interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents { /** * @notice Commits to an offer (first step of an exchange). * @@ -259,10 +258,4 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * @return receipt - the receipt for the exchange. See {BosonTypes.Receipt} */ function getReceipt(uint256 _exchangeId) external view returns (BosonTypes.Receipt memory receipt); - - function sequentialCommitToOffer( - address payable _buyer, - uint256 _exchangeId, - BosonTypes.PriceDiscovery calldata _priceDiscovery - ) external payable; } diff --git a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol new file mode 100644 index 000000000..de9c0862c --- /dev/null +++ b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.9; + +import { BosonTypes } from "../../domain/BosonTypes.sol"; +import { IBosonExchangeEvents } from "../events/IBosonExchangeEvents.sol"; +import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; +import { IERC721Receiver } from "../IERC721Receiver.sol"; + +/** + * @title ISequentialCommitHandler + * + * @notice Handles sequential commits. + * + * The ERC-165 identifier for this interface is: 0x1566334a + */ +interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IERC721Receiver { + /** + * @notice Commits to an existing exchange. Price discovery is oflaoaded to external contract. + * + * Emits a BuyerCommitted event if successful. + * Transfers voucher to the buyer address. + * + * Reverts if: + * - The exchanges region of protocol is paused + * - The buyers region of protocol is paused + * - Buyer address is zero + * - Exchange does not exist + * - Exchange is not in Committed state + * - Voucher has expired + * - It is a sell order and: + * - Caller is not the voucher holder + * - Voucher owner did not approve protocol to transfer the voucher + * - Price received from price discovery is lower than the expected price + * - Reseller did not approve protocol to transfer exchange token in escrow + * - It is a buy order and: + * - Offer price is in native token and caller does not send enough + * - Offer price is in some ERC20 token and caller also sends native currency + * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) + * - Received ERC20 token amount differs from the expected value + * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) + * - Call to price discovery contract fails + * - Protocol fee and royalties combined exceed the secondary price + * - Transfer of exchange token fails + * + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _exchangeId - the id of the exchange to commit to + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + */ + function sequentialCommitToOffer( + address payable _buyer, + uint256 _exchangeId, + BosonTypes.PriceDiscovery calldata _priceDiscovery + ) external payable; +} diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol new file mode 100644 index 000000000..bc8765060 --- /dev/null +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "../../domain/BosonConstants.sol"; +import { ProtocolLib } from "../libs/ProtocolLib.sol"; +import { IERC20 } from "../../interfaces/IERC20.sol"; +import { IWETH9Like } from "../../interfaces/IWETH9Like.sol"; +import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; +import { ProtocolBase } from "./../bases/ProtocolBase.sol"; +import { FundsLib } from "../libs/FundsLib.sol"; + +/** + * @title PriceDiscoveryBase + * + * @dev Provides methods for fulfiling orders on external price discovery contracts. + */ +contract PriceDiscoveryBase is ProtocolBase { + IWETH9Like public immutable weth; + + constructor(address _weth) { + weth = IWETH9Like(_weth); + } + + /** + * @notice Fulfils an order on external contract. Helper function passes data to either buy or sell order. + * + * See descriptions of `fulfilBuyOrder` and `fulfilSellOrder` for more details. + * + * @param _exchangeId - the id of the exchange to commit to + * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _initialSellerId - the id of the original seller + * @return actualPrice - the actual price of the order + */ + function fulFilOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + address _buyer, + uint256 _initialSellerId + ) internal returns (uint256 actualPrice) { + if (_priceDiscovery.direction == Direction.Buy) { + return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); + } else { + return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); + } + } + + /** + * @notice Fulfils a buy order on external contract. + * + * Reverts if: + * - Offer price is in native token and caller does not send enough + * - Offer price is in some ERC20 token and caller also sends native currency + * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) + * - Received ERC20 token amount differs from the expected value + * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) + * - Call to price discovery contract fails + * + * @param _exchangeId - the id of the exchange to commit to + * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _initialSellerId - the id of the original seller + * @return actualPrice - the actual price of the order + */ + function fulfilBuyOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + address _buyer, + uint256 _initialSellerId + ) internal returns (uint256 actualPrice) { + // Transfer buyers funds to protocol + FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); + + // At this point, protocol temporary holds buyer's payment + uint256 protocolBalanceBefore = getBalance(_exchangeToken); + + // If token is ERC20, approve price discovery contract to transfer funds + if (_exchangeToken != address(0)) { + IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); + } + + // Store the information about incoming voucher + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + address cloneAddress = protocolLookups().cloneAddress[_initialSellerId]; + ps.incomingVoucherId = _exchangeId; + ps.incomingVoucherCloneAddress = cloneAddress; + + { + // Call the price discovery contract + (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ + value: msg.value + }(_priceDiscovery.priceDiscoveryData); + + // If error, return error message + string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); + require(success, errorMessage); + } + // If token is ERC20, reset approval + if (_exchangeToken != address(0)) { + IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); + } + + // Clear the storage + delete ps.incomingVoucherId; + delete ps.incomingVoucherCloneAddress; + + // Check the escrow amount + uint256 protocolBalanceAfter = getBalance(_exchangeToken); + actualPrice = protocolBalanceBefore - protocolBalanceAfter; + + uint256 overchargedAmount = _priceDiscovery.price - actualPrice; + + if (overchargedAmount > 0) { + // Return the surplus to buyer + FundsLib.transferFundsFromProtocol(_exchangeToken, payable(_buyer), overchargedAmount); + } + + // Transfer voucher to buyer + IBosonVoucher(cloneAddress).transferFrom(address(this), _buyer, _exchangeId); + } + + /** + * @notice Fulfils a sell order on external contract. + * + * Reverts if: + * - Voucher owner did not approve protocol to transfer the voucher + * - Price received from price discovery is lower than the expected price + * - Reseller did not approve protocol to transfer exchange token in escrow + * + * @param _exchangeId - the id of the exchange to commit to + * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + * @param _initialSellerId - the id of the original seller + * @return actualPrice - the actual price of the order + */ + function fulfilSellOrder( + uint256 _exchangeId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + uint256 _initialSellerId + ) internal returns (uint256 actualPrice) { + // what about non-zero msg.value? + // No need to reset approval + + IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); + // Transfer seller's voucher to protocol + bosonVoucher.transferFrom(msgSender(), address(this), _exchangeId); + + if (_exchangeToken == address(0)) _exchangeToken = address(weth); + + // Get protocol balance before the exchange + uint256 protocolBalanceBefore = getBalance(_exchangeToken); + + // Approve price discovery contract to transfer voucher + bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, _exchangeId); + + { + // Call the price discovery contract + (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ + value: msg.value + }(_priceDiscovery.priceDiscoveryData); + + // If error, return error message + string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); + require(success, errorMessage); + } + + // Check the escrow amount + uint256 protocolBalanceAfter = getBalance(_exchangeToken); + + actualPrice = protocolBalanceAfter - protocolBalanceBefore; + require(actualPrice >= _priceDiscovery.price, "Price discovery contract returned less than expected"); + } + + /** + * @notice Returns the balance of the protocol for the given token address + * + * @param _tokenAddress - the address of the token to check the balance for + * @return balance - the balance of the protocol for the given token address + */ + function getBalance(address _tokenAddress) internal view returns (uint256) { + return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); + } +} diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index b26b86e06..1ab2adeb2 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.9; import { IBosonExchangeHandler } from "../../interfaces/handlers/IBosonExchangeHandler.sol"; -import { IBosonAccountHandler } from "../../interfaces/handlers/IBosonAccountHandler.sol"; import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; import { ITwinToken } from "../../interfaces/ITwinToken.sol"; import { DiamondLib } from "../../diamond/DiamondLib.sol"; @@ -15,22 +14,6 @@ import { Address } from "../../ext_libs/Address.sol"; import { IERC1155 } from "../../interfaces/IERC1155.sol"; import { IERC721 } from "../../interfaces/IERC721.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; -import { Math } from "../../ext_libs/Math.sol"; -import "hardhat/console.sol"; - -interface WETH9Like { - function withdraw(uint256) external; - - function deposit() external payable; - - function transfer(address, uint256) external returns (bool); - - function transferFrom( - address, - address, - uint256 - ) external returns (bool); -} /** * @title ExchangeHandlerFacet @@ -40,12 +23,6 @@ interface WETH9Like { contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { using Address for address; - WETH9Like private immutable weth; // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) - - constructor(address _weth) { - weth = WETH9Like(_weth); - } - /** * @notice Initializes facet. * This function is callable only once. @@ -103,248 +80,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { commitToOfferInternal(_buyer, offer, 0, false); } - function sequentialCommitToOffer( - address payable _buyer, - uint256 _exchangeId, - PriceDiscovery calldata _priceDiscovery - ) external payable exchangesNotPaused buyersNotPaused nonReentrant { - // Make sure buyer address is not zero address - require(_buyer != address(0), INVALID_ADDRESS); - - // Exchange must exist - (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); - - // Make sure the voucher is still valid - require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); - - // Fetch offer - (, Offer storage offer) = fetchOffer(exchange.offerId); - - // Get token address - address tokenAddress = offer.exchangeToken; - - // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred - address seller; - uint256 buyerId = exchange.buyerId; - { - (, Buyer storage currentBuyer) = fetchBuyer(buyerId); - seller = currentBuyer.wallet; - } - - if (_priceDiscovery.direction == Direction.Sell) { - require(seller == msgSender(), NOT_VOUCHER_HOLDER); - } - - // First call price discovery and get actual price - // It might be lower tha submitted for buy orders and higher for sell orders - uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer.sellerId); - - // Calculate the amount to be kept in escrow - uint256 escrowAmount; - { - // Get sequential commits for this exchange - SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; - - { - // Calculate fees - uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token - ? protocolFees().flatBoson - : (protocolFees().percentage * actualPrice) / 10000; - - // Calculate royalties - (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( - _exchangeId, - actualPrice - ); - - // Verify that fees and royalties are not higher than the price. - require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); - - // Get price paid by current buyer - uint256 len = sequentialCommits.length; - uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; - - // Calculate the amount to be immediately released to current voucher owner - // escrowAmount = - // actualPrice - - // Math.min(currentPrice, actualPrice - protocolFeeAmount - royaltyAmount); - // escrowAmount = - // Math.max(actualPrice-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow - escrowAmount = Math.max(actualPrice, protocolFeeAmount + royaltyAmount + currentPrice) - currentPrice; - - // Update sequential commit - sequentialCommits.push( - SequentialCommit({ - resellerId: buyerId, - price: actualPrice, - protocolFeeAmount: protocolFeeAmount, - royaltyAmount: royaltyAmount - }) - ); - } - - // Make sure enough get escrowed - if (_priceDiscovery.direction == Direction.Buy) { - if (escrowAmount > 0) { - // Price discovery should send funds to the seller - // Nothing in escrow, need to pull everything from seller - if (tokenAddress == address(0)) { - FundsLib.transferFundsToProtocol(address(weth), seller, escrowAmount); - weth.withdraw(escrowAmount); - } else { - FundsLib.transferFundsToProtocol(tokenAddress, seller, escrowAmount); - } - } - } else { - // when sell direction, we have full proceeds in escrow. Keep minimal in, return the difference - if (tokenAddress == address(0)) { - tokenAddress = address(weth); - if (escrowAmount > 0) weth.withdraw(escrowAmount); - } - - uint256 payout = actualPrice - escrowAmount; - if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(seller), payout); - } - } - - // since exchange and voucher are passed by reference, they are updated - emit BuyerCommitted(exchange.offerId, exchange.buyerId, _exchangeId, exchange, voucher, msgSender()); - // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred - } - - function fulFilOrder( - uint256 _exchangeId, - address _exchangeToken, - PriceDiscovery calldata _priceDiscovery, - address _buyer, - uint256 _initialSellerId - ) internal returns (uint256 actualPrice) { - if (_priceDiscovery.direction == Direction.Buy) { - return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); - } else { - // return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); - } - } - - function fulfilBuyOrder( - uint256 _exchangeId, - address _exchangeToken, - PriceDiscovery calldata _priceDiscovery, - address _buyer, - uint256 _initialSellerId - ) internal returns (uint256 actualPrice) { - // Transfer buyers funds to protocol - FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); - - // At this point, protocol temporary holds buyer's payment - uint256 protocolBalanceBefore = getBalance(_exchangeToken); - - // If token is ERC20, approve price discovery contract to transfer funds - if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); - } - - // Store the information about incoming voucher - ProtocolLib.ProtocolStatus storage ps = protocolStatus(); - address cloneAddress = protocolLookups().cloneAddress[_initialSellerId]; - ps.incomingVoucherId = _exchangeId; - ps.incomingVoucherCloneAddress = cloneAddress; - - { - // Call the price discovery contract - (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ - value: msg.value - }(_priceDiscovery.priceDiscoveryData); - - // If error, return error message - string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); - require(success, errorMessage); - } - // If token is ERC20, reset approval - if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); - } - - // Clear the storage - delete ps.incomingVoucherId; - delete ps.incomingVoucherCloneAddress; - - // Check the escrow amount - uint256 protocolBalanceAfter = getBalance(_exchangeToken); - actualPrice = protocolBalanceBefore - protocolBalanceAfter; - - uint256 overchargedAmount = _priceDiscovery.price - actualPrice; - - if (overchargedAmount > 0) { - // Return the surplus to buyer - FundsLib.transferFundsFromProtocol(_exchangeToken, payable(_buyer), overchargedAmount); - } - - // Transfer voucher to buyer - IBosonVoucher(cloneAddress).transferFrom(address(this), _buyer, _exchangeId); - } - - function fulfilSellOrder( - uint256 _exchangeId, - address _exchangeToken, - PriceDiscovery calldata _priceDiscovery, - uint256 _initialSellerId - ) internal returns (uint256 actualPrice) { - // what about non-zero msg.value? - // No need to reset approval - - IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); - // Transfer seller's voucher to protocol - bosonVoucher.transferFrom(msgSender(), address(this), _exchangeId); - - if (_exchangeToken == address(0)) _exchangeToken = address(weth); - - // Get protocol balance before the exchange - uint256 protocolBalanceBefore = getBalance(_exchangeToken); - - // Approve price discovery contract to transfer voucher - bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, _exchangeId); - - { - // Call the price discovery contract - (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ - value: msg.value - }(_priceDiscovery.priceDiscoveryData); - - // If error, return error message - string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); - require(success, errorMessage); - } - - // Check the escrow amount - uint256 protocolBalanceAfter = getBalance(_exchangeToken); - - actualPrice = protocolBalanceAfter - protocolBalanceBefore; - require(actualPrice >= _priceDiscovery.price, "Price discovery contract returned less than expected"); - } - - function getBalance(address _tokenAddress) internal view returns (uint256) { - return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); - } - - // during sequential commit to offer, we expect to receive the boson voucher, therefore we need to implement onERC721Received - // alternative option, where vouchers are modified to not invoke onERC721Received when to is protocol is unsafe, since one can abuse it to send vouchers to protocol - // this should return true value only when protocol expects to receive the voucher - // should revert if called from any other address - function onERC721Received( - address, - address, - uint256 _tokenId, - bytes calldata - ) external view override returns (bytes4) { - ProtocolLib.ProtocolStatus storage ps = protocolStatus(); - require( - ps.incomingVoucherId == _tokenId && ps.incomingVoucherCloneAddress == msg.sender, - UNEXPECTED_ERC721_RECEIVED - ); - return this.onERC721Received.selector; - } - /** * @notice Commits to a preminted offer (first step of an exchange). * diff --git a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol new file mode 100644 index 000000000..7f8a765a6 --- /dev/null +++ b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.9; + +import { IBosonSequentialCommitHandler } from "../../interfaces/handlers/IBosonSequentialCommitHandler.sol"; +import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; +import { DiamondLib } from "../../diamond/DiamondLib.sol"; +import { PriceDiscoveryBase } from "../bases/PriceDiscoveryBase.sol"; +import { ProtocolLib } from "../libs/ProtocolLib.sol"; +import { FundsLib } from "../libs/FundsLib.sol"; +import "../../domain/BosonConstants.sol"; +import { Address } from "../../ext_libs/Address.sol"; +import { Math } from "../../ext_libs/Math.sol"; + +interface WETH9Like { + function withdraw(uint256) external; + + function deposit() external payable; + + function transfer(address, uint256) external returns (bool); + + function transferFrom( + address, + address, + uint256 + ) external returns (bool); +} + +/** + * @title SequentialCommitHandlerFacet + * + * @notice Handles sequential commits. + */ +contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDiscoveryBase { + using Address for address; + + constructor(address _weth) PriceDiscoveryBase(_weth) {} + + /** + * @notice Initializes facet. + * This function is callable only once. + */ + function initialize() public onlyUninitialized(type(IBosonSequentialCommitHandler).interfaceId) { + DiamondLib.addSupportedInterface(type(IBosonSequentialCommitHandler).interfaceId); + } + + /** + * @notice Commits to an existing exchange. Price discovery is oflaoaded to external contract. + * + * Emits a BuyerCommitted event if successful. + * Transfers voucher to the buyer address. + * + * Reverts if: + * - The exchanges region of protocol is paused + * - The buyers region of protocol is paused + * - Buyer address is zero + * - Exchange does not exist + * - Exchange is not in Committed state + * - Voucher has expired + * - It is a sell order and: + * - Caller is not the voucher holder + * - Voucher owner did not approve protocol to transfer the voucher + * - Price received from price discovery is lower than the expected price + * - Reseller did not approve protocol to transfer exchange token in escrow + * - It is a buy order and: + * - Offer price is in native token and caller does not send enough + * - Offer price is in some ERC20 token and caller also sends native currency + * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) + * - Received ERC20 token amount differs from the expected value + * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) + * - Call to price discovery contract fails + * - Protocol fee and royalties combined exceed the secondary price + * - Transfer of exchange token fails + * + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _exchangeId - the id of the exchange to commit to + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + */ + function sequentialCommitToOffer( + address payable _buyer, + uint256 _exchangeId, + PriceDiscovery calldata _priceDiscovery + ) external payable exchangesNotPaused buyersNotPaused nonReentrant { + // Make sure buyer address is not zero address + require(_buyer != address(0), INVALID_ADDRESS); + + // Exchange must exist + (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); + + // Make sure the voucher is still valid + require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); + + // Fetch offer + (, Offer storage offer) = fetchOffer(exchange.offerId); + + // Get token address + address tokenAddress = offer.exchangeToken; + + // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred + address seller; + uint256 buyerId = exchange.buyerId; + { + (, Buyer storage currentBuyer) = fetchBuyer(buyerId); + seller = currentBuyer.wallet; + } + + if (_priceDiscovery.direction == Direction.Sell) { + require(seller == msgSender(), NOT_VOUCHER_HOLDER); + } + + // First call price discovery and get actual price + // It might be lower tha submitted for buy orders and higher for sell orders + uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer.sellerId); + + // Calculate the amount to be kept in escrow + uint256 escrowAmount; + { + // Get sequential commits for this exchange + SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; + + { + // Calculate fees + uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token + ? protocolFees().flatBoson + : (protocolFees().percentage * actualPrice) / 10000; + + // Calculate royalties + (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( + _exchangeId, + actualPrice + ); + + // Verify that fees and royalties are not higher than the price. + require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); + + // Get price paid by current buyer + uint256 len = sequentialCommits.length; + uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; + + // Calculate the amount to be immediately released to current voucher owner + // escrowAmount = + // actualPrice - + // Math.min(currentPrice, actualPrice - protocolFeeAmount - royaltyAmount); + // escrowAmount = + // Math.max(actualPrice-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow + escrowAmount = Math.max(actualPrice, protocolFeeAmount + royaltyAmount + currentPrice) - currentPrice; + + // Update sequential commit + sequentialCommits.push( + SequentialCommit({ + resellerId: buyerId, + price: actualPrice, + protocolFeeAmount: protocolFeeAmount, + royaltyAmount: royaltyAmount + }) + ); + } + + // Make sure enough get escrowed + if (_priceDiscovery.direction == Direction.Buy) { + if (escrowAmount > 0) { + // Price discovery should send funds to the seller + // Nothing in escrow, need to pull everything from seller + if (tokenAddress == address(0)) { + FundsLib.transferFundsToProtocol(address(weth), seller, escrowAmount); + weth.withdraw(escrowAmount); + } else { + FundsLib.transferFundsToProtocol(tokenAddress, seller, escrowAmount); + } + } + } else { + // when sell direction, we have full proceeds in escrow. Keep minimal in, return the difference + if (tokenAddress == address(0)) { + tokenAddress = address(weth); + if (escrowAmount > 0) weth.withdraw(escrowAmount); + } + + uint256 payout = actualPrice - escrowAmount; + if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(seller), payout); + } + } + + // Since exchange and voucher are passed by reference, they are updated + emit BuyerCommitted(exchange.offerId, exchange.buyerId, _exchangeId, exchange, voucher, msgSender()); + // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred + } + + /** + * @notice standard onERC721Received function + * + * During sequential commit to offer, we expect to receive the boson voucher, therefore we need to implement onERC721Received + * Alternative option, where vouchers are modified to not invoke onERC721Received when to is protocol is unsafe, since one can abuse it to send vouchers to protocol + * This should return true value only when protocol expects to receive the voucher + * Should revert if called from any other address + + * @return - the ERC721 received function signature + */ + function onERC721Received( + address, + address, + uint256 _tokenId, + bytes calldata + ) external view override returns (bytes4) { + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + require( + ps.incomingVoucherId == _tokenId && ps.incomingVoucherCloneAddress == msg.sender, + UNEXPECTED_ERC721_RECEIVED + ); + return this.onERC721Received.selector; + } +} diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 735dcb8f9..dcbcfa246 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -7,8 +7,6 @@ import { EIP712Lib } from "../libs/EIP712Lib.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { SafeERC20 } from "../../ext_libs/SafeERC20.sol"; -import { Math } from "../../ext_libs/Math.sol"; -import "hardhat/console.sol"; /** * @title FundsLib diff --git a/scripts/config/facet-deploy.js b/scripts/config/facet-deploy.js index 9e23ae3d7..4cc6d4dc1 100644 --- a/scripts/config/facet-deploy.js +++ b/scripts/config/facet-deploy.js @@ -54,6 +54,7 @@ const noArgFacetNames = [ "TwinHandlerFacet", "PauseHandlerFacet", "ProtocolInitializationHandlerFacet", // args are generated on cutDiamond function + "SequentialCommitHandlerFacet", ]; async function getFacets(config) { diff --git a/scripts/config/supported-interfaces.js b/scripts/config/supported-interfaces.js index 1607ffeb7..a0478c1e1 100644 --- a/scripts/config/supported-interfaces.js +++ b/scripts/config/supported-interfaces.js @@ -25,6 +25,7 @@ const interfaceImplementers = { ERC165Facet: "IERC165Extended", ConfigHandlerFacet: "IBosonConfigHandler", ProtocolInitializationHandlerFacet: "IBosonProtocolInitializationHandler", + SequentialCommitHandlerFacet: "IBosonSequentialCommitHandler", }; let interfacesCache; // if getInterfaceIds is called multiple times (e.g. during tests), calculate ids only once and store them to cache @@ -44,9 +45,11 @@ async function getInterfaceIds(useCache = true) { skip[iFace] = true; return skip; }, {}); - ["IBosonVoucher", "IERC1155", "IERC721", "IERC2981", "IAccessControl"].forEach((iFace) => { - skipBaseCheck[iFace] = false; - }); + ["IBosonVoucher", "IERC1155", "IERC721", "IERC2981", "IAccessControl", "IBosonSequentialCommitHandler"].forEach( + (iFace) => { + skipBaseCheck[iFace] = false; + } + ); for (const iFace of interfaces) { interfaceIds[iFace] = await getInterfaceId(iFace, skipBaseCheck[iFace]); diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 392f2f8d8..ba86b05b7 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -14,8 +14,6 @@ const ExchangeState = require("../../scripts/domain/ExchangeState"); const DisputeState = require("../../scripts/domain/DisputeState"); const Group = require("../../scripts/domain/Group"); const EvaluationMethod = require("../../scripts/domain/EvaluationMethod"); -const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); -const Direction = require("../../scripts/domain/Direction"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); @@ -65,7 +63,6 @@ describe("IBosonExchangeHandler", function () { treasury, rando, buyer, - buyer2, newOwner, fauxClient, assistantDR, @@ -110,7 +107,6 @@ describe("IBosonExchangeHandler", function () { let exchangesToComplete, exchangeId; let offer, offerFees; let offerDates, offerDurations; - let weth; before(async function () { // get interface Ids @@ -125,7 +121,6 @@ describe("IBosonExchangeHandler", function () { admin, treasury, buyer, - buyer2, rando, newOwner, fauxClient, @@ -225,13 +220,6 @@ describe("IBosonExchangeHandler", function () { const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); - const wethFactory = await ethers.getContractFactory("WETH9"); - weth = await wethFactory.deploy(); - await weth.deployed(); - - // Add WETH - facetsToDeploy["ExchangeHandlerFacet"].constructorArgs = [weth.address]; - // Cut the protocol handler facets into the Diamond await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); @@ -1398,1010 +1386,6 @@ describe("IBosonExchangeHandler", function () { }); }); - context("👉 sequentialCommitToOffer()", async function () { - let priceDiscoveryContract, priceDiscovery, price2; - let newBuyer; - let reseller; // for clarity in tests - - // TODO: - // * ERC20 as exchange token - - before(async function () { - // Deploy PriceDiscovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); - priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - await priceDiscoveryContract.deployed(); - }); - - beforeEach(async function () { - // Commit to offer with first buyer - tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - txReceipt = await tx.wait(); - event = getEvent(txReceipt, exchangeHandler, "BuyerCommitted"); - - // Get the block timestamp of the confirmed tx - blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); - - // 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); - - reseller = buyer; - }); - - context("Buy order", async function () { - context("General actions", async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: buyer.address, - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: offer.exchangeToken, - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); - - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); - - // Seller needs to deposit weth in order to fill the escrow at the last step - // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) - await weth.connect(buyer).deposit({ value: price2 }); - await weth.connect(buyer).approve(protocolDiamond.address, price2); - - // Approve transfers - // Buyer does not approve, since its in ETH. - // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); - - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; - }); - - it("should emit a BuyerCommitted event", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("should update state", async function () { - // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // buyer2 is exchange.buyerId - // Get the exchange as a struct - const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - - // Parse into entity - let returnedExchange = Exchange.fromStruct(exchangeStruct); - expect(returnedExchange.buyerId).to.equal(newBuyer.id); - - // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); - }); - - it("should transfer the voucher", async function () { - // buyer is owner of voucher - expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // buyer2 is owner of voucher - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); - - it("voucher should remain unchanged", async function () { - // Voucher before - let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - let returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Voucher after - [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - }); - - it("only new buyer can redeem voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherRedeemed") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("only new buyer can cancel voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherCanceled") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("should not increment the next exchange id counter", async function () { - const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Get the next exchange id and ensure it was incremented by the creation of the offer - const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); - expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); - }); - - it("Should not decrement quantityAvailable", async function () { - // Get quantityAvailable before - const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler - .connect(rando) - .getOffer(offerId); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); - - // Get quantityAvailable after - const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler - .connect(rando) - .getOffer(offerId); - - expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); - }); - - it("It is possible to commit on someone else's behalf", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(rando) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); - - // buyer2 is owner of voucher, not rando - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); - - it("It is possible to commit even if offer is voided", async function () { - // Void the offer - await offerHandler.connect(assistant).voidOffer(offerId); - - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("It is possible to commit even if redemption period has not started yet", async function () { - // Redemption not yet possible - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( - RevertReasons.VOUCHER_NOT_REDEEMABLE - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("It is possible to commit even if offer has expired", async function () { - // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); - - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - it("It is possible to commit even if is sold out", async function () { - // Commit to all remaining quantity - for (let i = 1; i < offer.quantityAvailable; i++) { - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - } - - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); - }); - - context("💔 Revert Reasons", async function () { - it("The exchanges region of protocol is paused", async function () { - // Pause the exchanges region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); - - it("The buyers region of protocol is paused", async function () { - // Pause the buyers region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); - - it("buyer address is the zero address", async function () { - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); - }); - - it("exchange id is invalid", async function () { - // An invalid offer id - exchangeId = "666"; - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); - }); - - it("voucher not valid anymore", async function () { - // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); - - // Attempt to sequentially commit to the expired voucher, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); - }); - - it("protocol fees to high", async function () { - // Set protocol fees to 95% - await configHandler.setProtocolFeePercentage(9500); - // Set royalty fees to 6% - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); - }); - - it("insufficient values sent", async function () { - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) - ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); - }); - }); - }); - - context("Escrow amount", async function () { - let scenarios = [ - { case: "Increasing price", multiplier: 11 }, - { case: "Constant price", multiplier: 10 }, - { case: "Decreasing price", multiplier: 9 }, - ]; - - async function getBalances() { - const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ - ethers.provider.getBalance(exchangeHandler.address), - ethers.provider.getBalance(buyer.address), - weth.balanceOf(buyer.address), - ethers.provider.getBalance(buyer2.address), - ethers.provider.getBalance(treasury.address), - ]); - - return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; - } - - scenarios.forEach((scenario) => { - context(scenario.case, async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: buyer.address, - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: offer.exchangeToken, - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ - order, - ]); - - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); - - // Seller needs to deposit weth in order to fill the escrow at the last step - // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) - await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow - await weth.connect(buyer).approve(protocolDiamond.address, price2); - - // Approve transfers - // Buyer does not approve, since its in ETH. - // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); - - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; - }); - - const fees = [ - { - protocol: 0, - royalties: 0, - }, - { - protocol: 500, - royalties: 0, - }, - { - protocol: 0, - royalties: 600, - }, - { - protocol: 300, - royalties: 400, // less than profit - }, - { - protocol: 500, - royalties: 700, // more than profit - }, - ]; - - fees.forEach((fee) => { - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - - const balancesBefore = await getBalances(); - - // Sequential commit to offer - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { - value: price2, - gasPrice: 0, - }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) - ); - }); - - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ - fee.royalties / 100 - }% - overpaid`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - - const balancesBefore = await getBalances(); - - // Sequential commit to offer. Buyer pays more than needed - priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); - - await exchangeHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { - value: priceDiscovery.price, - gasPrice: 0, - }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) - ); - }); - }); - }); - }); - }); - }); - - context("Sell order", async function () { - context("General actions", async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); - - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Sell - ); - - // Approve transfers - // Buyer2 needs to approve price discovery to transfer the ETH - await weth.connect(buyer2).deposit({ value: price2 }); - await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); - - // Seller approves protocol to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); - - mockBuyer(reseller.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; - }); - - it("should emit a BuyerCommitted event", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); - }); - - it("should update state", async function () { - // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); - - // Sequential commit to offer - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // buyer2 is exchange.buyerId - // Get the exchange as a struct - const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - - // Parse into entity - let returnedExchange = Exchange.fromStruct(exchangeStruct); - expect(returnedExchange.buyerId).to.equal(newBuyer.id); - - // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); - }); - - it("should transfer the voucher", async function () { - // reseller is owner of voucher - expect(await bosonVoucherClone.connect(reseller).ownerOf(exchangeId)).to.equal(reseller.address); - - // Sequential commit to offer - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // buyer2 is owner of voucher - expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); - }); - - it("voucher should remain unchanged", async function () { - // Voucher before - let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - let returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // Voucher after - [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); - returnedVoucher = Voucher.fromStruct(voucherStruct); - expect(returnedVoucher).to.deep.equal(voucher); - }); - - it("only new buyer can redeem voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherRedeemed") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("only new buyer can cancel voucher", async function () { - // Sequential commit to offer, creating a new exchange - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // Old buyer cannot redeem - await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( - RevertReasons.NOT_VOUCHER_HOLDER - ); - - // Redeem voucher, test for event - await setNextBlockTimestamp(Number(voucherRedeemableFrom)); - expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) - .to.emit(exchangeHandler, "VoucherCanceled") - .withArgs(offerId, exchangeId, buyer2.address); - }); - - it("should not increment the next exchange id counter", async function () { - const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // Get the next exchange id and ensure it was incremented by the creation of the offer - const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); - expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); - }); - - it("Should not decrement quantityAvailable", async function () { - // Get quantityAvailable before - const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler - .connect(rando) - .getOffer(offerId); - - // Sequential commit to offer, creating a new exchange - await exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); - - // Get quantityAvailable after - const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler - .connect(rando) - .getOffer(offerId); - - expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); - }); - - it("It is possible to commit even if offer is voided", async function () { - // Void the offer - await offerHandler.connect(assistant).voidOffer(offerId); - - // Committing directly is not possible - await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( - RevertReasons.OFFER_HAS_BEEN_VOIDED - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); - }); - - it("It is possible to commit even if redemption period has not started yet", async function () { - // Redemption not yet possible - await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( - RevertReasons.VOUCHER_NOT_REDEEMABLE - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); - }); - - it("It is possible to commit even if offer has expired", async function () { - // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); - - // Committing directly is not possible - await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( - RevertReasons.OFFER_HAS_EXPIRED - ); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); - }); - - it("It is possible to commit even if is sold out", async function () { - // Commit to all remaining quantity - for (let i = 1; i < offer.quantityAvailable; i++) { - await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - } - - // Committing directly is not possible - await expect( - exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) - ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); - - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) - .to.emit(exchangeHandler, "BuyerCommitted") - .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); - }); - - context("💔 Revert Reasons", async function () { - it("The exchanges region of protocol is paused", async function () { - // Pause the exchanges region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); - - it("The buyers region of protocol is paused", async function () { - // Pause the buyers region of the protocol - await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.REGION_PAUSED); - }); - - it("buyer address is the zero address", async function () { - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler - .connect(reseller) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.INVALID_ADDRESS); - }); - - it("exchange id is invalid", async function () { - // An invalid offer id - exchangeId = "666"; - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); - }); - - it("voucher not valid anymore", async function () { - // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); - - // Attempt to sequentially commit to the expired voucher, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); - }); - - it("protocol fees to high", async function () { - // Set protocol fees to 95% - await configHandler.setProtocolFeePercentage(9500); - // Set royalty fees to 6% - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); - - // Attempt to sequentially commit, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); - }); - - it("voucher transfer not approved", async function () { - // revoke approval - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, false); - - // Attempt to sequentially commit to, expecting revert - await expect( - exchangeHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); - }); - - it("Only seller can call, if direction is Sell", async function () { - // Sequential commit to offer, retrieving the event - await expect( - exchangeHandler.connect(rando).sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ).to.revertedWith(RevertReasons.NOT_VOUCHER_HOLDER); - }); - }); - }); - - context("Escrow amount", async function () { - let scenarios = [ - { case: "Increasing price", multiplier: 11 }, - { case: "Constant price", multiplier: 10 }, - { case: "Decreasing price", multiplier: 9 }, - ]; - - async function getBalances() { - const [protocol, seller, sellerWeth, newBuyer, newBuyerWeth, originalSeller] = await Promise.all([ - ethers.provider.getBalance(exchangeHandler.address), - ethers.provider.getBalance(reseller.address), - weth.balanceOf(reseller.address), - ethers.provider.getBalance(buyer2.address), - weth.balanceOf(buyer2.address), - ethers.provider.getBalance(treasury.address), - ]); - - return { protocol, seller: seller.add(sellerWeth), newBuyer: newBuyer.add(newBuyerWeth), originalSeller }; - } - - scenarios.forEach((scenario) => { - context(scenario.case, async function () { - beforeEach(async function () { - // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); - - // Prepare calldata for PriceDiscovery contract - let order = { - seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism - buyer: buyer2.address, - voucherContract: expectedCloneAddress, - tokenId: exchangeId, - exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH - price: price2, - }; - - const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [ - order, - ]); - - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Sell - ); - - // Approve transfers - // Buyer2 needs to approve price discovery to transfer the ETH - await weth.connect(buyer2).deposit({ value: price2 }); - await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); - - // Seller approves protocol to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); - - mockBuyer(buyer.address); // call only to increment account id counter - newBuyer = mockBuyer(buyer2.address); - exchange.buyerId = newBuyer.id; - }); - - const fees = [ - { - protocol: 0, - royalties: 0, - }, - { - protocol: 500, - royalties: 0, - }, - { - protocol: 0, - royalties: 600, - }, - { - protocol: 300, - royalties: 400, // less than profit - }, - { - protocol: 500, - royalties: 700, // more than profit - }, - ]; - - fees.forEach((fee) => { - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - - const balancesBefore = await getBalances(); - - // Sequential commit to offer - await exchangeHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { - gasPrice: 0, - }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) - ); - }); - - it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ - fee.royalties / 100 - }% - underpriced`, async function () { - await configHandler.setProtocolFeePercentage(fee.protocol); - await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); - - const balancesBefore = await getBalances(); - - // Sequential commit to offer. Buyer pays more than needed - priceDiscovery.price = ethers.BigNumber.from(price2).div(2).toString(); - - await exchangeHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { - gasPrice: 0, - }); - - const balancesAfter = await getBalances(); - - // Expected changes - const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; - - // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); - expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) - ); - }); - }); - }); - }); - }); - }); - }); - context("👉 completeExchange()", async function () { beforeEach(async function () { // Commit to offer diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js new file mode 100644 index 000000000..c96c23e4e --- /dev/null +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -0,0 +1,1361 @@ +const hre = require("hardhat"); +const ethers = hre.ethers; +const { expect } = require("chai"); + +const Role = require("../../scripts/domain/Role"); +const Exchange = require("../../scripts/domain/Exchange"); +const Voucher = require("../../scripts/domain/Voucher"); +const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); +const Direction = require("../../scripts/domain/Direction"); +const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); +const PausableRegion = require("../../scripts/domain/PausableRegion.js"); +const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); +const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); +const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); +const { deployAndCutFacets } = require("../../scripts/util/deploy-protocol-handler-facets.js"); +const { deployProtocolClients } = require("../../scripts/util/deploy-protocol-clients"); +const { + mockOffer, + mockDisputeResolver, + mockAuthToken, + mockVoucherInitValues, + mockSeller, + mockVoucher, + mockExchange, + mockBuyer, + accountId, +} = require("../util/mock"); +const { + setNextBlockTimestamp, + calculateVoucherExpiry, + calculateContractAddress, + applyPercentage, + getFacetsWithArgs, +} = require("../util/utils.js"); +const { oneWeek, oneMonth, maxPriorityFeePerGas } = require("../util/constants"); + +/** + * Test the Boson Sequential Commit Handler interface + */ +describe("IBosonSequentialCommitHandler", function () { + // Common vars + let InterfaceIds; + let deployer, + pauser, + assistant, + admin, + clerk, + treasury, + rando, + buyer, + buyer2, + assistantDR, + adminDR, + clerkDR, + treasuryDR, + protocolTreasury, + bosonToken; + let erc165, + protocolDiamond, + accessController, + accountHandler, + exchangeHandler, + offerHandler, + fundsHandler, + pauseHandler, + configHandler, + sequentialCommitHandler; + let bosonVoucherClone; + let buyerId, offerId, seller, disputeResolverId; + let block, blockNumber, tx; + let support; + let price, sellerPool; + let voucherRedeemableFrom; + let voucherValid; + let protocolFeePercentage, protocolFeeFlatBoson, buyerEscalationDepositPercentage; + let voucher; + let exchange; + let disputeResolver, disputeResolverFees; + let expectedCloneAddress; + let voucherInitValues; + let emptyAuthToken; + let agentId; + let exchangeId; + let offer, offerFees; + let offerDates, offerDurations; + let weth; + + before(async function () { + // get interface Ids + InterfaceIds = await getInterfaceIds(); + }); + + beforeEach(async function () { + // Make accounts available + [deployer, pauser, admin, treasury, buyer, buyer2, rando, adminDR, treasuryDR, protocolTreasury, bosonToken] = + await ethers.getSigners(); + + // make all account the same + assistant = clerk = admin; + assistantDR = clerkDR = adminDR; + + // Deploy the Protocol Diamond + [protocolDiamond, , , , accessController] = await deployProtocolDiamond(maxPriorityFeePerGas); + + // 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); + + // Temporarily grant PAUSER role to pauser account + await accessController.grantRole(Role.PAUSER, pauser.address); + + // Deploy the Protocol client implementation/proxy pairs (currently just the Boson Voucher) + const protocolClientArgs = [protocolDiamond.address]; + const [, beacons, proxies] = await deployProtocolClients(protocolClientArgs, maxPriorityFeePerGas); + const [beacon] = beacons; + const [proxy] = proxies; + + // 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: protocolTreasury.address, + token: bosonToken.address, + voucherBeacon: beacon.address, + beaconProxy: proxy.address, + }, + // Protocol limits + { + maxExchangesPerBatch: 50, + maxOffersPerGroup: 100, + maxTwinsPerBundle: 100, + maxOffersPerBundle: 100, + maxOffersPerBatch: 100, + maxTokensPerWithdrawal: 100, + maxFeesPerDisputeResolver: 100, + maxEscalationResponsePeriod: oneMonth, + maxDisputesPerBatch: 100, + maxAllowedSellers: 100, + maxTotalOfferFeePercentage: 4000, //40% + maxRoyaltyPecentage: 1000, //10% + maxResolutionPeriod: oneMonth, + minDisputePeriod: oneWeek, + maxPremintedVouchers: 1000, + }, + // Protocol fees + { + percentage: protocolFeePercentage, + flatBoson: protocolFeeFlatBoson, + buyerEscalationDepositPercentage, + }, + ]; + + const facetNames = [ + "AccountHandlerFacet", + "AgentHandlerFacet", + "SellerHandlerFacet", + "BuyerHandlerFacet", + "DisputeResolverHandlerFacet", + "ExchangeHandlerFacet", + "OfferHandlerFacet", + "FundsHandlerFacet", + "DisputeHandlerFacet", + "TwinHandlerFacet", + "BundleHandlerFacet", + "GroupHandlerFacet", + "PauseHandlerFacet", + "ProtocolInitializationHandlerFacet", + "ConfigHandlerFacet", + "SequentialCommitHandlerFacet", + ]; + + const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); + + const wethFactory = await ethers.getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.deployed(); + + // Add WETH + facetsToDeploy["SequentialCommitHandlerFacet"].constructorArgs = [weth.address]; + + // Cut the protocol handler facets into the Diamond + await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); + + // Cast Diamond to IERC165 + erc165 = await ethers.getContractAt("ERC165Facet", protocolDiamond.address); + + // Cast Diamond to IBosonAccountHandler. Use this interface to call all individual account handlers + accountHandler = await ethers.getContractAt("IBosonAccountHandler", protocolDiamond.address); + + // Cast Diamond to IBosonOfferHandler + offerHandler = await ethers.getContractAt("IBosonOfferHandler", protocolDiamond.address); + + // Cast Diamond to IBosonExchangeHandler + exchangeHandler = await ethers.getContractAt("IBosonExchangeHandler", protocolDiamond.address); + + // Cast Diamond to IBosonFundsHandler + fundsHandler = await ethers.getContractAt("IBosonFundsHandler", protocolDiamond.address); + + // Cast Diamond to IBosonPauseHandler + pauseHandler = await ethers.getContractAt("IBosonPauseHandler", protocolDiamond.address); + + // Cast Diamond to IConfigHandler + configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + + // Cast Diamond to ISequentialCommitHandler + sequentialCommitHandler = await ethers.getContractAt("IBosonSequentialCommitHandler", protocolDiamond.address); + }); + + // Interface support (ERC-156 provided by ProtocolDiamond, others by deployed facets) + context("📋 Interfaces", async function () { + context("👉 supportsInterface()", async function () { + it("should indicate support for IBosonSequentialCommitHandler interface", async function () { + // Current interfaceId for IBosonSequentialCommitHandler + support = await erc165.supportsInterface(InterfaceIds.IBosonSequentialCommitHandler); + + // Test + expect(support, "IBosonSequentialCommitHandler interface not supported").is.true; + }); + }); + }); + + // All supported Exchange methods + context("📋 Exchange Handler Methods", async function () { + beforeEach(async function () { + // Initial ids for all the things + exchangeId = offerId = "1"; + agentId = "0"; // agent id is optional while creating an offer + + // Create a valid seller + seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); + expect(seller.isValid()).is.true; + + // AuthToken + emptyAuthToken = mockAuthToken(); + expect(emptyAuthToken.isValid()).is.true; + + // VoucherInitValues + voucherInitValues = mockVoucherInitValues(); + expect(voucherInitValues.isValid()).is.true; + + await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); + expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); + + // 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); + + // Create the offer + const mo = await mockOffer(); + ({ offerDates, offerDurations } = mo); + offer = mo.offer; + offerFees = mo.offerFees; + offerFees.protocolFee = applyPercentage(offer.price, protocolFeePercentage); + + offer.quantityAvailable = "10"; + disputeResolverId = mo.disputeResolverId; + + offerDurations.voucherValid = (oneMonth * 12).toString(); + + // Check if domains are valid + expect(offer.isValid()).is.true; + expect(offerDates.isValid()).is.true; + expect(offerDurations.isValid()).is.true; + + // Create the offer + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + + // Set used variables + price = offer.price; + voucherRedeemableFrom = offerDates.voucherRedeemableFrom; + voucherValid = offerDurations.voucherValid; + sellerPool = ethers.utils.parseUnits("15", "ether").toString(); + + // Required voucher constructor params + voucher = mockVoucher(); + voucher.redeemedDate = "0"; + + // Mock exchange + exchange = mockExchange(); + + buyerId = accountId.next().value; + exchange.buyerId = buyerId; + exchange.finalizedDate = "0"; + + // Deposit seller funds so the commit will succeed + await fundsHandler + .connect(assistant) + .depositFunds(seller.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); + }); + + afterEach(async function () { + // Reset the accountId iterator + accountId.next(true); + }); + + context("👉 sequentialCommitToOffer()", async function () { + let priceDiscoveryContract, priceDiscovery, price2; + let newBuyer; + let reseller; // for clarity in tests + + // TODO: + // * ERC20 as exchange token + + before(async function () { + // Deploy PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + }); + + beforeEach(async function () { + // Commit to offer with first buyer + tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + // txReceipt = await tx.wait(); + + // Get the block timestamp of the confirmed tx + blockNumber = tx.blockNumber; + block = await ethers.provider.getBlock(blockNumber); + + // 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); + + reseller = buyer; + }); + + context("Buy order", async function () { + context("General actions", async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // Seller needs to deposit weth in order to fill the escrow at the last step + // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) + await weth.connect(buyer).deposit({ value: price2 }); + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); + + it("should transfer the voucher", async function () { + // buyer is owner of voucher + expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); + + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit on someone else's behalf", async function () { + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(rando) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); + + // buyer2 is owner of voucher, not rando + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("exchange id is invalid", async function () { + // An invalid offer id + exchangeId = "666"; + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); + + it("voucher not valid anymore", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(voucher.validUntilDate)); + + // Attempt to sequentially commit to the expired voucher, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("insufficient values sent", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + }); + }); + + context("Escrow amount", async function () { + let scenarios = [ + { case: "Increasing price", multiplier: 11 }, + { case: "Constant price", multiplier: 10 }, + { case: "Decreasing price", multiplier: 9 }, + ]; + + async function getBalances() { + const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ + ethers.provider.getBalance(exchangeHandler.address), + ethers.provider.getBalance(buyer.address), + weth.balanceOf(buyer.address), + ethers.provider.getBalance(buyer2.address), + ethers.provider.getBalance(treasury.address), + ]); + + return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; + } + + scenarios.forEach((scenario) => { + context(scenario.case, async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ + order, + ]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // Seller needs to deposit weth in order to fill the escrow at the last step + // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) + await weth.connect(buyer).deposit({ value: price2 }); // you don't need to approve whole amount, just what goes in escrow + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 500, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 500, + royalties: 700, // more than profit + }, + ]; + + fees.forEach((fee) => { + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + value: price2, + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ + fee.royalties / 100 + }% - overpaid`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer. Buyer pays more than needed + priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); + + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + value: priceDiscovery.price, + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + }); + }); + }); + }); + }); + + context("Sell order", async function () { + context("General actions", async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Sell + ); + + // Approve transfers + // Buyer2 needs to approve price discovery to transfer the ETH + await weth.connect(buyer2).deposit({ value: price2 }); + await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + + // Seller approves protocol to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + + mockBuyer(reseller.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit a BuyerCommitted event", async function () { + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // buyer2 is exchange.buyerId + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should increase for minimal escrow amount + const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); + expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + }); + + it("should transfer the voucher", async function () { + // reseller is owner of voucher + expect(await bosonVoucherClone.connect(reseller).ownerOf(exchangeId)).to.equal(reseller.address); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + it("voucher should remain unchanged", async function () { + // Voucher before + let [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + let returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Voucher after + [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + returnedVoucher = Voucher.fromStruct(voucherStruct); + expect(returnedVoucher).to.deep.equal(voucher); + }); + + it("only new buyer can redeem voucher", async function () { + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).redeemVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherRedeemed") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("only new buyer can cancel voucher", async function () { + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Old buyer cannot redeem + await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( + RevertReasons.NOT_VOUCHER_HOLDER + ); + + // Redeem voucher, test for event + await setNextBlockTimestamp(Number(voucherRedeemableFrom)); + expect(await exchangeHandler.connect(buyer2).cancelVoucher(exchangeId)) + .to.emit(exchangeHandler, "VoucherCanceled") + .withArgs(offerId, exchangeId, buyer2.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Get the next exchange id and ensure it was incremented by the creation of the offer + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + // Sequential commit to offer, creating a new exchange + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit even if offer is voided", async function () { + // Void the offer + await offerHandler.connect(assistant).voidOffer(offerId); + + // Committing directly is not possible + await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( + RevertReasons.OFFER_HAS_BEEN_VOIDED + ); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(exchangeHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if redemption period has not started yet", async function () { + // Redemption not yet possible + await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.revertedWith( + RevertReasons.VOUCHER_NOT_REDEEMABLE + ); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if offer has expired", async function () { + // Advance time to after offer expiry + await setNextBlockTimestamp(Number(offerDates.validUntil)); + + // Committing directly is not possible + await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( + RevertReasons.OFFER_HAS_EXPIRED + ); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + it("It is possible to commit even if is sold out", async function () { + // Commit to all remaining quantity + for (let i = 1; i < offer.quantityAvailable; i++) { + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + } + + // Committing directly is not possible + await expect( + exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_SOLD_OUT); + + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ) + .to.emit(sequentialCommitHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("exchange id is invalid", async function () { + // An invalid offer id + exchangeId = "666"; + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); + + it("voucher not valid anymore", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(voucher.validUntilDate)); + + // Attempt to sequentially commit to the expired voucher, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("voucher transfer not approved", async function () { + // revoke approval + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, false); + + // Attempt to sequentially commit to, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); + }); + + it("Only seller can call, if direction is Sell", async function () { + // Sequential commit to offer, retrieving the event + await expect( + sequentialCommitHandler + .connect(rando) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.NOT_VOUCHER_HOLDER); + }); + }); + }); + + context("Escrow amount", async function () { + let scenarios = [ + { case: "Increasing price", multiplier: 11 }, + { case: "Constant price", multiplier: 10 }, + { case: "Decreasing price", multiplier: 9 }, + ]; + + async function getBalances() { + const [protocol, seller, sellerWeth, newBuyer, newBuyerWeth, originalSeller] = await Promise.all([ + ethers.provider.getBalance(exchangeHandler.address), + ethers.provider.getBalance(reseller.address), + weth.balanceOf(reseller.address), + ethers.provider.getBalance(buyer2.address), + weth.balanceOf(buyer2.address), + ethers.provider.getBalance(treasury.address), + ]); + + return { protocol, seller: seller.add(sellerWeth), newBuyer: newBuyer.add(newBuyerWeth), originalSeller }; + } + + scenarios.forEach((scenario) => { + context(scenario.case, async function () { + beforeEach(async function () { + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [ + order, + ]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Sell + ); + + // Approve transfers + // Buyer2 needs to approve price discovery to transfer the ETH + await weth.connect(buyer2).deposit({ value: price2 }); + await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + + // Seller approves protocol to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + + mockBuyer(buyer.address); // call only to increment account id counter + newBuyer = mockBuyer(buyer2.address); + exchange.buyerId = newBuyer.id; + }); + + const fees = [ + { + protocol: 0, + royalties: 0, + }, + { + protocol: 500, + royalties: 0, + }, + { + protocol: 0, + royalties: 600, + }, + { + protocol: 300, + royalties: 400, // less than profit + }, + { + protocol: 500, + royalties: 700, // more than profit + }, + ]; + + fees.forEach((fee) => { + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${fee.royalties / 100}%`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ + fee.royalties / 100 + }% - underpriced`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + // Sequential commit to offer. Buyer pays more than needed + priceDiscovery.price = ethers.BigNumber.from(price2).div(2).toString(); + + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + gasPrice: 0, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = ethers.BigNumber.from(price2) + .mul(10000 - fee.protocol - fee.royalties) + .div(10000); + const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; + const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); + const expectedOriginalSellerChange = 0; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); + expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller.add(expectedOriginalSellerChange) + ); + }); + }); + }); + }); + }); + }); + }); + }); +}); From 741b079fa30a46e1b4a11891c29a0dd8e03f3655 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 28 Feb 2023 12:04:07 +0100 Subject: [PATCH 26/47] sequentialCommitToOffer tests --- scripts/config/revert-reasons.js | 3 + test/protocol/SequentialCommitHandlerTest.js | 158 ++++++++++++++++++- 2 files changed, 157 insertions(+), 4 deletions(-) diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index 02d8217a6..ee26af1d5 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -127,8 +127,11 @@ exports.RevertReasons = { 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", + + // Sequential commit related UNEXPECTED_ERC721_RECEIVED: "Unexpected ERC721 received", FEE_AMOUNT_TOO_HIGH: "Fee amount is too high", + VOUCHER_NOT_RECEIVED: "Voucher not received", // Voucher related EXCHANGE_ID_IN_RESERVED_RANGE: "Exchange id falls within a pre-minted offer's range", diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index c96c23e4e..14cc34aa4 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -14,6 +14,7 @@ const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); const { deployAndCutFacets } = require("../../scripts/util/deploy-protocol-handler-facets.js"); const { deployProtocolClients } = require("../../scripts/util/deploy-protocol-clients"); +const { deployMockTokens } = require("../../scripts/util/deploy-mock-tokens"); const { mockOffer, mockDisputeResolver, @@ -322,9 +323,6 @@ describe("IBosonSequentialCommitHandler", function () { let newBuyer; let reseller; // for clarity in tests - // TODO: - // * ERC20 as exchange token - before(async function () { // Deploy PriceDiscovery contract const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); @@ -335,7 +333,6 @@ describe("IBosonSequentialCommitHandler", function () { beforeEach(async function () { // Commit to offer with first buyer tx = await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); - // txReceipt = await tx.wait(); // Get the block timestamp of the confirmed tx blockNumber = tx.blockNumber; @@ -1357,5 +1354,158 @@ describe("IBosonSequentialCommitHandler", function () { }); }); }); + + context("👉 onERC721Received()", async function () { + let priceDiscoveryContract, priceDiscovery, price2; + let reseller; // for clarity in tests + + beforeEach(async function () { + // Commit to offer with first buyer + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + + reseller = buyer; + + // Price on secondary market + price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + + // Seller needs to deposit weth in order to fill the escrow at the last step + // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) + await weth.connect(buyer).deposit({ value: price2 }); + await weth.connect(buyer).approve(protocolDiamond.address, price2); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + }); + + it("should transfer the voucher during sequential commit", async function () { + // Deploy PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: reseller.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + + // Seller approves price discovery to transfer the voucher + await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); + + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Direction.Buy); + + // buyer is owner of voucher + expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.connect(buyer2).ownerOf(exchangeId)).to.equal(buyer2.address); + }); + + context("💔 Revert Reasons", async function () { + it("Correct caller, wrong id", async function () { + // Commit to offer with first buyer once more (so they have two vouchers) + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); + + // Deploy Bad PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryModifyTokenId"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: reseller.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + + // Seller approves price discovery to transfer the voucher + await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); + }); + + it("Correct token id, wrong caller", async function () { + // Deploy mock erc721 contract + const [foreign721] = await deployMockTokens(["Foreign721"]); + + // Deploy Bad PriceDiscovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryModifyVoucherContract"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(foreign721.address); + await priceDiscoveryContract.deployed(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: reseller.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + + // Seller approves price discovery to transfer the voucher + await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); + }); + + it("Random erc721 transfer", async function () { + // Deploy mock erc721 contract + const [foreign721] = await deployMockTokens(["Foreign721"]); + + const tokenId = 123; + await foreign721.mint(tokenId, 1); + + // Attempt to sequentially commit, expecting revert + await expect( + foreign721["safeTransferFrom(address,address,uint256)"](deployer.address, protocolDiamond.address, tokenId) + ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); + }); + }); + }); }); }); From 5b6162077da6cc7e0f21bb8724baa1013182b2b3 Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 28 Feb 2023 12:12:38 +0100 Subject: [PATCH 27/47] verify incoming voucher --- contracts/domain/BosonConstants.sol | 3 + .../IBosonSequentialCommitHandler.sol | 1 + contracts/mock/PriceDiscovery.sol | 59 ++++++++++++++++++- .../protocol/bases/PriceDiscoveryBase.sol | 7 +++ .../facets/SequenatialCommitHandlerFacet.sol | 3 +- test/protocol/SequentialCommitHandlerTest.js | 33 +++++++++++ 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 5c5fad598..f7e59675d 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -111,8 +111,11 @@ string constant TOO_MANY_EXCHANGES = "Exceeded maximum exchanges in a single tra 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"; + +// Revert Reasons: Sequential commit related string constant UNEXPECTED_ERC721_RECEIVED = "Unexpected ERC721 received"; string constant FEE_AMOUNT_TOO_HIGH = "Fee amount is too high"; +string constant VOUCHER_NOT_RECEIVED = "Voucher not received"; // Revert Reasons: Twin related string constant NO_SUCH_TWIN = "No such twin"; diff --git a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol index de9c0862c..3aa9797c2 100644 --- a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol +++ b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol @@ -37,6 +37,7 @@ interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibE * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value + * - Protocol does not receive the voucher * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) * - Call to price discovery contract fails * - Protocol fee and royalties combined exceed the secondary price diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index 5e8749f78..bdeae4199 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.9; import "../interfaces/IERC20.sol"; import "../interfaces/IERC721.sol"; +import "./Foreign721.sol"; /** * @dev Simple price discovery contract used in tests @@ -26,7 +27,7 @@ contract PriceDiscovery { * It just transfers the voucher and exchange token to the buyer * If any of the transfers fail, the whole transaction will revert */ - function fulfilBuyOrder(Order calldata _order) external payable { + function fulfilBuyOrder(Order memory _order) public payable virtual { // transfer voucher try IERC721(_order.voucherContract).safeTransferFrom(_order.seller, msg.sender, _order.tokenId) {} catch ( bytes memory reason @@ -66,7 +67,7 @@ contract PriceDiscovery { } } - function fulfilSellOrder(Order calldata _order) external payable { + function fulfilSellOrder(Order memory _order) public payable virtual { // transfer voucher try IERC721(_order.voucherContract).safeTransferFrom(msg.sender, _order.buyer, _order.tokenId) {} catch ( bytes memory reason @@ -96,3 +97,57 @@ contract PriceDiscovery { } } } + +/** + * @dev Simple bad price discovery contract used in tests + * + * This contract modifies the token id, simulates bad/malicious contract + */ +contract PriceDiscoveryModifyTokenId is PriceDiscovery { + /** + * @dev simple fulfillOrder that does not perform any checks + * Bump token id by 1 + */ + function fulfilBuyOrder(Order memory _order) public payable override { + _order.tokenId++; + super.fulfilBuyOrder(_order); + } +} + +/** + * @dev Simple bad price discovery contract used in tests + * + * This contract modifies the erc721 token, simulates bad/malicious contract + */ +contract PriceDiscoveryModifyVoucherContract is PriceDiscovery { + Foreign721 private erc721; + + constructor(address _erc721) { + erc721 = Foreign721(_erc721); + } + + /** + * @dev simple fulfillOrder that does not perform any checks + * Change order voucher address with custom erc721 + * Mint tokenId on custom erc721 + */ + function fulfilBuyOrder(Order memory _order) public payable override { + erc721.mint(_order.tokenId, 1); + + _order.seller = address(this); + _order.voucherContract = address(erc721); + super.fulfilBuyOrder(_order); + } +} + +/** + * @dev Simple bad price discovery contract used in tests + * + * This contract modifies simply does not transfer the voucher to the caller + */ +contract PriceDiscoveryNoTransfer is PriceDiscovery { + /** + * @dev do nothing + */ + function fulfilBuyOrder(Order memory _order) public payable override {} +} diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index bc8765060..c63298094 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -55,6 +55,7 @@ contract PriceDiscoveryBase is ProtocolBase { * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value + * - Protocol does not receive the voucher * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) * - Call to price discovery contract fails * @@ -99,6 +100,10 @@ contract PriceDiscoveryBase is ProtocolBase { string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); require(success, errorMessage); } + + // Make sure that the price discovery contract has transferred the voucher to the protocol + require(IBosonVoucher(cloneAddress).ownerOf(_exchangeId) == address(this), VOUCHER_NOT_RECEIVED); + // If token is ERC20, reset approval if (_exchangeToken != address(0)) { IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); @@ -147,7 +152,9 @@ contract PriceDiscoveryBase is ProtocolBase { // No need to reset approval IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); + // Transfer seller's voucher to protocol + // Don't need to use safe transfer from, since that protocol can handle the voucher bosonVoucher.transferFrom(msgSender(), address(this), _exchangeId); if (_exchangeToken == address(0)) _exchangeToken = address(weth); diff --git a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol index 7f8a765a6..0709b7bad 100644 --- a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol @@ -66,6 +66,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value + * - Protocol does not receive the voucher * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) * - Call to price discovery contract fails * - Protocol fee and royalties combined exceed the secondary price @@ -190,7 +191,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis * During sequential commit to offer, we expect to receive the boson voucher, therefore we need to implement onERC721Received * Alternative option, where vouchers are modified to not invoke onERC721Received when to is protocol is unsafe, since one can abuse it to send vouchers to protocol * This should return true value only when protocol expects to receive the voucher - * Should revert if called from any other address + * Should revert if called from any other address or with any other token id * @return - the ERC721 received function signature */ diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index 14cc34aa4..73ca2e8eb 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -688,6 +688,39 @@ describe("IBosonSequentialCommitHandler", function () { .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); + + it("price discovery does not send the voucher to the protocol", async function () { + // Deploy bad price discovery contract + const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryNoTransfer"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.deployed(); + + // Prepare calldata for PriceDiscovery contract + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: exchangeId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + + priceDiscovery = new PriceDiscovery( + price2, + priceDiscoveryContract.address, + priceDiscoveryData, + Direction.Buy + ); + + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.VOUCHER_NOT_RECEIVED); + }); }); }); From d96727028fc517b51ba4010cdf6fe7f6b0a8eaa3 Mon Sep 17 00:00:00 2001 From: zajck Date: Wed, 1 Mar 2023 08:37:29 +0100 Subject: [PATCH 28/47] Fix failing tests --- contracts/protocol/bases/PriceDiscoveryBase.sol | 5 +++-- test/protocol/FundsHandlerTest.js | 13 +++++++++---- test/protocol/SequentialCommitHandlerTest.js | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index c63298094..8aa993a56 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -102,7 +102,8 @@ contract PriceDiscoveryBase is ProtocolBase { } // Make sure that the price discovery contract has transferred the voucher to the protocol - require(IBosonVoucher(cloneAddress).ownerOf(_exchangeId) == address(this), VOUCHER_NOT_RECEIVED); + IBosonVoucher bosonVoucher = IBosonVoucher(cloneAddress); + require(bosonVoucher.ownerOf(_exchangeId) == address(this), VOUCHER_NOT_RECEIVED); // If token is ERC20, reset approval if (_exchangeToken != address(0)) { @@ -125,7 +126,7 @@ contract PriceDiscoveryBase is ProtocolBase { } // Transfer voucher to buyer - IBosonVoucher(cloneAddress).transferFrom(address(this), _buyer, _exchangeId); + bosonVoucher.transferFrom(address(this), _buyer, _exchangeId); } /** diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 73b9c7732..a5f9fe805 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -63,7 +63,8 @@ describe("IBosonFundsHandler", function () { offerHandler, configHandler, disputeHandler, - pauseHandler; + pauseHandler, + sequentialCommitHandler; let support; let seller; let buyer, offerToken, offerNative; @@ -206,6 +207,7 @@ describe("IBosonFundsHandler", function () { "AccountHandlerFacet", "ProtocolInitializationHandlerFacet", "ConfigHandlerFacet", + "SequentialCommitHandlerFacet", ]; const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); @@ -215,7 +217,7 @@ describe("IBosonFundsHandler", function () { await weth.deployed(); // Add WETH - facetsToDeploy["ExchangeHandlerFacet"].constructorArgs = [weth.address]; + facetsToDeploy["SequentialCommitHandlerFacet"].constructorArgs = [weth.address]; // Cut the protocol handler facets into the Diamond const { deployedFacets } = await deployAndCutFacets(protocolDiamond.address, facetsToDeploy, maxPriorityFeePerGas); @@ -242,6 +244,9 @@ describe("IBosonFundsHandler", function () { // Cast Diamond to IBosonConfigHandler configHandler = await ethers.getContractAt("IBosonConfigHandler", protocolDiamond.address); + // Cast Diamond to IBosonSequentialCommitHandler + sequentialCommitHandler = await ethers.getContractAt("IBosonSequentialCommitHandler", protocolDiamond.address); + // Deploy the mock token [mockToken] = await deployMockTokens(["Foreign20"]); }); @@ -4575,7 +4580,7 @@ describe("IBosonFundsHandler", function () { await mockToken.connect(trade.buyer).approve(protocolDiamond.address, order.price); // commit to offer - await exchangeHandler + await sequentialCommitHandler .connect(trade.buyer) .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { gasPrice: 0, @@ -6182,7 +6187,7 @@ describe("IBosonFundsHandler", function () { await mockToken.connect(trade.buyer).approve(protocolDiamond.address, order.price); // commit to offer - await exchangeHandler + await sequentialCommitHandler .connect(trade.buyer) .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { gasPrice: 0, diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index 73ca2e8eb..5778488a2 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -227,8 +227,8 @@ describe("IBosonSequentialCommitHandler", function () { }); }); - // All supported Exchange methods - context("📋 Exchange Handler Methods", async function () { + // All supported Sequential commit methods + context("📋 Sequential Commit Methods", async function () { beforeEach(async function () { // Initial ids for all the things exchangeId = offerId = "1"; @@ -692,7 +692,7 @@ describe("IBosonSequentialCommitHandler", function () { it("price discovery does not send the voucher to the protocol", async function () { // Deploy bad price discovery contract const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryNoTransfer"); - priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); await priceDiscoveryContract.deployed(); // Prepare calldata for PriceDiscovery contract From 3d3e14f0499fcef83279be0e06e16ef345dbdc76 Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 2 Mar 2023 08:34:56 +0100 Subject: [PATCH 29/47] Protocol Diamond diagram --- .../Boson_Protocol_V2_-_Protocol_Diamond.png | Bin 451023 -> 458485 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/Boson_Protocol_V2_-_Protocol_Diamond.png b/docs/images/Boson_Protocol_V2_-_Protocol_Diamond.png index b13c6162f51626ec82e46f1c343a639c0c041c0c..01366e552739c9a3cf13fbff682c5a3e9178675e 100644 GIT binary patch literal 458485 zcmeEuXIN8N)Gm%~?4yX-5F4N%AR-_jpd%J~2PpvuM5F|ygdRYhks^Z9L5d{52`hD}<-}l#jh8Z4BOwQSR?X}l>*ZZ!0{MA+OAJ}_xFB==% z0fh(hnrv+QN7>l+(0<g^kUfmyONmZ#K3y@RrXc8yo5>8{70F zHa4j!Ha5;@v85U}!9VOWRk|ZL@p z{<){a!GR<3zEkF+a~$%*Kd7%PXGJ$gIl)_{`>XCvuAhz`}L7O-fsW*>pj1J z+xD;5K09`RwfNtkTfhBx`~S7Zf5!h`Td=YH=X(FP#(x{{-`4p5EbKfFCP@7Zli>@g zb>ra$h$_C_)luyitm8ZzY(`R4=0~+!!wV=;75DDlsq!&M`0ZBYN7bFcB#%7!en#Nl zC(Y&KYaxa2rOfkuVv%jcaTL!GyJ)3-Vf_djGEEEKpm%UN4fWfL_;@p?)-9;b}q*k{+i-_1t1Em#rUq@D>58X6c8fuM}&K@;WiMv)ZK;@;# zojZTtnP^#eyp)7Je7L>s>WA*W-#$Ahx=~7tf25FxlXnW&O^Y>iNZw<4E$6ltS6n!6 z+GOl;FJZ94kW^8*j84>RjmNcYr7NF|Bb#aDR#DH=MuzrnafMN7)sJR}xVw+vIpy+N zfuZL2SC#YYT7>UIB%4njslZ;VhV+GC9b*`*G5Js{RXL~@vAZtbBhc@i$LyJ9G^0z( zdTS#5_qOtv_O}jm0n@2G4t@4WzCi9%yS=PTPdm(|s%YhDIqJq= z+}or!zx!GI{?G5WuXA$N!rxZAa!_Y2LzP!u%BOc9O|C3^z>X>W@aWTFYRMQM8u6BNy4Z# z7YSn*o$J^4r=c9S9kC?MsvMUSS7rK6i9A{QX27>^^NJrX%2>s1{_X7Wgv8)UlQrV% zJn$ca)a9PyRltO9rRcX&Tf><=ubXWd6y0OQ zEz$2}SWr;VB_-MQ*-Y)GKKUY>HUSuF7=B&YbG6@fyoA##ZVM6kB%-srA(&!g%Nqc5 z%^k)RnbodN$fJj3ajFM(IyyRz&<9zMFrnMj*)w~t*B!#HC@9BWlWdV;G(k(<4eBha zRBKwP?c+g8KgoWFYdXJqwGXckZ-EFz8#Ho_+)7XJT0>(Z7K`n!|G3u}v2YI)%BJq< zINtI)@>u!liMYkF8;^!7&4;|R<7GzL?(p`kUEkbJKFC|a27H?r{bmOi+n#`v=#o02 zUH@}(PJ5yW4qlEuGB{NT7uncu-QL0@ANIW$busRjV0<*-d%4%z($e39>YdYF&o-%P zi+7Cpuw{V{jcX4v?3-V7nt}Gee_B?|eR)QooTPp(TgdHGw*s5rkE)YqPe*LsSV#&H zC&H@vM@Oxb;?ffE&ue3UCA{>7>FH^!xVxJdVZ&^pqa4Kht6ny-jOBdAL9ccJrSs>9 z(=8_MS9fzD`G3R@H3x;N-TX-GyAytsRvn5{{Vj*9jE?sXpQK9I5|3KH@2OcR8Vu?& z_7*)Rnes&$^Rk+-VfyH0U*vb>l~VXI4o50jvKt$t43|^-p?tTAw~W{>N_Reu?vi z-R99WDoxrVoEZf@Ksh!?jXb|~TPw`{pC%)Pl$Mqr7j=Ie>p8r~ez7$>2=0YG8WV&% z!rD@rk6vWC2Jpb7?m}i6nPyUL$)_UB5b=6eqn4O%se@7D*u3J|^IJx{RRaQ6jeTcO z;Hm5~ey}T*EAXlDaVBJ~Y2BC@-Md|?e1%PB~uuU>7lUk;C~iL7+?f<|2M8gGi@9Fk(q7$m6L z^cP%?_na2p|s- zSIV-4Sq+I=tF+HpXj(P_xY!bfBmqPBs{^oB)t{Q7yF{J6r zA3YdO)0pGhz&;PNyhhgs`TMie z{O!%|yOhW_C)Ow#kV8#d&G6_tWPZ=4Up0*VETFoj4nseCTbpp^J*Q}PNo5i;RW;fO zcQ!J)QLnm$(^G;VYg;~>k;u2e&7zxJvi1TIw0dSYw|~Nrt94(eK^B~8SAJ_9mD{Tz z&Bw=dnX1QKcZM0gvh42gk|DF3dQ%gC)I8KMs68_k&8q+(*JL_DRr~4q?JST>7$3R6T zb2!UU2yAK(7DJCi%?$Xu5sGqvOLd2^F37-ezX@3;y2g~p%)iMOIQe@4Saa8vcar9U zF45N{D3h()<;KjS%xvMQwPT$oaFg^emi5^);_{DB7zLpzb$b%xA~J%IRt6kJ>ucB$ z>cB{_48Q6cqZQ@vi~&FbhG`g|%n$t@4;IdIw%9D=SfJ(o2_p$<^x@J5*m$ngGmz;~WM7sAlf3H})-h8+L&wWb$|?h~a_; zz694lF>0AtV!#Ye*_L9?k71F~n-P5NP-mG= zw1C(jd>0&N>$p9%W1sbl_>+4XPvo?n&N`ff!4bj)u>-PCK|n20yU8IjxIoU^)ln!0 zG6g2{QTT;FGLBta)wcTJZR-__YS}4*Y8bsipApTB;T+4*`jxnWU&^XVTe`>IV6Hen zmn@s*di1qk#m*f5CzITedcaP?SkWxH1 z+_-aJfyUIWSkG%;_z~x>rh@2kb0M`P{pIgcA<@^3U;CFuJ8E~Qy#hJt+^$0>qujqT zKsuX_VBa9YqyP+J*j}29QT{BCnU4N50>A7$D77KsGN4shUcaNpXtj#Uq0dPfFO`WZ8VsZ#CH!7p&)1oPO8p$GXtd~uVMfC&-(!rrtD~#n4iL!j zZoT!S8Ab)OML+jhy8vZ&$ZDVwPPR?w7rcGs(J@eYexRd6GxSj`z*H;1m4owJNEx5-BVLCv~FwUIzJ|6$Yt5m?`GdE$yt3sV&IsdprDS7GuS&2 z3fv&a?#mGW%L{y9p5PsOxb-IePeO}^{wHF}>Fbv_Gfo27m_2LSirv7to>SHndKgq^ucRtrWAJX-Q=*g5hT{#jKlggqIdx8BF-)~s?GACbe)cV{G!!48{$Dd43!fA*sJ8-w)B z%-2*jBSDioQAC$fCu?0+Nb$nxyp^x+c2DX39%f1bK|OCKRrAY@6LE2LbUt?gysT+m z0&Do|N;874ZNoZjsHY%Fe?jeDI%91;F4j|LH(Q!I{Tx@5=5$r{9`B{pBg`ncj3CQk z6+N&RSSR?{`~+(av=c=x^(3q-V<02hacY5sBkrxiWE3<|4c}c}2*zW*#^oZ=V$ZP| znJS~rY26pwnlf(S4&H0naZbndH%F~be)BgmRsg`5NRtD#8Bt(lwnds%-OgBYSN7P! z28GNHux70uQwucII>;9pNBrDCjDbL;k4^$e7hgh-55R_mjK>NPQo=I~z+55b#q|tn z)0oB}^6DU4^+3RQ?LC)`m^Oel*e%Q!Ip)J}-H&>P2~`hEFj~ydIAhm7%!9BBaggOS z_H?_s*uM{_dhafZ;9q_s+UJd;^5d3RZ$aIf%Z~wMXq0{K20jh_73Aaof=>s70p8T2 zn0@(LgOh!ehy)NrsAQ(WfbUb>KHppNvr6*SAZ|1eE1Bb1539V60azAb_366IVlWl9 zHo~vDDCIY(;9j_a#H&Fd+|H2MBmRclXg)Mx=gTew9N(Q(k=FlOTn!65ov`l_(d z6;e{qIF3AQ@?@i+J5@UjS(25j1AxUkb5C6|v5tGr!0D)cqg5H6W`#KY7@Y zZwW4=ImtxtKy7_7h&`av>QFh|5{AQ`{NrtF;lu84&fk;#(+f>1hcN~Te`;(j^_J)D zP}&cwRv7zXp)=2XU3mhO{0boxyxQkq?F`#5Y0LX86f5hDY<3RtRhyOQY!%m4btP+x}SAm@1WCi{T#^t z8EZ9jaHC(zoL~BhH*T!AU2f0UYfYn)QkRFsf7S|#dJ)y$LyjiXBNz}=ew7K*OcKbl zoa;rJi*8?6a@&jShE!R$pkP0S!vo_-xh&miiQY;|ebYZ?c4;vRAm%Y?wt& zDA(z}i4pwl24Z@oIa*p6g0h1$j~z0YaAN6v*XUqG7U=n|!KfLd z4Fa|Oh#+1S2mODrG(Vl`kA}msgMXFo)$vhzaXy)2oYJ`HQbx zUXG9B%qjQbC}GueH~Zl=&xdCE)DlU@iJB6N1bv5!Ga7?F#62T*wG%50`i0 zn4s}yMV*0$OF9&7Zwz70_aRudxQi_X!fFG+?oZ&#MjIpEYGUu^K@AXi=b%(h)yO@0 z-{(7$Rp)Nf$rjRi15@6 zu($mnz@Gc~MyEL;2#W?8Z2nLJ%LcMEhTC_7a%`BuV^@vBue9dvxM;NYDVobT0b^mkjn`2B-mi)~Mz~z3+N9ur{&;XUjes1D%K2wc%15A2 zX{G-7iHUXRN-fr|n%xTybZnIs^;#Oqw9X4T3OA{f`7g4jH@@F-XnT4g_qE6BHJ!bE zNt4iNtu5jXL(Zq4F@(2bQsbrVf~>RGG+@|(pl`A-b1}OjHBR})U+cZaKjfb9#-|On zT|JAfT*$kz(fdi03x7KFoHY`Vh3R}<@y;DfkTf}><|hXXYfsV9tNwf5OpA;4POe$d zja0xMoxT2pJ`BhbPO<1>z`f057F&a;CQHpB!7d=PDej#S(9>6vvNY2Fbli-l$Z3OH z#}rwk_i~kv(OCR$q#sp>TPufK?vdg7Ee16s32dBcVXMeB4vNF%P_`@< zS_njoOYr7p-(SP2$QFpeQ07Tq&zDxQCf5HAOBl%GwD0KXU-AMBi`LGUk9FB6Th4er zR?VP@vP&s3OVPmT?5A8Tl45$@fpJa2BKa3FI?Wa}RUU5;^)12*tQxnm(^bSiidjA~ z(YtI7>JcHy18@@n!Zoa124P4fAjNG~(z;XC)fb7V^oAh`{QPqDGP#~F{*Nl(@WO^> zSYO8UX|>JQo#8N%SzJ;j$K_YLDo%fB@mvG-HyGrnPLIV=cC`oSb%3ai77Fnj+4|{0 zOqi5>&uF?2^aFo-Vm!~(ww&C%Ei&2}+nc8gwu+}kty_99qklb8kvNZZ2D4-cG)vz6R59qSvDVj@8+BG36tiHV9%_pHbFA6=Fo$oyhH7tare^JMo( zt?mpj{q0_L_kx7yQ_EElMdWlkoPmWeUH+UK3t0hZEDdi%J-x_Mu2ze2jv?XFLB?!R zt6GJkGBCM+k+qdUIDgs_*y()6%U2RWAWYZw%0xYL?06I#DDvrkZMfXJafVr|;JKU> zfFX2zyonnCH~Du4XFoW*Nf|ob{|Q;Y!ua*-y{*#nKkI&eBSDghyi?qO%@JbG2IgT~ zLYP2}C10F;sLLLp+c^5=$2u=j{8 zr&Kb+Wc9s}j>yGD-yzn7{IWH%l&6O-y!Iqf=RIIGPedXd{>m_`Tx@BmCiO3n%M@je zK~Of|lhzDlclwKID~^66tE;NDK(~P8nb7JAfWUkQeGKPm(&;UdNEXj|H1{UMU&!;* z4le*Tx5Cpxd@kQ8@*MD$?FCoqpy(&Mpe`ZW?wsl z10=orVaS}M#$A_5QH_qJ<68;^34Vv~D1q=Jpkx(}fJjYWlcb52?Ew2prz1n8%2zxk zgA_Ap@{?!<^s~UKD}*t5bV|!=0}F&zknDI4Dqp@IXFn~ctQL0&Ff&nBjHGSPAgL1) ztkdFL@$s))8E3D+{nD%J`IA8RL8^cnv%GZljq8j*U*i!!_FUA9rzL!<$2RTg(RNop zsUZ@o40)+A7;gMnOn3w7B?Xe)#zg7}*wdFhO8L`)*rF{^?K$7Y-WZ<($h_LF%b`l~ zkJo$I)sBg^U#Db-`ZkCL%C4)E$fNhjJs?zGDY*ud4qbNTNWd)`f0_E^w zJ|=SlMDm8)yCaMyD$5oszIhEE^RL z8CrV*XR_1~A}?@F2Fy-Lm7IIun|FD&5CxeWSf-l~Y?As;61tn+1wLW0l7TcE&^~HF z<;YjLgj4$v|BT|>zT=8v~84_+ay&X%?I9%P{bC0Cwlb=qUeLaJh- z)Jp$-4z)O>2m#on>)9%WF>Frv0Ss2=T9GKQFA6DCI(b@C=svgo!wgFBN?AHc`e2vq z{IO8IhY?mz0bHKS65Ts?QNF$|2GE)u`xfFu649Qv8%i7sTIa_0V_KP>V>C*sxE zAJeC{(}hWr_q%MDH@i3{6|*+M;%8?Hl-KYG1X1xoqh7gAxNvI%002;A!R7M?$*hTT zMCX*)^cik#shWkvO1lJY6KC(m<}vft4tLzm<(c)6B0)N5H|b=F#Z;&?1a+V)lZsQ1 z8}Gkx9uUjgg^OHb4)o&r(oEAx#9CxUIWp56S#NC@Rvxgctt9Eoxv>!5tKv}An<9QM z*miQHHb}^gG$s&2=t;(2RH>2fnYpsGVcN}J@fMh|vnD1>y7w+u`o9AwJ z`7xMI((r&)4CjvnWbtJ%+b}!xO%8Cf<(E2Q2(mvRsaBB;a3_1mcTHVIUkysLMNOCOy+4#o-S`trny z3PAKj#3PUm?mHIde9cZ$&#&HMGaw^78C>2nUJ%dmHl-*S;x1d?a)C&&i}8SD!hiqS zC?Ge@8$g&=4-=WOjh7)n=T}c(7w$uLdN7+p5ESVnH2Z(s3L2;G2J2)h<_%eYu zww~cB3zh|}E1=}L0cLPDbs^SQ0{tOBal%-r;iQc;*ouuAMUjB_v+14F_=4vaw<+q) z?cqD3vXqSj%5B=zJYS%2Yl9t7a+L~*Dwn19-K0Dad!YzqSi^;Y%aEq>HrEFT+iKOXib3vq} zKB-*DUF`R7eGxWlnO`7%+s_vtaLR5KCuMyq^a%$J1dCF6?wkm>TXhlH4~iO0cx=ee zK}#K=hE8m)mAV;|L8yb7hTjB;5PC*#rhr-CkgKI#O9w1&Hn7ZqZt@F4X$Yu>tmAeR zih_A7;?Nj9y~`1|V!yi6rrnuAR#5DhKfFyc+<1?zhq`}Z`N`!rz(w(U?T0rz`|D;G zqHo1s-mOBk{q1(gBv3dpp||7%`>b0H_&%nTn&udk96Bj=nsk=d#KsUK$Cu zs{tP@V%V${w?a;s0!U)8zv2@J@x9igXGCmzZGm|AlI?WiWvGI|>ID;ednX1_RtV?c z0m|4bh{D8C7`HOJQ`SV-;FZe$8up*_TTmRO(jKh~YK|2PNY%yx# zM*tc(afr4B;^Z`gcG)8ac8*~3H${hEgCXIdZHijtqyfwuW76+tU$JDe;+%;h)ajFG zGUNBs^cRpH0G}vJtm`$dTo?e_3RsON9m=k=(*pKkufuPqrSt&_I)g&*H1Z2GXEH7q2yf({D&%`8Rq!S1F4Ej)%7qTlk3(qez0!d|~)_BX2 z6u-S&Ipi}_e9Y~_n)T8?JN0!R2E zMFr7x6wC!a{OCd;-+oh;m92lTTYjqEMI{xt!g!j&o+3K-j(^ets2zFo}bw*zg-TPWU zIyv(}zIy}QQt_35&N6Oi27`SQQiKHR`S66vK}1ECAdr(oiYTh)Z@z+sP2T&^-w&#k zR_K#E2Y^b*V@Coo&uDGn1R(Yz`413&|B$rSu>+H|C*pDh1M6_og$op)jS(e6a3Jb? zw)TKehC*4$?c~Z?#KB~2spdXQJ+(f7H}7^v@=hj4`8Rk39yeat(971>YIhWl#2R3E3OZtV?8+v? zd-y|m1Rg2)mmUP1Tze0ew6GRFwn#S4#v~AXGkHnjwh7 ztzAroX=XtNQJ;70ozTONW#M9ZPz8VjYw}<#ry9f8AVKlA<;x=txl%2F!^NV+odjMR z>vtq?FDDWKznfkNktT*9MY>JstHl8qP&5|pdsxr;*g6uQaR)I0iadz)2 z)nit`jRbQ=yLTB?Hd)Qx8|wDbL%>5p84n5zrxhy5Me+Q|*3^enA1+un8W8u?YnwnB z^I0d-d`~UduH@ILx)&d23BcSqjRpxl>~z*`+J1*Jq_J5@`9}EZ@9@9-uXXF1!vsHH9OOLwO&LLD8U(SCrK=fCrO& z=i%&mFk0uRBC3(X(ur`i$ppUF0caQwsFgzH+`P{OcfMuh8XJ<5s6bbI!VA1V~3dttEBx)=NfGc2) z2gEX$2==hHIQlqrd>s~2Koftz=E(`zd*E`Bo|QFONAoe^xa*;9=~!T)&5E@}YT`v6 zwmVNSJBa--PtTqc5cC0jg~F&^$Q`q*K#(sw2^=`ET@w?gIs1Ja;&l1|DDN{%J09^| zF`3s@AOS=p*ggahC~S3uGGAV7_GdXbr{167Kph4A;tcTD$a1C{c2&Pm_mU;Z-%w}< z83c54rhWEv;74^>VA*A*1qn3JL#EZ&VhIRYm~lgjgv~=X3O>ncEzE;1INkNB+>TDD zeX_1Crt;W%+q|TWOq{i%6L|1{{Adfr_JqI8{qDw>C2^ ztD4kQziN@75bZjguWnOhQbJM3@YPyS`VhZ~z^l z5o4ztIQ)7L<$0sa27Kpo3${hV>yLiVE`$so|BCHO&b==YdtgJ`-}6(t^$KMTLqJLH z65P&$bNz5+3&pUBX(+PYHG}|Q9mHeMEC_b0{Z#Oil(j@RB8PN`a*_=F#p{M6VWHyR zet@Qli#qD6Aott=Z)zq(#e&b6i*KaZEa(AhMoFmb+B~Xwns^HKqAAHf>ClNT{3lQR zul0xQg#wdm!Ohl8s7$v5U5bnc(#&IKGai9PZ?5sYH|w7p%qH(ZDD~#;d~tLk?!-j$ zL8mDp&~z1fN&UQ5ZAX_t$reQXn9=l$+4$&2bjOl?6ld>b1XUN46xjtsxk4?nbSo>k^-RR zk*AVDS!7q~G6o)KSnq<>`w#gC1m5nP6r-=t;%O11!(0k1{+jk z0dI1{er?{r12$sUKru>YJ8SyyR#gy6Ajb=eZl2w*VFN~L!fG(s{hcG=AIWpF{jdvT znpUQ%_E^S#&E9{*3-rjPZd5I4lR9%9!m1S40MWnI(;X<^(VZzOw07Ibx&9v!&!=X- z-aWvqPmGpc=4I>Vg2tv7-%Fo^@?|pu)c1IULBsBoVFo8!^k7u6T{{U9=7{`R6#`b? z2Emnj%-jRCmSlJ}Ih>SmcA7>*mTYrukQ$t(!gjz6Yr9LpIuEV~^V#~{6f7n9$#4w&(B21XS79>YY z>k54kn&qH5*{^nEO35B3QILG9i*rAjb*41u0=WHfqnnfqHZ@dn<94aT{?icvzEWe_ z&_D$l-WXz?gXEM5Xg520vj}X-;oLgbX%>o3ZMgQ_6Hv&i6;`$MN~RiYa7ZH7!M<4- zX`vhm5DFRnF1o4GX*HnOx*KgCT`dL35mmm)iTsL4vC?ed6lWlv)Ls9;relL$Gkg_f zf|34xm8T%<3$euh6W1<*?d=b4Y(QQyv}I+hHa-ODgol8GO-UzZC2#eShXMaSEG^oi zRH4`m9R$kx-risBxoD?gb0H*lyPn@TF~p$B>BzJe?%X8*o}S9ZQ5RMc5@zR0B|t#g z%ytNHW(>sQ?w?1ZtWp4z>$Sr44(e=Ouj^uz(l>UG0KL|R={6mPG3hr3Z3KBcb{0Sd z1*Yqy!B*o7bU%;Aw?IC$BSQhr_yE`LtS&l5D`HOtL^HcmIl{( ze5`RAY%*cQ8<9hI>6oiFe2|BJ?!!FE9{f6l_L##356)D z%auS#D4ykH>1+0tgT^q!?Xtc={+5@A-K58%o85ZQV9_-B&U{Y`Y^KvaB@U{R{U8gS zPt3|peC)|ew6hh>yb4@Zn;FID>1?3?;;}=vM~aGY;gGIX8#gqEjevXC*4c+!=8t*1 zP$IZ>cHqHp^SSRDg>haKeGbUF%nlervK)0}rHq7#5vOwY&uP_ag? zG!aT#mDx(ps6eZBCs0o!t&VuQT(Op0-;j$;&T3~mO@LwmGlm4{O-!W<-UG_#Vrhw+ zCDi)&fP`xO38OzO;v8>pz5oX(%+<)SXfxMEc>t}rat5^emCBaUBL4x2}K( z|Ln^{x_Zngu<(IEf6ka;sC3vtY7&|+BS9L@(&?w(;S(bbq!Ny3?T34ye80JLXaJe= zXKr7$)HeOojz{~v`X|_DNT+ybET?lgf%swuOq@>7uZR#U4Tkbr1GiVU;qH;gv1^BL znM1IfplGk_!+Ba6%$>SG_5mCZAFx<9N>fS)tFz00<*OxM_fkv(=&5Y%kl1W9m{s1Y zZrXU@rYzHbrig92IPai#^CoB#!O7xuA+iA>(rM~LEW9T%6Pxzn13Y^I3E7az%~Jj)EU5&^KtEr4JDYoQlib%1`3d_QZ>(0c!^oH4UH{Yqu4cT@IDzWdqy-;`gYysoqXPsS|*tlaXO!_z<6iEC8yjLm=5{#vx>mwzY zb>i%AZ`)tn7944^KtT#!{Y1=+fWaSX&K68tU~`1j18_(s`<+LZ)E?5`YnABXX#rt! z$a5TwA42$166x{P7NN4G?7m!I(3J|i*L8xSz|nMr72BOqrYUy}u6>5ePg5tKzG^=t zgj1{swiyAJ%ACA7zpsU91*0rZmT(0jcq1c*-Ay!ef7im98R$UX?Gge<0z=`E4Y)PmE8a8Gnw^UP~q^qa;p#pZzz%i^( zNA}cy^i>`l&t`yz4$zGtm+^GY&?3|F05|dByiTt3%(8#LY{KJf0v`zus3Z`&Z3+4O z>G#g!KI_BbKBfLj__t)?K7-Atsw*=E#Sx6|0prS;dP*-*jK_=d1pH zd;3VOp%)j3i+aqJRdB+=57w4Y9D`tt12Vb|_OON8uUWP0h3JEulov7%1Mt<4i)TZlQy z4x|wKJ|50vI~3o;5JA)cG6uL42VBx(8lPMM>~R5kJk#Km*DolMXO2#d zbItbpVWyE2AT7@D!9piNN)WpWbUC!c~^rv(LcvK@d#TLBE4J*?#4t zmeonW&`bc4Ii%@Ll4{axqGGJ!b((H>S|C{1dqb7zPxd`uawpDQFa<=rbNYgj5?q@` z;ZUA*Z6@qZ~8+e8c_U%-gdx91&(2~}(Kkq{hFr*dpOjzR>wW1v>$ zy(#FJoF|g)YQNsizG#g60EmNLi~d0oOV%F9pz4^sOL_>J%yNe*F7TrOqxDF|mdgOg z2kmrxysEu?_hw5xbY{r_Oqg!$+Xs6N>Xi0z^29Gmf~+_r>Z}aUHbeoj&{pS|=)}ex z_r%3g7BPMioay7Y)@kc1$Vj4;YBAbCLB#EuQ~IQiI!(7Xv|PxrLnq|zItlBNUy6jr zwj(f#0ippmcU`^rre8+mtg6K-M2;9d)G%O&6TF|F-_w;*pW~p>D0S?d%HV9C5-TrO zEq$!Qs7G8Sp@2A+26|quxSK0L1<)0g&;BnJpiBVx6b_GBkwhzIP+`X?s${mPu+$1) z20jd%!RG<85z0u2m`dF|i(y*bme_DNy(du`mk$>}-ES*&KYFj;B>&w(Ed{$N5A@5| zn(~_Knx418pqyDqCHf%kVfP1vy*tA$X=&dPxy>_hnJw5R4mX4qgjc!v>tG{%0e+55 zYLFrN|DT^*EAEI?83jT@!w+? z{%wu_9=q^wYy5xq*oFVs&$`)a=bN-W<1swt>ev_1oq&JVuw=lur%;+K;dwOQi++pT z>Z`n=9e8~3A>sP^bdbKjT;{&g?0$x2HNfPfAJkB*=sSA!@eYpp-ttsK?-o5hmx*f~ zg}^xUwB$xKZ|p{&St4CkI)#{8$)m0io>r8ROU1hOTM3NcbYu*Wtc-8* z*fgU2H)Ppt7zK3o6Aj1M6hOsSsAO1Z<1f~1&5q1>WO?CK#mx)d7EMfdPe??>@whHSU~s8ROrlFe=M_#--`U;mdXV0;UWjuEi6g4Mw>Q zPedlh@N3xVm<=K3iwU|-ZsWAFB0{;3Vs8ZbNUJ-W`YDA3dZ?9jOJPL;J^Y)NSBH)t@ya(>7~9_V&y6k*|yR z2rE1of2hlqi4bpDyRs7r3Zn5k-)Ldph&T3^!6xdZg{_sFWQnRqpJAvS*CTgkWhjnL z5#%b9x>I+u+1vq?{>7#K^_}zJ*Pd=?QCGcZWO%fdn_5G6egfoa4E?rzb=teKI!@K- zK_p&tZN{Y4YyxOsrmiTxNVZFn;DG`Iv%3<~V-0XqU?l8zLLLI=&(r!mvXR)}^ww=- zk#6Gteo@S}PlpeSEPq!JaqFl)elzW3cl}FUYB|F=YiLd*L!35cVUo3M879x@VOEql z-SsR^kvyNMbkotsdIUU5%La!DJ`j;1EvtpXS@h0@oEwGT*$l2nb_@F}$6jr@bDd4TItS>;NE2yr6lh7W5VUK* z>-G?3n6KP7C`$sDU+RB*xnC)#g_a56zk+t}tL?1T)#;}o1Eqd8udzE}S6dw8yS<1sho$imh^ z4-PTo#ie}VCo+IeT*z7J+8B`@z=HTfSUYNM+x2Z=1URA|`LRu>ZYA9d^oJ!sj6Uhp z0*LID%>L|Mbg;F_J83OUM$I5lNZ{5+;fuutu1>%cI^gA$kBj5>T{E#_Q|A(y=symN zxT@b-ALy!|uajIH2AV^t*yYK{qW%-2Eeypnrjt%~MQ#>&b_Sa%IRiCdpJ<;u=x$*S zp7}A*5hU%EI6wuKnd!%y%*g#^q|BNTnFsOGzDI^$#PF*Y>lr!?PJdg=iVP9cQFp)k3P3hyu@3yVh5!um$Lb7Y7D8}QhN{$ zz@I?}8=I5;U<2F5E{{~Mk!toJB;CV=IaoQ9Q&l%Ic_F2?Pg3AQ#p0~TyfNqi;mdwn zS-6u8QB_!JHJ~Jn*Cb#3EN)jlR~uzNsAmeSPGXXl@#TWucHr2DLelGc=2Zor{U=xI zvpg1RcD`n(nk|mwAFC)!uXOI#=6l(23v5{wp?){l8zn)*4QEW~eF-ozwl11`ztV_UOVl8xuMxKod_$z$d-6~{oo+QdQ={Ettr=Jls? ztq8v01X-p5U&IonQ<~Oq<{Egs`#6IlEj}M04KqnicI6pv5F!vCYW)6sBjVijEu`{@ zM$3{c>B*7i7Ur~B^&EbzuT0>*>7Z6?4e$iq8MEQP?c0HQ%odXT5ewi1-fl4%)+_{H zGgae-H@tw)c7Oja>p|s^o|&8P%`;cJ37!90dnSe@&djen+SJ(mo^=Gc$n^~iATbqB z5+1G&GB75m&VcshUbB_9~5eBQs>r2`JGJUZ7OUnXG;p* z3T;*mF~aBFsRd?%XK?r7?{Id-F-PuH1-p~)C0IPxiQRsGm2rdzX%;rxEVkcgbzHYd9`kB^L}YH_uXs zEC$^dF8k)aTL*mkD#xRlLPkqg<+Qbqhh?^CGCHh4@SEU;ID)lhk-$9?IR{)&-<-!$ zj?4A)--!ztB>K$ZT-M+Am?=At)_d}vKf- z{uInC!EJDhWQo8)yOW-8-WcJUZK+dEtIf+=6uQ6K)PD#`8c=>uDkgnbZN+_Jq*L%X z%06&#l{xcT-{gMd5|@+|OF7z;^2S`h$NW2Zmeq>=C#6{>BuE0TjJWXmX=}S$4=PJ= zt~h@k=@DE=5kPfMJm}!Ie80ODzv7Te!l`J@;=uV7H_Sv&pG*;$5P0kf2|Q!SC4Y@0 zI?u?8>tQpwvwdR$=MlYzhIdG%ahoh=x5A*81;TWm$xCGhvG9sH&F$N$S`FBq&Qm(O z-Ipd0o5(G8t@NqjTlih9doaN!10n(~_0h##8t!G0*)o1BBvB(t;!8hdYHO#)5V|o! z5G)006OS==Bgu0w`MBR+8i*UH8=LMqYE3-sTR2Xd@jdhA!l8taw9{j|aDm(sQcvWT zo~Dvy-Y=r2lua(zJ0~w~Z*1S-QLsw}cDfseM_h7|)(!H~Ac@5G*Y8G9q{dD~Hb~sl zZ?R7=8qz<(jkOeVGvb#JR9Fzv;ko1#b`z{dHKh**gS~z zg&wqol^&1$(-Tp;DJ+e&n}@89xBcaf4U>I5>u4x%PgnXVmDM(6Uh6FiH%Pr%Xxvl= zv$v@%(IVda^7%wmAO^`8&_~C;E8u=FdGG#|(ESR-M)8uUh5VtFTpk&@Cq!b=p#BNr zigiWuB69YIs|a0ulRL?5NBW+>juyg12O( zykuGDKHboQNx8nPluPWU9QL-pqln)9){YfzQCR3k(3iA(U#BK3im$pUWC4gt={U_s zg>hp$hS1GW&XKw|LC+6t=a)SgU8vTpYk_{H%nXyfN0PmhORXU`P#8E&)wL;X_i?ly zffkKNR#HnCIUsBn_1diu@-&xB@>B2eW1<#J8eAQ1jfK3Hm$RfkbWPOsi(jOl_z<<0 z(Sdwsf%b8zk2@9i&a$dw@RQ{b*95-{i9|ENVO~29;azCh4{fJEG)aF@q${wMo&x<; zJd*%N%#_s4RV(@0_2u!*mQcS$Ev{&}vCt5B{2MDy$Vp(T{C$vg#`Rby?l4gd2Yufl}l+h~$A98mQyh9lWl!q399`v~M`}w9 zIqQ9~UWaH8W_-9CG%mOV3w!@aDnIltxY3qmEU`mK56b9SLTw_CYANZ(M=I zI6s9l1PLcqDd?pf`j!TRa;2h#ZZ9~SzkEvk@fh<&s}io|!d8Da zKtPi{8x>yE(vma+ke_wj4fPqXeaWJsJ<*J(5FU{&;tjes92y^dbjKKxlIx2=MMr$+ zTQj5**Z1FR&tAI|yKBKwQ&5OON)rW=j6dfUq`a#>Gi)Y{Ss7#QX=SIE0-C zKbn-`8PAquR>lNfo*W3AC=-g__|nsQ6E`U?)LC~WthR7@2=u8BV!ecMts07}hMnbw zX%u3L<+tz8?SHJA2)M>A4miTJ?t3QMFb8dA7;)izB`^ZNxCRepxxNS=w6+nwCSyuw zYR`BH*bj0g=TpQcNvSTLQ%mfxD|c~)uNE@O=MpN7=Q;0L9^KpEh5{f0glkbP`+@#u6Jr&Mdd)Ox0g6c6o;OkSrGzT8!z|JfoJ#cPNr zt4chRBxW;-)t3_nm?p7w{!p;&;2KNoQ2bFDk6HCD=aOZ!Ty}>J+OPPkU5c2rxguaG zeN3a$OJnx9sv(H3cau&<#;kUl6f|Z#yiwwqzCh2Up7M5Gns=_FToIPA+fXuMHInjz zSQPIych4C!6a}l)QO{K}v0SC}By4KhX1QBhAV1zdAia|{gg*2H(W5+sy8!FGaL&%4 zn^{#LSVq~93PsOLaV<7u=zC?#3S?~Wqoc3WiK! zy=6pyZMfKd2ib~2N?C~v4rriHX!N@d9+dVhNA#7g)kQkF_S0i@aio-}g=a(CgC7r= z=^1atIZuQ#BucJP`d~d+aN(dJ$2`g60Iz1J|v0RTgV)R9#3_gXW8^e zfhE7-(#n|k`EwA_*JaDF!qro;HdZ}naD>A3{U>f-1xFZ9+$ETC5#Au{D59;RHRY!+ znFr?Q(_|foVqL%HnsZYjeC}U+&(h1WZ8*&+HajRh-S-rL&}zHc%A!XDZyCyZP5Gv_ zUS0rT+?DGbvsP2$I;Bi1%3LZ7ZW(OE9f)Wy8oEvu5m*+=5><#lw|6-_q)RyNjY8yf zE?<0*?-{qONf$PS+JS2m53MA#X^sXRE>du*>$n!96Xgn>0m$cU@>MpLciPxE_r_sq zPyXT+Z_?6iL(0lLo|QE)RD|&jT|xz%;I@4~dy#s%h$n8DFg%|+3?5ALaOt5-h|7iK zu0sd~p24X?g1(_ngQ)#|baDTN+x^xmg6+#g8%rDJ=rcmR2ck`O@B@*=w@hb@E-w-PBaw0EF&Fp)~dKbPN*Y zh$qb1=f&e|?_aMm{4j?+gmbLEP8i!Q5)EX(J9 z@rT%{abr1kegQnyLWL?#t>koJIXgpEiz`LKzbPr~;`7MXW|Kc)jBSW`Wh5}1dYQ6}UXz$N z2qp9R@4XP+<(Pt|4=%Z@$8vE(Yh&57%1&}{=HRJbn7c(B3XgYjz8;_xQRER|IvnE~ z!UFiawo_aM0I|ihkwrBJB!=28O9$QP70_p{Xq2r0Oyss(+pz+izYU0B2HlkQzHLOy zmZQwoAZ!hUD~6{NiN(sN3?_Lj3|pCETw}8?+C(^Am_T6a)9_B1+RZ_dy(@3z$ZU@5 ze*S-Wdh56*+c$35<~ESqx=|@bLP0@5=`cW~OF&vl>1NbGMFj*w>7Girv;)QrkYS-|8y&+Q_ zMgHJkefw#0+@{UG0jc}))H!Z8!M>pyJ&_v0G~kU;350uA4N{aRxQ3&OaTu2gow&W%q= zBusi{*4&Qzm)hhpF;vAFx-`j9?9V5pM*m9WnVgxPPCm0~@^GlJ^-5;Vsy%b0P9XGF z)eZw8ELQybrSCd9Y>di(wO7H(5l6EpebQC*YT3xEEy|bCBK7tK+qS6&;^`{M9DE^w zQ;;LD79lZRwhM63eCtG-`lc;JP{%cl9e%w^!BdGm#<^?N8SkH%Odh}1n zNoG&11P2#UO;Uep* zZlsbHd&nrV%84bR&wNtyW>{a3(;3zO+EOz+lP$g?=k@2?{FF&>+-S zZKhMTTF->&>UGKLy#-GVGCHG8|JvM7wOz2(`Y@Qehn*)BgdpqVdKf~~2hz0r!c0P{ z8Bu9+peOP{QKE5`%PgdLQ3djnpY=I|YLSK_i#KpeES;x;qcd%Wc$0<5{QwlD(k6X} z8s&z$Bpb>7|0^>a3zMmOYd^)S^)|f<2=LR-DLs(D5KMkoNRDXg9bg~dWvAV+nkDXi zy;goEH+E~=#v z3P{jZ*}VnH%HEuT$hCzrG#r!#Ukse<*CvMFWNg#-1={+d>8C)1wa+iThurb8! zA^sdQ&OEKa&?$uiZxAcoTqc)}8l`C1FFNg=RC0_YGd^i9Hu5~ml7C7Vt3C&mI_4<}Qul817s#}fe_xc-2w zAV_KpCgrAD*!c!S*ElKZMgFUk=Ph9~=wqz3~hP2 zH}SP}7(LOtElCzxaH^sKZd3!-4TK5j+G1^!{mf_cI!Z4H@>39qZnZlMz4d>wKS@;s zx?#;+0wTs>lXxNONQ5+%yWH#RcEU?La~puLlZL?&9*3pu!I=te&O!KGhU27r=4i)* zMBEi{BE&_d@SQmT*4_j&C%Ef+2B9^C`bw{x5E3euV|Utgb6b}vKR>%*_&y^;Y+9;a z+3RUaV0`?fcRvI!5)X1^{CnjM`M*=Aj&t8|Wf-plE&2MEMh+n~^l zPB&gpX70hJqbm0h7WY&H?c2YsR&UI+lKkWhmYF+))1Xd&Y_Via@6Ch;$2wISpiyYomC|g(yiEPo7)Gctcq0K4W61Rnh&d~qhA^}aQCw1t`pcbG0P)>-wr8ypK(#Tkwl30c*=Ap z@}TrKLvWT8^ZMOYdKt!*p0@K-oueoH z@|0ce-BFOc;k&++C`m-B!fF?QBJ&G~>ZUrdYt8pr*i3u;Yr+x=YKQ;BJ1y@s>&F{; zi#eB~_m#wRY+loMT9d$T;0El{i;U<4<)hW1_5ygKGnc{eM%UH; zbjbv29fG#;?altfdvvINpsJu`z6j$XFn1c(QH;4Z4*qVjVkmxzl2~Vvxdw7^11dc$ z8`~hhml?~~5$;JF?A#?Z1Hr?;ZMPdBIx(jBVs1CqoDcB1RV6Roc(7a~d3H0%u1{(M zo^pA&gZfI!DkvYS+xp0i|1QE>XbBcI{%B@Jn9pX3N^KQ({hT-Kt6K*Uh=(Y!+|+17 z_Y2l4%AB+Z@uX;)NzvX0j;D9FFx}1-t2PPrA1AhDXk{lQFBkpMCi147+L-tFdj^um zw43@b-8LY?$Z3aG*gFSaXJ`%r*^GcORPxaxkL;->yT^|=-;$9_nJt12!jS4p9uT+N zZ37-ekb|-uAzU+@1-RYch+e+9ge`nnJPY! z{nNj8UAd|6tjR$6c1{}4GxKG>r(P3b0JIz%K*x-SR&Jo01~xmwsP!hT?17$OT7%r& z>%(!iyYwuQcS8B@!fb7YkP$`o>1Ch-k1OiqgQ~QOFz;D;!uF(|nAgNip}C((pj~eL zZ4|0=a}k+VgAt4>sZD}O45{HZp4b?#RO)t5Bt>cR-ohT}JVE2pUE!Ci@?B;L=iC&- zL{ zaZ}Sf3{@XVP?wHELMegq?TQ7O zwX;+dxHU`JmVI&al#x&OuPG(+`73YaewC+h{NsQdB%Sif;u2^d8u_(%R_y_;^C zJhVXApK|IakAwQHUqGgIB!_`e2-=!u^cRZbI8QHzee5A{L*|6L&mt&SadPQZU95d{ zKn{Z>ANny+!0@hNEPsFm2?+gIMcO_4sO%aF;R@)wsH(8SfJi`XUl;Vggi1Sk7X|lMnIC5LFS9;*fYP*sdP7- zH&A2Qk6}PTln5rfyUfz8D&Fua@i&y=%A}5&KL{>((?|9()KdynO_mPE=fX zxQ6su*6LPC;t6r4UK9^ubGkq-zTvzgNrVEK<>UK3NkM_mLX{w<=r_iS&x z6Oe13)cI(9Zj^tg0YRt97~DXr#Ej#Xy3MNt84%|RdjIdAStm=tt*qZLb^*n<>Oz?+Wg z%lGSA_JJ>$ux@F-tOew%-3X&af*=lnx3}0YDZZt;dtd*XGxpqA?{-;i+9g9Vv!6vC zuJVEef|hff=$(#a+-UFpBV2u1Ipc%yakC<|^EI?7aE=S*=p4ffg}_!>*?mz>jDd=m z)gGE0f@0#b4|kUOnU7|I-y0q1?5s;)l|)QQAb=|^36NmEjg@VW=0DQ1KoV=wvPLq7 zpfHb_dFM-zLUdo`k`Nh}ob|@|zW!A_u)V$_MS^iTj*3G$as35!I>KZXg0>);mtxuo z`K6HKHFvyNHxbNQ)a99Yqa4gkiRlEUw6|Ju|5qyjr22ZQ39<8G6jf#SNT|3xVYNX5 z3j7hIvy$qS=c{%`f;vhymsB%p-rKkoU3LJJ7LaE{%F6>Sh40A0)X!-SFyx&3_WBin zxa3Z8waeiyk?r3P7Zno#AcV{=4VnWY8-1xLlAck4?&mDZ0Y9kmjhFN~%R}eH~ z-7wEYd{yKocBdc9ue#ZZ3_(P%ztdQX=o6ccNUoT|X97~#Y;#AKk`eBlL!Q_^Udz;k zh{Vaf@H^e*y%3qVSQ-M=BM74z&AV9&4f8Ch(1GoRZ*V}0e_UuWmkDVG<%gWN@|N#s zxApWrIOhII=A+dn>Uagi0!hssisx)hEiI2*!JlJMuGsO747MbOmmdzickuz1$PWFvKh zAf0?|Nm9}_>2_(0r$Y6ZKMurfLl`=IeOh3;DzOd>Ti7#Q5@UgH*&k|fa|uXgB^cd) zE6GJ$Qn-L5uT-nlgA?BIz}q^VZtELpmZ?1~;4SWoxY?A~M?Ujk(uBzL?v-`4%gXW< z3D?f(;j|6PXHUBuc5O@U2t_wmQX5aVoJ8cG{b!mxIA9y8d#xK8$5QqZ)}^Lp)_`eo zqkJzrL_~Faqq8YYBo82Qnux_dSUz z2kCgx@~X%k45s}J^k#{DEq&jB&4L1sE~6A&B}}{ zWhu!sMkRBHo_^5=nGz5A8YdYa)_w~t{fDALEgAx+B-ln?s03*V5cYbdv8KHh5)j{} z;nr7CHX4iWv3wT5AFn{51DLUVC!L{ziLtVa(14RF6YGTGawIKSG)QG}a>!TA^vVV-y&etT+!w z1S&SoNpWoCLecNIy?Ag(vqNzM7;W!QyWgQfn&UxYk3^bmsTfDLgv@qL3*@guk{6WE z=UT@)^jo{fpGGHk0=;3l%I*WJW8L`CEOTDpnFw|dnrS&a`~~zTGSeQJJl|89;ttIr zHMvW!H{6Fr<8u8K$S>JGE_$w2V4Vh7W%!v7I%IKSPcA=E`+0rliY~B_MpO;(iazeD z_0)}UfYLgc%CDi|KTM+LvKa1IOt`SYzj~B{vVoyVsKTy_Rd}S5x_)wj!*%R`o%j#U z3?0J&OS398%ir#YH`jV)-=I;;Z}3(;{1ZF(0Q^Nbd3;4Q_dUZq&a+c&LXvoLC_x7f zE^0Kunh5GOhKYxOBzc#fmy=3`DAv@iCDrJ8)wqRZ*8bO!H`gKJLY9m07{h?`=4}3Y z+kt@++Ma>(%y=srLj%(-6$macLZ?5xSvYKP{e1lhh!pvCYAq`h=^(46yxq4`(Mnmi z#et=C$%VC#X*qSI41?LYsTmoV7i!2TPvnEr2WK!U!k1PVjwwy8iuNP*-aFHPQ=Ivl zU;FE-^qKDeKg;%1(NU^~c*eibNFTiOohuuezy6!9Q09%8ve?dQ`SG(G0T76?JiFkG zjMt(Mv~ygBF2{msKW^eYB`A7|?*X7Y({@cq4F; z;hzK21!012HRPxGjW7UigTz%e>IKB1^nIB;zfP8$11Ddn1$aRlEPm(anSV`6yt?Fq zws(#n&p!*9s|?|d1g7tH0viyu+K2&oQEzKBX_?Q3B4eXoE0vQlltYD&MZ0I|^ifoxMo1*&&iFXoi{#LeuBhf!vs0W?R+X^2H5Qeij(DGp1cxho{VdIU}-9 z-_jn7*VtcCloD6-G&g;*5#n;Waiy-Xuq85=G=7K#ksyRnh z$N2-&sN1;WaB9DHKZ5|fvTfG0`gT2aZ(uR~?67%a*PgER)$=N#z(a@ysC~5&>G(GR z^!O&=f%+~iOn1B8PwFFy2{9CA=h4)y{10J?{F$TZasR{_r$}@b>vFm^s0+g7wmi4~ zCTlblE`-9YOUO40K&>I01w>HUrbh97a0Uph)i0CXWKGQgWsa*GGs%tE+Hn`f=bow0 zd7q;d_weZpZ0ZjxTL;qe-$B!XlgHm-BE2kghPxft2&Nu5i8E6l2J`7%%x$okI8OD& zlg*GuT+C7d$=;Eg9w}P2yC&>AkZ$0O4XJVr-B?+@3=TM~&beD*+-7KX6q>R`_iP_9 z02LCypmCW`?@3&jz^2cLi{jpoyNUcNIK4OBO<7u}w?)35t8q{0`bxBuNGOjC*P5#4 zU7RvN>=Eieo1N~-^|;BJQ44JDCkoo?2c}IGKr-I6JWI#-mmP2B-^nKpZ0%>C>KA%)|9M}Xz>ZXis!SHuyY7}`Olk8+k6Zu)DomG?N?(WJRG^}1 zESP_~i*Zv7Uf;h4wV_Zo_;#FVV6&bSR!GpklTU4s0_#{+i-n}(?Y&@UcP0iDifBpK zE9(X&SIt}`KM4x*Bhpnax7M+0z}~GPbGTo1v<9p^`y+FuZa(Zm1M@0!f4)oPHaMxc z4YD0Trb>`t+qN&~tG?U>dc0EKG+)qGa0|B_b>t)YdSGB0G_rU8(D@JONb5fHVn4e} zkWEjv%@8t8ASL4)DT8>O3d$M8$R(oaRtclCsB862OA>o1$MF22Aw!%(R z`lM(V(wlp|k@{6Cw@9hZUhg)!i8?Kk&n+`iMmDF$!Hce-2thMEE|I0ftXnWak55`G zBzgR)$w^&8!h(Ktg(JG8T)gw^BTa1M#W`b8d8G7rS!lhFcA&}Z012yZpoVMHt+>MkWLHIO-+%v!pO$rkWB{t*pTGoeO=~qz~(E$YP zDrmjd;WJ2ITB2lZuSDA=n%jZx5w}&ZPJ_+2-h0DXE9&>^h?Nw5M6y4r|6X$G#x-Zt zEHFau#LlS}$i9^!4V%S*J}=&8>8@U%9g`*bMa;ZyUj<>*;tH-wBbVG__{1DKi6Pg@ zOJMnr0jv$DN6D}#WtR|YrziZMNOcbM&)G+L`r`}jJAFiU18>(P%qM&*IO@}XK51!U zIbKk8=uuBgB7sGY;=<0eTL?!{xs$!y2N;USe(Mq|mCrE!Q(2S7#>ny?4@M{|e}(tL zZO1YUq}c6^@P>Qhf79}F(#4-QKZfLl@|?391H!PR0I9h-64rY;z}j(Xmuz2Ii6t z(>i?V7`R*#KAl(w&Xw|*g*77?-k%cFQ|B_jlB4C<-%SMkXU$zh1AkK*hRTEY@8HwO zK#Nan361*ll#&Wrc~8VHn6q|f6DB%MKm|W#xBEj2+6-5{`%(fe*Oyx68Zq7-^(f?C zo@2Z-aLJS=BfZp`#})1%1ysA)=ViXp=b=Zc@4#+>%KtyLCiHu5OO_7hiN@vs;~w^e zBENu{JBt^`S{7Qn`jveG>HiW2qtvrD^>c1Yln*1ar^5XN3^%&@GRkS(Zfe#j5onIP zl}o?dW=_rNB{@t@#V^9mR{xJA-QFIHS4TuNxPlcn^1N2p#BzX3+w5>Bl( zaWoNPM{`k+-;IZI?fp%!R$ z&Ij~9r7PQL0aSin?0HS4eU+9f((dhzXi&uJC8BfNKfXZpxmAM&DJp@V^-o-?`u*Np zTBnK)Cb7cKU2x1X>bvAnbx*RO*A+|`l9~6APL=No?tkZHHtL9FgmeD^5rDUEsq1HP z`}bre#22I0ZPVeruoKjJzHYMl{K+ENyi7D7Z_{qzXOBgl$AxLb+BOKNQi5TwjFL$b za4jkjaqQFPrG5ybx7)K}5Ag2qLy#l%i}RG0ajia3^gS4_F;#A$;W&l75i3;bLP3E2 zUnT1AZB? zQomHb({XBx&$@8%e`?`6wWu8|^jfbK#e>}fReAZ4Hi%o0yMERtw1D}`!z|ip-bgNs zm-XYNMo6cRzxk=y81RI~(@F*tPp#++)Atk=;<}*xWmvXkB(x!eegg~OyrbT%7~NKJ zFGt2|qv|}EgtHl8Bf?hg5#~z9+G?)fp6AwWxK3D|YvJ%eh!T8DRazJuMY0Pa)s42n zqDH=FFdK7qm1g%`sMM(ZvUQEUJqK~4Wc`0mbww!)zmNG>>J>-FKR+*B&57gU9DzN}DJ8mAek}7sucBh!#f84DQAOf8R040{*Mo1RPsChJJazxu^F#kE z?!Wn};7r3>Jz6bel7gaBC=D$Peb^s%S#6Kk(4@OsWdj|Dz*zo_et1(lHw%Ehy@6zBQ{}bBwU!b04#DlF3N+M8d}Sej1r4`+rXO6fE^A zk(nzPI!EkQW*J77uRTmAs}8uPO5|>=C2Y&}wpuxdNf(u<9uM0fH%J#0l$axKAPWaF z5ZaMSX4A&e=9qmWOK?Ft$=Zd`U#=)(VXJ&a*>5w0ylXs}H&FGywcai8$G%WWmV_<+ zXkh2XGs1mJzPLy?>3quhs)w^{GyWg+vp<@k%e4aCegrBL>rPcO!b(~?H>7GD^Hzuz z#><^5HHx3^YlK(~&D{m~ORJglly8FO1;qk+O50{Lr&%X5l0}F+>Y$rcYKIC9%IRZg z$FMCDDd*n}`0E9J=FVNB3c4`z70i6hJ#(+OvK#9eb^9*yJ?z^5T0*u|bhb*x+*zVJLovr;tR-LIi4%OG2RCIM4T9=Zn)9W%? zE`-Nv@b&fea=NLV_0t~PDPunh@VRz6_VJNR8tSZ;>deOrR`>MH%*=>(Nk{S@Bs_$j zGETetU2d1SWsdm9m6gzn5Dzs4vo=!f5lkvr!nsX@fLZ%VOD<;8var@WcU z`gKX6qf46p!%70m(n&hrYk{dNuS06Z%eWCk4CkKxwzMx9*vwuZ^sJWDARqV1T zHmg*|y8I|_%~#HZK|T{@fzqQvqo%1inv=~#sBsgjZ7JBB+PdzGS z#C!dk9@+R(Coc5ip-^bw=xdo87!L!zgp;kDw_HE70?+JWMJlMEiS)OBS^+1Ta5#imE>Q1TLiF5T+Qi?pK5n zf}xPA10z&ci|VvnZa?5ta+CLYXx8?(Y^?0RsT6T!kY+oQtd;WQLpsk0GlIqp#6=Rp z`C`r48J(+}D^;qI3Bt3c*&it|xf+$jJw&*z!JXeee+(bQ$F7XH6IE)w-~!d}9^v1A zxbns|-GV*!NYvV2Q8GZphGyJRVvKJ$v1J^To|&joxnSlU|6mDcieKH?4Z$rKi%#k} z`Q6*M+zX`>+G4khoj{FbdCD{QG3UF3Cf3(Z_CBZJXgp56ol1@?7^>f@VObQ~OR-X{ zAwMs56eGKlZ&cJklG6?}EsRIi{uqt_+kFU0OvA?N|9*33Z@N_i#phw^cEK{uZdid&1CAts{{W%ua4pO(%CnI& zH6N7C9@i`tfoHh5uaduo*+k@8eR!y@s1MV2#~0I-EyC{p~FmGlOVvbOR;Iu*52;`C0Mk zozul1MSmjG+Go^@r$15ZqM{SV4?J>E2&tW1w{Am8(Cg^h<-=m8!QkXG0C~=O_1letf#$(8kz0!!MJQ7=h({rrn83 z3^_6kYH@x0xB*GdBViZqHG5=d#=PZEM<70%{dvkqHBkq&AtZzHdOaIVD0WRSzbj8! z*qD~y*75Wme4;b^tqP}zblB}r2#mQnLUw+$;8=PTrC&aoVSeZ~vuyE+Oz{K;WMsf; z+YyUa2atmy9o6j@)>%_(7n&K8H6?e|#Qc3qhZTtR>&Y>JwBmd{J6x6jMn=BL zY|rg8<&Nw({b_B6F6IyUZPp4>&T{vRrkZPm@I$s1lwg^sGj9fo`Zem2h{v@2Nxll8 z$GmC|Q*KE^t#-c6vGLRt&6MU ze!wNkG5JVNN%B7OB>s8>Nj6JBV&?SXO;Q; z2$w)m$Pa1oG>U9t*nKq?*Cz`9(deITD&AUWi_f1#)U*!ta`t#REp!Ki=(DgzaK$F> z`}67*@4l_<;M}|ZJ`h&u{g7|hgJbMnB=^CBUeB*gqb?<~=Z0QIUGX|%5OCm-cb0-4 zPCq9gJfpU}QGyv^>t*QNpCN|Ttym{tey!O1I>E6w>F5O-bf^98Vi(lB;z@UF^fgociX6?rb@QD@jPD!z^3R1{xg@f&fDV)Z&q$Y>tt`6 zt*@iT)2ruVA7U~H(kyQ;5i4WwT;o{CeEztde}2)-yFz=2yS|=3eJL-pqwYoB)PJ$+ zj-Xh;?KppspA^|G>55<~DvlW6ZmKVQ#FgzLu_<%6UeW~r;!#!9Bj+FMtSt})^M$76 z!+GKPZ`B1G8LgDC3@10kH%<7A@aeBjxPwit%Nqq{KJQx`2ON{1291t|NQXHzA3Cr< z(@aJIS3KS9ncmmp)VSbnX_;un)b)4Xy*k;UDj6~aIKG|;uHYkO>YqCj=ihr<_&%`FQnxuZ zGH&-hXLWfg$85~V{M4WY=cUQHvIrlgYwrb!41xF9-L2wyG=tHMQS#j}O*_{);y;qJ z&m~W{y_Gq<`$qCQwhX|>VYd(q3%27#l+Ju?u^Enz{4oDaDD+WEdUsBy8aRPp0#kC% zwUyHO=BLYigWTSHh+wWoE&K89Q)7lVFWHG_uK)S;GY#uy=CzTOeaQ3_%k3XURzXw2 zoQ0nEdRT?Rs21-~H`PU8rCAe`;-b?$qUC6uN@VL6C2SYO31GEA6$tq=)v=CWvaM%Z zfqY2whgtSJW=bP*quO>}g*Z`N+V&@j+lJQd(`z$r1HEl+xK91Th`)Zd(rDUimu6zd zI|bRk8YfwM9JnPW3 zH%i=n$stXBK*)rRoBfpn|25y=gzd@pu1*CIBooT$gYx<>=w8bM6&a=R=U&fV>!M1s zSxZGbzZEm@OzUPY7zs7J$O$+UQsgItAjV(Hy!7usU2vFWO4`_5ZLt^H)AUA^La-}z z|Ee@x{h?Kk8|J>+E=^hvs_ej`LB1eV=wG@Ro%9m^7YhGbU4>y)d_ZP8J!t0U*C`^)8H$1#JJ!+p(*T3vmJ z@<|hKtsztMh-SeDPvAF2t@v4;g?{tP>9rvwdPe8BfBgD;Iko#6&Mb5e7df^c3%A{L z;Pg8EBLMF$*`YMOG>~^09qDVj*D! zac}O%((u<$lt%hk9hW3YR&I#x(Ovl{c{a!Fz{9lcKO0m08!MjO%EP|?xe#MA*x8U= z0EtrTJozSroPSE$>rgdI>eT%*_z(5+`(eVP=YGHEtv&-Y$*kzp(g?{UNJV-LAz&BJ zY>=1?#r-m8obK`Dq>dVxDKn3|DiefS7_09>c(bi=pBeoA&U)!t*Uk(Nc^8lmKlkT4 zs<7{U!JSK&;wW2GsOvtMHWBztFM6WiNVw&n1zwh$NAe-|=t9Wgn|Tq&ZN(p77@9w9 zRrfJx(w1tjd?C!~wQkqP3zZ`m(!$&gdt*cB`X5HpR4~1q^w8}qA07@xrPxXo=X!N$ zL=}F0<`Ym@9enWAuRGLrO^U%|x$D|b-%x_ZUkIB_b*8JOtUvD-uSPqCW&9tgb+r&M z*oZx4{PqYa+-5t>%#;W@la-3G{}Qu>yC+Xjin6YrMe|N^QnHg_oWdQi*OT$P`b-8U zP#HlubSFz#>AtIS&4A|>q@0}Ga&SMY-mO9ME}t7KvI;X_`ZM@a`t=Rf{(|}GsibG4 zYQ_61AK_4rW+}I}0>i-*uD|I%?F7nUe?Je_##_GKS1LSs%<(WM9(e#c>5CE=nY1OWYs$r2s|^o@1SQdQL?c7ab3!Cv`~mJjokMpI(ZwRR99h^e zo6rEFa(RytWXRa^b;x&em-p$Cc5Xn=#4S&kYMCsis`rVLO3Gv6tIkij<3Eer+(n{< zPshp+{BUn$z{<*#Q4E7OS%&FuXbqIy(M3lqGkFDP?XK8{_2e~$e1qQ;=W4CX7gG`I z_VFs`o*g6cK9A)d`3q zo>5^v3_9_Uk4mg$%{?BXfvgysxo=Vyjne+*sy6yG7IqU>vM!hGJbbww0P7 zsXqN&>+HN(< zxb0Er<5HLXIn@v$ISYu^w{_gg106j_pXBrJS5KgRasC35h7POrq^q)@uIl*L4%wIu z(9}{ycem5izMD3*dTm|ctyB~^LPF&|RX|7D4k5tTXuVnxxt;2S89l@uC?Y3$x}~gK z?8fwjZ?jA#SNcxUYTxh0=#d91Aj0-{exE9iWV}?SC+f_NSw(Q1C|&S$RgvJ};`6;u z1=3H-xx`(;m1)0YLfMfd-cn@HJp%kU;MiFB@AQUT`3oDOg* z-Wq?bR>!fwnKKOw(#(&s*Q)X~mxv|WyRi_iOS+2AhGJs8^Aie%bWaulopnho!zm{P zlF}kx3v`MCNPN6GPsY8uFUuGH>rjybnFJ{G%mn+nf{&q4L;HqqeIQ<m`BCah<(wv-C(n$>^o6;2KleR?&O_ljSb@fP`qE?wL|r!al|1V#YhOV54J& zysZ%uTJ(wN0hyLvw*QUlFw(C4sU}R?X_ey}mPJB0L|G^96jKZl*dpl{_TCTaids&y z??Gp-r=C8E_#l~W#dmNV`W)rhC@lOQ4abNZ&fsJ0?)ob(Rd3}?xTe5LVQ+TN#|Y_#4zAWbVWCu>@kd9@PIHcx<5x87&BYe_U$q~byb#-(b)gyhVU$-A z0frI8AA68%bJQ1K+_vUl&YCPvvq>Up~WGAG@Vy;#0emaZPH} zy*<3&g&4(MO?>et>fQgs-ZIMEOJJ3y1>d^mh%w`}QRim~=$%s@|a9;Er#MB zA1+`&((D6DSHxp2^?8pcuZ+C2s7iog!4d6OGY^^Ft|7z)DJ7TS!6&BOyr5YCqc$1q zDUX%bb($sso?pXEez=p0P_GcrKOb3Mwev`->{h8C&)p|-{W&6W+`H{`YIb;Dp=C6; z1B_g(=)vB#P-|0t3V+{IC#F^sJH9xoDi^V(wYgVbE*2K}Lzb3Tv-Z$Ldpey+d-k{N zq)}6~+?j^AN30N+i*VcqffdLmTyelV8DZAy{iRGByUdC!gLQ>dRotWd$d3@mg~E78 zYO(>=4-V2Def&b#J?s%NFbOC-rC2>;gA3?j?9bmx04FuES)xjACOZ*(tXIyqqviS- zHiog59WcnJM2*{N-StnE?TicC*AUTP;L@ufADmLim`uIN-@*0b&LtezW9N(B(ixq_ zC^JQZs_GvXeD;ZIiqXD5;pv&DeD?j;l%HHK&vm3d=+gU}6?!GDr!VEjoo~L+2Y2r1 zwyS>hoozSQN{21KpDpM;&Z?01Ix=CIvfr=R@w(i|zBv@@`Jf3Dvwk;@?l5l`L$ z>=W9fO|=cqT1#8GgRk^DEw5LHIk?&d{86R6*o)W1C!+vATORbxyr?^?=@YH+S2OxzpCV8W)3C(t0;|PG0?O?hreV>d z)A_c&CL-qUaDAKpJL%Z{>-t=OfDIr!v$PZO?`L|Y<*hRc{hkl(A|B@-(GKQY_0kU> z;(MN;E|><-)}G8i$s~Fq5aPGXj*?b%AFMIAN{$u+o^Sa=&+W&j=@G|+YKbo>u|6cRokY>Dy37;?VMz%i!avw$&OkC zkbID|pbh)+wE4DDyzsJaa1DHkzmh7~RNBlq=ceRL>D-=_R7@{nX={4F)6oz-6++oI zzfPS*KFK@F&6(zUJ2{_9E~`w=l}M-wS;z)DYrXEu38HL9^7D8mMmg=2`{|cuz7C)F z_MH67V{&Y6bbbuOTua&szAK&lr-b!bZsj(w>f8kU4PwMk551f93K6R=*j9O+q^{!C z;zp{EqFnh9b7SJCt1$hZ@RzB7N~A?m@|8#IZQ0$Ag|%(AUAMmU3Y!*QdDF9FwKP6u zz}R|jx6}}-oa1PTE(WEn5cL@Sv@Ry6@_64-OYjX-#8JP}GmnrVV}Am|=SK6rh?>=A zX-0_160F^I+xR+R7Oy7T(;r^c_{pnEipDro`6R_dr7A(DFb<1R?ZZ}kDa|cpeCDai zdrhMkoFl&V_sTn7g0-o|yvU^^2j0dtV^J6JK%k?D(k?Vm#NZLiS6W>cZ&~H0xlK;< z!0(|;=6=GT))x4`2tDa+xi_do46F!aM}G0u`GKy{J5<UGNnCgs1t{gPekDgjXv*$861&1{W`^7~iPgeu~?c82pAh6Zmg$f5i#6 z=YL9SXRf8%<(?bu|M-R3bYJ@|IC$;S^y#f4C7GGJ$Y2YIOe{w(ew1lwU3?O!-najW z{`3L|$QMod@CQocU3w};K(dHv41Teo61cC2cc0*_#3j=uUfHC|04P>8jxToHsOBL) zNgZ(G*4G(FxcA*P6dVv2lXrDh@yeW$lazbL^V>+^4!@`N&Tn(Zr@Abma{nB`4-YqA zps^!Nj1~l~?sMAMR%SW*WL4^mZfV;%i=^gF|peV}hCA#;J4p zGWWxQ?w__i(*$I73tR80W*b&rER&z8I*Qtp;y^oR{rai5zKD1Cq>5$}{yg$ND}HoA zf4A1vy&3k}-AxVKtD1-yaWtLWMTSy`@qJ7496U|q^n69t88c~d4rHm_0EfA`ZRTHZ zLj7^KZ!Xg0ad+>k*xl@D_U8>7kYuQJrd67I@9yjyv$q_sfb zaFj4q1Rp4+r78Z(npVTeUVE<3ekSA}dN=pF?o!@Y_M_;+hzKT3Qiz5p$`}M5xSKm| zyGC-kzE?HHv(m^1#8d2xMU+iqXa0t+y56ndA@oy3%3uWj)0EP3y$mqHpDCnSH5LUW z5@~fe(WML-FCur~Y`Y`kU^)B;K*44|nkh2-Xo}M{ESY!Gxv^onEUn2s#pJ|h_=6IC z=?j=2c<(yej4@{yy~SYpa53w0V0bXwPIY9rtxew5u*fj61L&}2Y>R5dyG_|ik{u(n z3Mo)xHI1e(BJ8Kml-E3l>JoVH|k`~Ptz8ic}BV%#~n>mx!1)|mX zDaTUQPd#+Jl-WM{I4CuZ!Mkj28*uA+!%wh4HfwY|n9E}jXqewzHgx*;aPU{HG{A*w zR}m4y;t}llR#uzspL|7p=|!P=^N@b(eQ)}!JC~kVC0H)q)e$;fbJ(mu7k>W-hHq!q zr_aF#2y0HytB{3yq&Jp)udZH`2nc@?L?ZCqs3tKw?Ot?Vdosy;wj`}-=G*1U>^ryj zT{jCsEvDRhzuY{WnHl5)JXUzHMt$DHdOJ>Hlw{x+Jr%@Q)pSYjvyH7Ydm=glwwKGN zEnLJCjixlsm9Ie;7<4I>dFfiJ>AMjOan+^xdt%$pO?3O^Z$Zov_Z!uL$%K(h*J+mA zH5|<(<@dfDzb)Wj-0QOPd=X$7W@$7a8te6?1|s86s`J;dKJ+IwgXwF z;9d%=YXk9BuL6sj?auV`R<;;tB4)`lG|M6QKgQ_2=gQ5qS|PJ5k?BAjm>*BGld0)h zR~)4A$EwehZ&NL%c+@0zwYArnyn^&y`_CH^sp_(PZhMcL1kQqYfX8Lu+wH9ThPaNp zk%Q#<(TR!rL$s_wf$^!0upp{{LAHGes=QIZ+?zXAeU`4GzA7F$N+~sXT>LfU_ZS`$ ze<6R0%-(Jq@U6fG%1>g2iN&v*!*~yFI?z6d@roe+W}D2S5O99AQt|)bmQ9i~A=c=< zJGg^geKLDM6SsJIEx^S+q4;6u1yv*Py}+?_6!P@FS_#y=eXo0N!Hwde%VQCb&IpZF z;QwV<2fB5Rq>3hyxo`7(X^%_#`BfcSUl#fprXW!`R{e&_=D5D315_>Do3z6KzIO9P zM%FFJNLE@_-ZskwX5DGsFRs0^I@`miMm!CpBuAN|W9_Po>WKyn78`QG;j@hpiB}^(*o-ZEe4rngcf?Z0* znU9+^P5U7f-G7bLr|6KqmqkKK7~^g>=l?n6QCBTmCII?=adHx99F6gaF|hjM(?*>? zM`Jky05H43c$wYkp%Z+vOkAdn!Fk)Hf@R`DFl>*|UFyrCSbaoFRGYgSo$(3CgUjm? zPm|hgCmFFxEg;-H36}=lT5BTk)hy05!& zbAQ0E&5@(F0lzp}EpOk^BMxhw4G4Wu;^hFEU0yJAIv<{1jDX)Q_abgHd91>6-4JLw z=Bo&wivnXvHLhrk5U;=k4=@~FLuirZl`mj>%&&>=_7A&UiSQ3&YYLv~B&m(b*wB!U zpJT~6N%sAWb~m=zXLRivcyOerHjz&nMyBBgaua%fFQVt8rE_FXeJMLpJ|Vhb_4H_hW!iHrZd&jdQ1bLC;x3EK2eBY40O{fJ%r_-Fv%QR$15ILm&)|xg2cBG4l`u zPLF~n&q-eGjDIXaMOk=Z2~4$LO3FE=E~?X}R@{sh9xk~FTy5}(<3#YmoEw#$o@5KL z5n0)(Z>FG~CWP1|3`iqQ7fh0i;1bfuyp)@Xyqstch(ur`uPHzb^#KP)oT~XN6T3YiuEDRR#mA9ncQ`VB7WKq3}k9!q$hqZwtm@W>#jV36v3uBlIdN!!~b zYN4McWVBv!vH@{Ha{J22WU|j;m8Kk=np}BPLDa?H?tQvcNW3A!#yYGQ##W|GJUg?j zaKQN)ufr({_hz3#swa`S!3BQqj$31D2+|`w?X20ov0ew7&F75u?>YdVJ_Kk{wXLw? zp{cl-_zN~(<=IW)ttRC=ZZ`I9>AGh}{y*B@IxMO+`WMGIHY%ux21P;zK|txyBhsOu zbSSB$#E=dcC?X{wC8dNkNDa-1B1j0*F$}0fr{n;`z;C^J&iCHu-uv&5=h4Ganc1`V zyVtwcr*`%7j}uYbRqUTG?iI|wB3nO38wu)aFhYlM1^9eeyqKXxx5vck>#|?Gfczjf61QB^U!fxd1)F zOfZL!S-HQiwNHpc=)JUN;OC_&{?W(w9JF8eNFTqFJNX7GUBah@o%_Umi)98FZnj-b zh|H#{3F}b&ZF?PyS&Lk`$1EW*3ez1-|Z#twTtav@% zx8(8?z4&&=v{OUBUF};^xSRlxoL?3the~FOg;O>NHur6kovzZ2kDs^U$Pmj|86nsX zExB6mSXoz1w&tj3d^uNi1T*Y7PyE?|Ct z_|j#`ArhbkwXKTqqIoIti@0=>15|7l) z-gAgm@3tqK&r$1hw`|VqkbQKFo97AUnDT+#+&lHDtd`hmx~q} zCZMRJPZYKA!vo8OpWh*pP!V3GwAiN5LfUB}!vdK1^svXf>5DZiwk^7Bcw(`o;o!&# z42C}4`zMftxGEpev+X}-*3vc)w{AGZ)k$q7II!f%ns)>%-k9FFIdB_uDFsMr?jI6a z8b?oa3d20E99>IWYCqpPAi1s{G=4sSXSdT;tf-kZpX$Zf@CE)1KyzR>DN?6DOf!Cr z-kIjRqlp2uX~UKCj)m3ay3TC@Ja+C{LtF-$U>=;AIL`Wh@u_Z_&-RYy;LCu%We)pZ zPV}%z8?7zrylem!itunKFu#f}q5qRK;{ebw%tyo;mjrp2c&SQ)_3@1o%UKf22diREYX`lyVgA1;C25fY>SV29P>RjQTqXmV4-%b zz5?4DL@@w7?J6x8bW)nwZN<8`-G}_xZtHF(V&|duLERc9lEoSq8@9WJzr{zhxT-y4 zk)3gF#*AA+ot5?QT6bJ|DRlb|D+%_Az&(dEDQanlXBa6f@Kb2wGL*Rie+oO@fPa5e^xq6r^>+%?nRahulD^71di`Ak?dlW=D)RXqoK)o`$YS z8PLAAovYoKtxNI|tP361NdLHLfb7YOYT2>zVbtR*wPr4=aaEM9cX#9LWVbYqT!CKs z4BBSktcRLJjM*xxpM)W)I7h%*^xj8tf_q#(`MP^s|I+HDZz=beuXs#|{RIiW?4(>D zUxZw5urB!cc1GDRc4T!&P2*rng3sbz%<(!yT@CbPVjCx>3WNssk?!QgZrtb6H(^h@ ztr3z-^;$h5Xt?oc3^&;KZttA2ozy!++?-N5QFV;5YhLc?5@QDSCs0x|Ys4vwop6g@ zvz|MMAdP~;MidkQEu6|TR;qiSNgnmmgw@9 zKmERbUzaXnmpKR3ZeeHhxDC0>dw_lI3hi;_uJIeXTraxE7vh**YqD{zS`H*1vn54B zNm~3=Yyrj{c1H#)`$QE=tcZIW+^!@@c+YMGw@dbl>jwM8dtoJ3M0P%Um^&YvVVcTA zTR+W^seBmK)~-e@hI5axPl#<`zy7p}9js+g64ri(9E-o#IbU5wmk+rsZ7lLhVyO zrw)-chNCmP0vv-11k-(TnB!AK?r9YA6}=m9&<#2Vd=iig(Q1vhW8S``EpAx*s=hwa zvo5ey9#oTcDb)&c4UE1gB)nx;jIlme&rZ38SAh5i&Wr z`gQ)+-{#y_y04;HgA?w>V&SXAl#I57AlrUMM zD&doVf5dA1+(NLB{z{?)6sO!aCfjf+eCDGJzykE~KR(^Xi*%1{H6Pg1*LsQ6??2G% zJ7by{(({8nRDGS&ubAVeVBIoh9mvo(V3BUS62gLTuNND08y1~`Y=%+?f@S$$5pfw)HxY|qfCvSRLrd}_8 zJ$8=N%u$FHK&|<}IyEp`tAh9Nm<3_#*D$`U$xVi3Wes)(=LbIXUGqQ*69#RU*{a6X=PwYtAytuoDEM%5QQ~Y+FE^sr_R&k*uLFJh8YeN zwhjtk3$Lv1$1Ek5D ze9DnxI&WCQMQrO=I^2GyjZ6uIGG_xE1H1w>tI;0dA+5nBM z{gd<0U=nCf-A^*xs{B#o{kML2uw>;wQS-NaJ6jm#-6Z|vr}h)6alj*Z&znpz{`!8+ zQ+t2v1*`93a)*G~3#>GT8lZPz&q#Ad*l9D9bj@umPpwhqhO1+GB3GkB7;Ok*W1_r? z@Fopx56Tyw-Rtn$OifD&aNpAXLxI~Jd6>XhR2~zmUgfNr9m~Ju5Sv?KEQyzaYtigh zXR8_P(zoxnP||e$`c30olj63;rdD6eOmGjXKH<)p=9nY8=b$5A9ps}1LymAntwTjX z>y}Y@*dATJ93|(BpRTzD0+zvd(q~$ZYZPt0CNCxbwZtDFe=O@Ryj=SPUQ8S$R18OBp8Z@pK{W?BaSt2EDBdfSOGvd&9&}2(=AbHD?E3yX|BX34>d*EQr_++FTD1J@q{bJWpA?SdB{d29te1@wGU4>9daaTGzEJkGWW5T4 z9gAt4@c_2_#iFpjbFXIF__QPcJO~k`?T=_VZMa68KYoY644`DM$YyU3I!b2qTjx0I z@lwWu1vP>ZAQZBx)FG|#9s;VW_rR)~0aZhhk{R-f$Nmf;ciq&$^&wNs#EFlg-oEJ@ zTBS)MAQ&`gxMEK^w6N-wCAOL{yYLhKo?ll=-Z0gD7eqv42x{ggY|ZUt zbd1sZDo2?APh7JnC{Nat&I(GWKiA-qM<*^}ApFu{X7uK?#l*S(|2%XI6 z8fWAz$o~ouCZb-~2T3&)kV@#)j~)vSV6j{<+qrM0TswIyeSzRU>{)3p_u<~4Jeg@6 zI4E2|omXImWYBYr-FsMAX(Q=5IX=#S{Ta(q&ImB)P-hS$yot7M_o-r$P8*))nD$n_ z*V*aivkyCWK=DnaBzEOL@f z_bCT5TrPFPPV&ZJT{%2*fle9gFkiE$fwHIu#Wx*TwdrYJRN&yd+5UfWtSdBD9=6Ij zRZX{^jrx*ZFas14|M0PVS{-xe7Q%`7nwCJ(KE}3-y|nc%Cy2t!Zg$@W`^A)I6TN0} zr4?CPa1(vSg_oDlUYZ-$y0biLLLnvbTWTNcGoNj^X^x?Jlb^C@_d_m#znkLqJ4f3s zx8Iz&jMxiyF<^{cFjth^-FQ3@C-+_b=yIHOcEXh{r^nKSJ~k&F(%8o0|Z9tNHQK-{w*n z>oU-LHGSHTYd@18*MAt(Jnava_PEQKP7!cwqwiuni*+8u4%2MDU3PIy_W24;a%$NH ztHOxd4Bo++;qagT=&ybck10CBG8&9AspoUdxxJ^9=jJV&B>%Ar*P-)8GI8os2|v%D zmVTS1g=*ER8*}fZx=8wGwg>2C;WK63;y@<=@NkzB`VNrS*kauQ6&P=1F0wMp#|Fpb zm%|~wq8pD8`j)3H#`LHfc-q%NBk7NKtQXGIavNA4laSIYU#_VXc02ivamL*oI07T% z2u^FlQIhy)Z9s9e=Rw2bHF%q+6dMBjRE{yP2i@jdv>K;;D9y!>LHoB#%M2!{00h0!CQW%fY9MFJL`YD=Gtm-|SFm?D`_`4Gb5k{ zw58eu$n^L57+zrV->r}*T^lyzN(i@?)*|O>euX<78eDck7t3wf*=Y1G!CR72Kj|1> z)69Rvn|^xzdBF>dcgs%)otY~S!X<>e^qXX!f^dkE^Q@6$aiEx5{%@_hWS(C~9F$_D zN3=ij%_E4?F5h-Ec$_g{)xJvTm;9_EXF&bvE%4PO{xvx1fZrf2z=K|VzOjj+DGpxs zB~02HhD4~@4;&f1Nc{~l?W1`Ev9MhPNb%%T2$|A`6=JPLYZd^`x?K`=SS}@Y3Bm_m zTwC-v;}I^0wxi97J5>*FI~Z5)x##<;q}Uv<8C&$n+Fe-ht?MqcyJrg&Pv29QpEs($ zH&z_et7xrz#%lO;s6{PE3c~$4pp{7aCJ2{?BujQPD^8kJ;gV$FqS#ripC*$tsdK9v zV&NQfMi(?G2EtQ2*y?{5isuDRozI@f+xs31wjB2ObTPN)W^zot9wtsh`b8e-ztNHv zBE))F{yJA*2Y5jK!h<$@j?+wV5gUln1x+y_)O|Fu&rUeg#77qv78( ->7-MQl9 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%0A^RmD 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

280WW3kOEriP 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

N#_>M< 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 H_?!L*`X$?` From f4bee2686b3928d4c2d081708342d464ec3358ef Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 3 Mar 2023 09:54:21 +0100 Subject: [PATCH 30/47] review fixes --- contracts/domain/BosonConstants.sol | 1 + contracts/domain/BosonTypes.sol | 9 +- .../handlers/IBosonFundsHandler.sol | 2 + .../protocol/bases/PriceDiscoveryBase.sol | 64 +++--- .../protocol/facets/FundsHandlerFacet.sol | 37 +-- .../facets/SequenatialCommitHandlerFacet.sol | 30 +-- contracts/protocol/libs/FundsLib.sol | 68 ++++-- scripts/domain/Direction.js | 12 - scripts/domain/PriceDiscovery.js | 30 +-- scripts/domain/RoyaltyRecipient.js | 214 ++++++++++++++++++ scripts/domain/Side.js | 12 + test/domain/PriceDiscoveryTest.js | 36 +-- test/protocol/FundsHandlerTest.js | 6 +- test/protocol/SequentialCommitHandlerTest.js | 49 +--- 14 files changed, 393 insertions(+), 177 deletions(-) delete mode 100644 scripts/domain/Direction.js create mode 100644 scripts/domain/RoyaltyRecipient.js create mode 100644 scripts/domain/Side.js diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index f7e59675d..57e2188cc 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -153,6 +153,7 @@ string constant TOKEN_TRANSFER_FAILED = "Token transfer failed"; string constant INSUFFICIENT_VALUE_RECEIVED = "Insufficient value received"; string constant INSUFFICIENT_AVAILABLE_FUNDS = "Insufficient available funds"; string constant NATIVE_NOT_ALLOWED = "Transfer of native currency not allowed"; +string constant ZERO_DEPOSIT_NOT_ALLOWED = "Zero deposit not allowed"; // Revert Reasons: Meta-Transactions related string constant NONCE_USED_ALREADY = "Nonce used already"; diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 53d06742e..3919c3082 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -292,16 +292,15 @@ contract BosonTypes { uint256 royaltyPercentage; } - // temporary struct for development purposes struct PriceDiscovery { uint256 price; address priceDiscoveryContract; bytes priceDiscoveryData; - Direction direction; + Side side; } - enum Direction { - Buy, - Sell + enum Side { + Ask, + Bid } } diff --git a/contracts/interfaces/handlers/IBosonFundsHandler.sol b/contracts/interfaces/handlers/IBosonFundsHandler.sol index c233ce575..e31e14eeb 100644 --- a/contracts/interfaces/handlers/IBosonFundsHandler.sol +++ b/contracts/interfaces/handlers/IBosonFundsHandler.sol @@ -20,9 +20,11 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents { * * Reverts if: * - The funds region of protocol is paused + * - Amount to deposit is zero * - Seller id does not exist * - It receives some native currency (e.g. ETH), but token address is not zero * - It receives some native currency (e.g. ETH), and the amount does not match msg.value + * - It receives no native currency, but token address is zero * - Contract at token address does not support ERC20 function transferFrom * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index 8aa993a56..b41b9d676 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -8,6 +8,7 @@ import { IWETH9Like } from "../../interfaces/IWETH9Like.sol"; import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; import { ProtocolBase } from "./../bases/ProtocolBase.sol"; import { FundsLib } from "../libs/FundsLib.sol"; +import { Address } from "../../ext_libs/Address.sol"; /** * @title PriceDiscoveryBase @@ -15,6 +16,8 @@ import { FundsLib } from "../libs/FundsLib.sol"; * @dev Provides methods for fulfiling orders on external price discovery contracts. */ contract PriceDiscoveryBase is ProtocolBase { + using Address for address; + IWETH9Like public immutable weth; constructor(address _weth) { @@ -22,7 +25,7 @@ contract PriceDiscoveryBase is ProtocolBase { } /** - * @notice Fulfils an order on external contract. Helper function passes data to either buy or sell order. + * @notice @notice Fulfils an order on an external contract. Helper function passes data to either ask or bid orders. * * See descriptions of `fulfilBuyOrder` and `fulfilSellOrder` for more details. * @@ -40,15 +43,15 @@ contract PriceDiscoveryBase is ProtocolBase { address _buyer, uint256 _initialSellerId ) internal returns (uint256 actualPrice) { - if (_priceDiscovery.direction == Direction.Buy) { - return fulfilBuyOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); + if (_priceDiscovery.side == Side.Ask) { + return fulfilAskOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); } else { - return fulfilSellOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); + return fulfilBidOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); } } /** - * @notice Fulfils a buy order on external contract. + * @notice Fulfils an ask order on external contract. * * Reverts if: * - Offer price is in native token and caller does not send enough @@ -66,7 +69,7 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _initialSellerId - the id of the original seller * @return actualPrice - the actual price of the order */ - function fulfilBuyOrder( + function fulfilAskOrder( uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, @@ -90,16 +93,8 @@ contract PriceDiscoveryBase is ProtocolBase { ps.incomingVoucherId = _exchangeId; ps.incomingVoucherCloneAddress = cloneAddress; - { - // Call the price discovery contract - (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ - value: msg.value - }(_priceDiscovery.priceDiscoveryData); - - // If error, return error message - string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); - require(success, errorMessage); - } + // Call the price discovery contract + _priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value); // Make sure that the price discovery contract has transferred the voucher to the protocol IBosonVoucher bosonVoucher = IBosonVoucher(cloneAddress); @@ -130,7 +125,7 @@ contract PriceDiscoveryBase is ProtocolBase { } /** - * @notice Fulfils a sell order on external contract. + * @notice Fulfils a bid order on external contract. * * Reverts if: * - Voucher owner did not approve protocol to transfer the voucher @@ -143,15 +138,12 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _initialSellerId - the id of the original seller * @return actualPrice - the actual price of the order */ - function fulfilSellOrder( + function fulfilBidOrder( uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, uint256 _initialSellerId ) internal returns (uint256 actualPrice) { - // what about non-zero msg.value? - // No need to reset approval - IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); // Transfer seller's voucher to protocol @@ -163,25 +155,31 @@ contract PriceDiscoveryBase is ProtocolBase { // Get protocol balance before the exchange uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // Approve price discovery contract to transfer voucher - bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, _exchangeId); + // Track native balance just in case if seller send some native currency or price discovery contract does + uint256 protocolNativeBalanceBefore = getBalance(address(0)); - { - // Call the price discovery contract - (bool success, bytes memory returnData) = address(_priceDiscovery.priceDiscoveryContract).call{ - value: msg.value - }(_priceDiscovery.priceDiscoveryData); + // Approve price discovery contract to transfer voucher. There is no need to reset approval afterwards, since protocol is not the voucher owner anymore + bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, _exchangeId); - // If error, return error message - string memory errorMessage = (returnData.length == 0) ? FUNCTION_CALL_NOT_SUCCESSFUL : (string(returnData)); - require(success, errorMessage); - } + // Call the price discovery contract + _priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value); // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); + // Check the native balance and return the surplus to seller + uint256 protocolNativeBalanceAfter = getBalance(address(0)); + if (protocolNativeBalanceAfter > protocolNativeBalanceBefore) { + // Return the surplus to seller + FundsLib.transferFundsFromProtocol( + address(0), + payable(msgSender()), + protocolNativeBalanceAfter - protocolNativeBalanceBefore + ); + } + actualPrice = protocolBalanceAfter - protocolBalanceBefore; - require(actualPrice >= _priceDiscovery.price, "Price discovery contract returned less than expected"); + require(actualPrice >= _priceDiscovery.price, INSUFFICIENT_VALUE_RECEIVED); } /** diff --git a/contracts/protocol/facets/FundsHandlerFacet.sol b/contracts/protocol/facets/FundsHandlerFacet.sol index 40babb89e..5f5f18f24 100644 --- a/contracts/protocol/facets/FundsHandlerFacet.sol +++ b/contracts/protocol/facets/FundsHandlerFacet.sol @@ -32,9 +32,11 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { * * Reverts if: * - The funds region of protocol is paused + * - Amount to deposit is zero * - Seller id does not exist * - It receives some native currency (e.g. ETH), but token address is not zero * - It receives some native currency (e.g. ETH), and the amount does not match msg.value + * - It receives no native currency, but token address is zero * - Contract at token address does not support ERC20 function transferFrom * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value @@ -48,27 +50,28 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase { address _tokenAddress, uint256 _amount ) external payable override fundsNotPaused nonReentrant { - if (_amount > 0) { - // Check seller exists in sellers mapping - (bool exists, , ) = fetchSeller(_sellerId); + require(_amount > 0, ZERO_DEPOSIT_NOT_ALLOWED); - // Seller must exist - require(exists, NO_SUCH_SELLER); + // Check seller exists in sellers mapping + (bool exists, , ) = fetchSeller(_sellerId); - if (msg.value != 0) { - // Receiving native currency - require(_tokenAddress == address(0), NATIVE_WRONG_ADDRESS); - require(_amount == msg.value, NATIVE_WRONG_AMOUNT); - } else { - // Transfer tokens from the caller - FundsLib.transferFundsToProtocol(_tokenAddress, _amount); - } - - // Increase available funds - FundsLib.increaseAvailableFunds(_sellerId, _tokenAddress, _amount); + // Seller must exist + require(exists, NO_SUCH_SELLER); - emit FundsDeposited(_sellerId, msgSender(), _tokenAddress, _amount); + if (msg.value != 0) { + // Receiving native currency + require(_tokenAddress == address(0), NATIVE_WRONG_ADDRESS); + require(_amount == msg.value, NATIVE_WRONG_AMOUNT); + } else { + // Transfer tokens from the caller + require(_tokenAddress != address(0), INVALID_ADDRESS); + FundsLib.transferFundsToProtocol(_tokenAddress, _amount); } + + // Increase available funds + FundsLib.increaseAvailableFunds(_sellerId, _tokenAddress, _amount); + + emit FundsDeposited(_sellerId, msgSender(), _tokenAddress, _amount); } /** diff --git a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol index 0709b7bad..19909a622 100644 --- a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol @@ -11,20 +11,6 @@ import "../../domain/BosonConstants.sol"; import { Address } from "../../ext_libs/Address.sol"; import { Math } from "../../ext_libs/Math.sol"; -interface WETH9Like { - function withdraw(uint256) external; - - function deposit() external payable; - - function transfer(address, uint256) external returns (bool); - - function transferFrom( - address, - address, - uint256 - ) external returns (bool); -} - /** * @title SequentialCommitHandlerFacet * @@ -104,7 +90,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis seller = currentBuyer.wallet; } - if (_priceDiscovery.direction == Direction.Sell) { + if (_priceDiscovery.side == Side.Bid) { require(seller == msgSender(), NOT_VOUCHER_HOLDER); } @@ -137,12 +123,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis uint256 len = sequentialCommits.length; uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; - // Calculate the amount to be immediately released to current voucher owner - // escrowAmount = - // actualPrice - - // Math.min(currentPrice, actualPrice - protocolFeeAmount - royaltyAmount); - // escrowAmount = - // Math.max(actualPrice-currentPrice, protocolFeeAmount + royaltyAmount); // can underflow + // Calculate the minimal amount to be kept in the escrow escrowAmount = Math.max(actualPrice, protocolFeeAmount + royaltyAmount + currentPrice) - currentPrice; // Update sequential commit @@ -157,11 +138,14 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } // Make sure enough get escrowed - if (_priceDiscovery.direction == Direction.Buy) { + if (_priceDiscovery.side == Side.Ask) { if (escrowAmount > 0) { // Price discovery should send funds to the seller // Nothing in escrow, need to pull everything from seller if (tokenAddress == address(0)) { + // If exchange is native currency, seller cannot directly approve protocol to transfer funds + // They need to approve wrapper contract, so protocol can pull funds from wrapper + // But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw) FundsLib.transferFundsToProtocol(address(weth), seller, escrowAmount); weth.withdraw(escrowAmount); } else { @@ -169,7 +153,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } } } else { - // when sell direction, we have full proceeds in escrow. Keep minimal in, return the difference + // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference if (tokenAddress == address(0)) { tokenAddress = address(weth); if (escrowAmount > 0) weth.withdraw(escrowAmount); diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index dcbcfa246..aaf6e5efe 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -213,7 +213,7 @@ library FundsLib { price, exchangeToken ); - sellerPayoff += sequentialRoyalties; // revisit this + sellerPayoff += sequentialRoyalties; protocolFee += sequentialProtocolFee; } @@ -237,6 +237,19 @@ library FundsLib { } } + /** + * @notice Takes in the exchange id and releases the funds to all intermediate reseller, depending on the state of the exchange. + * It is called only from releaseFunds. Protocol fee and royalties are calculated and returned to releaseFunds, where are added to the total. + * + * Emits FundsReleased events for non zero payoffs. + * + * @param _exchangeId - exchange id + * @param _exchangeState - state of the exchange + * @param _initialPrice - initial price of the offer + * @param _exchangeToken - address of the token used for the exchange + * @return protocolFee - protocol fee from secondary sales + * @return royalties - royalties from secondary sales + */ function releaseFundsToIntermediateSellers( uint256 _exchangeId, BosonTypes.ExchangeState _exchangeState, @@ -252,6 +265,7 @@ library FundsLib { sequentialCommits = pe.sequentialCommits[_exchangeId]; + // if no sequential commit happened, just return if (sequentialCommits.length == 0) { return (0, 0); } @@ -286,24 +300,38 @@ library FundsLib { } } - uint256 resellerBuyPrice = _initialPrice; + uint256 resellerBuyPrice = _initialPrice; // the price that reseller paid for the voucher address msgSender = EIP712Lib.msgSender(); uint256 len = sequentialCommits.length; for (uint256 i = 0; i < len; i++) { - BosonTypes.SequentialCommit memory sc = sequentialCommits[i]; // we need all members of the struct - - protocolFee += sc.protocolFeeAmount; - royalties += sc.royaltyAmount; - - uint256 reducedSecondaryPrice = sc.price - sc.protocolFeeAmount - sc.royaltyAmount; + BosonTypes.SequentialCommit storage sc = sequentialCommits[i]; - uint256 currentResellerAmount = ( - reducedSecondaryPrice > resellerBuyPrice - ? effectivePriceMultiplier * (reducedSecondaryPrice - resellerBuyPrice) - : (10000 - effectivePriceMultiplier) * (resellerBuyPrice - reducedSecondaryPrice) - ) / 10000; + // amount to be released + uint256 currentResellerAmount; - resellerBuyPrice = sc.price; + // inside the scope to avoid stack too deep error + { + uint256 price = sc.price; + uint256 protocolFeeAmount = sc.protocolFeeAmount; + uint256 royaltyAmount = sc.royaltyAmount; + + protocolFee += protocolFeeAmount; + royalties += royaltyAmount; + + // secondary price without protocol fee and royalties + uint256 reducedSecondaryPrice = price - protocolFeeAmount - royaltyAmount; + + // current reseller gets the difference between final payout and the immediate payout they received at the time of secondary sale + currentResellerAmount = + ( + reducedSecondaryPrice > resellerBuyPrice + ? effectivePriceMultiplier * (reducedSecondaryPrice - resellerBuyPrice) + : (10000 - effectivePriceMultiplier) * (resellerBuyPrice - reducedSecondaryPrice) + ) / + 10000; + + resellerBuyPrice = price; + } if (currentResellerAmount > 0) { increaseAvailableFundsAndEmitEvent( @@ -316,10 +344,22 @@ library FundsLib { } } + // protocolFee and royalties can be multiplied by effectivePriceMultiplier just at the end protocolFee = (protocolFee * effectivePriceMultiplier) / 10000; royalties = (royalties * effectivePriceMultiplier) / 10000; } + /** + * @notice Forwared values to increaseAvailableFunds and emits notifies external listeners. + * + * Emits FundsReleased events + * + * @param _exchangeId - exchange id + * @param _entityId - id of the entity to which the funds are released + * @param _tokenAddress - address of the token used for the exchange + * @param _amount - amount of tokens to be released + * @param _sender - address of the sender that executed the transaction + */ function increaseAvailableFundsAndEmitEvent( uint256 _exchangeId, uint256 _entityId, diff --git a/scripts/domain/Direction.js b/scripts/domain/Direction.js deleted file mode 100644 index 197c41a61..000000000 --- a/scripts/domain/Direction.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Boson Protocol Domain Enum: Direction - */ -class Direction {} - -Direction.Buy = 0; -Direction.Sell = 1; - -Direction.Types = [Direction.Buy, Direction.Sell]; - -// Export -module.exports = Direction; diff --git a/scripts/domain/PriceDiscovery.js b/scripts/domain/PriceDiscovery.js index 9e45cbbc5..1a1c19284 100644 --- a/scripts/domain/PriceDiscovery.js +++ b/scripts/domain/PriceDiscovery.js @@ -1,4 +1,4 @@ -const Direction = require("./Direction"); +const Side = require("./Side"); const { bigNumberIsValid, addressIsValid, bytesIsValid, enumIsValid } = require("../util/validations.js"); /** @@ -12,15 +12,15 @@ class PriceDiscovery { uint256 price; address priceDiscoveryContract; bytes priceDiscoveryData; - Direction direction; + Side side; } */ - constructor(price, priceDiscoveryContract, priceDiscoveryData, direction) { + constructor(price, priceDiscoveryContract, priceDiscoveryData, side) { this.price = price; this.priceDiscoveryContract = priceDiscoveryContract; this.priceDiscoveryData = priceDiscoveryData; - this.direction = direction; + this.side = side; } /** @@ -29,8 +29,8 @@ class PriceDiscovery { * @returns {PriceDiscovery} */ static fromObject(o) { - const { price, priceDiscoveryContract, priceDiscoveryData, direction } = o; - return new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + const { price, priceDiscoveryContract, priceDiscoveryData, side } = o; + return new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); } /** @@ -39,16 +39,16 @@ class PriceDiscovery { * @returns {*} */ static fromStruct(struct) { - let price, priceDiscoveryContract, priceDiscoveryData, direction; + let price, priceDiscoveryContract, priceDiscoveryData, side; // destructure struct - [price, priceDiscoveryContract, priceDiscoveryData, direction] = struct; + [price, priceDiscoveryContract, priceDiscoveryData, side] = struct; return PriceDiscovery.fromObject({ price: price.toString(), priceDiscoveryContract: priceDiscoveryContract, priceDiscoveryData: priceDiscoveryData, - direction: direction, + side: side, }); } @@ -73,7 +73,7 @@ class PriceDiscovery { * @returns {string} */ toStruct() { - return [this.price, this.priceDiscoveryContract, this.priceDiscoveryData, this.direction]; + return [this.price, this.priceDiscoveryContract, this.priceDiscoveryData, this.side]; } /** @@ -112,12 +112,12 @@ class PriceDiscovery { } /** - * Is this PriceDiscovery instance's direction field valid? - * Must be a number belonging to the Direction enum + * Is this PriceDiscovery instance's side field valid? + * Must be a number belonging to the Side enum * @returns {boolean} */ - directionIsValid() { - return enumIsValid(this.direction, Direction.Types); + sideIsValid() { + return enumIsValid(this.side, Side.Types); } /** @@ -129,7 +129,7 @@ class PriceDiscovery { this.priceIsValid() && this.priceDiscoveryContractIsValid() && this.priceDiscoveryDataIsValid() && - this.directionIsValid() + this.sideIsValid() ); } } diff --git a/scripts/domain/RoyaltyRecipient.js b/scripts/domain/RoyaltyRecipient.js new file mode 100644 index 000000000..9320c24ce --- /dev/null +++ b/scripts/domain/RoyaltyRecipient.js @@ -0,0 +1,214 @@ +const { bigNumberIsValid, stringIsValid, addressIsValid } = require("../util/validations.js"); + +/** + * Boson Protocol Domain Entity: RoyaltyRecipient + * + * See: {BosonTypes.RoyaltyRecipient} + */ +class RoyaltyRecipient { + /* + struct RoyaltyRecipient { + address wallet; + uint256 minRoyaltyPercentage; + string externalId; + } + */ + + constructor(wallet, minRoyaltyPercentage, externalId) { + this.wallet = wallet; + this.minRoyaltyPercentage = minRoyaltyPercentage; + this.externalId = externalId; + } + + /** + * Get a new RoyaltyRecipient instance from a pojo representation + * @param o + * @returns {RoyaltyRecipient} + */ + static fromObject(o) { + const { wallet, minRoyaltyPercentage, externalId } = o; + return new RoyaltyRecipient(wallet, minRoyaltyPercentage, externalId); + } + + /** + * Get a new RoyaltyRecipient instance from a returned struct representation + * @param struct + * @returns {*} + */ + static fromStruct(struct) { + let wallet, minRoyaltyPercentage, externalId; + + // destructure struct + [wallet, minRoyaltyPercentage, externalId] = struct; + + return RoyaltyRecipient.fromObject({ + wallet, + minRoyaltyPercentage: minRoyaltyPercentage.toString(), + externalId, + }); + } + + /** + * Get a database representation of this RoyaltyRecipient instance + * @returns {object} + */ + toObject() { + return JSON.parse(this.toString()); + } + + /** + * Get a string representation of this RoyaltyRecipient instance + * @returns {string} + */ + toString() { + return JSON.stringify(this); + } + + /** + * Get a struct representation of this RoyaltyRecipient instance + * @returns {string} + */ + toStruct() { + return [this.wallet, this.minRoyaltyPercentage, this.externalId]; + } + + /** + * Clone this RoyaltyRecipient + * @returns {RoyaltyRecipient} + */ + clone() { + return RoyaltyRecipient.fromObject(this.toObject()); + } + + /** + * Is this RoyaltyRecipient instance's wallet field valid? + * Must be a eip55 compliant Ethereum address + * @returns {boolean} + */ + walletIsValid() { + return addressIsValid(this.wallet); + } + + /** + * Is this RoyaltyRecipient instance's minRoyaltyPercentage field valid? + * Must be a string representation of a big number + * @returns {boolean} + */ + minRoyaltyPercentageIsValid() { + return bigNumberIsValid(this.minRoyaltyPercentage); + } + + /** + * Is this RoyaltyRecipient instance's externalId field valid? + * Always present, must be a string + * + * @returns {boolean} + */ + externalIdIsValid() { + return stringIsValid(this.externalId); + } + + /** + * Is this RoyaltyRecipient instance valid? + * @returns {boolean} + */ + isValid() { + return this.walletIsValid() && this.idIsValid() && this.externalIdIsValid(); + } +} + +/** + * Boson Protocol Domain Entity: Collection of RoyaltyRecipient + * + * See: {BosonTypes.RoyaltyRecipient} + */ +class RoyaltyRecipientList { + constructor(royaltyRecipient) { + this.royaltyRecipient = royaltyRecipient; + } + + /** + * Get a new RoyaltyRecipientList instance from a pojo representation + * @param o + * @returns {RoyaltyRecipientList} + */ + static fromObject(o) { + const { royaltyRecipient } = o; + return new RoyaltyRecipientList(royaltyRecipient.map((f) => RoyaltyRecipient.fromObject(f))); + } + + /** + * Get a new RoyaltyRecipientList instance from a returned struct representation + * @param struct + * @returns {*} + */ + static fromStruct(struct) { + return RoyaltyRecipientList.fromObject({ + royaltyRecipient: struct.map((royaltyRecipient) => RoyaltyRecipient.fromStruct(royaltyRecipient)), + }); + } + + /** + * Get a database representation of this RoyaltyRecipientList instance + * @returns {object} + */ + toObject() { + return JSON.parse(this.toString()); + } + + /** + * Get a string representation of this RoyaltyRecipientList instance + * @returns {string} + */ + toString() { + return JSON.stringify(this); + } + + /** + * Get a struct representation of this RoyaltyRecipientList instance + * @returns {string} + */ + toStruct() { + return this.royaltyRecipient.map((f) => f.toStruct()); + } + + /** + * Clone this RoyaltyRecipientList + * @returns {RoyaltyRecipientList} + */ + clone() { + return RoyaltyRecipientList.fromObject(this.toObject()); + } + + /** + * Is this RoyaltyRecipientList instance's royaltyRecipient field valid? + * Must be a list of RoyaltyRecipient instances + * @returns {boolean} + */ + royaltyRecipientIsValid() { + let valid = false; + let { royaltyRecipient } = this; + try { + valid = + Array.isArray(royaltyRecipient) && + royaltyRecipient.reduce( + (previousRoyaltyRecipient, currentRoyaltyRecipient) => + previousRoyaltyRecipient && currentRoyaltyRecipient.isValid(), + true + ); + } catch (e) {} + return valid; + } + + /** + * Is this RoyaltyRecipientList instance valid? + * @returns {boolean} + */ + isValid() { + return this.royaltyRecipientIsValid(); + } +} + +// Export +exports.RoyaltyRecipient = RoyaltyRecipient; +exports.RoyaltyRecipientList = RoyaltyRecipientList; diff --git a/scripts/domain/Side.js b/scripts/domain/Side.js new file mode 100644 index 000000000..d989349b0 --- /dev/null +++ b/scripts/domain/Side.js @@ -0,0 +1,12 @@ +/** + * Boson Protocol Domain Enum: Side + */ +class Side {} + +Side.Ask = 0; +Side.Bid = 1; + +Side.Types = [Side.Ask, Side.Bid]; + +// Export +module.exports = Side; diff --git a/test/domain/PriceDiscoveryTest.js b/test/domain/PriceDiscoveryTest.js index 19e97098b..b290793c4 100644 --- a/test/domain/PriceDiscoveryTest.js +++ b/test/domain/PriceDiscoveryTest.js @@ -2,7 +2,7 @@ const hre = require("hardhat"); const ethers = hre.ethers; const { expect } = require("chai"); const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); -const Direction = require("../../scripts/domain/Direction"); +const Side = require("../../scripts/domain/Side"); /** * Test the PriceDiscovery domain entity @@ -10,7 +10,7 @@ const Direction = require("../../scripts/domain/Direction"); describe("PriceDiscovery", function () { // Suite-wide scope let priceDiscovery, object, promoted, clone, dehydrated, rehydrated, key, value, struct; - let accounts, price, priceDiscoveryContract, priceDiscoveryData, direction; + let accounts, price, priceDiscoveryContract, priceDiscoveryData, side; beforeEach(async function () { // Get a list of accounts @@ -20,16 +20,16 @@ describe("PriceDiscovery", function () { price = "150"; priceDiscoveryContract = accounts[1].address; priceDiscoveryData = "0xdeadbeef"; - direction = Direction.Buy; + side = Side.Ask; }); context("📋 Constructor", async function () { it("Should allow creation of valid, fully populated PriceDiscovery instance", async function () { - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); expect(priceDiscovery.priceIsValid()).is.true; expect(priceDiscovery.priceDiscoveryContractIsValid()).is.true; expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; - expect(priceDiscovery.directionIsValid()).is.true; + expect(priceDiscovery.sideIsValid()).is.true; expect(priceDiscovery.isValid()).is.true; }); }); @@ -37,7 +37,7 @@ describe("PriceDiscovery", function () { context("📋 Field validations", async function () { beforeEach(async function () { // Create a valid priceDiscovery, then set fields in tests directly - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); expect(priceDiscovery.isValid()).is.true; }); @@ -122,25 +122,25 @@ describe("PriceDiscovery", function () { expect(priceDiscovery.isValid()).is.true; }); - it("If present, direction must be a Direction enum", async function () { + it("If present, side must be a Side enum", async function () { // Invalid field value - priceDiscovery.direction = "zedzdeadbaby"; - expect(priceDiscovery.directionIsValid()).is.false; + priceDiscovery.side = "zedzdeadbaby"; + expect(priceDiscovery.sideIsValid()).is.false; expect(priceDiscovery.isValid()).is.false; // Invalid field value - priceDiscovery.direction = new Date(); - expect(priceDiscovery.directionIsValid()).is.false; + priceDiscovery.side = new Date(); + expect(priceDiscovery.sideIsValid()).is.false; expect(priceDiscovery.isValid()).is.false; // Invalid field value - priceDiscovery.direction = 12; - expect(priceDiscovery.directionIsValid()).is.false; + priceDiscovery.side = 12; + expect(priceDiscovery.sideIsValid()).is.false; expect(priceDiscovery.isValid()).is.false; // Valid field value - priceDiscovery.direction = Direction.Sell; - expect(priceDiscovery.directionIsValid()).is.true; + priceDiscovery.side = Side.Bid; + expect(priceDiscovery.sideIsValid()).is.true; expect(priceDiscovery.isValid()).is.true; }); }); @@ -148,7 +148,7 @@ describe("PriceDiscovery", function () { context("📋 Utility functions", async function () { beforeEach(async function () { // Create a valid priceDiscovery, then set fields in tests directly - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, direction); + priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); expect(priceDiscovery.isValid()).is.true; // Get plain object @@ -156,11 +156,11 @@ describe("PriceDiscovery", function () { price, priceDiscoveryContract, priceDiscoveryData, - direction, + side, }; // Struct representation - struct = [price, priceDiscoveryContract, priceDiscoveryData, direction]; + struct = [price, priceDiscoveryContract, priceDiscoveryData, side]; }); context("👉 Static", async function () { diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index a5f9fe805..23c823480 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -6,7 +6,7 @@ const { Funds, FundsList } = require("../../scripts/domain/Funds"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); -const Direction = require("../../scripts/domain/Direction"); +const Side = require("../../scripts/domain/Side"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); const { deployProtocolDiamond } = require("../../scripts/util/deploy-protocol-diamond.js"); @@ -4565,7 +4565,7 @@ describe("IBosonFundsHandler", function () { order.price, priceDiscoveryContract.address, priceDiscoveryData, - Direction.Buy + Side.Ask ); // voucher owner approves protocol to transfer the tokens @@ -6172,7 +6172,7 @@ describe("IBosonFundsHandler", function () { order.price, priceDiscoveryContract.address, priceDiscoveryData, - Direction.Buy + Side.Ask ); // voucher owner approves protocol to transfer the tokens diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index 5778488a2..bf3f7b7a7 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -6,7 +6,7 @@ const Role = require("../../scripts/domain/Role"); const Exchange = require("../../scripts/domain/Exchange"); const Voucher = require("../../scripts/domain/Voucher"); const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); -const Direction = require("../../scripts/domain/Direction"); +const Side = require("../../scripts/domain/Side"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); @@ -347,7 +347,7 @@ describe("IBosonSequentialCommitHandler", function () { reseller = buyer; }); - context("Buy order", async function () { + context("Ask order", async function () { context("General actions", async function () { beforeEach(async function () { // Price on secondary market @@ -365,12 +365,7 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); // Seller needs to deposit weth in order to fill the escrow at the last step // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) @@ -707,12 +702,7 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); // Attempt to sequentially commit, expecting revert await expect( @@ -767,7 +757,7 @@ describe("IBosonSequentialCommitHandler", function () { price2, priceDiscoveryContract.address, priceDiscoveryData, - Direction.Buy + Side.Ask ); // Seller needs to deposit weth in order to fill the escrow at the last step @@ -887,7 +877,7 @@ describe("IBosonSequentialCommitHandler", function () { }); }); - context("Sell order", async function () { + context("Bid order", async function () { context("General actions", async function () { beforeEach(async function () { // Price on secondary market @@ -905,12 +895,7 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Sell - ); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Bid); // Approve transfers // Buyer2 needs to approve price discovery to transfer the ETH @@ -1216,7 +1201,7 @@ describe("IBosonSequentialCommitHandler", function () { ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); - it("Only seller can call, if direction is Sell", async function () { + it("Only seller can call, if side is Ask", async function () { // Sequential commit to offer, retrieving the event await expect( sequentialCommitHandler @@ -1271,7 +1256,7 @@ describe("IBosonSequentialCommitHandler", function () { price2, priceDiscoveryContract.address, priceDiscoveryData, - Direction.Sell + Side.Bid ); // Approve transfers @@ -1433,7 +1418,7 @@ describe("IBosonSequentialCommitHandler", function () { // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Direction.Buy); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); // buyer is owner of voucher expect(await bosonVoucherClone.connect(buyer).ownerOf(exchangeId)).to.equal(buyer.address); @@ -1472,12 +1457,7 @@ describe("IBosonSequentialCommitHandler", function () { // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); // Attempt to sequentially commit, expecting revert await expect( @@ -1511,12 +1491,7 @@ describe("IBosonSequentialCommitHandler", function () { // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); - priceDiscovery = new PriceDiscovery( - price2, - priceDiscoveryContract.address, - priceDiscoveryData, - Direction.Buy - ); + priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); // Attempt to sequentially commit, expecting revert await expect( From c66b00f824fcd6cbbe99003998282d47e134027c Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 3 Mar 2023 11:59:08 +0100 Subject: [PATCH 31/47] additional tests --- .../handlers/IBosonSequentialCommitHandler.sol | 6 +++--- .../facets/SequenatialCommitHandlerFacet.sol | 6 +++--- scripts/config/revert-reasons.js | 1 + test/protocol/FundsHandlerTest.js | 16 ++++++++++++++++ test/protocol/SequentialCommitHandlerTest.js | 14 +++++++++++++- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol index 3aa9797c2..a8f7cdc68 100644 --- a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol +++ b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol @@ -27,18 +27,18 @@ interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibE * - Exchange does not exist * - Exchange is not in Committed state * - Voucher has expired - * - It is a sell order and: + * - It is a bid order and: * - Caller is not the voucher holder * - Voucher owner did not approve protocol to transfer the voucher * - Price received from price discovery is lower than the expected price - * - Reseller did not approve protocol to transfer exchange token in escrow - * - It is a buy order and: + * - It is a ask order and: * - Offer price is in native token and caller does not send enough * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value * - Protocol does not receive the voucher * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) + * - Reseller did not approve protocol to transfer exchange token in escrow * - Call to price discovery contract fails * - Protocol fee and royalties combined exceed the secondary price * - Transfer of exchange token fails diff --git a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol index 19909a622..9be0f9dee 100644 --- a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol @@ -42,18 +42,18 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis * - Exchange does not exist * - Exchange is not in Committed state * - Voucher has expired - * - It is a sell order and: + * - It is a bid order and: * - Caller is not the voucher holder * - Voucher owner did not approve protocol to transfer the voucher * - Price received from price discovery is lower than the expected price - * - Reseller did not approve protocol to transfer exchange token in escrow - * - It is a buy order and: + * - It is a ask order and: * - Offer price is in native token and caller does not send enough * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value * - Protocol does not receive the voucher * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) + * - Reseller did not approve protocol to transfer exchange token in escrow * - Call to price discovery contract fails * - Protocol fee and royalties combined exceed the secondary price * - Transfer of exchange token fails diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index ee26af1d5..b370efc42 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -14,6 +14,7 @@ exports.RevertReasons = { ALREADY_PAUSED: "Protocol is already paused", NOT_PAUSED: "Protocol is not currently paused", REGION_PAUSED: "This region of the protocol is currently paused", + ZERO_DEPOSIT_NOT_ALLOWED: "Zero deposit not allowed", // General INVALID_ADDRESS: "Invalid address", diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 23c823480..f947db230 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -374,6 +374,15 @@ describe("IBosonFundsHandler", function () { ).to.revertedWith(RevertReasons.REGION_PAUSED); }); + it("Amount to deposit is zero", async function () { + depositAmount = 0; + + // Attempt to deposit funds, expecting revert + await expect( + fundsHandler.connect(assistant).depositFunds(seller.id, mockToken.address, depositAmount) + ).to.revertedWith(RevertReasons.ZERO_DEPOSIT_NOT_ALLOWED); + }); + it("Seller id does not exist", async function () { // Attempt to deposit the funds, expecting revert seller.id = "555"; @@ -410,6 +419,13 @@ describe("IBosonFundsHandler", function () { ).to.revertedWith(RevertReasons.SAFE_ERC20_LOW_LEVEL_CALL); }); + it("No native currency deposited and token address is zero", async function () { + // Attempt to deposit the funds, expecting revert + await expect( + fundsHandler.connect(rando).depositFunds(seller.id, ethers.constants.AddressZero, depositAmount) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + it("Token address is not a contract", async function () { // Attempt to deposit the funds, expecting revert await expect( diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index bf3f7b7a7..d030d3ef3 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -1201,7 +1201,19 @@ describe("IBosonSequentialCommitHandler", function () { ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); - it("Only seller can call, if side is Ask", async function () { + it("price discovery sends less than expected", async function () { + // Set higher price in price discovery + priceDiscovery.price = ethers.BigNumber.from(priceDiscovery.price).add(1); + + // Attempt to sequentially commit to, expecting revert + await expect( + sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + + it("Only seller can call, if side is bid", async function () { // Sequential commit to offer, retrieving the event await expect( sequentialCommitHandler From 1f8314dbf65432e551656903f0d353c510d8f844 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 3 Mar 2023 16:41:34 +0100 Subject: [PATCH 32/47] remove RoyaltyRecipient + fix typos --- contracts/protocol/libs/FundsLib.sol | 4 +- scripts/domain/RoyaltyRecipient.js | 214 --------------------------- 2 files changed, 2 insertions(+), 216 deletions(-) delete mode 100644 scripts/domain/RoyaltyRecipient.js diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index aaf6e5efe..c3e5620ca 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -238,8 +238,8 @@ library FundsLib { } /** - * @notice Takes in the exchange id and releases the funds to all intermediate reseller, depending on the state of the exchange. - * It is called only from releaseFunds. Protocol fee and royalties are calculated and returned to releaseFunds, where are added to the total. + * @notice Takes the exchange id and releases the funds to all intermediate resellers, depending on the state of the exchange. + * It is called only from releaseFunds. Protocol fee and royalties are calculated and returned to releaseFunds, where they are added to the total. * * Emits FundsReleased events for non zero payoffs. * diff --git a/scripts/domain/RoyaltyRecipient.js b/scripts/domain/RoyaltyRecipient.js deleted file mode 100644 index 9320c24ce..000000000 --- a/scripts/domain/RoyaltyRecipient.js +++ /dev/null @@ -1,214 +0,0 @@ -const { bigNumberIsValid, stringIsValid, addressIsValid } = require("../util/validations.js"); - -/** - * Boson Protocol Domain Entity: RoyaltyRecipient - * - * See: {BosonTypes.RoyaltyRecipient} - */ -class RoyaltyRecipient { - /* - struct RoyaltyRecipient { - address wallet; - uint256 minRoyaltyPercentage; - string externalId; - } - */ - - constructor(wallet, minRoyaltyPercentage, externalId) { - this.wallet = wallet; - this.minRoyaltyPercentage = minRoyaltyPercentage; - this.externalId = externalId; - } - - /** - * Get a new RoyaltyRecipient instance from a pojo representation - * @param o - * @returns {RoyaltyRecipient} - */ - static fromObject(o) { - const { wallet, minRoyaltyPercentage, externalId } = o; - return new RoyaltyRecipient(wallet, minRoyaltyPercentage, externalId); - } - - /** - * Get a new RoyaltyRecipient instance from a returned struct representation - * @param struct - * @returns {*} - */ - static fromStruct(struct) { - let wallet, minRoyaltyPercentage, externalId; - - // destructure struct - [wallet, minRoyaltyPercentage, externalId] = struct; - - return RoyaltyRecipient.fromObject({ - wallet, - minRoyaltyPercentage: minRoyaltyPercentage.toString(), - externalId, - }); - } - - /** - * Get a database representation of this RoyaltyRecipient instance - * @returns {object} - */ - toObject() { - return JSON.parse(this.toString()); - } - - /** - * Get a string representation of this RoyaltyRecipient instance - * @returns {string} - */ - toString() { - return JSON.stringify(this); - } - - /** - * Get a struct representation of this RoyaltyRecipient instance - * @returns {string} - */ - toStruct() { - return [this.wallet, this.minRoyaltyPercentage, this.externalId]; - } - - /** - * Clone this RoyaltyRecipient - * @returns {RoyaltyRecipient} - */ - clone() { - return RoyaltyRecipient.fromObject(this.toObject()); - } - - /** - * Is this RoyaltyRecipient instance's wallet field valid? - * Must be a eip55 compliant Ethereum address - * @returns {boolean} - */ - walletIsValid() { - return addressIsValid(this.wallet); - } - - /** - * Is this RoyaltyRecipient instance's minRoyaltyPercentage field valid? - * Must be a string representation of a big number - * @returns {boolean} - */ - minRoyaltyPercentageIsValid() { - return bigNumberIsValid(this.minRoyaltyPercentage); - } - - /** - * Is this RoyaltyRecipient instance's externalId field valid? - * Always present, must be a string - * - * @returns {boolean} - */ - externalIdIsValid() { - return stringIsValid(this.externalId); - } - - /** - * Is this RoyaltyRecipient instance valid? - * @returns {boolean} - */ - isValid() { - return this.walletIsValid() && this.idIsValid() && this.externalIdIsValid(); - } -} - -/** - * Boson Protocol Domain Entity: Collection of RoyaltyRecipient - * - * See: {BosonTypes.RoyaltyRecipient} - */ -class RoyaltyRecipientList { - constructor(royaltyRecipient) { - this.royaltyRecipient = royaltyRecipient; - } - - /** - * Get a new RoyaltyRecipientList instance from a pojo representation - * @param o - * @returns {RoyaltyRecipientList} - */ - static fromObject(o) { - const { royaltyRecipient } = o; - return new RoyaltyRecipientList(royaltyRecipient.map((f) => RoyaltyRecipient.fromObject(f))); - } - - /** - * Get a new RoyaltyRecipientList instance from a returned struct representation - * @param struct - * @returns {*} - */ - static fromStruct(struct) { - return RoyaltyRecipientList.fromObject({ - royaltyRecipient: struct.map((royaltyRecipient) => RoyaltyRecipient.fromStruct(royaltyRecipient)), - }); - } - - /** - * Get a database representation of this RoyaltyRecipientList instance - * @returns {object} - */ - toObject() { - return JSON.parse(this.toString()); - } - - /** - * Get a string representation of this RoyaltyRecipientList instance - * @returns {string} - */ - toString() { - return JSON.stringify(this); - } - - /** - * Get a struct representation of this RoyaltyRecipientList instance - * @returns {string} - */ - toStruct() { - return this.royaltyRecipient.map((f) => f.toStruct()); - } - - /** - * Clone this RoyaltyRecipientList - * @returns {RoyaltyRecipientList} - */ - clone() { - return RoyaltyRecipientList.fromObject(this.toObject()); - } - - /** - * Is this RoyaltyRecipientList instance's royaltyRecipient field valid? - * Must be a list of RoyaltyRecipient instances - * @returns {boolean} - */ - royaltyRecipientIsValid() { - let valid = false; - let { royaltyRecipient } = this; - try { - valid = - Array.isArray(royaltyRecipient) && - royaltyRecipient.reduce( - (previousRoyaltyRecipient, currentRoyaltyRecipient) => - previousRoyaltyRecipient && currentRoyaltyRecipient.isValid(), - true - ); - } catch (e) {} - return valid; - } - - /** - * Is this RoyaltyRecipientList instance valid? - * @returns {boolean} - */ - isValid() { - return this.royaltyRecipientIsValid(); - } -} - -// Export -exports.RoyaltyRecipient = RoyaltyRecipient; -exports.RoyaltyRecipientList = RoyaltyRecipientList; From 30344ab4b73048f71ddbf72135ec00a7811c36fc Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 6 Apr 2023 08:15:21 +0200 Subject: [PATCH 33/47] Fix typo SequentialCommitHandler.sol --- ...ialCommitHandlerFacet.sol => SequentialCommitHandlerFacet.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/protocol/facets/{SequenatialCommitHandlerFacet.sol => SequentialCommitHandlerFacet.sol} (100%) diff --git a/contracts/protocol/facets/SequenatialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol similarity index 100% rename from contracts/protocol/facets/SequenatialCommitHandlerFacet.sol rename to contracts/protocol/facets/SequentialCommitHandlerFacet.sol From 86bcb9e997726617a482c3599dd4566703103602 Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 7 Aug 2023 12:02:32 +0200 Subject: [PATCH 34/47] New chunks --- test/util/test-chunks.txt | 61 ++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/test/util/test-chunks.txt b/test/util/test-chunks.txt index f94802e2b..3799251c6 100644 --- a/test/util/test-chunks.txt +++ b/test/util/test-chunks.txt @@ -1,59 +1,60 @@ [ [ - "test/protocol/clients/BosonVoucherTest.js", "test/protocol/ExchangeHandlerTest.js", - "test/protocol/OrchestrationHandlerTest.js", - "test/domain/AuthTokenTest.js" + "test/example/SnapshotGateTest.js", + "test/protocol/FundsHandlerTest.js", + "test/protocol/MetaTransactionsHandlerTest.js" ], [ - "test/protocol/FundsHandlerTest.js", + "test/protocol/OrchestrationHandlerTest.js", "test/protocol/DisputeHandlerTest.js", - "test/protocol/OfferHandlerTest.js", + "test/protocol/clients/BosonVoucherTest.js", "test/protocol/GroupHandlerTest.js", + "test/protocol/clients/BeaconClientProxy.js", "test/protocol/ProtocolInitializationHandlerTest.js" ], [ + "test/protocol/SellerHandlerTest.js", "test/protocol/DisputeResolverHandlerTest.js", - "test/protocol/ProtocolDiamondTest.js", + "test/protocol/OfferHandlerTest.js", "test/protocol/BundleHandlerTest.js", - "test/protocol/MetaTransactionsHandlerTest.js", - "test/protocol/SellerHandlerTest.js", - "test/protocol/TwinHandlerTest.js", - "test/protocol/BuyerHandlerTest.js", "test/access/AccessControllerTest.js", - "test/protocol/AgentHandlerTest.js", - "test/domain/ReceiptTest.js" + "test/protocol/ProtocolDiamondTest.js", + "test/protocol/BuyerHandlerTest.js", + "test/protocol/TwinHandlerTest.js", + "test/protocol/AgentHandlerTest.js" ], [ "test/protocol/ConfigHandlerTest.js", - "test/protocol/PauseHandlerTest.js", "test/protocol/AccountHandlerTest.js", + "test/protocol/PauseHandlerTest.js", "test/protocol/clients/ClientExternalAddressesTest.js", - "test/example/SnapshotGateTest.js", - "test/protocol/clients/BeaconClientProxy.js", - "test/domain/VoucherTest.js", - "test/domain/DisputeTest.js", - "test/domain/ConditionTest.js", + "test/domain/AgentTest.js", "test/domain/TwinReceiptTest.js", - "test/domain/DisputeResolutionTermsTest.js", "test/domain/SellerTest.js", - "test/domain/RangeTest.js", + "test/domain/OfferTest.js", "test/domain/DisputeResolverFeeTest.js", - "test/domain/AgentTest.js", - "test/domain/FundsTest.js", + "test/domain/BuyerTest.js", "test/domain/DisputeResolverTest.js", - "test/domain/BundleTest.js", - "test/domain/OfferDatesTest.js", - "test/domain/VoucherInitValuesTest.js", - "test/domain/DisputeDatesTest.js", + "test/domain/ConditionTest.js", "test/domain/TwinTest.js", + "test/domain/OfferFeesTest.js", + "test/domain/FundsTest.js", + "test/domain/FacetCutTest.js", "test/domain/ExchangeTest.js", "test/domain/FacetTest.js", - "test/domain/BuyerTest.js", + "test/domain/BundleTest.js", "test/domain/OfferDurationsTest.js", - "test/domain/OfferTest.js", + "test/domain/RangeTest.js", + "test/domain/CollectionTest.js", + "test/domain/ReceiptTest.js", + "test/domain/OfferDatesTest.js", + "test/domain/DisputeResolutionTermsTest.js", + "test/domain/AuthTokenTest.js", "test/domain/GroupTest.js", - "test/domain/OfferFeesTest.js", - "test/domain/FacetCutTest.js" + "test/domain/VoucherInitValuesTest.js", + "test/domain/VoucherTest.js", + "test/domain/DisputeDatesTest.js", + "test/domain/DisputeTest.js" ] ] \ No newline at end of file From 2518ffd81d296ea121871c1ad5c939768877d79b Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 7 Aug 2023 12:03:00 +0200 Subject: [PATCH 35/47] correctly toggle preprocessing --- .../ProtocolInitializationHandlerTest.js | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 4a3bfe7a2..4eb3eb2f8 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -683,6 +683,7 @@ describe("ProtocolInitializationHandler", async function () { voucherBeacon: await beacon.getAddress(), }; + let doPreprocess = true; // Due to "hardhat-preprocessor" way of caching, we need a workaround to toggle preprocessing on and off // Make initial deployment (simulate v2.2.1) // The new config initialization deploys the same voucher proxy as initV2_3_0, which makes the initV2_3_0 test fail // One way to approach would be to checkout the contracts from the previous tag. @@ -690,17 +691,19 @@ describe("ProtocolInitializationHandler", async function () { hre.config.preprocess = { eachLine: () => ({ transform: (line) => { - if ( - line.includes("address beaconProxy = address(new BeaconClientProxy{ salt: VOUCHER_PROXY_SALT }());") - ) { - // comment out the proxy deployment - line = "//" + line; - } else if (line.includes("setBeaconProxyAddress(beaconProxy)")) { - // set beacon proxy from config, not the deployed one - line = line.replace( - "setBeaconProxyAddress(beaconProxy)", - "setBeaconProxyAddress(_addresses.beaconProxy)" - ); + if (doPreprocess) { + if ( + line.includes("address beaconProxy = address(new BeaconClientProxy{ salt: VOUCHER_PROXY_SALT }());") + ) { + // comment out the proxy deployment + line = "//" + line; + } else if (line.includes("setBeaconProxyAddress(beaconProxy)")) { + // set beacon proxy from config, not the deployed one + line = line.replace( + "setBeaconProxyAddress(beaconProxy)", + "setBeaconProxyAddress(_addresses.beaconProxy)" + ); + } } return line; }, @@ -724,10 +727,9 @@ describe("ProtocolInitializationHandler", async function () { await accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues); // Deploy v2.3.0 facets - // Remove preprocess - hre.config.preprocess = {}; - // Compile old version - await hre.run("compile"); + // Skip preprocessing and compile new version + doPreprocess = false; + await hre.run("compile", { force: true }); [{ contract: deployedProtocolInitializationHandlerFacet }, { contract: configHandler }] = await deployProtocolFacets( From 18097afcc7485b6e3b42bfe9eac9e5f7fe18a59d Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 8 Sep 2023 14:21:56 +0200 Subject: [PATCH 36/47] SequentialCommitHandlerFacet constructor arg in deployment --- scripts/config/facet-deploy.js | 1 + scripts/config/protocol-parameters.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/scripts/config/facet-deploy.js b/scripts/config/facet-deploy.js index 5f604bb89..4ae78de6f 100644 --- a/scripts/config/facet-deploy.js +++ b/scripts/config/facet-deploy.js @@ -77,6 +77,7 @@ async function getFacets(config) { facetArgs["ConfigHandlerFacet"] = { init: ConfigHandlerFacetInitArgs }; facetArgs["ExchangeHandlerFacet"] = { init: [], constructorArgs: [protocolConfig.EXCHANGE_ID_2_2_0[network]] }; + facetArgs["SequentialCommitHandlerFacet"] = { init: [], constructorArgs: [protocolConfig.WETH[network]] }; // metaTransactionsHandlerFacet initializer arguments. const MetaTransactionsHandlerFacetInitArgs = await getMetaTransactionsHandlerFacetInitArgs( diff --git a/scripts/config/protocol-parameters.js b/scripts/config/protocol-parameters.js index 3ee913f39..763c236ae 100644 --- a/scripts/config/protocol-parameters.js +++ b/scripts/config/protocol-parameters.js @@ -77,4 +77,14 @@ module.exports = { polygon: 2, // TODO: adjust for actual deployment localhost: 1, }, + + WETH: { + mainnet: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy + hardhat: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy + localhost: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy + test: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy + mumbai: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy + polygon: "0x17CDD65bebDe68cd8A4045422Fcff825A0740Ef9", //dummy + goerli: "0x17CDD65bebDe68cd8A4045422Fcff825A0740Ef9", //dummy + }, }; From bfefe833f8d0a22ae1da037cfece9a3a5808f7e2 Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 11 Sep 2023 11:03:41 +0200 Subject: [PATCH 37/47] fix unit tests --- test/protocol/AccountHandlerTest.js | 4 +- test/protocol/AgentHandlerTest.js | 4 +- test/protocol/ExchangeHandlerTest.js | 2 +- test/protocol/FundsHandlerTest.js | 17 +- test/protocol/OfferHandlerTest.js | 4 +- test/protocol/PauseHandlerTest.js | 4 +- test/protocol/ProtocolDiamondTest.js | 4 +- test/protocol/SequentialCommitHandlerTest.js | 310 ++++++++++--------- 8 files changed, 182 insertions(+), 167 deletions(-) diff --git a/test/protocol/AccountHandlerTest.js b/test/protocol/AccountHandlerTest.js index a8b230949..b31e97453 100644 --- a/test/protocol/AccountHandlerTest.js +++ b/test/protocol/AccountHandlerTest.js @@ -1,5 +1,5 @@ -const hre = require("hardhat"); -const { ZeroAddress } = hre.ethers; +const { ethers } = require("hardhat"); +const { ZeroAddress } = ethers; const { expect } = require("chai"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); diff --git a/test/protocol/AgentHandlerTest.js b/test/protocol/AgentHandlerTest.js index 1147cec43..1aed229dd 100644 --- a/test/protocol/AgentHandlerTest.js +++ b/test/protocol/AgentHandlerTest.js @@ -1,5 +1,5 @@ -const hre = require("hardhat"); -const { ZeroAddress } = hre.ethers; +const { ethers } = require("hardhat"); +const { ZeroAddress } = ethers; const { expect } = require("chai"); const Agent = require("../../scripts/domain/Agent"); diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 736e773ee..c79897758 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -307,7 +307,7 @@ describe("IBosonExchangeHandler", function () { offer.quantityAvailable = "10"; disputeResolverId = mo.disputeResolverId; - offerDurations.voucherValid = (oneMonth * 12).toString(); + offerDurations.voucherValid = (oneMonth * 12n).toString(); // Check if domains are valid expect(offer.isValid()).is.true; diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index b3229c388..cd0dab857 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4550,8 +4550,7 @@ describe("IBosonFundsHandler", function () { const expectedCloneAddress = calculateCloneAddress( await accountHandler.getAddress(), beaconProxyAddress, - admin.address, - "" + admin.address ); bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); @@ -6164,8 +6163,7 @@ describe("IBosonFundsHandler", function () { const expectedCloneAddress = calculateCloneAddress( await accountHandler.getAddress(), beaconProxyAddress, - admin.address, - "" + admin.address ); const bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); await configHandler.setProtocolFeePercentage(fee.protocol); @@ -6182,7 +6180,9 @@ describe("IBosonFundsHandler", function () { await fundsHandler.connect(assistant).withdrawFunds(seller.id, [], []); // withdraw all, so it's easier to test await mockToken.connect(assistant).mint(assistant.address, offer.sellerDeposit); await mockToken.connect(assistant).approve(await fundsHandler.getAddress(), offer.sellerDeposit); - await fundsHandler.connect(assistant).depositFunds(seller.id, mockTokenAddress, offer.sellerDeposit); + await fundsHandler + .connect(assistant) + .depositFunds(seller.id, await mockToken.getAddress(), offer.sellerDeposit); await offerHandler .connect(assistant) @@ -6194,7 +6194,7 @@ describe("IBosonFundsHandler", function () { buyerId = 5; // Create buyer with protocol address to not mess up ids in tests - await accountHandler.createBuyer(mockBuyer(exchangeHandler.address)); + await accountHandler.createBuyer(mockBuyer(await exchangeHandler.getAddress())); // commit to offer await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id); @@ -6225,9 +6225,10 @@ describe("IBosonFundsHandler", function () { order, ]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); const priceDiscovery = new PriceDiscovery( order.price, - priceDiscoveryContract.address, + priceDiscoveryContractAddress, priceDiscoveryData, Side.Ask ); @@ -6237,7 +6238,7 @@ describe("IBosonFundsHandler", function () { await mockToken.connect(voucherOwner).approve(protocolDiamondAddress, order.price); // Voucher owner approves PriceDiscovery contract to transfer the tokens - await bosonVoucherClone.connect(voucherOwner).setApprovalForAll(priceDiscoveryContract.address, true); + await bosonVoucherClone.connect(voucherOwner).setApprovalForAll(priceDiscoveryContractAddress, true); // Buyer approves protocol to transfer the tokens await mockToken.mint(trade.buyer.address, order.price); diff --git a/test/protocol/OfferHandlerTest.js b/test/protocol/OfferHandlerTest.js index 77df565b1..d09fd8f79 100644 --- a/test/protocol/OfferHandlerTest.js +++ b/test/protocol/OfferHandlerTest.js @@ -1,5 +1,5 @@ -const hre = require("hardhat"); -const { getContractAt, ZeroAddress, getSigners, MaxUint256, provider, parseUnits } = hre.ethers; +const { ethers } = require("hardhat"); +const { getContractAt, ZeroAddress, getSigners, MaxUint256, provider, parseUnits } = ethers; const { assert, expect } = require("chai"); const Offer = require("../../scripts/domain/Offer"); diff --git a/test/protocol/PauseHandlerTest.js b/test/protocol/PauseHandlerTest.js index f6cab7712..565cfedc7 100644 --- a/test/protocol/PauseHandlerTest.js +++ b/test/protocol/PauseHandlerTest.js @@ -1,6 +1,6 @@ -const hre = require("hardhat"); +const { ethers } = require("hardhat"); const { expect } = require("chai"); -const { id } = hre.ethers; +const { id } = ethers; const { getStorageAt } = require("@nomicfoundation/hardhat-network-helpers"); const PausableRegion = require("../../scripts/domain/PausableRegion.js"); const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); diff --git a/test/protocol/ProtocolDiamondTest.js b/test/protocol/ProtocolDiamondTest.js index 8be65e4f6..f19d05806 100644 --- a/test/protocol/ProtocolDiamondTest.js +++ b/test/protocol/ProtocolDiamondTest.js @@ -1,6 +1,6 @@ const { assert, expect } = require("chai"); -const hre = require("hardhat"); -const { getSigners, getContractAt, getContractFactory, Interface, ZeroAddress } = hre.ethers; +const { ethers } = require("hardhat"); +const { getSigners, getContractAt, getContractFactory, Interface, ZeroAddress } = ethers; const Role = require("../../scripts/domain/Role"); const Facet = require("../../scripts/domain/Facet"); diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index 20f912be6..4352e76f0 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -1,5 +1,5 @@ -const hre = require("hardhat"); -const ethers = hre.ethers; +const { ethers } = require("hardhat"); +const { ZeroAddress, getContractFactory, getSigners, parseUnits, provider, getContractAt } = ethers; const { expect } = require("chai"); const Exchange = require("../../scripts/domain/Exchange"); @@ -25,7 +25,8 @@ const { const { setNextBlockTimestamp, calculateVoucherExpiry, - calculateContractAddress, + calculateBosonProxyAddress, + calculateCloneAddress, applyPercentage, setupTestEnvironment, getSnapshot, @@ -40,19 +41,7 @@ const { oneMonth } = require("../util/constants"); describe("IBosonSequentialCommitHandler", function () { // Common vars let InterfaceIds; - let deployer, - pauser, - assistant, - admin, - clerk, - treasury, - rando, - buyer, - buyer2, - assistantDR, - adminDR, - clerkDR, - treasuryDR; + let deployer, pauser, assistant, admin, treasury, rando, buyer, buyer2, assistantDR, adminDR, treasuryDR; let erc165, accountHandler, exchangeHandler, @@ -91,9 +80,9 @@ describe("IBosonSequentialCommitHandler", function () { InterfaceIds = await getInterfaceIds(); // Add WETH - const wethFactory = await ethers.getContractFactory("WETH9"); + const wethFactory = await getContractFactory("WETH9"); weth = await wethFactory.deploy(); - await weth.deployed(); + await weth.waitForDeployment(); // Specify contracts needed for this test const contracts = { @@ -121,18 +110,18 @@ describe("IBosonSequentialCommitHandler", function () { }, protocolConfig: [, , { percentage: protocolFeePercentage }], diamondAddress: protocolDiamondAddress, - } = await setupTestEnvironment(contracts, { wethAddress: weth.address })); + } = await setupTestEnvironment(contracts, { wethAddress: await weth.getAddress() })); // make all account the same - assistant = clerk = admin; - assistantDR = clerkDR = adminDR; + assistant = admin; + assistantDR = adminDR; - [deployer] = await ethers.getSigners(); + [deployer] = await getSigners(); // Deploy PriceDiscovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + const PriceDiscoveryFactory = await getContractFactory("PriceDiscovery"); priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - await priceDiscoveryContract.deployed(); + await priceDiscoveryContract.waitForDeployment(); // Get snapshot id snapshotId = await getSnapshot(); @@ -143,7 +132,7 @@ describe("IBosonSequentialCommitHandler", function () { snapshotId = await getSnapshot(); }); - // Interface support (ERC-156 provided by ProtocolDiamond, others by deployed facets) + // Interface support (ERC-156 provided by ProtocolDiamond, others by waitForDeployment facets) context("📋 Interfaces", async function () { context("👉 supportsInterface()", async function () { it("should indicate support for IBosonSequentialCommitHandler interface", async function () { @@ -164,7 +153,7 @@ describe("IBosonSequentialCommitHandler", function () { agentId = "0"; // agent id is optional while creating an offer // Create a valid seller - seller = mockSeller(assistant.address, admin.address, clerk.address, treasury.address); + seller = mockSeller(assistant.address, admin.address, ZeroAddress, treasury.address); expect(seller.isValid()).is.true; // AuthToken @@ -176,20 +165,22 @@ describe("IBosonSequentialCommitHandler", function () { expect(voucherInitValues.isValid()).is.true; await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); - expectedCloneAddress = calculateContractAddress(accountHandler.address, "1"); + + const beaconProxyAddress = await calculateBosonProxyAddress(protocolDiamondAddress); + expectedCloneAddress = calculateCloneAddress(protocolDiamondAddress, beaconProxyAddress, admin.address); // Create a valid dispute resolver disputeResolver = mockDisputeResolver( assistantDR.address, adminDR.address, - clerkDR.address, + ZeroAddress, treasuryDR.address, true ); expect(disputeResolver.isValid()).is.true; //Create DisputeResolverFee array so offer creation will succeed - disputeResolverFees = [new DisputeResolverFee(ethers.constants.AddressZero, "Native", "0")]; + disputeResolverFees = [new DisputeResolverFee(ZeroAddress, "Native", "0")]; // Make empty seller list, so every seller is allowed const sellerAllowList = []; @@ -209,7 +200,7 @@ describe("IBosonSequentialCommitHandler", function () { offer.quantityAvailable = "10"; disputeResolverId = mo.disputeResolverId; - offerDurations.voucherValid = (oneMonth * 12).toString(); + offerDurations.voucherValid = (oneMonth * 12n).toString(); // Check if domains are valid expect(offer.isValid()).is.true; @@ -220,10 +211,10 @@ describe("IBosonSequentialCommitHandler", function () { await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); // Set used variables - price = offer.price; + price = BigInt(offer.price); voucherRedeemableFrom = offerDates.voucherRedeemableFrom; voucherValid = offerDurations.voucherValid; - sellerPool = ethers.utils.parseUnits("15", "ether").toString(); + sellerPool = parseUnits("15", "ether").toString(); // Required voucher constructor params voucher = mockVoucher(); @@ -237,9 +228,7 @@ describe("IBosonSequentialCommitHandler", function () { exchange.finalizedDate = "0"; // Deposit seller funds so the commit will succeed - await fundsHandler - .connect(assistant) - .depositFunds(seller.id, ethers.constants.AddressZero, sellerPool, { value: sellerPool }); + await fundsHandler.connect(assistant).depositFunds(seller.id, ZeroAddress, sellerPool, { value: sellerPool }); }); afterEach(async function () { @@ -254,9 +243,9 @@ describe("IBosonSequentialCommitHandler", function () { // before(async function () { // // Deploy PriceDiscovery contract - // const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + // const PriceDiscoveryFactory = await getContractFactory("PriceDiscovery"); // priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - // await priceDiscoveryContract.deployed(); + // await priceDiscoveryContract.waitForDeployment(); // }); beforeEach(async function () { @@ -265,7 +254,7 @@ describe("IBosonSequentialCommitHandler", function () { // Get the block timestamp of the confirmed tx blockNumber = tx.blockNumber; - block = await ethers.provider.getBlock(blockNumber); + block = await provider.getBlock(blockNumber); // Update the committed date in the expected exchange struct with the block timestamp of the tx voucher.committedDate = block.timestamp.toString(); @@ -280,7 +269,7 @@ describe("IBosonSequentialCommitHandler", function () { context("General actions", async function () { beforeEach(async function () { // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + price2 = (BigInt(price) * 11n) / 10n; // 10% above the original price // Prepare calldata for PriceDiscovery contract let order = { @@ -294,7 +283,12 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Ask + ); // Seller needs to deposit weth in order to fill the escrow at the last step // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) @@ -304,8 +298,8 @@ describe("IBosonSequentialCommitHandler", function () { // Approve transfers // Buyer does not approve, since its in ETH. // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); mockBuyer(buyer.address); // call only to increment account id counter newBuyer = mockBuyer(buyer2.address); @@ -325,7 +319,7 @@ describe("IBosonSequentialCommitHandler", function () { it("should update state", async function () { // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + const escrowBefore = await provider.getBalance(await exchangeHandler.getAddress()); // Sequential commit to offer await sequentialCommitHandler @@ -341,8 +335,8 @@ describe("IBosonSequentialCommitHandler", function () { expect(returnedExchange.buyerId).to.equal(newBuyer.id); // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + const escrowAfter = await provider.getBalance(await exchangeHandler.getAddress()); + expect(escrowAfter).to.equal(escrowBefore + price2 - price); }); it("should transfer the voucher", async function () { @@ -496,7 +490,7 @@ describe("IBosonSequentialCommitHandler", function () { it("It is possible to commit even if offer has expired", async function () { // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); + await setNextBlockTimestamp(Number(offerDates.validUntil) + 1); // Committing directly is not possible await expect( @@ -564,7 +558,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(ZeroAddress, exchangeId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); @@ -582,7 +576,7 @@ describe("IBosonSequentialCommitHandler", function () { it("voucher not valid anymore", async function () { // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); + await setNextBlockTimestamp(Number(voucher.validUntilDate) + 1); // Attempt to sequentially commit to the expired voucher, expecting revert await expect( @@ -617,9 +611,9 @@ describe("IBosonSequentialCommitHandler", function () { it("price discovery does not send the voucher to the protocol", async function () { // Deploy bad price discovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryNoTransfer"); + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryNoTransfer"); const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - await priceDiscoveryContract.deployed(); + await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract let order = { @@ -633,7 +627,12 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Ask + ); // Attempt to sequentially commit, expecting revert await expect( @@ -654,21 +653,21 @@ describe("IBosonSequentialCommitHandler", function () { async function getBalances() { const [protocol, seller, sellerWeth, newBuyer, originalSeller] = await Promise.all([ - ethers.provider.getBalance(exchangeHandler.address), - ethers.provider.getBalance(buyer.address), + provider.getBalance(await exchangeHandler.getAddress()), + provider.getBalance(buyer.address), weth.balanceOf(buyer.address), - ethers.provider.getBalance(buyer2.address), - ethers.provider.getBalance(treasury.address), + provider.getBalance(buyer2.address), + provider.getBalance(treasury.address), ]); - return { protocol, seller: seller.add(sellerWeth), newBuyer, originalSeller }; + return { protocol, seller: seller + sellerWeth, newBuyer, originalSeller }; } scenarios.forEach((scenario) => { context(scenario.case, async function () { beforeEach(async function () { // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + price2 = (BigInt(price) * BigInt(scenario.multiplier)) / 10n; // Prepare calldata for PriceDiscovery contract let order = { @@ -677,7 +676,7 @@ describe("IBosonSequentialCommitHandler", function () { voucherContract: expectedCloneAddress, tokenId: deriveTokenId(offer.id, exchangeId), exchangeToken: offer.exchangeToken, - price: price2, + price: price2.toString(), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ @@ -686,7 +685,7 @@ describe("IBosonSequentialCommitHandler", function () { priceDiscovery = new PriceDiscovery( price2, - priceDiscoveryContract.address, + await priceDiscoveryContract.getAddress(), priceDiscoveryData, Side.Ask ); @@ -699,8 +698,10 @@ describe("IBosonSequentialCommitHandler", function () { // Approve transfers // Buyer does not approve, since its in ETH. // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(buyer).setApprovalForAll(priceDiscoveryContract.address, true); + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone + .connect(buyer) + .setApprovalForAll(await priceDiscoveryContract.getAddress(), true); mockBuyer(buyer.address); // call only to increment account id counter newBuyer = mockBuyer(buyer2.address); @@ -749,19 +750,18 @@ describe("IBosonSequentialCommitHandler", function () { // Expected changes const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; + const reducedSecondaryPrice = + (BigInt(price2) * BigInt(10000 - fee.protocol - fee.royalties)) / 10000n; + const expectedSellerChange = reducedSecondaryPrice <= price ? reducedSecondaryPrice : price; + const expectedProtocolChange = price2 - expectedSellerChange; + const expectedOriginalSellerChange = 0n; // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol + expectedProtocolChange); + expect(balancesAfter.seller).to.equal(balancesBefore.seller + expectedSellerChange); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer - expectedBuyerChange); expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) + balancesBefore.originalSeller + expectedOriginalSellerChange ); }); @@ -774,7 +774,7 @@ describe("IBosonSequentialCommitHandler", function () { const balancesBefore = await getBalances(); // Sequential commit to offer. Buyer pays more than needed - priceDiscovery.price = ethers.BigNumber.from(price2).mul(3).toString(); + priceDiscovery.price = price2 * 3n; await sequentialCommitHandler .connect(buyer2) @@ -787,19 +787,17 @@ describe("IBosonSequentialCommitHandler", function () { // Expected changes const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; + const reducedSecondaryPrice = (price2 * BigInt(10000 - fee.protocol - fee.royalties)) / 10000n; + const expectedSellerChange = reducedSecondaryPrice <= price ? reducedSecondaryPrice : price; + const expectedProtocolChange = price2 - expectedSellerChange; + const expectedOriginalSellerChange = 0n; // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol + expectedProtocolChange); + expect(balancesAfter.seller).to.equal(balancesBefore.seller + expectedSellerChange); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer - expectedBuyerChange); expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) + balancesBefore.originalSeller + expectedOriginalSellerChange ); }); }); @@ -812,30 +810,35 @@ describe("IBosonSequentialCommitHandler", function () { context("General actions", async function () { beforeEach(async function () { // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + price2 = (price * 11n) / 10n; // 10% above the original price // Prepare calldata for PriceDiscovery contract let order = { - seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + seller: await exchangeHandler.getAddress(), // since protocol owns the voucher, it acts as seller from price discovery mechanism buyer: buyer2.address, voucherContract: expectedCloneAddress, tokenId: deriveTokenId(offer.id, exchangeId), - exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH - price: price2, + exchangeToken: await weth.getAddress(), // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2.toString(), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Bid); + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Bid + ); // Approve transfers // Buyer2 needs to approve price discovery to transfer the ETH await weth.connect(buyer2).deposit({ value: price2 }); - await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + await weth.connect(buyer2).approve(await priceDiscoveryContract.getAddress(), price2); // Seller approves protocol to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await exchangeHandler.getAddress(), true); mockBuyer(reseller.address); // call only to increment account id counter newBuyer = mockBuyer(buyer2.address); @@ -855,7 +858,7 @@ describe("IBosonSequentialCommitHandler", function () { it("should update state", async function () { // Escrow amount before - const escrowBefore = await ethers.provider.getBalance(exchangeHandler.address); + const escrowBefore = await provider.getBalance(await exchangeHandler.getAddress()); // Sequential commit to offer await sequentialCommitHandler @@ -871,8 +874,8 @@ describe("IBosonSequentialCommitHandler", function () { expect(returnedExchange.buyerId).to.equal(newBuyer.id); // Contract's balance should increase for minimal escrow amount - const escrowAfter = await ethers.provider.getBalance(exchangeHandler.address); - expect(escrowAfter).to.equal(escrowBefore.add(price2).sub(price)); + const escrowAfter = await provider.getBalance(await exchangeHandler.getAddress()); + expect(escrowAfter).to.equal(escrowBefore + price2 - price); }); it("should transfer the voucher", async function () { @@ -1011,7 +1014,7 @@ describe("IBosonSequentialCommitHandler", function () { it("It is possible to commit even if offer has expired", async function () { // Advance time to after offer expiry - await setNextBlockTimestamp(Number(offerDates.validUntil)); + await setNextBlockTimestamp(Number(offerDates.validUntil) + 1); // Committing directly is not possible await expect(exchangeHandler.connect(buyer2).commitToOffer(buyer2.address, offerId)).to.revertedWith( @@ -1079,7 +1082,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(ethers.constants.AddressZero, exchangeId, priceDiscovery) + .sequentialCommitToOffer(ZeroAddress, exchangeId, priceDiscovery) ).to.revertedWith(RevertReasons.INVALID_ADDRESS); }); @@ -1097,7 +1100,7 @@ describe("IBosonSequentialCommitHandler", function () { it("voucher not valid anymore", async function () { // Go past offer expiration date - await setNextBlockTimestamp(Number(voucher.validUntilDate)); + await setNextBlockTimestamp(Number(voucher.validUntilDate) + 1); // Attempt to sequentially commit to the expired voucher, expecting revert await expect( @@ -1123,7 +1126,7 @@ describe("IBosonSequentialCommitHandler", function () { it("voucher transfer not approved", async function () { // revoke approval - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, false); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await exchangeHandler.getAddress(), false); // Attempt to sequentially commit to, expecting revert await expect( @@ -1135,7 +1138,7 @@ describe("IBosonSequentialCommitHandler", function () { it("price discovery sends less than expected", async function () { // Set higher price in price discovery - priceDiscovery.price = ethers.BigNumber.from(priceDiscovery.price).add(1); + priceDiscovery.price = BigInt(priceDiscovery.price) + 1n; // Attempt to sequentially commit to, expecting revert await expect( @@ -1165,31 +1168,31 @@ describe("IBosonSequentialCommitHandler", function () { async function getBalances() { const [protocol, seller, sellerWeth, newBuyer, newBuyerWeth, originalSeller] = await Promise.all([ - ethers.provider.getBalance(exchangeHandler.address), - ethers.provider.getBalance(reseller.address), + provider.getBalance(await exchangeHandler.getAddress()), + provider.getBalance(reseller.address), weth.balanceOf(reseller.address), - ethers.provider.getBalance(buyer2.address), + provider.getBalance(buyer2.address), weth.balanceOf(buyer2.address), - ethers.provider.getBalance(treasury.address), + provider.getBalance(treasury.address), ]); - return { protocol, seller: seller.add(sellerWeth), newBuyer: newBuyer.add(newBuyerWeth), originalSeller }; + return { protocol, seller: seller + sellerWeth, newBuyer: newBuyer + newBuyerWeth, originalSeller }; } scenarios.forEach((scenario) => { context(scenario.case, async function () { beforeEach(async function () { // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(scenario.multiplier).div(10).toString(); + price2 = (price * BigInt(scenario.multiplier)) / 10n; // Prepare calldata for PriceDiscovery contract let order = { - seller: exchangeHandler.address, // since protocol owns the voucher, it acts as seller from price discovery mechanism + seller: await exchangeHandler.getAddress(), // since protocol owns the voucher, it acts as seller from price discovery mechanism buyer: buyer2.address, voucherContract: expectedCloneAddress, tokenId: deriveTokenId(offer.id, exchangeId), - exchangeToken: weth.address, // buyer pays in ETH, but they cannot approve ETH, so we use WETH - price: price2, + exchangeToken: await weth.getAddress(), // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price2.toString(), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [ @@ -1198,7 +1201,7 @@ describe("IBosonSequentialCommitHandler", function () { priceDiscovery = new PriceDiscovery( price2, - priceDiscoveryContract.address, + await priceDiscoveryContract.getAddress(), priceDiscoveryData, Side.Bid ); @@ -1206,11 +1209,11 @@ describe("IBosonSequentialCommitHandler", function () { // Approve transfers // Buyer2 needs to approve price discovery to transfer the ETH await weth.connect(buyer2).deposit({ value: price2 }); - await weth.connect(buyer2).approve(priceDiscoveryContract.address, price2); + await weth.connect(buyer2).approve(await priceDiscoveryContract.getAddress(), price2); // Seller approves protocol to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); - await bosonVoucherClone.connect(reseller).setApprovalForAll(exchangeHandler.address, true); + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await exchangeHandler.getAddress(), true); mockBuyer(buyer.address); // call only to increment account id counter newBuyer = mockBuyer(buyer2.address); @@ -1258,19 +1261,17 @@ describe("IBosonSequentialCommitHandler", function () { // Expected changes const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; + const reducedSecondaryPrice = (price2 * BigInt(10000 - fee.protocol - fee.royalties)) / 10000n; + const expectedSellerChange = reducedSecondaryPrice <= price ? reducedSecondaryPrice : price; + const expectedProtocolChange = price2 - expectedSellerChange; + const expectedOriginalSellerChange = 0n; // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol + expectedProtocolChange); + expect(balancesAfter.seller).to.equal(balancesBefore.seller + expectedSellerChange); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer - expectedBuyerChange); expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) + balancesBefore.originalSeller + expectedOriginalSellerChange ); }); @@ -1283,7 +1284,7 @@ describe("IBosonSequentialCommitHandler", function () { const balancesBefore = await getBalances(); // Sequential commit to offer. Buyer pays more than needed - priceDiscovery.price = ethers.BigNumber.from(price2).div(2).toString(); + priceDiscovery.price = price2 / 2n; await sequentialCommitHandler .connect(reseller) @@ -1295,19 +1296,17 @@ describe("IBosonSequentialCommitHandler", function () { // Expected changes const expectedBuyerChange = price2; - const reducedSecondaryPrice = ethers.BigNumber.from(price2) - .mul(10000 - fee.protocol - fee.royalties) - .div(10000); - const expectedSellerChange = reducedSecondaryPrice.lte(price) ? reducedSecondaryPrice : price; - const expectedProtocolChange = ethers.BigNumber.from(price2).sub(expectedSellerChange); - const expectedOriginalSellerChange = 0; + const reducedSecondaryPrice = (price2 * BigInt(10000 - fee.protocol - fee.royalties)) / 10000n; + const expectedSellerChange = reducedSecondaryPrice <= price ? reducedSecondaryPrice : price; + const expectedProtocolChange = price2 - expectedSellerChange; + const expectedOriginalSellerChange = 0n; // Contract's balance should increase for minimal escrow amount - expect(balancesAfter.protocol).to.equal(balancesBefore.protocol.add(expectedProtocolChange)); - expect(balancesAfter.seller).to.equal(balancesBefore.seller.add(expectedSellerChange)); - expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer.sub(expectedBuyerChange)); + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol + expectedProtocolChange); + expect(balancesAfter.seller).to.equal(balancesBefore.seller + expectedSellerChange); + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer - expectedBuyerChange); expect(balancesAfter.originalSeller).to.equal( - balancesBefore.originalSeller.add(expectedOriginalSellerChange) + balancesBefore.originalSeller + expectedOriginalSellerChange ); }); }); @@ -1328,7 +1327,7 @@ describe("IBosonSequentialCommitHandler", function () { reseller = buyer; // Price on secondary market - price2 = ethers.BigNumber.from(price).mul(11).div(10).toString(); // 10% above the original price + price2 = (price * 11n) / 10n; // 10% above the original price // Seller needs to deposit weth in order to fill the escrow at the last step // Price2 is theoretically the highest amount needed, in practice it will be less (around price2-price) @@ -1338,14 +1337,14 @@ describe("IBosonSequentialCommitHandler", function () { // Approve transfers // Buyer does not approve, since its in ETH. // Seller approves price discovery to transfer the voucher - bosonVoucherClone = await ethers.getContractAt("IBosonVoucher", expectedCloneAddress); + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); }); it("should transfer the voucher during sequential commit", async function () { // Deploy PriceDiscovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscovery"); + const PriceDiscoveryFactory = await getContractFactory("PriceDiscovery"); priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - await priceDiscoveryContract.deployed(); + await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract let order = { @@ -1360,9 +1359,14 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); // Seller approves price discovery to transfer the voucher - await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Ask + ); // buyer is owner of voucher const tokenId = deriveTokenId(offer.id, exchangeId); @@ -1383,9 +1387,9 @@ describe("IBosonSequentialCommitHandler", function () { await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offerId, { value: price }); // Deploy Bad PriceDiscovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryModifyTokenId"); + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryModifyTokenId"); priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - await priceDiscoveryContract.deployed(); + await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract let order = { @@ -1400,9 +1404,14 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); // Seller approves price discovery to transfer the voucher - await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Ask + ); // Attempt to sequentially commit, expecting revert await expect( @@ -1417,9 +1426,9 @@ describe("IBosonSequentialCommitHandler", function () { const [foreign721] = await deployMockTokens(["Foreign721"]); // Deploy Bad PriceDiscovery contract - const PriceDiscoveryFactory = await ethers.getContractFactory("PriceDiscoveryModifyVoucherContract"); - priceDiscoveryContract = await PriceDiscoveryFactory.deploy(foreign721.address); - await priceDiscoveryContract.deployed(); + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryModifyVoucherContract"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(await foreign721.getAddress()); + await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract let order = { @@ -1434,9 +1443,14 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); // Seller approves price discovery to transfer the voucher - await bosonVoucherClone.connect(reseller).setApprovalForAll(priceDiscoveryContract.address, true); - - priceDiscovery = new PriceDiscovery(price2, priceDiscoveryContract.address, priceDiscoveryData, Side.Ask); + await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + priceDiscovery = new PriceDiscovery( + price2, + await priceDiscoveryContract.getAddress(), + priceDiscoveryData, + Side.Ask + ); // Attempt to sequentially commit, expecting revert await expect( From c5ff0be6024fcb74994265be725b9b7ebb7ca1bc Mon Sep 17 00:00:00 2001 From: zajck Date: Tue, 24 Oct 2023 11:04:41 +0200 Subject: [PATCH 38/47] test isEligibleToCommit --- test/protocol/ExchangeHandlerTest.js | 59 +++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index c79897758..8eea2752f 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -28,6 +28,7 @@ const Bundle = require("../../scripts/domain/Bundle"); const ExchangeState = require("../../scripts/domain/ExchangeState"); const DisputeState = require("../../scripts/domain/DisputeState"); const Group = require("../../scripts/domain/Group"); +const Condition = require("../../scripts/domain/Condition"); const EvaluationMethod = require("../../scripts/domain/EvaluationMethod"); const GatingType = require("../../scripts/domain/GatingType"); const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); @@ -6827,7 +6828,7 @@ describe("IBosonExchangeHandler", function () { }); context("👉 isEligibleToCommit()", async function () { - context("✋ No condition", async function () { + context("✋ No group", async function () { it("buyer is eligible, no commits yet", async function () { const [isEligible, commitCount, maxCommits] = await exchangeHandler.isEligibleToCommit( buyer.address, @@ -6859,6 +6860,62 @@ describe("IBosonExchangeHandler", function () { }); }); + context("✋ Condition None", async function () { + beforeEach(async function () { + // Required constructor params for Group + groupId = "1"; + offerIds = [offerId]; + + // Create Condition + condition = new Condition(EvaluationMethod.None, 0, ZeroAddress, 0, 0, 0, 0, 0); + // expect(condition.isValid()).to.be.true; + + // Create Group + group = new Group(groupId, seller.id, offerIds); + expect(group.isValid()).is.true; + await groupHandler.connect(assistant).createGroup(group, condition); + }); + + it("buyer is eligible, no commits yet", async function () { + const [isEligible, commitCount, maxCommits] = await exchangeHandler.isEligibleToCommit( + buyer.address, + offerId, + 0 + ); + + expect(isEligible).to.be.true; + expect(commitCount).to.equal(0); + expect(maxCommits).to.equal(condition.maxCommits); + }); + + it("buyer is eligible, with existing commits", async function () { + // Commit to offer the maximum number of times + for (let i = 0; i < Number(condition.maxCommits); i++) { + // Commit to offer. + await exchangeHandler + .connect(buyer) + .commitToConditionalOffer(await buyer.getAddress(), offerId, 0, { value: price }); + + const [isEligible, commitCount, maxCommits] = await exchangeHandler.isEligibleToCommit( + buyer.address, + offerId, + 0 + ); + + expect(isEligible).to.equal(i + 1 < Number(condition.maxCommits)); + expect(commitCount).to.equal(i + 1); + expect(maxCommits).to.equal(condition.maxCommits); + } + }); + + context("💔 Revert Reasons", async function () { + it("Caller sends non-zero tokenId", async function () {}); + await expect(exchangeHandler.connect(buyer).isEligibleToCommit(buyer.address, offerId, 1)).to.revertedWith( + RevertReasons.INVALID_TOKEN_ID + ); + }); + }); + context("✋ Threshold ERC20", async function () { beforeEach(async function () { // Required constructor params for Group From 7ad553d4e0598c61576b8d0c525c9f4b348a1be5 Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 9 Nov 2023 13:50:25 +0100 Subject: [PATCH 39/47] review fixes --- contracts/domain/BosonConstants.sol | 1 + .../{IWETH9Like.sol => IWrappedNative.sol} | 4 +- .../IBosonSequentialCommitHandler.sol | 2 +- contracts/mock/PriceDiscovery.sol | 6 +- .../protocol/bases/PriceDiscoveryBase.sol | 61 ++++++++++++------- .../facets/SequentialCommitHandlerFacet.sol | 36 +++++++---- contracts/protocol/libs/FundsLib.sol | 24 +++++++- scripts/config/facet-deploy.js | 5 +- scripts/config/protocol-parameters.js | 2 +- test/util/utils.js | 2 +- 10 files changed, 100 insertions(+), 43 deletions(-) rename contracts/interfaces/{IWETH9Like.sol => IWrappedNative.sol} (87%) diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index 2f0d41e17..d567979b8 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -125,6 +125,7 @@ string constant INVALID_RANGE_LENGTH = "Range length is too large or zero"; string constant UNEXPECTED_ERC721_RECEIVED = "Unexpected ERC721 received"; string constant FEE_AMOUNT_TOO_HIGH = "Fee amount is too high"; string constant VOUCHER_NOT_RECEIVED = "Voucher not received"; +string constant NEGATIVE_PRICE_NOT_ALLOWED = "Negative price not allowed"; // Revert Reasons: Twin related uint256 constant SINGLE_TWIN_RESERVED_GAS = 160000; diff --git a/contracts/interfaces/IWETH9Like.sol b/contracts/interfaces/IWrappedNative.sol similarity index 87% rename from contracts/interfaces/IWETH9Like.sol rename to contracts/interfaces/IWrappedNative.sol index ff2fd3016..8bee69f22 100644 --- a/contracts/interfaces/IWETH9Like.sol +++ b/contracts/interfaces/IWrappedNative.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.21; /** - * @title IWETH9Like + * @title IWrappedNative * * @notice Provides the minimum interface for native token wrapper */ -interface IWETH9Like { +interface IWrappedNative { function withdraw(uint256) external; function deposit() external payable; diff --git a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol index a18e7efd1..2cd44a445 100644 --- a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol +++ b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol @@ -15,7 +15,7 @@ import { IERC721Receiver } from "../IERC721Receiver.sol"; */ interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IERC721Receiver { /** - * @notice Commits to an existing exchange. Price discovery is oflaoaded to external contract. + * @notice Commits to an existing exchange. Price discovery is offloaded to external contract. * * Emits a BuyerCommitted event if successful. * Transfers voucher to the buyer address. diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index bb40b62e0..d41cb5b07 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -9,7 +9,7 @@ import "./Foreign721.sol"; /** * @dev Simple price discovery contract used in tests * - * This contract simluates external price discovery mechanism. + * This contract simulates external price discovery mechanism. * When user commits to an offer, protocol talks to this contract to validate the exchange. */ contract PriceDiscovery { @@ -24,7 +24,7 @@ contract PriceDiscovery { /** * @dev simple fulfillOrder that does not perform any checks - * It just transfers the voucher and exchange token to the buyer + * It just transfers the voucher from the seller to the caller (buyer) and exchange token from the caller to the seller * If any of the transfers fail, the whole transaction will revert */ function fulfilBuyOrder(Order memory _order) public payable virtual { @@ -143,7 +143,7 @@ contract PriceDiscoveryModifyVoucherContract is PriceDiscovery { /** * @dev Simple bad price discovery contract used in tests * - * This contract modifies simply does not transfer the voucher to the caller + * This contract simply does not transfer the voucher to the caller */ contract PriceDiscoveryNoTransfer is PriceDiscovery { /** diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index 1658e7e0d..2713c636a 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.21; import "../../domain/BosonConstants.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; -import { IWETH9Like } from "../../interfaces/IWETH9Like.sol"; +import { IWrappedNative } from "../../interfaces/IWrappedNative.sol"; import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; import { ProtocolBase } from "./../bases/ProtocolBase.sol"; import { FundsLib } from "../libs/FundsLib.sol"; @@ -18,10 +18,25 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; contract PriceDiscoveryBase is ProtocolBase { using Address for address; - IWETH9Like public immutable weth; + IWrappedNative public immutable wNative; + uint256 private immutable EXCHANGE_ID_2_2_0; // solhint-disable-line - constructor(address _weth) { - weth = IWETH9Like(_weth); + /** + * @notice + * For offers with native exchange token, it is expected the the price discovery contracts will + * operate with wrapped native token. Set the address of the wrapped native token in the constructor. + * + * After v2.2.0, token ids are derived from offerId and exchangeId. + * EXCHANGE_ID_2_2_0 is the first exchange id to use for 2.2.0. + * Set EXCHANGE_ID_2_2_0 in the constructor. + * + * @param _wNative - the address of the wrapped native token + * @param _firstExchangeId2_2_0 - the first exchange id to use for 2.2.0 + */ + //solhint-disable-next-line + constructor(address _wNative, uint256 _firstExchangeId2_2_0) { + wNative = IWrappedNative(_wNative); + EXCHANGE_ID_2_2_0 = _firstExchangeId2_2_0; } /** @@ -33,7 +48,7 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _initialSellerId - the id of the original seller + * @param _offer - the pointer to offer struct, corresponding to the exchange * @return actualPrice - the actual price of the order */ function fulFilOrder( @@ -41,12 +56,12 @@ contract PriceDiscoveryBase is ProtocolBase { address _exchangeToken, PriceDiscovery calldata _priceDiscovery, address _buyer, - uint256 _initialSellerId + Offer storage _offer ) internal returns (uint256 actualPrice) { if (_priceDiscovery.side == Side.Ask) { - return fulfilAskOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _initialSellerId); + return fulfilAskOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _offer); } else { - return fulfilBidOrder(_exchangeId, _exchangeToken, _priceDiscovery, _initialSellerId); + return fulfilBidOrder(_exchangeId, _exchangeToken, _priceDiscovery, _offer); } } @@ -66,7 +81,7 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _initialSellerId - the id of the original seller + * @param _offer - the pointer to offer struct, corresponding to the exchange * @return actualPrice - the actual price of the order */ function fulfilAskOrder( @@ -74,7 +89,7 @@ contract PriceDiscoveryBase is ProtocolBase { address _exchangeToken, PriceDiscovery calldata _priceDiscovery, address _buyer, - uint256 _initialSellerId + Offer storage _offer ) internal returns (uint256 actualPrice) { // Transfer buyers funds to protocol FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); @@ -89,11 +104,11 @@ contract PriceDiscoveryBase is ProtocolBase { // Store the information about incoming voucher ProtocolLib.ProtocolStatus storage ps = protocolStatus(); - address cloneAddress = protocolLookups().cloneAddress[_initialSellerId]; + address cloneAddress = getCloneAddress(protocolLookups(), _offer.sellerId, _offer.collectionIndex); uint256 tokenId; { - uint256 offerId = protocolEntities().exchanges[_exchangeId].offerId; - tokenId = _exchangeId | (offerId << 128); + tokenId = _exchangeId; + if (tokenId >= EXCHANGE_ID_2_2_0) tokenId |= (_offer.id << 128); ps.incomingVoucherId = tokenId; ps.incomingVoucherCloneAddress = cloneAddress; } @@ -115,6 +130,7 @@ contract PriceDiscoveryBase is ProtocolBase { // Check the escrow amount uint256 protocolBalanceAfter = getBalance(_exchangeToken); + require(protocolBalanceBefore >= protocolBalanceAfter, NEGATIVE_PRICE_NOT_ALLOWED); actualPrice = protocolBalanceBefore - protocolBalanceAfter; uint256 overchargedAmount = _priceDiscovery.price - actualPrice; @@ -139,30 +155,33 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _exchangeId - the id of the exchange to commit to * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct - * @param _initialSellerId - the id of the original seller + * @param _offer - the pointer to offer struct, corresponding to the exchange * @return actualPrice - the actual price of the order */ function fulfilBidOrder( uint256 _exchangeId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - uint256 _initialSellerId + Offer storage _offer ) internal returns (uint256 actualPrice) { - IBosonVoucher bosonVoucher = IBosonVoucher(protocolLookups().cloneAddress[_initialSellerId]); + IBosonVoucher bosonVoucher = IBosonVoucher( + getCloneAddress(protocolLookups(), _offer.sellerId, _offer.collectionIndex) + ); // Transfer seller's voucher to protocol // Don't need to use safe transfer from, since that protocol can handle the voucher - uint256 offerId = protocolEntities().exchanges[_exchangeId].offerId; - uint256 tokenId = _exchangeId | (offerId << 128); + uint256 tokenId = _exchangeId; + if (tokenId >= EXCHANGE_ID_2_2_0) tokenId |= (_offer.id << 128); bosonVoucher.transferFrom(msgSender(), address(this), tokenId); - if (_exchangeToken == address(0)) _exchangeToken = address(weth); + if (_exchangeToken == address(0)) _exchangeToken = address(wNative); // Get protocol balance before the exchange uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // Track native balance just in case if seller send some native currency or price discovery contract does - uint256 protocolNativeBalanceBefore = getBalance(address(0)); + // Track native balance just in case if seller sends some native currency or price discovery contract does + // This is the balance that protocol had, before commit to offer was called + uint256 protocolNativeBalanceBefore = getBalance(address(0)) - msg.value; // Approve price discovery contract to transfer voucher. There is no need to reset approval afterwards, since protocol is not the voucher owner anymore bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, tokenId); diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index d252361f9..85753ee8b 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -19,7 +19,20 @@ import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDiscoveryBase { using Address for address; - constructor(address _weth) PriceDiscoveryBase(_weth) {} + /** + * @notice + * For offers with native exchange token, it is expected the the price discovery contracts will + * operate with wrapped native token. Set the address of the wrapped native token in the constructor. + * + * After v2.2.0, token ids are derived from offerId and exchangeId. + * EXCHANGE_ID_2_2_0 is the first exchange id to use for 2.2.0. + * Set EXCHANGE_ID_2_2_0 in the constructor. + * + * @param _wNative - the address of the wrapped native token + * @param _firstExchangeId2_2_0 - the first exchange id to use for 2.2.0 + */ + //solhint-disable-next-line + constructor(address _wNative, uint256 _firstExchangeId2_2_0) PriceDiscoveryBase(_wNative, _firstExchangeId2_2_0) {} /** * @notice Initializes facet. @@ -30,7 +43,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } /** - * @notice Commits to an existing exchange. Price discovery is oflaoaded to external contract. + * @notice Commits to an existing exchange. Price discovery is offloaded to external contract. * * Emits a BuyerCommitted event if successful. * Transfers voucher to the buyer address. @@ -95,8 +108,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } // First call price discovery and get actual price - // It might be lower tha submitted for buy orders and higher for sell orders - uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer.sellerId); + // It might be lower than submitted for buy orders and higher for sell orders + uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer); // Calculate the amount to be kept in escrow uint256 escrowAmount; @@ -111,10 +124,9 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis : (protocolFees().percentage * actualPrice) / 10000; // Calculate royalties - (, uint256 royaltyAmount) = IBosonVoucher(protocolLookups().cloneAddress[offer.sellerId]).royaltyInfo( - _exchangeId, - actualPrice - ); + (, uint256 royaltyAmount) = IBosonVoucher( + getCloneAddress(protocolLookups(), offer.sellerId, offer.collectionIndex) + ).royaltyInfo(_exchangeId, actualPrice); // Verify that fees and royalties are not higher than the price. require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); @@ -146,8 +158,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // If exchange is native currency, seller cannot directly approve protocol to transfer funds // They need to approve wrapper contract, so protocol can pull funds from wrapper // But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw) - FundsLib.transferFundsToProtocol(address(weth), seller, escrowAmount); - weth.withdraw(escrowAmount); + FundsLib.transferFundsToProtocol(address(wNative), seller, escrowAmount); + wNative.withdraw(escrowAmount); } else { FundsLib.transferFundsToProtocol(tokenAddress, seller, escrowAmount); } @@ -155,8 +167,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } else { // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference if (tokenAddress == address(0)) { - tokenAddress = address(weth); - if (escrowAmount > 0) weth.withdraw(escrowAmount); + tokenAddress = address(wNative); + if (escrowAmount > 0) wNative.withdraw(escrowAmount); } uint256 payout = actualPrice - escrowAmount; diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 2babab449..16077c373 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -346,7 +346,7 @@ library FundsLib { } /** - * @notice Forwared values to increaseAvailableFunds and emits notifies external listeners. + * @notice Forwards values to increaseAvailableFunds and emits notifies external listeners. * * Emits FundsReleased events * @@ -397,6 +397,13 @@ library FundsLib { } } + /** + * @notice Same as transferFundsToProtocol(address _tokenAddress, address _from, uint256 _amount), + * but _from is message sender + * + * @param _tokenAddress - address of the token to be transferred + * @param _amount - amount to be transferred + */ function transferFundsToProtocol(address _tokenAddress, uint256 _amount) internal { transferFundsToProtocol(_tokenAddress, EIP712Lib.msgSender(), _amount); } @@ -412,6 +419,7 @@ library FundsLib { * - Contract at token address does not support ERC20 function transfer * - Available funds is less than amount to be decreased * + * @param _entityId - id of entity for which funds should be decreased, or 0 for protocol * @param _tokenAddress - address of the token to be transferred * @param _to - address of the recipient * @param _amount - amount to be transferred @@ -432,6 +440,20 @@ library FundsLib { emit FundsWithdrawn(_entityId, _to, _tokenAddress, _amount, EIP712Lib.msgSender()); } + /** + * @notice Tries to transfer native currency or tokens from the protocol to the recipient. + * + * Emits ERC20 Transfer event in call stack if ERC20 token is withdrawn and transfer is successful. + * + * Reverts if: + * - Transfer of native currency is not successful (i.e. recipient is a contract which reverted) + * - Contract at token address does not support ERC20 function transfer + * - Available funds is less than amount to be decreased + * + * @param _tokenAddress - address of the token to be transferred + * @param _to - address of the recipient + * @param _amount - amount to be transferred + */ function transferFundsFromProtocol(address _tokenAddress, address payable _to, uint256 _amount) internal { // try to transfer the funds if (_tokenAddress == address(0)) { diff --git a/scripts/config/facet-deploy.js b/scripts/config/facet-deploy.js index 4ae78de6f..acf3df832 100644 --- a/scripts/config/facet-deploy.js +++ b/scripts/config/facet-deploy.js @@ -77,7 +77,10 @@ async function getFacets(config) { facetArgs["ConfigHandlerFacet"] = { init: ConfigHandlerFacetInitArgs }; facetArgs["ExchangeHandlerFacet"] = { init: [], constructorArgs: [protocolConfig.EXCHANGE_ID_2_2_0[network]] }; - facetArgs["SequentialCommitHandlerFacet"] = { init: [], constructorArgs: [protocolConfig.WETH[network]] }; + facetArgs["SequentialCommitHandlerFacet"] = { + init: [], + constructorArgs: [protocolConfig.WrappedNative[network], protocolConfig.EXCHANGE_ID_2_2_0[network]], + }; // metaTransactionsHandlerFacet initializer arguments. const MetaTransactionsHandlerFacetInitArgs = await getMetaTransactionsHandlerFacetInitArgs( diff --git a/scripts/config/protocol-parameters.js b/scripts/config/protocol-parameters.js index 763c236ae..c6591cbb1 100644 --- a/scripts/config/protocol-parameters.js +++ b/scripts/config/protocol-parameters.js @@ -78,7 +78,7 @@ module.exports = { localhost: 1, }, - WETH: { + WrappedNative: { mainnet: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy hardhat: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy localhost: "0x4102621Ac55e068e148Da09151ce92102c952aab", //dummy diff --git a/test/util/utils.js b/test/util/utils.js index 454c1f1a7..2ca93b96e 100644 --- a/test/util/utils.js +++ b/test/util/utils.js @@ -453,7 +453,7 @@ async function setupTestEnvironment(contracts, { bosonTokenAddress, forwarderAdd ]; const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); - facetsToDeploy["SequentialCommitHandlerFacet"].constructorArgs = [wethAddress || ZeroAddress]; + facetsToDeploy["SequentialCommitHandlerFacet"].constructorArgs[0] = wethAddress || ZeroAddress; // update only weth address // Cut the protocol handler facets into the Diamond await deployAndCutFacets(await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas); From 4685fbdccdcda585c90f94cb3b4baded233a7394 Mon Sep 17 00:00:00 2001 From: zajck Date: Thu, 9 Nov 2023 16:42:38 +0100 Subject: [PATCH 40/47] add events for encumbering/releasing funds --- contracts/protocol/bases/DisputeBase.sol | 14 ++++++--- .../facets/SequentialCommitHandlerFacet.sol | 30 ++++++++++++------- hardhat.config.js | 1 + 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/contracts/protocol/bases/DisputeBase.sol b/contracts/protocol/bases/DisputeBase.sol index b88613d90..5f24cd83f 100644 --- a/contracts/protocol/bases/DisputeBase.sol +++ b/contracts/protocol/bases/DisputeBase.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.21; import { IBosonDisputeEvents } from "../../interfaces/events/IBosonDisputeEvents.sol"; +import { IBosonFundsLibEvents } from "../../interfaces/events/IBosonFundsEvents.sol"; import { ProtocolBase } from "./../bases/ProtocolBase.sol"; import { ProtocolLib } from "./../libs/ProtocolLib.sol"; import { FundsLib } from "./../libs/FundsLib.sol"; @@ -12,7 +13,7 @@ import "../../domain/BosonConstants.sol"; * @title DisputeBase * @notice Provides methods for dispute that can be shared across facets. */ -contract DisputeBase is ProtocolBase, IBosonDisputeEvents { +contract DisputeBase is ProtocolBase, IBosonDisputeEvents, IBosonFundsLibEvents { /** * @notice Raises a dispute * @@ -84,7 +85,8 @@ contract DisputeBase is ProtocolBase, IBosonDisputeEvents { (Exchange storage exchange, ) = getValidExchange(_exchangeId, ExchangeState.Disputed); // Make sure the caller is buyer associated with the exchange - checkBuyer(exchange.buyerId); + uint256 buyerId = exchange.buyerId; + checkBuyer(buyerId); // Fetch the dispute and dispute dates (, Dispute storage dispute, DisputeDates storage disputeDates) = fetchDispute(_exchangeId); @@ -105,7 +107,9 @@ contract DisputeBase is ProtocolBase, IBosonDisputeEvents { (, Offer storage offer) = fetchOffer(exchange.offerId); // make sure buyer sent enough funds to proceed - FundsLib.validateIncomingPayment(offer.exchangeToken, disputeResolutionTerms.buyerEscalationDeposit); + address exchangeToken = offer.exchangeToken; + uint256 buyerEscalationDeposit = disputeResolutionTerms.buyerEscalationDeposit; + FundsLib.validateIncomingPayment(exchangeToken, buyerEscalationDeposit); // fetch the escalation period from the storage uint256 escalationResponsePeriod = disputeResolutionTerms.escalationResponsePeriod; @@ -118,6 +122,8 @@ contract DisputeBase is ProtocolBase, IBosonDisputeEvents { dispute.state = DisputeState.Escalated; // Notify watchers of state change - emit DisputeEscalated(_exchangeId, disputeResolutionTerms.disputeResolverId, msgSender()); + address sender = msgSender(); + emit FundsEncumbered(buyerId, exchangeToken, buyerEscalationDeposit, sender); + emit DisputeEscalated(_exchangeId, disputeResolutionTerms.disputeResolverId, sender); } } diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index 85753ee8b..36eea6295 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -90,10 +90,11 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); // Fetch offer - (, Offer storage offer) = fetchOffer(exchange.offerId); + uint256 offerId = exchange.offerId; + (, Offer storage offer) = fetchOffer(offerId); // Get token address - address tokenAddress = offer.exchangeToken; + address exchangeToken = offer.exchangeToken; // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred address seller; @@ -103,23 +104,25 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis seller = currentBuyer.wallet; } + address sender = msgSender(); if (_priceDiscovery.side == Side.Bid) { - require(seller == msgSender(), NOT_VOUCHER_HOLDER); + require(seller == sender, NOT_VOUCHER_HOLDER); } // First call price discovery and get actual price // It might be lower than submitted for buy orders and higher for sell orders - uint256 actualPrice = fulFilOrder(_exchangeId, tokenAddress, _priceDiscovery, _buyer, offer); + uint256 actualPrice = fulFilOrder(_exchangeId, exchangeToken, _priceDiscovery, _buyer, offer); // Calculate the amount to be kept in escrow uint256 escrowAmount; + // address sender ; { // Get sequential commits for this exchange SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; { // Calculate fees - uint256 protocolFeeAmount = tokenAddress == protocolAddresses().token + uint256 protocolFeeAmount = exchangeToken == protocolAddresses().token ? protocolFees().flatBoson : (protocolFees().percentage * actualPrice) / 10000; @@ -154,30 +157,35 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis if (escrowAmount > 0) { // Price discovery should send funds to the seller // Nothing in escrow, need to pull everything from seller - if (tokenAddress == address(0)) { + if (exchangeToken == address(0)) { // If exchange is native currency, seller cannot directly approve protocol to transfer funds // They need to approve wrapper contract, so protocol can pull funds from wrapper // But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw) FundsLib.transferFundsToProtocol(address(wNative), seller, escrowAmount); wNative.withdraw(escrowAmount); } else { - FundsLib.transferFundsToProtocol(tokenAddress, seller, escrowAmount); + FundsLib.transferFundsToProtocol(exchangeToken, seller, escrowAmount); } } } else { // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference - if (tokenAddress == address(0)) { - tokenAddress = address(wNative); + if (exchangeToken == address(0)) { + exchangeToken = address(wNative); if (escrowAmount > 0) wNative.withdraw(escrowAmount); } uint256 payout = actualPrice - escrowAmount; - if (payout > 0) FundsLib.transferFundsFromProtocol(tokenAddress, payable(seller), payout); + if (payout > 0) { + emit FundsReleased(_exchangeId, buyerId, exchangeToken, payout, sender); // buyerId here is the old buyer id (= reseller) + FundsLib.transferFundsFromProtocol(buyerId, exchangeToken, payable(seller), payout); // also emits FundsWithdrawn + } } } // Since exchange and voucher are passed by reference, they are updated - emit BuyerCommitted(exchange.offerId, exchange.buyerId, _exchangeId, exchange, voucher, msgSender()); + buyerId = exchange.buyerId; + emit FundsEncumbered(buyerId, exchangeToken, actualPrice, sender); + emit BuyerCommitted(offerId, buyerId, _exchangeId, exchange, voucher, sender); // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred } diff --git a/hardhat.config.js b/hardhat.config.js index 17bfd721c..0ce81d9fc 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -181,6 +181,7 @@ module.exports = { { version: "0.8.21", settings: { + viaIR: true, optimizer: { enabled: true, runs: 200, From 69e249d9494728ac19cb95157f2f69b68850f9ab Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 10 Nov 2023 10:42:40 +0100 Subject: [PATCH 41/47] add tests for events for encumbering/releasing funds --- .../facets/SequentialCommitHandlerFacet.sol | 27 +++++---- test/protocol/DisputeHandlerTest.js | 23 +++++-- test/protocol/FundsHandlerTest.js | 4 +- test/protocol/OrchestrationHandlerTest.js | 28 ++++++--- test/protocol/SequentialCommitHandlerTest.js | 60 ++++++++++++------- 5 files changed, 93 insertions(+), 49 deletions(-) diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index 36eea6295..6bd372389 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -98,9 +98,9 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred address seller; - uint256 buyerId = exchange.buyerId; + uint256 resellerId = exchange.buyerId; { - (, Buyer storage currentBuyer) = fetchBuyer(buyerId); + (, Buyer storage currentBuyer) = fetchBuyer(resellerId); seller = currentBuyer.wallet; } @@ -115,7 +115,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // Calculate the amount to be kept in escrow uint256 escrowAmount; - // address sender ; + uint256 payout; { // Get sequential commits for this exchange SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; @@ -144,7 +144,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // Update sequential commit sequentialCommits.push( SequentialCommit({ - resellerId: buyerId, + resellerId: resellerId, price: actualPrice, protocolFeeAmount: protocolFeeAmount, royaltyAmount: royaltyAmount @@ -153,6 +153,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } // Make sure enough get escrowed + payout = actualPrice - escrowAmount; + if (_priceDiscovery.side == Side.Ask) { if (escrowAmount > 0) { // Price discovery should send funds to the seller @@ -169,22 +171,23 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } } else { // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference - if (exchangeToken == address(0)) { - exchangeToken = address(wNative); - if (escrowAmount > 0) wNative.withdraw(escrowAmount); + if (actualPrice > 0 && exchangeToken == address(0)) { + wNative.withdraw(actualPrice); } - uint256 payout = actualPrice - escrowAmount; if (payout > 0) { - emit FundsReleased(_exchangeId, buyerId, exchangeToken, payout, sender); // buyerId here is the old buyer id (= reseller) - FundsLib.transferFundsFromProtocol(buyerId, exchangeToken, payable(seller), payout); // also emits FundsWithdrawn + FundsLib.transferFundsFromProtocol(exchangeToken, payable(seller), payout); // also emits FundsWithdrawn } } } // Since exchange and voucher are passed by reference, they are updated - buyerId = exchange.buyerId; - emit FundsEncumbered(buyerId, exchangeToken, actualPrice, sender); + uint256 buyerId = exchange.buyerId; + if (actualPrice > 0) emit FundsEncumbered(buyerId, exchangeToken, actualPrice, sender); + if (payout > 0) { + emit FundsReleased(_exchangeId, resellerId, exchangeToken, payout, sender); + emit FundsWithdrawn(resellerId, seller, exchangeToken, payout, sender); + } emit BuyerCommitted(offerId, buyerId, _exchangeId, exchange, voucher, sender); // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred } diff --git a/test/protocol/DisputeHandlerTest.js b/test/protocol/DisputeHandlerTest.js index f2580707e..25162fdab 100644 --- a/test/protocol/DisputeHandlerTest.js +++ b/test/protocol/DisputeHandlerTest.js @@ -1260,11 +1260,17 @@ describe("IBosonDisputeHandler", function () { timeout = BigInt(disputedDate) + resolutionPeriod.toString(); }); - it("should emit a DisputeEscalated event", async function () { + it("should emit FundsEncumbered and DisputeEscalated events", async function () { // Escalate the dispute, testing for the event - await expect( - disputeHandler.connect(buyer).escalateDispute(exchangeId, { value: buyerEscalationDepositNative }) - ) + const tx = await disputeHandler + .connect(buyer) + .escalateDispute(exchangeId, { value: buyerEscalationDepositNative }); + + await expect(tx) + .to.emit(disputeHandler, "FundsEncumbered") + .withArgs(buyerId, ZeroAddress, buyerEscalationDepositNative, await buyer.getAddress()); + + await expect(tx) .to.emit(disputeHandler, "DisputeEscalated") .withArgs(exchangeId, disputeResolverId, await buyer.getAddress()); }); @@ -1320,8 +1326,13 @@ describe("IBosonDisputeHandler", function () { // Protocol balance before const escrowBalanceBefore = await mockToken.balanceOf(await disputeHandler.getAddress()); - // Escalate the dispute, testing for the event - await expect(disputeHandler.connect(buyer).escalateDispute(exchangeId)) + // Escalate the dispute, testing for the events + const tx = await disputeHandler.connect(buyer).escalateDispute(exchangeId); + await expect(tx) + .to.emit(disputeHandler, "FundsEncumbered") + .withArgs(buyerId, await mockToken.getAddress(), buyerEscalationDepositToken, await buyer.getAddress()); + + await expect(tx) .to.emit(disputeHandler, "DisputeEscalated") .withArgs(exchangeId, disputeResolverId, await buyer.getAddress()); diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index cd0dab857..4284f7d78 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -4600,7 +4600,7 @@ describe("IBosonFundsHandler", function () { voucherContract: expectedCloneAddress, tokenId: deriveTokenId(offer.id, exchangeId), exchangeToken: offer.exchangeToken, - price: (BigInt(offer.price) * BigInt(trade.price)) / 100n, + price: BigInt(trade.price), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ @@ -6218,7 +6218,7 @@ describe("IBosonFundsHandler", function () { voucherContract: expectedCloneAddress, tokenId: deriveTokenId(offer.id, exchangeId), exchangeToken: offer.exchangeToken, - price: (BigInt(offer.price) * BigInt(trade.price)) / 100n, + price: BigInt(trade.price), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ diff --git a/test/protocol/OrchestrationHandlerTest.js b/test/protocol/OrchestrationHandlerTest.js index e8d776f70..0d2be6a07 100644 --- a/test/protocol/OrchestrationHandlerTest.js +++ b/test/protocol/OrchestrationHandlerTest.js @@ -391,13 +391,17 @@ describe("IBosonOrchestrationHandler", function () { .withArgs(exchangeId, buyerId, seller.id, await buyer.getAddress()); }); - it("should emit a DisputeEscalated event", async function () { - // Raise and Escalate a dispute, testing for the event - await expect( - orchestrationHandler - .connect(buyer) - .raiseAndEscalateDispute(exchangeId, { value: buyerEscalationDepositNative }) - ) + it("should emit FundsEncumbered and DisputeEscalated event", async function () { + // Raise and Escalate a dispute, testing for the events + const tx = await orchestrationHandler + .connect(buyer) + .raiseAndEscalateDispute(exchangeId, { value: buyerEscalationDepositNative }); + + await expect(tx) + .to.emit(disputeHandler, "FundsEncumbered") + .withArgs(buyerId, ZeroAddress, buyerEscalationDepositNative, await buyer.getAddress()); + + await expect(tx) .to.emit(disputeHandler, "DisputeEscalated") .withArgs(exchangeId, disputeResolverId, await buyer.getAddress()); }); @@ -456,8 +460,14 @@ describe("IBosonOrchestrationHandler", function () { // Protocol balance before const escrowBalanceBefore = await mockToken.balanceOf(protocolDiamondAddress); - // Escalate the dispute, testing for the event - await expect(orchestrationHandler.connect(buyer).raiseAndEscalateDispute(exchangeId)) + // Escalate the dispute, testing for the events + const tx = await orchestrationHandler.connect(buyer).raiseAndEscalateDispute(exchangeId); + + await expect(tx) + .to.emit(disputeHandler, "FundsEncumbered") + .withArgs(buyerId, await mockToken.getAddress(), buyerEscalationDepositToken, await buyer.getAddress()); + + await expect(tx) .to.emit(disputeHandler, "DisputeEscalated") .withArgs(exchangeId, disputeResolverId, await buyer.getAddress()); diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index 4352e76f0..dc155b06b 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -239,14 +239,7 @@ describe("IBosonSequentialCommitHandler", function () { context("👉 sequentialCommitToOffer()", async function () { let priceDiscovery, price2; let newBuyer; - let reseller; // for clarity in tests - - // before(async function () { - // // Deploy PriceDiscovery contract - // const PriceDiscoveryFactory = await getContractFactory("PriceDiscovery"); - // priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); - // await priceDiscoveryContract.waitForDeployment(); - // }); + let reseller, resellerId; // for clarity in tests beforeEach(async function () { // Commit to offer with first buyer @@ -263,6 +256,7 @@ describe("IBosonSequentialCommitHandler", function () { voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); reseller = buyer; + resellerId = buyerId; }); context("Ask order", async function () { @@ -306,13 +300,26 @@ describe("IBosonSequentialCommitHandler", function () { exchange.buyerId = newBuyer.id; }); - it("should emit a BuyerCommitted event", async function () { + it("should emit FundsEncumbered, FundsReleased, FundsWithdrawn and BuyerCommitted events", async function () { // Sequential commit to offer, retrieving the event - await expect( - sequentialCommitHandler - .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ) + const tx = sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsEncumbered") + .withArgs(newBuyer.id, ZeroAddress, price2, buyer2.address); + + const immediatePayout = BigInt(price); + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, ZeroAddress, immediatePayout, buyer2.address); + + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsWithdrawn") + .withArgs(resellerId, reseller.address, ZeroAddress, immediatePayout, buyer2.address); + + await expect(tx) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); }); @@ -845,13 +852,26 @@ describe("IBosonSequentialCommitHandler", function () { exchange.buyerId = newBuyer.id; }); - it("should emit a BuyerCommitted event", async function () { + it("should emit FundsEncumbered, FundsReleased, FundsWithdrawn and BuyerCommitted events", async function () { // Sequential commit to offer, retrieving the event - await expect( - sequentialCommitHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) - ) + const tx = sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsEncumbered") + .withArgs(newBuyer.id, ZeroAddress, price2, reseller.address); + + const immediatePayout = BigInt(price); + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsReleased") + .withArgs(exchangeId, buyerId, ZeroAddress, immediatePayout, reseller.address); + + await expect(tx) + .to.emit(sequentialCommitHandler, "FundsWithdrawn") + .withArgs(resellerId, reseller.address, ZeroAddress, immediatePayout, reseller.address); + + await expect(tx) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); }); From 03d65bf1d35a48eebb63f9200b938f3b650f848e Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 10 Nov 2023 11:21:13 +0100 Subject: [PATCH 42/47] use safeerc20 for approval --- contracts/protocol/bases/PriceDiscoveryBase.sol | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index 2713c636a..e6aa7c569 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -3,12 +3,13 @@ pragma solidity 0.8.21; import "../../domain/BosonConstants.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; -import { IERC20 } from "../../interfaces/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IWrappedNative } from "../../interfaces/IWrappedNative.sol"; import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; import { ProtocolBase } from "./../bases/ProtocolBase.sol"; import { FundsLib } from "../libs/FundsLib.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @title PriceDiscoveryBase @@ -17,6 +18,7 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; */ contract PriceDiscoveryBase is ProtocolBase { using Address for address; + using SafeERC20 for IERC20; IWrappedNative public immutable wNative; uint256 private immutable EXCHANGE_ID_2_2_0; // solhint-disable-line @@ -99,7 +101,7 @@ contract PriceDiscoveryBase is ProtocolBase { // If token is ERC20, approve price discovery contract to transfer funds if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); + IERC20(_exchangeToken).forceApprove(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); } // Store the information about incoming voucher @@ -121,7 +123,7 @@ contract PriceDiscoveryBase is ProtocolBase { // If token is ERC20, reset approval if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).approve(address(_priceDiscovery.priceDiscoveryContract), 0); + IERC20(_exchangeToken).forceApprove(address(_priceDiscovery.priceDiscoveryContract), 0); } // Clear the storage From 0ca1b01ecec3d8b32aa28a839abfed46367d65a8 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 10 Nov 2023 11:32:48 +0100 Subject: [PATCH 43/47] extend test timeout --- test/protocol/ProtocolInitializationHandlerTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index 43609c528..ba0ebee64 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -660,6 +660,7 @@ describe("ProtocolInitializationHandler", async function () { let protocolDiamondAddress; beforeEach(async function () { + this.timeout(1000000); if (snapshotId) { await revertToSnapshot(snapshotId); snapshotId = await getSnapshot(); From db883ccd648b47780786f4469667bba9c84c28eb Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 10 Nov 2023 12:44:25 +0100 Subject: [PATCH 44/47] Bump code coverage --- contracts/mock/PriceDiscovery.sol | 3 ++ test/protocol/SequentialCommitHandlerTest.js | 38 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index d41cb5b07..7e4c261c5 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -95,6 +95,9 @@ contract PriceDiscovery { } } } + + // return half of the sent value back to the caller + payable(msg.sender).transfer(msg.value / 2); } } diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index dc155b06b..d57fed78e 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -1329,6 +1329,44 @@ describe("IBosonSequentialCommitHandler", function () { balancesBefore.originalSeller + expectedOriginalSellerChange ); }); + + it(`protocol fee: ${fee.protocol / 100}%; royalties: ${ + fee.royalties / 100 + }% - non zero msg.value`, async function () { + await configHandler.setProtocolFeePercentage(fee.protocol); + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); + + const balancesBefore = await getBalances(); + + const sellerMsgValue = parseUnits("0.001", "ether"); + + // Sequential commit to offer + await sequentialCommitHandler + .connect(reseller) + .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + gasPrice: 0, + value: sellerMsgValue, + }); + + const balancesAfter = await getBalances(); + + // Expected changes + const expectedBuyerChange = price2; + const reducedSecondaryPrice = (price2 * BigInt(10000 - fee.protocol - fee.royalties)) / 10000n; + const expectedSellerChange = reducedSecondaryPrice <= price ? reducedSecondaryPrice : price; + const expectedProtocolChange = price2 - expectedSellerChange; + const expectedOriginalSellerChange = 0n; + + // Contract's balance should increase for minimal escrow amount + expect(balancesAfter.protocol).to.equal(balancesBefore.protocol + expectedProtocolChange); + expect(balancesAfter.seller).to.equal( + balancesBefore.seller + expectedSellerChange - sellerMsgValue / 2n + ); // PriceDiscovery returns back half of the sent native value + expect(balancesAfter.newBuyer).to.equal(balancesBefore.newBuyer - expectedBuyerChange); + expect(balancesAfter.originalSeller).to.equal( + balancesBefore.originalSeller + expectedOriginalSellerChange + ); + }); }); }); }); From f6afca00d8a2d06fd5f6bb71601cc885528d7f75 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 10 Nov 2023 15:26:20 +0100 Subject: [PATCH 45/47] refactor + disable viaIR compilation --- .../facets/SequentialCommitHandlerFacet.sol | 63 ++++++++++--------- hardhat.config.js | 4 +- .../ProtocolInitializationHandlerTest.js | 1 - 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index 6bd372389..d78299c40 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -89,18 +89,15 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // Make sure the voucher is still valid require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); - // Fetch offer - uint256 offerId = exchange.offerId; - (, Offer storage offer) = fetchOffer(offerId); - - // Get token address - address exchangeToken = offer.exchangeToken; + // Create a memory struct for sequential commit and populate it as we go + // This is done to avoid stack too deep error, while still keeping the number of SLOADs to a minimum + SequentialCommit memory sequentialCommit; // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred address seller; - uint256 resellerId = exchange.buyerId; + sequentialCommit.resellerId = exchange.buyerId; { - (, Buyer storage currentBuyer) = fetchBuyer(resellerId); + (, Buyer storage currentBuyer) = fetchBuyer(sequentialCommit.resellerId); seller = currentBuyer.wallet; } @@ -109,9 +106,16 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis require(seller == sender, NOT_VOUCHER_HOLDER); } + // Fetch offer + uint256 offerId = exchange.offerId; + (, Offer storage offer) = fetchOffer(offerId); + + // Get token address + address exchangeToken = offer.exchangeToken; + // First call price discovery and get actual price // It might be lower than submitted for buy orders and higher for sell orders - uint256 actualPrice = fulFilOrder(_exchangeId, exchangeToken, _priceDiscovery, _buyer, offer); + sequentialCommit.price = fulFilOrder(_exchangeId, exchangeToken, _priceDiscovery, _buyer, offer); // Calculate the amount to be kept in escrow uint256 escrowAmount; @@ -122,38 +126,39 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis { // Calculate fees - uint256 protocolFeeAmount = exchangeToken == protocolAddresses().token + sequentialCommit.protocolFeeAmount = exchangeToken == protocolAddresses().token ? protocolFees().flatBoson - : (protocolFees().percentage * actualPrice) / 10000; + : (protocolFees().percentage * sequentialCommit.price) / 10000; // Calculate royalties - (, uint256 royaltyAmount) = IBosonVoucher( + (, sequentialCommit.royaltyAmount) = IBosonVoucher( getCloneAddress(protocolLookups(), offer.sellerId, offer.collectionIndex) - ).royaltyInfo(_exchangeId, actualPrice); + ).royaltyInfo(_exchangeId, sequentialCommit.price); // Verify that fees and royalties are not higher than the price. - require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); + require( + (sequentialCommit.protocolFeeAmount + sequentialCommit.royaltyAmount) <= sequentialCommit.price, + FEE_AMOUNT_TOO_HIGH + ); // Get price paid by current buyer uint256 len = sequentialCommits.length; uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; // Calculate the minimal amount to be kept in the escrow - escrowAmount = Math.max(actualPrice, protocolFeeAmount + royaltyAmount + currentPrice) - currentPrice; + escrowAmount = + Math.max( + sequentialCommit.price, + sequentialCommit.protocolFeeAmount + sequentialCommit.royaltyAmount + currentPrice + ) - + currentPrice; // Update sequential commit - sequentialCommits.push( - SequentialCommit({ - resellerId: resellerId, - price: actualPrice, - protocolFeeAmount: protocolFeeAmount, - royaltyAmount: royaltyAmount - }) - ); + sequentialCommits.push(sequentialCommit); } // Make sure enough get escrowed - payout = actualPrice - escrowAmount; + payout = sequentialCommit.price - escrowAmount; if (_priceDiscovery.side == Side.Ask) { if (escrowAmount > 0) { @@ -171,8 +176,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } } else { // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference - if (actualPrice > 0 && exchangeToken == address(0)) { - wNative.withdraw(actualPrice); + if (sequentialCommit.price > 0 && exchangeToken == address(0)) { + wNative.withdraw(sequentialCommit.price); } if (payout > 0) { @@ -183,10 +188,10 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis // Since exchange and voucher are passed by reference, they are updated uint256 buyerId = exchange.buyerId; - if (actualPrice > 0) emit FundsEncumbered(buyerId, exchangeToken, actualPrice, sender); + if (sequentialCommit.price > 0) emit FundsEncumbered(buyerId, exchangeToken, sequentialCommit.price, sender); if (payout > 0) { - emit FundsReleased(_exchangeId, resellerId, exchangeToken, payout, sender); - emit FundsWithdrawn(resellerId, seller, exchangeToken, payout, sender); + emit FundsReleased(_exchangeId, sequentialCommit.resellerId, exchangeToken, payout, sender); + emit FundsWithdrawn(sequentialCommit.resellerId, seller, exchangeToken, payout, sender); } emit BuyerCommitted(offerId, buyerId, _exchangeId, exchange, voucher, sender); // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred diff --git a/hardhat.config.js b/hardhat.config.js index 84624b734..f1eacf161 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -201,7 +201,7 @@ module.exports = { { version: "0.8.21", settings: { - viaIR: true, + viaIR: false, optimizer: { enabled: true, runs: 200, @@ -209,7 +209,7 @@ module.exports = { yul: true, }, }, - evmVersion: "london", // for ethereum mainnet, use shanghai + evmVersion: "shanghai", // for ethereum mainnet, use shanghai }, }, { diff --git a/test/protocol/ProtocolInitializationHandlerTest.js b/test/protocol/ProtocolInitializationHandlerTest.js index ba0ebee64..43609c528 100644 --- a/test/protocol/ProtocolInitializationHandlerTest.js +++ b/test/protocol/ProtocolInitializationHandlerTest.js @@ -660,7 +660,6 @@ describe("ProtocolInitializationHandler", async function () { let protocolDiamondAddress; beforeEach(async function () { - this.timeout(1000000); if (snapshotId) { await revertToSnapshot(snapshotId); snapshotId = await getSnapshot(); From e0d4660b44fa6abd2db848931ce7b69e6f887201 Mon Sep 17 00:00:00 2001 From: Ana Julia Bittencourt Date: Mon, 20 Nov 2023 13:55:26 -0300 Subject: [PATCH 46/47] Price discovery (#578) * AMM integration tests (WIP) * Make hardhat task work with submodules which uses foundry * Update hardhat config * Fix diamond deploy * Renaming folders * Continue AMM POC * Add price type to offer struct and continue sudoswap poc * Fix interface ids * Moving lssvm submodule * Make boson voucher compatible with price discovery offers * Continue POC work * Finalize AMM POC * Finalize sudoswap POC * Seaport: Create offer with criteria (WIP) * Seaport: advance order with criteria * Move test files * Fix sudoswap tests * Fix seaport tests * Make commitToOffer and commitToPriceDiscoveryOffer a single method * Price discovery POC: auction * Implement new voucher transfer and protocol communication design (WIP) * Removing logs * Fixing compile * Finish new voucher transfer and protocol communication design * SneakAuction (WIP) * Supports bid commits that requires pre-holding the NFT * Token id is mandatory on bid commits * Continue work on PD * Fix submodules hardhat compilation * Finish refactor * Remove SneakyAuction test and add Zora test * Offer can't be price discovery and be transfer externally at the sime time * Add new validations to fulfilBidOrder * Decoupling bid function * Add natspec comments * Fix set committed * Split commitToOffer and commitToPreMintedOffer * Review changes * Apply more review changes * Owner must be priceDiscoveryContract when caller is not voucher owner * Add some others verifications * Fix Zora integration test without wrapper * Fix stack too deep issue by enabling Yul via iR * Split commitToPriceDiscovery in a new PriceDiscoveryFacet * Add weth to protocol protocol addresses storage * Fix interface ids * Creating SudoswapWrraper * Update SudoswapWrapper contract to allow wrapping multiple tokens at once and modify integration test accordingly * Update SudoswapWrapper contract to allow wrapping by sender * Continue SudoswapWrapper * Finalise sudoswap wrapper poc * Adding missing natspec comments * Upgrade hardhat config and minors changes * Fix pre-minted voucher transfer to check for offer price type and transaction origin * update integration tests * update inegration test - sudoswap * Extend priceDiscovery struct * deprecate commitToPremintedOffer * Fix fundsHandler tests * Fix metaTransactionsHandler tests * Fix Offer domain script * Fix sequentialCommitHandler tests * review fixes 1st iter * Correct resellerId in initial exchangeCosts for price discovery * review fixes 2nd iter * More minore fixes in tests/contracts * PriceDiscoveryHandler unit tests * onPremintedVoucherTransferred test + other minor fixes * Zora wrapper (#598) * initial wrapper * Zora auction PoC * code cleanup, add comments * additional auction tests * Review fixes * Remove ownable * review fixes * wrapper tests * custom mock auction * handling wrapper another type of "Side" * Improve wrapper handling --------- Co-authored-by: zajck Co-authored-by: Klemen <64400885+zajck@users.noreply.github.com> --- .gitmodules | 3 + contracts/domain/BosonConstants.sol | 13 + contracts/domain/BosonTypes.sol | 14 +- .../example/SnapshotGate/SnapshotGate.sol | 2 +- .../example/Sudoswap/SudoswapWrapper.sol | 257 ++++ contracts/example/ZoraWrapper/ZoraWrapper.sol | 260 ++++ .../{SnapshotGate => }/support/ERC721.sol | 4 +- .../support/IERC721Metadata.sol | 2 +- contracts/interfaces/IWrappedNative.sol | 2 + .../handlers/IBosonExchangeHandler.sol | 57 +- .../handlers/IBosonOfferHandler.sol | 2 +- .../handlers/IBosonOrchestrationHandler.sol | 2 +- .../handlers/IBosonPriceDiscoveryHandler.sol | 46 + .../IBosonSequentialCommitHandler.sol | 5 +- contracts/mock/AuctionHouse.sol | 371 +++++ contracts/mock/IAuctionHouse.sol | 119 ++ contracts/mock/MockAuction.sol | 207 +++ contracts/mock/PriceDiscovery.sol | 24 + contracts/mock/TestProtocolFunctions.sol | 1 + contracts/protocol/bases/BeaconClientBase.sol | 24 +- contracts/protocol/bases/BuyerBase.sol | 30 + contracts/protocol/bases/OfferBase.sol | 5 +- .../protocol/bases/PriceDiscoveryBase.sol | 274 ++-- contracts/protocol/bases/ProtocolBase.sol | 15 + .../protocol/clients/voucher/BosonVoucher.sol | 106 +- .../protocol/facets/ExchangeHandlerFacet.sol | 304 ++-- .../facets/PriceDiscoveryHandlerFacet.sol | 175 +++ .../facets/SequentialCommitHandlerFacet.sol | 100 +- contracts/protocol/libs/FundsLib.sol | 36 +- contracts/protocol/libs/ProtocolLib.sol | 2 +- hardhat-fork.config.js | 58 +- hardhat.config.js | 6 + package-lock.json | 892 ++++++++++++ package.json | 4 + remappings.txt | 5 + scripts/config/facet-deploy.js | 5 + scripts/config/revert-reasons.js | 6 + scripts/config/supported-interfaces.js | 1 + scripts/domain/Offer.js | 35 +- scripts/domain/PriceDiscovery.js | 51 +- scripts/domain/PriceType.js | 12 + scripts/domain/Side.js | 3 +- scripts/manage-roles.js | 2 +- scripts/util/constants.js | 9 +- submodules/lssvm | 1 + test/domain/OfferTest.js | 56 +- test/domain/PriceDiscoveryTest.js | 83 +- test/integration/price-discovery/auction.js | 294 ++++ test/integration/price-discovery/seaport.js | 194 +++ test/integration/price-discovery/sudoswap.js | 228 +++ test/integration/seaport/ItemTypeEnum.js | 23 + test/integration/seaport/SideEnum.js | 12 + test/integration/seaport/fixtures.js | 85 +- .../seaport/seaport-integration.js | 28 +- test/integration/seaport/utils.js | 148 +- test/protocol/ExchangeHandlerTest.js | 273 +++- test/protocol/FundsHandlerTest.js | 22 +- test/protocol/MetaTransactionsHandlerTest.js | 2 +- test/protocol/OfferHandlerTest.js | 2 +- test/protocol/PriceDiscoveryHandlerFacet.js | 1239 +++++++++++++++++ test/protocol/SequentialCommitHandlerTest.js | 250 ++-- test/protocol/clients/BosonVoucherTest.js | 12 +- test/util/constants.js | 7 +- test/util/mock.js | 16 +- test/util/utils.js | 26 +- 65 files changed, 5911 insertions(+), 641 deletions(-) create mode 100644 contracts/example/Sudoswap/SudoswapWrapper.sol create mode 100644 contracts/example/ZoraWrapper/ZoraWrapper.sol rename contracts/example/{SnapshotGate => }/support/ERC721.sol (99%) rename contracts/example/{SnapshotGate => }/support/IERC721Metadata.sol (94%) create mode 100644 contracts/interfaces/handlers/IBosonPriceDiscoveryHandler.sol create mode 100644 contracts/mock/AuctionHouse.sol create mode 100644 contracts/mock/IAuctionHouse.sol create mode 100644 contracts/mock/MockAuction.sol create mode 100644 contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol create mode 100644 scripts/domain/PriceType.js create mode 160000 submodules/lssvm create mode 100644 test/integration/price-discovery/auction.js create mode 100644 test/integration/price-discovery/seaport.js create mode 100644 test/integration/price-discovery/sudoswap.js create mode 100644 test/integration/seaport/ItemTypeEnum.js create mode 100644 test/integration/seaport/SideEnum.js create mode 100644 test/protocol/PriceDiscoveryHandlerFacet.js diff --git a/.gitmodules b/.gitmodules index 4497ba287..7188e920a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "submodules/seaport"] path = submodules/seaport url = git@github.com:ProjectOpenSea/seaport.git +[submodule "submodules/lssvm"] + path = submodules/lssvm + url = https://github.com/sudoswap/lssvm diff --git a/contracts/domain/BosonConstants.sol b/contracts/domain/BosonConstants.sol index d567979b8..fcd78eea7 100644 --- a/contracts/domain/BosonConstants.sol +++ b/contracts/domain/BosonConstants.sol @@ -257,3 +257,16 @@ string constant RETRACT_DISPUTE = "retractDispute(uint256)"; string constant RAISE_DISPUTE = "raiseDispute(uint256)"; string constant ESCALATE_DISPUTE = "escalateDispute(uint256)"; string constant RESOLVE_DISPUTE = "resolveDispute(uint256,uint256,bytes32,bytes32,uint8)"; + +// Price discovery related +string constant PRICE_TOO_HIGH = "Price discovery returned a price that is too high"; +string constant PRICE_TOO_LOW = "Price discovery returned a price that is too low"; +string constant TOKEN_ID_MANDATORY = "Token id is mandatory for bid orders"; +string constant TOKEN_ID_MISMATCH = "Token id mismatch"; +string constant INVALID_PRICE_TYPE = "Invalid price type"; +string constant INVALID_PRICE_DISCOVERY = "Invalid price discovery argument"; +string constant INCOMING_VOUCHER_ALREADY_SET = "Incoming voucher already set"; +string constant NEW_OWNER_AND_BUYER_MUST_MATCH = "New owner and buyer must match"; +string constant PRICE_DISCOVERY_CONTRACTS_NOT_SET = "PriceDiscoveryContract and Conduit must be set"; +string constant TOKEN_ID_NOT_SET = "Token id not set"; +string constant VOUCHER_TRANSFER_NOT_ALLOWED = "Voucher transfer not allowed"; diff --git a/contracts/domain/BosonTypes.sol b/contracts/domain/BosonTypes.sol index 6502e4d38..50f40befd 100644 --- a/contracts/domain/BosonTypes.sol +++ b/contracts/domain/BosonTypes.sol @@ -88,6 +88,11 @@ contract BosonTypes { Clerk // Deprecated. } + enum PriceType { + Static, // Default should always be at index 0. Never change this value. + Discovery + } + struct AuthToken { uint256 tokenId; AuthTokenType tokenType; @@ -152,6 +157,7 @@ contract BosonTypes { string metadataHash; bool voided; uint256 collectionIndex; + PriceType priceType; } struct OfferDates { @@ -192,7 +198,7 @@ contract BosonTypes { ExchangeState state; } - struct SequentialCommit { + struct ExchangeCosts { uint256 resellerId; uint256 price; uint256 protocolFeeAmount; @@ -310,13 +316,15 @@ contract BosonTypes { struct PriceDiscovery { uint256 price; + Side side; address priceDiscoveryContract; + address conduit; bytes priceDiscoveryData; - Side side; } enum Side { Ask, - Bid + Bid, + Wrapper // Side is not relevant from the protocol perspective } } diff --git a/contracts/example/SnapshotGate/SnapshotGate.sol b/contracts/example/SnapshotGate/SnapshotGate.sol index e302246a3..bae18982a 100644 --- a/contracts/example/SnapshotGate/SnapshotGate.sol +++ b/contracts/example/SnapshotGate/SnapshotGate.sol @@ -6,7 +6,7 @@ import { IBosonOfferHandler } from "../../interfaces/handlers/IBosonOfferHandler import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { BosonTypes } from "../../domain/BosonTypes.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ERC721 } from "./support/ERC721.sol"; +import { ERC721 } from "./../support/ERC721.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /** diff --git a/contracts/example/Sudoswap/SudoswapWrapper.sol b/contracts/example/Sudoswap/SudoswapWrapper.sol new file mode 100644 index 000000000..6f41cd6b3 --- /dev/null +++ b/contracts/example/Sudoswap/SudoswapWrapper.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.9; +import { IBosonExchangeHandler } from "../../interfaces/handlers/IBosonExchangeHandler.sol"; +import { IBosonOfferHandler } from "../../interfaces/handlers/IBosonOfferHandler.sol"; +import { DAIAliases as DAI } from "../../interfaces/DAIAliases.sol"; +import { BosonTypes } from "../../domain/BosonTypes.sol"; +import { ERC721 } from "./../support/ERC721.sol"; +import { IERC721Metadata } from "./../support/IERC721Metadata.sol"; +import { IERC165 } from "../../interfaces/IERC165.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IPool { + function swapTokenForSpecificNFTs( + uint256[] calldata nftIds, + uint256 maxExpectedTokenInput, + address nftRecipient, + bool isRouter, + address routerCaller + ) external payable returns (uint256 inputAmount); +} + +/** + * @title SudoswapWrapper + * @notice Wraps Boson Vouchers so they can be used with Sudoswap. + * + * Features: + * - Wraps vouchers into ERC721 tokens that can be used with Sudoswap. + * - Tracks the price agreed in Sudoswap. + * - Allows to unwrap the voucher and sends funds to the protocol. + * - Owner of wrapped voucher has the right to receive the true corresponding Boson voucher + * + * Out-of-band setup: + * - Create a seller in Boson Protocol and get the Boson Voucher address. + * - Deploy a SudoswapWrapper contract and pass the Boson Voucher address. + * - Approve SudoswapWrapper to transfer Boson Vouchers on behalf of the seller. + * + * Usage: + * - Seller wraps a voucher by calling `wrap` function. + * - Seller calls Sudoswap method `createAuction` with the wrapped voucher address. + * - Auction proceeds normally and either finishes with `endAuction` or `cancelAuction`. + * - If auction finishes with `endAuction`: + * - Bidder gets wrapped voucher and this contract gets the price. + * - `unwrap` must be executed via the Boson Protocol `commitToOffer` method. + * - If auction finishes with `cancelAuction`: + * - This contract gets wrapped voucher back and the bidder gets the price. + * - `unwrap` can be executed by the owner of the wrapped voucher. + * + * N.B. Although Sudoswap can send ethers, it's preffered to receive + * WETH instead. For that reason `receive` is not implemented, so it automatically sends WETH. + */ +contract SudoswapWrapper is BosonTypes, Ownable, ERC721 { + // Add safeTransferFrom to IERC20 + using SafeERC20 for IERC20; + + // Contract addresses + address private immutable voucherAddress; + address private poolAddress; + address private immutable factoryAddress; + address private immutable protocolAddress; + address private immutable wethAddress; + + // Mapping from token ID to price. If pendingTokenId == tokenId, this is not the final price. + mapping(uint256 => uint256) private price; + + // Mapping to cache exchange token address, so costly call to the protocol is not needed every time. + mapping(uint256 => address) private cachedExchangeToken; + + /** + * @notice Constructor + * + * @param _voucherAddress The address of the voucher that are wrapped by this contract. + * @param _factoryAddress The address of the Sudoswap factory. + * @param _protocolAddress The address of the Boson Protocol. + * @param _wethAddress The address of the WETH token. + */ + constructor( + address _voucherAddress, + address _factoryAddress, + address _protocolAddress, + address _wethAddress + ) ERC721(getVoucherName(_voucherAddress), getVoucherSymbol(_voucherAddress)) { + voucherAddress = _voucherAddress; + factoryAddress = _factoryAddress; + protocolAddress = _protocolAddress; + wethAddress = _wethAddress; + + // Approve pool to transfer wrapped vouchers + _setApprovalForAll(address(this), _factoryAddress, true); + } + + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + */ + function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC721) returns (bool) { + return (_interfaceId == type(IERC721).interfaceId || _interfaceId == type(IERC165).interfaceId); + } + + /** + * @notice Wraps the vouchers, transfer true vouchers to this contract and mint wrapped vouchers + * + * Reverts if: + * - caller is not the contract owner + * + * @param _tokenIds The token ids. + */ + function wrap(uint256[] memory _tokenIds) external onlyOwner { + for (uint256 i = 0; i < _tokenIds.length; i++) { + uint256 tokenId = _tokenIds[i]; + + // Transfer vouchers to this contract + // Instead of msg.sender it could be voucherAddress, if vouchers were preminted to contract itself + // Not using safeTransferFrom since this contract is the recipient and we are sure it can handle the vouchers + IERC721(voucherAddress).transferFrom(msg.sender, address(this), tokenId); + + // Mint to caller, so it can be used with Sudoswap + _mint(msg.sender, tokenId); + } + } + + /** + * @notice Unwraps the voucher, transfer true voucher to owner and funds to the protocol. + * + * Reverts if: + * - caller is neither protocol nor voucher owner + * + * @param _tokenId The token id. + */ + function unwrap(uint256 _tokenId) external { + address wrappedVoucherOwner = ownerOf(_tokenId); + + // Either contract owner or protocol can unwrap + // If contract owner is unwrapping, this is equivalent to removing the voucher from the pool + require( + msg.sender == protocolAddress || wrappedVoucherOwner == msg.sender, + "SudoswapWrapper: Only owner or protocol can unwrap" + ); + + uint256 priceToPay = price[_tokenId]; + + // Delete price and pendingTokenId to prevent reentrancy + delete price[_tokenId]; + + // transfer Boson Voucher to voucher owner + IERC721(voucherAddress).safeTransferFrom(address(this), wrappedVoucherOwner, _tokenId); + + // Transfer token to protocol + if (priceToPay > 0) { + // This example only supports WETH + IERC20(cachedExchangeToken[_tokenId]).safeTransfer(protocolAddress, priceToPay); + } + + delete cachedExchangeToken[_tokenId]; // gas refund + + // Burn wrapped voucher + _burn(_tokenId); + } + + /** + * @notice Set the pool address + * + * @param _poolAddress The pool address + */ + function setPoolAddress(address _poolAddress) external onlyOwner { + poolAddress = _poolAddress; + } + + /** + * @notice swap token for specific NFT + * + * @param _tokenId - the token id + * @param _maxPrice - the max price + */ + function swapTokenForSpecificNFT(uint256 _tokenId, uint256 _maxPrice) external { + (address exchangeToken, uint256 balanceBefore) = getCurrentBalance(_tokenId); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = _tokenId; + + IERC20(exchangeToken).safeTransferFrom(msg.sender, address(this), _maxPrice); + IERC20(exchangeToken).forceApprove(poolAddress, _maxPrice); + + IPool(poolAddress).swapTokenForSpecificNFTs(tokenIds, _maxPrice, msg.sender, false, address(0)); + + (, uint256 balanceAfter) = getCurrentBalance(_tokenId); + + uint256 actualPrice = balanceAfter - balanceBefore; + require(actualPrice <= _maxPrice, "SudoswapWrapper: Price too high"); + + price[_tokenId] = actualPrice; + } + + /** + * @notice Gets own token balance for the exchange token, associated with the token ID. + * + * @dev If the exchange token is not known, it is fetched from the protocol and cached for future use. + * + * @param _tokenId The token id. + */ + function getCurrentBalance(uint256 _tokenId) internal returns (address exchangeToken, uint256 balance) { + exchangeToken = cachedExchangeToken[_tokenId]; + + // If exchange token is not known, get it from the protocol. + if (exchangeToken == address(0)) { + uint256 offerId = _tokenId >> 128; // OfferId is the first 128 bits of the token ID. + + if (offerId == 0) { + // pre v2.2.0. Token does not have offerId, so we need to get it from the protocol. + // Get Boson exchange. Don't explicitly check if the exchange exists, since existance of the token implies it does. + uint256 exchangeId = _tokenId & type(uint128).max; // ExchangeId is the last 128 bits of the token ID. + (, BosonTypes.Exchange memory exchange, ) = IBosonExchangeHandler(protocolAddress).getExchange( + exchangeId + ); + offerId = exchange.offerId; + } + + // Get Boson offer. Don't explicitly check if the offer exists, since existance of the token implies it does. + (, BosonTypes.Offer memory offer, , , , ) = IBosonOfferHandler(protocolAddress).getOffer(offerId); + exchangeToken = offer.exchangeToken; + + // If exchange token is 0, it means native token is used. In that case, use WETH. + if (exchangeToken == address(0)) exchangeToken = wethAddress; + cachedExchangeToken[_tokenId] = exchangeToken; + } + + balance = IERC20(exchangeToken).balanceOf(address(this)); + } + + /** + * @notice Gets the Boson Voucher token name and adds "Wrapped" prefix. + * + * @dev Used only in the constructor. + * + * @param _voucherAddress Boson Voucher address + */ + function getVoucherName(address _voucherAddress) internal view returns (string memory) { + string memory name = IERC721Metadata(_voucherAddress).name(); + return string.concat("Wrapped ", name); + } + + /** + * @notice Gets the the Boson Voucher symbol and adds "W" prefix. + * + * @dev Used only in the constructor. + * + * @param _voucherAddress Boson Voucher address + */ + function getVoucherSymbol(address _voucherAddress) internal view returns (string memory) { + string memory symbol = IERC721Metadata(_voucherAddress).symbol(); + return string.concat("W", symbol); + } +} diff --git a/contracts/example/ZoraWrapper/ZoraWrapper.sol b/contracts/example/ZoraWrapper/ZoraWrapper.sol new file mode 100644 index 000000000..44071b319 --- /dev/null +++ b/contracts/example/ZoraWrapper/ZoraWrapper.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.9; + +import { IBosonOfferHandler } from "../../interfaces/handlers/IBosonOfferHandler.sol"; +import { IBosonExchangeHandler } from "../../interfaces/handlers/IBosonExchangeHandler.sol"; +import { BosonTypes } from "../../domain/BosonTypes.sol"; +import { ERC721 } from "./../support/ERC721.sol"; +import { IERC721Metadata } from "./../support/IERC721Metadata.sol"; +import { IERC721 } from "../../interfaces/IERC721.sol"; +import { IERC165 } from "../../interfaces/IERC165.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721Receiver } from "../../interfaces/IERC721Receiver.sol"; + +/** + * @title ZoraWrapper + * @notice Wraps Boson Vouchers so they can be used with Zora Auction House. + * + * Features: + * - Wraps vouchers into ERC721 tokens that can be used with Zora Auction House. + * - Tracks the price agreed in Zora Auction House. + * - Allows to unwrap the voucher and sends funds to the protocol. + * - Owner of wrapped voucher has the right to receive the true corresponding Boson voucher + * + * Out-of-band setup: + * - Create a seller in Boson Protocol and get the Boson Voucher address. + * - Deploy a ZoraWrapper contract and pass the Boson Voucher address. + * - Approve ZoraWrapper to transfer Boson Vouchers on behalf of the seller. + * + * Usage: + * - Seller wraps a voucher by calling `wrap` function. + * - Seller calls Zora Auction House method `createAuction` with the wrapped voucher address. + * - Auction proceeds normally and either finishes with `endAuction` or `cancelAuction`. + * - If auction finishes with `endAuction`: + * - Bidder gets wrapped voucher and this contract gets the price. + * - `unwrap` must be executed via the Boson Protocol `commitToOffer` method. + * - If auction finishes with `cancelAuction`: + * - This contract gets wrapped voucher back and the bidder gets the price. + * - `unwrap` can be executed by the owner of the wrapped voucher. + * + * N.B. Although Zora Auction House can send ethers, it's preffered to receive + * WETH instead. For that reason `receive` is not implemented, so it automatically sends WETH. + */ +contract ZoraWrapper is BosonTypes, ERC721, IERC721Receiver { + // Add safeTransferFrom to IERC20 + using SafeERC20 for IERC20; + + // Contract addresses + address private immutable voucherAddress; + address private immutable zoraAuctionHouseAddress; + address private immutable protocolAddress; + address private immutable wethAddress; + + // Token ID for which the price is not yet known + uint256 private pendingTokenId; + + // Mapping from token ID to price. If pendingTokenId == tokenId, this is not the final price. + mapping(uint256 => uint256) private price; + + // Mapping to cache exchange token address, so costly call to the protocol is not needed every time. + mapping(uint256 => address) private cachedExchangeToken; + + mapping(uint256 => address) private wrapper; + + /** + * @notice Constructor + * + * @param _voucherAddress The address of the voucher that are wrapped by this contract. + * @param _zoraAuctionHouseAddress The address of Zora Auction House. + */ + constructor( + address _voucherAddress, + address _zoraAuctionHouseAddress, + address _protocolAddress, + address _wethAddress + ) ERC721(getVoucherName(_voucherAddress), getVoucherSymbol(_voucherAddress)) { + voucherAddress = _voucherAddress; + zoraAuctionHouseAddress = _zoraAuctionHouseAddress; + protocolAddress = _protocolAddress; + wethAddress = _wethAddress; + + // Approve Zora Auction House to transfer wrapped vouchers + _setApprovalForAll(address(this), _zoraAuctionHouseAddress, true); + } + + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + */ + function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC721) returns (bool) { + return (_interfaceId == type(IERC721).interfaceId || _interfaceId == type(IERC165).interfaceId); + } + + /** + * @notice Wraps the voucher, transfer true voucher to itself and approves the contract owner to operate on it. + * + * Reverts if: + * - caller is not the contract owner + * + * @param _tokenId The token id. + */ + function wrap(uint256 _tokenId) external { + // Transfer voucher to this contract + // Instead of msg.sender it could be voucherAddress, if vouchers were preminted to contract itself + // Not using safeTransferFrom since this contract is the recipient and we are sure it can handle the vouchers + IERC721(voucherAddress).transferFrom(msg.sender, address(this), _tokenId); + + // Mint to itself, so it can be used with Zora Auction House + _mint(address(this), _tokenId); // why not sender instead of address(this)? + + // Approves original token owner to operate on wrapped token + _approve(msg.sender, _tokenId); + + wrapper[_tokenId] = msg.sender; + } + + /** + * @notice Unwraps the voucher, transfer true voucher to owner and funds to the protocol. + * + * Reverts if: + * - caller is neither protocol nor voucher owner + * + * @param _tokenId The token id. + */ + function unwrap(uint256 _tokenId) external { + address wrappedVoucherOwner = ownerOf(_tokenId); + if (wrappedVoucherOwner == address(this)) wrappedVoucherOwner = wrapper[_tokenId]; + + // Either contract owner or protocol can unwrap + // If contract owner is unwrapping, this is equivalent to canceled auction + require( + msg.sender == protocolAddress || wrappedVoucherOwner == msg.sender, + "ZoraWrapper: Only owner or protocol can unwrap" + ); + + // If some token price is not know yet, update it now + if (pendingTokenId != 0) updatePendingTokenPrice(); + + uint256 priceToPay = price[_tokenId]; + + // Delete price and pendingTokenId to prevent reentrancy + delete price[_tokenId]; + delete pendingTokenId; + + // transfer voucher to voucher owner + IERC721(voucherAddress).safeTransferFrom(address(this), wrappedVoucherOwner, _tokenId); + + // Transfer token to protocol + if (priceToPay > 0) { + // No need to handle native separately, since Zora Auction House always sends WETH + IERC20(cachedExchangeToken[_tokenId]).safeTransfer(protocolAddress, priceToPay); + } + + delete cachedExchangeToken[_tokenId]; // gas refund + delete wrapper[_tokenId]; + + // Burn wrapped voucher + _burn(_tokenId); + } + + /** + * @notice Handle transfers out of Zora Auction House. + * + * @param _from The address of the sender. + * @param _to The address of the recipient. + * @param _tokenId The token id. + */ + function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override(ERC721) { + if (_from == zoraAuctionHouseAddress && _to != address(this)) { + // Auction is over, and wrapped voucher is being transferred to voucher owner + // If recipient is address(this), it means the auction was canceled and price updating can be skipped + + // If some token price is not know yet, update it now + if (pendingTokenId != 0) updatePendingTokenPrice(); + + // Store current balance and set the pending token id + price[_tokenId] = getCurrentBalance(_tokenId); + pendingTokenId = _tokenId; + } + + super._beforeTokenTransfer(_from, _to, _tokenId); + } + + function updatePendingTokenPrice() internal { + uint256 tokenId = pendingTokenId; + price[tokenId] = getCurrentBalance(tokenId) - price[tokenId]; + } + + /** + * @notice Gets own token balance for the exchange token, associated with the token ID. + * + * @dev If the exchange token is not known, it is fetched from the protocol and cached for future use. + * + * @param _tokenId The token id. + */ + function getCurrentBalance(uint256 _tokenId) internal returns (uint256) { + address exchangeToken = cachedExchangeToken[_tokenId]; + + // If exchange token is not known, get it from the protocol. + if (exchangeToken == address(0)) { + uint256 offerId = _tokenId >> 128; // OfferId is the first 128 bits of the token ID. + + if (offerId == 0) { + // pre v2.2.0. Token does not have offerId, so we need to get it from the protocol. + // Get Boson exchange. Don't explicitly check if the exchange exists, since existance of the token implies it does. + uint256 exchangeId = _tokenId & type(uint128).max; // ExchangeId is the last 128 bits of the token ID. + (, BosonTypes.Exchange memory exchange, ) = IBosonExchangeHandler(protocolAddress).getExchange( + exchangeId + ); + offerId = exchange.offerId; + } + + // Get Boson offer. Don't explicitly check if the offer exists, since existance of the token implies it does. + (, BosonTypes.Offer memory offer, , , , ) = IBosonOfferHandler(protocolAddress).getOffer(offerId); + exchangeToken = offer.exchangeToken; + + // If exchange token is 0, it means native token is used. In that case, use WETH. + if (exchangeToken == address(0)) exchangeToken = wethAddress; + cachedExchangeToken[_tokenId] = exchangeToken; + } + + return IERC20(exchangeToken).balanceOf(address(this)); + } + + /** + * @notice Gets the Boson Voucher token name and adds "Wrapped" prefix. + * + * @dev Used only in the constructor. + * + * @param _voucherAddress Boson Voucher address + */ + function getVoucherName(address _voucherAddress) internal view returns (string memory) { + string memory name = IERC721Metadata(_voucherAddress).name(); + return string.concat("Wrapped ", name); + } + + /** + * @notice Gets the the Boson Voucher symbol and adds "W" prefix. + * + * @dev Used only in the constructor. + * + * @param _voucherAddress Boson Voucher address + */ + function getVoucherSymbol(address _voucherAddress) internal view returns (string memory) { + string memory symbol = IERC721Metadata(_voucherAddress).symbol(); + return string.concat("W", symbol); + } + + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/contracts/example/SnapshotGate/support/ERC721.sol b/contracts/example/support/ERC721.sol similarity index 99% rename from contracts/example/SnapshotGate/support/ERC721.sol rename to contracts/example/support/ERC721.sol index c37161193..d58bb24a9 100644 --- a/contracts/example/SnapshotGate/support/ERC721.sol +++ b/contracts/example/support/ERC721.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.21; -import "../../../interfaces/IERC721.sol"; -import "../../../interfaces/IERC721Receiver.sol"; +import "../../interfaces/IERC721.sol"; +import "../../interfaces/IERC721Receiver.sol"; import "./IERC721Metadata.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; diff --git a/contracts/example/SnapshotGate/support/IERC721Metadata.sol b/contracts/example/support/IERC721Metadata.sol similarity index 94% rename from contracts/example/SnapshotGate/support/IERC721Metadata.sol rename to contracts/example/support/IERC721Metadata.sol index b58d0a0c7..c8ffac583 100644 --- a/contracts/example/SnapshotGate/support/IERC721Metadata.sol +++ b/contracts/example/support/IERC721Metadata.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.21; -import "../../../interfaces/IERC721.sol"; +import "../../interfaces/IERC721.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension diff --git a/contracts/interfaces/IWrappedNative.sol b/contracts/interfaces/IWrappedNative.sol index 8bee69f22..1e311d02a 100644 --- a/contracts/interfaces/IWrappedNative.sol +++ b/contracts/interfaces/IWrappedNative.sol @@ -14,4 +14,6 @@ interface IWrappedNative { function transfer(address, uint256) external returns (bool); function transferFrom(address, address, uint256) external returns (bool); + + function approve(address, uint256) external returns (bool); } diff --git a/contracts/interfaces/handlers/IBosonExchangeHandler.sol b/contracts/interfaces/handlers/IBosonExchangeHandler.sol index da1464e0e..d5fbbb83e 100644 --- a/contracts/interfaces/handlers/IBosonExchangeHandler.sol +++ b/contracts/interfaces/handlers/IBosonExchangeHandler.sol @@ -11,11 +11,11 @@ import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; * * @notice Handles exchanges associated with offers within the protocol. * - * The ERC-165 identifier for this interface is: 0xf34a48fa + * The ERC-165 identifier for this interface is: 0x0e1fefcb */ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents { /** - * @notice Commits to an offer (first step of an exchange). + * @notice Commits to a static offer (first step of an exchange). * * Emits a BuyerCommitted event if successful. * Issues a voucher to the buyer address. @@ -74,30 +74,6 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I */ function commitToConditionalOffer(address payable _buyer, uint256 _offerId, uint256 _tokenId) external payable; - /** - * @notice Commits to a preminted offer (first step of an exchange). - * - * Emits a BuyerCommitted event if successful. - * - * Reverts if: - * - The exchanges region of protocol is paused - * - The buyers region of protocol is paused - * - Caller is not the voucher contract, owned by the seller - * - Exchange exists already - * - Offer has been voided - * - Offer has expired - * - Offer is not yet available for commits - * - Buyer account is inactive - * - Buyer is token-gated (conditional commit requirements not met or already used) - * - Buyer is token-gated and condition has a range. - * - Seller has less funds available than sellerDeposit and price - * - * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _offerId - the id of the offer to commit to - * @param _exchangeId - the id of the exchange - */ - function commitToPreMintedOffer(address payable _buyer, uint256 _offerId, uint256 _exchangeId) external; - /** * @notice Completes an exchange. * @@ -217,6 +193,7 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * Emits a VoucherTransferred event if successful. * * Reverts if + * - The exchanges region of protocol is paused * - The buyers region of protocol is paused * - Caller is not a clone address associated with the seller * - Exchange does not exist @@ -224,10 +201,34 @@ interface IBosonExchangeHandler is IBosonExchangeEvents, IBosonFundsLibEvents, I * - Voucher has expired * - New buyer's existing account is deactivated * - * @param _exchangeId - the id of the exchange + * @param _tokenId - the voucher id * @param _newBuyer - the address of the new buyer */ - function onVoucherTransferred(uint256 _exchangeId, address payable _newBuyer) external; + function onVoucherTransferred(uint256 _tokenId, address payable _newBuyer) external; + + /** + * @notice Handle pre-minted voucher transfer + * + * Reverts if: + * - The exchanges region of protocol is paused + * - The buyers region of protocol is paused + * - Caller is not a clone address associated with the seller + * - Incoming voucher clone address is not the caller + * - Offer price is discovery, transaction is not starting from protocol nor seller is _from address + * - Any reason that ExchangeHandler commitToOfferInternal reverts. See ExchangeHandler.commitToOfferInternal + * + * @param _tokenId - the voucher id + * @param _to - the receiver address + * @param _from - the address of current owner + * @param _rangeOwner - the address of the preminted range owner + * @return committed - true if the voucher was committed + */ + function onPremintedVoucherTransferred( + uint256 _tokenId, + address payable _to, + address _from, + address _rangeOwner + ) external returns (bool committed); /** * @notice Checks if the given exchange in a finalized state. diff --git a/contracts/interfaces/handlers/IBosonOfferHandler.sol b/contracts/interfaces/handlers/IBosonOfferHandler.sol index 172d4240e..9eefbaa3c 100644 --- a/contracts/interfaces/handlers/IBosonOfferHandler.sol +++ b/contracts/interfaces/handlers/IBosonOfferHandler.sol @@ -9,7 +9,7 @@ import { IBosonOfferEvents } from "../events/IBosonOfferEvents.sol"; * * @notice Handles creation, voiding, and querying of offers within the protocol. * - * The ERC-165 identifier for this interface is: 0xa1e3b91c + * The ERC-165 identifier for this interface is: 0x67991d09 */ interface IBosonOfferHandler is IBosonOfferEvents { /** diff --git a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol index c71c6a781..b2de2bb5d 100644 --- a/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol +++ b/contracts/interfaces/handlers/IBosonOrchestrationHandler.sol @@ -13,7 +13,7 @@ import { IBosonBundleEvents } from "../events/IBosonBundleEvents.sol"; * * @notice Combines creation of multiple entities (accounts, offers, groups, twins, bundles) in a single transaction * - * The ERC-165 identifier for this interface is: 0x7e216084 + * The ERC-165 identifier for this interface is: 0xb8b97453 */ interface IBosonOrchestrationHandler is IBosonAccountEvents, diff --git a/contracts/interfaces/handlers/IBosonPriceDiscoveryHandler.sol b/contracts/interfaces/handlers/IBosonPriceDiscoveryHandler.sol new file mode 100644 index 000000000..b93a7daf3 --- /dev/null +++ b/contracts/interfaces/handlers/IBosonPriceDiscoveryHandler.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.21; + +import { BosonTypes } from "../../domain/BosonTypes.sol"; +import { IBosonExchangeEvents } from "../events/IBosonExchangeEvents.sol"; +import { IBosonTwinEvents } from "../events/IBosonTwinEvents.sol"; +import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; + +/** + * @title IBosonPriceDiscoveryHandler + * + * @notice Handles exchanges associated with offers within the protocol. + * + * The ERC-165 identifier for this interface is: 0xdec319c9 + */ +interface IBosonPriceDiscoveryHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IBosonTwinEvents { + /** + * @notice Commits to a price discovery offer (first step of an exchange). + * + * Emits a BuyerCommitted event if successful. + * Issues a voucher to the buyer address. + * + * Reverts if: + * - Offer price type is not price discovery. See BosonTypes.PriceType + * - Price discovery contract address is zero + * - Price discovery calldata is empty + * - Exchange exists already + * - Offer has been voided + * - Offer has expired + * - Offer is not yet available for commits + * - Buyer address is zero + * - Buyer account is inactive + * - Buyer is token-gated (conditional commit requirements not met or already used) + * - Any reason that PriceDiscoveryBase fulfilOrder reverts. See PriceDiscoveryBase.fulfilOrder + * - Any reason that ExchangeHandler onPremintedVoucherTransfer reverts. See ExchangeHandler.onPremintedVoucherTransfer + * + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _tokenIdOrOfferId - the id of the offer to commit to or the id of the voucher (if pre-minted) + * @param _priceDiscovery - price discovery data (if applicable). See BosonTypes.PriceDiscovery + */ + function commitToPriceDiscoveryOffer( + address payable _buyer, + uint256 _tokenIdOrOfferId, + BosonTypes.PriceDiscovery calldata _priceDiscovery + ) external payable; +} diff --git a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol index 2cd44a445..461b58597 100644 --- a/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol +++ b/contracts/interfaces/handlers/IBosonSequentialCommitHandler.sol @@ -4,16 +4,15 @@ pragma solidity 0.8.21; import { BosonTypes } from "../../domain/BosonTypes.sol"; import { IBosonExchangeEvents } from "../events/IBosonExchangeEvents.sol"; import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol"; -import { IERC721Receiver } from "../IERC721Receiver.sol"; /** * @title ISequentialCommitHandler * * @notice Handles sequential commits. * - * The ERC-165 identifier for this interface is: 0x1566334a + * The ERC-165 identifier for this interface is: 0x34780cc6 */ -interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibEvents, IERC721Receiver { +interface IBosonSequentialCommitHandler is IBosonExchangeEvents, IBosonFundsLibEvents { /** * @notice Commits to an existing exchange. Price discovery is offloaded to external contract. * diff --git a/contracts/mock/AuctionHouse.sol b/contracts/mock/AuctionHouse.sol new file mode 100644 index 000000000..6c304d5b8 --- /dev/null +++ b/contracts/mock/AuctionHouse.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.9; +pragma experimental ABIEncoderV2; + +import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import { IERC721, IERC165 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Counters } from "@openzeppelin/contracts/utils/Counters.sol"; +import { IAuctionHouse } from "./IAuctionHouse.sol"; + +interface IWETH { + function deposit() external payable; + + function withdraw(uint256 wad) external; + + function transfer(address to, uint256 value) external returns (bool); +} + +/** + * @title An open auction house, enabling collectors and curators to run their own auctions + * @dev This is an minimal clone of zora's AuctionHouse contract https://github.com/ourzora/auction-house/blob/01c4e8085c6815bf3233057dee8e628aca07813f/contracts/AuctionHouse.sol + */ +contract AuctionHouse is IAuctionHouse, ReentrancyGuard { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Counters for Counters.Counter; + + // The minimum amount of time left in an auction after a new bid is created + uint256 public timeBuffer; + + // The minimum percentage difference between the last bid amount and the current bid. + uint8 public minBidIncrementPercentage; + + // / The address of the WETH contract, so that any ETH transferred can be handled as an ERC-20 + address public wethAddress; + + // A mapping of all of the auctions currently running. + mapping(uint256 => IAuctionHouse.Auction) public auctions; + + bytes4 internal constant INTERFACE_ID = 0x80ac58cd; // 721 interface id + + Counters.Counter private _auctionIdTracker; + + /** + * @notice Require that the specified auction exists + */ + modifier auctionExists(uint256 auctionId) { + require(_exists(auctionId), "Auction doesn't exist"); + _; + } + + /* + * Constructor + */ + constructor(address _weth) { + wethAddress = _weth; + timeBuffer = 15 * 60; // extend 15 minutes after every bid made in last 15 minutes + minBidIncrementPercentage = 5; // 5% + } + + /** + * @notice Create an auction. + * @dev Store the auction details in the auctions mapping and emit an AuctionCreated event. + * If there is no curator, or if the curator is the auction creator, automatically approve the auction. + */ + function createAuction( + uint256 tokenId, + address tokenContract, + uint256 duration, + uint256 reservePrice, + address payable curator, + uint8 curatorFeePercentage, + address auctionCurrency + ) public override nonReentrant returns (uint256) { + require( + IERC165(tokenContract).supportsInterface(INTERFACE_ID), + "tokenContract does not support ERC721 interface" + ); + require(curatorFeePercentage < 100, "curatorFeePercentage must be less than 100"); + address tokenOwner = IERC721(tokenContract).ownerOf(tokenId); + require( + msg.sender == IERC721(tokenContract).getApproved(tokenId) || msg.sender == tokenOwner, + "Caller must be approved or owner for token id" + ); + uint256 auctionId = _auctionIdTracker.current(); + + Auction storage auction = auctions[auctionId]; + auction.tokenId = tokenId; + auction.tokenContract = tokenContract; + auction.approved = false; + auction.amount = 0; + auction.duration = duration; + auction.firstBidTime = 0; + auction.reservePrice = reservePrice; + auction.curatorFeePercentage = curatorFeePercentage; + auction.tokenOwner = tokenOwner; + auction.bidder = address(0); + auction.auctionCurrency = auctionCurrency; + auction.curator = curator; + + IERC721(tokenContract).transferFrom(tokenOwner, address(this), tokenId); + + _auctionIdTracker.increment(); + + emit AuctionCreated( + auctionId, + tokenId, + tokenContract, + duration, + reservePrice, + tokenOwner, + curator, + curatorFeePercentage, + auctionCurrency + ); + + if (auctions[auctionId].curator == address(0) || curator == tokenOwner) { + _approveAuction(auctionId, true); + } + + return auctionId; + } + + /** + * @notice Approve an auction, opening up the auction for bids. + * @dev Only callable by the curator. Cannot be called if the auction has already started. + */ + function setAuctionApproval(uint256 auctionId, bool approved) external override auctionExists(auctionId) { + require(msg.sender == auctions[auctionId].curator, "Must be auction curator"); + require(auctions[auctionId].firstBidTime == 0, "Auction has already started"); + _approveAuction(auctionId, approved); + } + + function setAuctionReservePrice( + uint256 auctionId, + uint256 reservePrice + ) external override auctionExists(auctionId) { + require( + msg.sender == auctions[auctionId].curator || msg.sender == auctions[auctionId].tokenOwner, + "Must be auction curator or token owner" + ); + require(auctions[auctionId].firstBidTime == 0, "Auction has already started"); + + auctions[auctionId].reservePrice = reservePrice; + + emit AuctionReservePriceUpdated( + auctionId, + auctions[auctionId].tokenId, + auctions[auctionId].tokenContract, + reservePrice + ); + } + + /** + * @notice Create a bid on a token, with a given amount. + * @dev If provided a valid bid, transfers the provided amount to this contract. + * If the auction is run in native ETH, the ETH is wrapped so it can be identically to other + * auction currencies in this contract. + */ + function createBid( + uint256 auctionId, + uint256 amount + ) external payable override auctionExists(auctionId) nonReentrant { + address lastBidder = auctions[auctionId].bidder; + require(auctions[auctionId].approved, "Auction must be approved by curator"); + require( + auctions[auctionId].firstBidTime == 0 || + block.timestamp < auctions[auctionId].firstBidTime.add(auctions[auctionId].duration), + "Auction expired" + ); + require(amount >= auctions[auctionId].reservePrice, "Must send at least reservePrice"); + require( + amount >= + auctions[auctionId].amount.add(auctions[auctionId].amount.mul(minBidIncrementPercentage).div(100)), + "Must send more than last bid by minBidIncrementPercentage amount" + ); + + // If this is the first valid bid, we should set the starting time now. + // If it's not, then we should refund the last bidder + if (auctions[auctionId].firstBidTime == 0) { + auctions[auctionId].firstBidTime = block.timestamp; + } else if (lastBidder != address(0)) { + _handleOutgoingBid(lastBidder, auctions[auctionId].amount, auctions[auctionId].auctionCurrency); + } + + _handleIncomingBid(amount, auctions[auctionId].auctionCurrency); + + auctions[auctionId].amount = amount; + auctions[auctionId].bidder = msg.sender; + + bool extended = false; + // at this point we know that the timestamp is less than start + duration (since the auction would be over, otherwise) + // we want to know by how much the timestamp is less than start + duration + // if the difference is less than the timeBuffer, increase the duration by the timeBuffer + if (auctions[auctionId].firstBidTime.add(auctions[auctionId].duration).sub(block.timestamp) < timeBuffer) { + // Playing code golf for gas optimization: + // uint256 expectedEnd = auctions[auctionId].firstBidTime.add(auctions[auctionId].duration); + // uint256 timeRemaining = expectedEnd.sub(block.timestamp); + // uint256 timeToAdd = timeBuffer.sub(timeRemaining); + // uint256 newDuration = auctions[auctionId].duration.add(timeToAdd); + uint256 oldDuration = auctions[auctionId].duration; + auctions[auctionId].duration = oldDuration.add( + timeBuffer.sub(auctions[auctionId].firstBidTime.add(oldDuration).sub(block.timestamp)) + ); + extended = true; + } + + emit AuctionBid( + auctionId, + auctions[auctionId].tokenId, + auctions[auctionId].tokenContract, + msg.sender, + amount, + lastBidder == address(0), // firstBid boolean + extended + ); + + if (extended) { + emit AuctionDurationExtended( + auctionId, + auctions[auctionId].tokenId, + auctions[auctionId].tokenContract, + auctions[auctionId].duration + ); + } + } + + /** + * @notice End an auction paying out the respective parties. + * @dev If for some reason the auction cannot be finalized (invalid token recipient, for example), + * The auction is reset and the NFT is transferred back to the auction creator. + */ + function endAuction(uint256 auctionId) external override auctionExists(auctionId) nonReentrant { + require(uint256(auctions[auctionId].firstBidTime) != 0, "Auction hasn't begun"); + require( + block.timestamp >= auctions[auctionId].firstBidTime.add(auctions[auctionId].duration), + "Auction hasn't completed" + ); + + address currency = auctions[auctionId].auctionCurrency == address(0) + ? wethAddress + : auctions[auctionId].auctionCurrency; + uint256 curatorFee = 0; + + uint256 tokenOwnerProfit = auctions[auctionId].amount; + + // Otherwise, transfer the token to the winner and pay out the participants below + try + IERC721(auctions[auctionId].tokenContract).safeTransferFrom( + address(this), + auctions[auctionId].bidder, + auctions[auctionId].tokenId + ) + {} catch { + _handleOutgoingBid( + auctions[auctionId].bidder, + auctions[auctionId].amount, + auctions[auctionId].auctionCurrency + ); + _cancelAuction(auctionId); + return; + } + + if (auctions[auctionId].curator != address(0)) { + curatorFee = tokenOwnerProfit.mul(auctions[auctionId].curatorFeePercentage).div(100); + tokenOwnerProfit = tokenOwnerProfit.sub(curatorFee); + _handleOutgoingBid(auctions[auctionId].curator, curatorFee, auctions[auctionId].auctionCurrency); + } + _handleOutgoingBid(auctions[auctionId].tokenOwner, tokenOwnerProfit, auctions[auctionId].auctionCurrency); + + emit AuctionEnded( + auctionId, + auctions[auctionId].tokenId, + auctions[auctionId].tokenContract, + auctions[auctionId].tokenOwner, + auctions[auctionId].curator, + auctions[auctionId].bidder, + tokenOwnerProfit, + curatorFee, + currency + ); + delete auctions[auctionId]; + } + + /** + * @notice Cancel an auction. + * @dev Transfers the NFT back to the auction creator and emits an AuctionCanceled event + */ + function cancelAuction(uint256 auctionId) external override nonReentrant auctionExists(auctionId) { + require( + auctions[auctionId].tokenOwner == msg.sender || auctions[auctionId].curator == msg.sender, + "Can only be called by auction creator or curator" + ); + require(uint256(auctions[auctionId].firstBidTime) == 0, "Can't cancel an auction once it's begun"); + _cancelAuction(auctionId); + } + + /** + * @dev Given an amount and a currency, transfer the currency to this contract. + * If the currency is ETH (0x0), attempt to wrap the amount as WETH + */ + function _handleIncomingBid(uint256 amount, address currency) internal { + // If this is an ETH bid, ensure they sent enough and convert it to WETH under the hood + if (currency == address(0)) { + require(msg.value == amount, "Sent ETH Value does not match specified bid amount"); + IWETH(wethAddress).deposit{ value: amount }(); + } else { + // We must check the balance that was actually transferred to the auction, + // as some tokens impose a transfer fee and would not actually transfer the + // full amount to the market, resulting in potentally locked funds + IERC20 token = IERC20(currency); + uint256 beforeBalance = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), amount); + uint256 afterBalance = token.balanceOf(address(this)); + require(beforeBalance.add(amount) == afterBalance, "Token transfer call did not transfer expected amount"); + } + } + + function _handleOutgoingBid(address to, uint256 amount, address currency) internal { + // If the auction is in ETH, unwrap it from its underlying WETH and try to send it to the recipient. + if (currency == address(0)) { + IWETH(wethAddress).withdraw(amount); + + // If the ETH transfer fails (sigh), rewrap the ETH and try send it as WETH. + if (!_safeTransferETH(to, amount)) { + IWETH(wethAddress).deposit{ value: amount }(); + IERC20(wethAddress).safeTransfer(to, amount); + } + } else { + IERC20(currency).safeTransfer(to, amount); + } + } + + function _safeTransferETH(address to, uint256 value) internal returns (bool) { + (bool success, ) = to.call{ value: value }(new bytes(0)); + return success; + } + + function _cancelAuction(uint256 auctionId) internal { + address tokenOwner = auctions[auctionId].tokenOwner; + IERC721(auctions[auctionId].tokenContract).safeTransferFrom( + address(this), + tokenOwner, + auctions[auctionId].tokenId + ); + + emit AuctionCanceled(auctionId, auctions[auctionId].tokenId, auctions[auctionId].tokenContract, tokenOwner); + delete auctions[auctionId]; + } + + function _approveAuction(uint256 auctionId, bool approved) internal { + auctions[auctionId].approved = approved; + emit AuctionApprovalUpdated( + auctionId, + auctions[auctionId].tokenId, + auctions[auctionId].tokenContract, + approved + ); + } + + function _exists(uint256 auctionId) internal view returns (bool) { + return auctions[auctionId].tokenOwner != address(0); + } + + // TODO: consider reverting if the message sender is not WETH + receive() external payable {} + + fallback() external payable {} +} diff --git a/contracts/mock/IAuctionHouse.sol b/contracts/mock/IAuctionHouse.sol new file mode 100644 index 000000000..44181f24c --- /dev/null +++ b/contracts/mock/IAuctionHouse.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.9; +pragma experimental ABIEncoderV2; + +/** + * @title Interface for Auction Houses + */ +interface IAuctionHouse { + struct Auction { + // ID for the ERC721 token + uint256 tokenId; + // Address for the ERC721 contract + address tokenContract; + // Whether or not the auction curator has approved the auction to start + bool approved; + // The current highest bid amount + uint256 amount; + // The length of time to run the auction for, after the first bid was made + uint256 duration; + // The time of the first bid + uint256 firstBidTime; + // The minimum price of the first bid + uint256 reservePrice; + // The sale percentage to send to the curator + uint8 curatorFeePercentage; + // The address that should receive the funds once the NFT is sold. + address tokenOwner; + // The address of the current highest bid + address bidder; + // The address of the auction's curator. + // The curator can reject or approve an auction + address curator; + // The address of the ERC-20 currency to run the auction with. + // If set to 0x0, the auction will be run in ETH + address auctionCurrency; + } + + event AuctionCreated( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + uint256 duration, + uint256 reservePrice, + address tokenOwner, + address curator, + uint8 curatorFeePercentage, + address auctionCurrency + ); + + event AuctionApprovalUpdated( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + bool approved + ); + + event AuctionReservePriceUpdated( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + uint256 reservePrice + ); + + event AuctionBid( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + address sender, + uint256 value, + bool firstBid, + bool extended + ); + + event AuctionDurationExtended( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + uint256 duration + ); + + event AuctionEnded( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + address tokenOwner, + address curator, + address winner, + uint256 amount, + uint256 curatorFee, + address auctionCurrency + ); + + event AuctionCanceled( + uint256 indexed auctionId, + uint256 indexed tokenId, + address indexed tokenContract, + address tokenOwner + ); + + function createAuction( + uint256 tokenId, + address tokenContract, + uint256 duration, + uint256 reservePrice, + address payable curator, + uint8 curatorFeePercentages, + address auctionCurrency + ) external returns (uint256); + + function setAuctionApproval(uint256 auctionId, bool approved) external; + + function setAuctionReservePrice(uint256 auctionId, uint256 reservePrice) external; + + function createBid(uint256 auctionId, uint256 amount) external payable; + + function endAuction(uint256 auctionId) external; + + function cancelAuction(uint256 auctionId) external; +} diff --git a/contracts/mock/MockAuction.sol b/contracts/mock/MockAuction.sol new file mode 100644 index 000000000..3467e3088 --- /dev/null +++ b/contracts/mock/MockAuction.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.9; +pragma experimental ABIEncoderV2; + +import { IERC721, IERC165 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Counters } from "@openzeppelin/contracts/utils/Counters.sol"; + +interface IWETH { + function deposit() external payable; + + function withdraw(uint256 wad) external; + + function transfer(address to, uint256 value) external returns (bool); +} + +/** + * @title An open auction house, enabling collectors and curators to run their own auctions + * @dev This is an inspired by zora's AuctionHouse contract https://github.com/ourzora/auction-house/blob/01c4e8085c6815bf3233057dee8e628aca07813f/contracts/AuctionHouse.sol + * But stripped down and only for test purposes + */ +contract MockAuction { + using SafeERC20 for IERC20; + + event AuctionCanceled(); + + struct Auction { + // ID for the ERC721 token + uint256 tokenId; + // Address for the ERC721 contract + address tokenContract; + // The current highest bid amount + uint256 amount; + // The address that should receive the funds once the NFT is sold. + address tokenOwner; + // The address of the current highest bid + address bidder; + // The address of the ERC-20 currency to run the auction with. + // If set to 0x0, the auction will be run in ETH + address auctionCurrency; + address curator; + } + + address public immutable wethAddress; + + // A mapping of all of the auctions currently running. + mapping(uint256 => Auction) public auctions; + + uint256 public auctionIdCounter; + + /* + * Constructor + */ + constructor(address _weth) { + wethAddress = _weth; + } + + /** + * @notice Create an auction. + * @dev Store the auction details in the auctions mapping + */ + function createAuction(uint256 tokenId, address tokenContract, address auctionCurrency, address curator) external { + address tokenOwner = IERC721(tokenContract).ownerOf(tokenId); + require( + msg.sender == IERC721(tokenContract).getApproved(tokenId) || msg.sender == tokenOwner, + "Caller must be approved or owner for token id" + ); + uint256 auctionId = auctionIdCounter++; + + Auction storage auction = auctions[auctionId]; + auction.tokenId = tokenId; + auction.tokenContract = tokenContract; + auction.amount = 0; + auction.tokenOwner = tokenOwner; + auction.bidder = address(0); + auction.auctionCurrency = auctionCurrency; + auction.curator = curator; + + IERC721(tokenContract).transferFrom(tokenOwner, address(this), tokenId); + } + + /** + * @notice Create a bid on a token, with a given amount. + * @dev If provided a valid bid, transfers the provided amount to this contract. + * If the auction is run in native ETH, the ETH is wrapped so it can be identically to other + * auction currencies in this contract. + */ + function createBid(uint256 auctionId, uint256 amount) external payable { + address lastBidder = auctions[auctionId].bidder; + + require( + amount > auctions[auctionId].amount, + "Must send more than last bid by minBidIncrementPercentage amount" + ); + + // If it's not, then we should refund the last bidder + if (lastBidder != address(0)) { + _handleOutgoingBid(lastBidder, auctions[auctionId].amount, auctions[auctionId].auctionCurrency); + } + + _handleIncomingBid(amount, auctions[auctionId].auctionCurrency); + + auctions[auctionId].amount = amount; + auctions[auctionId].bidder = msg.sender; + } + + /** + * @notice End an auction paying out the respective parties. + * @dev If for some reason the auction cannot be finalized (invalid token recipient, for example), + * The auction reverts. + */ + function endAuction(uint256 auctionId) external { + uint256 tokenOwnerProfit = auctions[auctionId].amount; + + // Otherwise, transfer the token to the winner and pay out the participants below + try + IERC721(auctions[auctionId].tokenContract).safeTransferFrom( + address(this), + auctions[auctionId].bidder, + auctions[auctionId].tokenId + ) + {} catch (bytes memory reason) { + if (reason.length == 0) { + revert("Voucher transfer failed"); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + + _handleOutgoingBid(auctions[auctionId].tokenOwner, tokenOwnerProfit, auctions[auctionId].auctionCurrency); + + delete auctions[auctionId]; + } + + /** + * @notice Cancel an auction. + * @dev Transfers the NFT back to the auction creator and emits an AuctionCanceled event + */ + function cancelAuction(uint256 auctionId) external { + require( + auctions[auctionId].tokenOwner == msg.sender || auctions[auctionId].curator == msg.sender, + "Can only be called by auction creator or curator" + ); + _cancelAuction(auctionId); + } + + /** + * @dev Given an amount and a currency, transfer the currency to this contract. + * If the currency is ETH (0x0), attempt to wrap the amount as WETH + */ + function _handleIncomingBid(uint256 amount, address currency) internal { + // If this is an ETH bid, ensure they sent enough and convert it to WETH under the hood + if (currency == address(0)) { + require(msg.value == amount, "Sent ETH Value does not match specified bid amount"); + IWETH(wethAddress).deposit{ value: amount }(); + } else { + // We must check the balance that was actually transferred to the auction, + // as some tokens impose a transfer fee and would not actually transfer the + // full amount to the market, resulting in potentally locked funds + IERC20 token = IERC20(currency); + uint256 beforeBalance = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), amount); + uint256 afterBalance = token.balanceOf(address(this)); + require(beforeBalance + amount == afterBalance, "Token transfer call did not transfer expected amount"); + } + } + + function _handleOutgoingBid(address to, uint256 amount, address currency) internal { + // If the auction is in ETH, unwrap it from its underlying WETH and try to send it to the recipient. + if (currency == address(0)) { + IWETH(wethAddress).withdraw(amount); + + // If the ETH transfer fails (sigh), rewrap the ETH and try send it as WETH. + if (!_safeTransferETH(to, amount)) { + IWETH(wethAddress).deposit{ value: amount }(); + IERC20(wethAddress).safeTransfer(to, amount); + } + } else { + IERC20(currency).safeTransfer(to, amount); + } + } + + function _safeTransferETH(address to, uint256 value) internal returns (bool) { + (bool success, ) = to.call{ value: value }(new bytes(0)); + return success; + } + + function _cancelAuction(uint256 auctionId) internal { + address tokenOwner = auctions[auctionId].tokenOwner; + IERC721(auctions[auctionId].tokenContract).safeTransferFrom( + address(this), + tokenOwner, + auctions[auctionId].tokenId + ); + + emit AuctionCanceled(); + delete auctions[auctionId]; + } + + receive() external payable {} + + fallback() external payable {} +} diff --git a/contracts/mock/PriceDiscovery.sol b/contracts/mock/PriceDiscovery.sol index 7e4c261c5..2888fb0a0 100644 --- a/contracts/mock/PriceDiscovery.sol +++ b/contracts/mock/PriceDiscovery.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.21; import "../interfaces/IERC20.sol"; import "../interfaces/IERC721.sol"; import "./Foreign721.sol"; +import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol"; /** * @dev Simple price discovery contract used in tests @@ -154,3 +155,26 @@ contract PriceDiscoveryNoTransfer is PriceDiscovery { */ function fulfilBuyOrder(Order memory _order) public payable override {} } + +/** + * @dev Simple bad price discovery contract used in tests + * + * This contract transfers the voucher to itself instead of the origina msg.sender + */ +contract PriceDiscoveryTransferElsewhere is PriceDiscovery, IERC721Receiver { + /** + * @dev invoke fulfilBuyOrder on itself, making it the msg.sender + */ + function fulfilBuyOrderElsewhere(Order memory _order) public payable { + this.fulfilBuyOrder(_order); + } + + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/contracts/mock/TestProtocolFunctions.sol b/contracts/mock/TestProtocolFunctions.sol index 678a23530..dd808b7a8 100644 --- a/contracts/mock/TestProtocolFunctions.sol +++ b/contracts/mock/TestProtocolFunctions.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; +import { BosonTypes } from "../domain/BosonTypes.sol"; import { IBosonExchangeHandler } from "../interfaces/handlers/IBosonExchangeHandler.sol"; /** diff --git a/contracts/protocol/bases/BeaconClientBase.sol b/contracts/protocol/bases/BeaconClientBase.sol index a93222c3f..c80d2b9fb 100644 --- a/contracts/protocol/bases/BeaconClientBase.sol +++ b/contracts/protocol/bases/BeaconClientBase.sol @@ -62,12 +62,30 @@ abstract contract BeaconClientBase is BosonTypes { /** * @notice Informs protocol of new buyer associated with an exchange * - * @param _exchangeId - the id of the exchange + * @param _tokenId - the voucher id * @param _newBuyer - the address of the new buyer */ - function onVoucherTransferred(uint256 _exchangeId, address payable _newBuyer) internal { + function onVoucherTransferred(uint256 _tokenId, address payable _newBuyer) internal { + address protocolDiamond = IClientExternalAddresses(BeaconClientLib._beacon()).getProtocolAddress(); + IBosonExchangeHandler(protocolDiamond).onVoucherTransferred(_tokenId, _newBuyer); + } + + /** + * @notice Informs protocol of a pre-minted voucher transfer + * + * @param _tokenId - the voucher id + * @param _to - the address of the new buyer + * @param _from - the address of current owner + * @param _rangeOwner - the address of the preminted range owner + */ + function onPremintedVoucherTransferred( + uint256 _tokenId, + address payable _to, + address _from, + address _rangeOwner + ) internal returns (bool) { address protocolDiamond = IClientExternalAddresses(BeaconClientLib._beacon()).getProtocolAddress(); - IBosonExchangeHandler(protocolDiamond).onVoucherTransferred(_exchangeId, _newBuyer); + return IBosonExchangeHandler(protocolDiamond).onPremintedVoucherTransferred(_tokenId, _to, _from, _rangeOwner); } /** diff --git a/contracts/protocol/bases/BuyerBase.sol b/contracts/protocol/bases/BuyerBase.sol index a1a35cb00..6aa7e89e0 100644 --- a/contracts/protocol/bases/BuyerBase.sol +++ b/contracts/protocol/bases/BuyerBase.sol @@ -61,4 +61,34 @@ contract BuyerBase is ProtocolBase, IBosonAccountEvents { //Map the buyer's wallet address to the buyerId. protocolLookups().buyerIdByWallet[_buyer.wallet] = _buyer.id; } + + /** + * @notice Checks if buyer exists for buyer address. If not, account is created for buyer address. + * + * Reverts if buyer exists but is inactive. + * + * @param _buyer - the buyer address to check + * @return buyerId - the buyer id + */ + function getValidBuyer(address payable _buyer) internal returns (uint256 buyerId) { + // Find or create the account associated with the specified buyer address + bool exists; + (exists, buyerId) = getBuyerIdByWallet(_buyer); + + if (exists) { + // Fetch the existing buyer account + (, Buyer storage buyer) = fetchBuyer(buyerId); + + // Make sure buyer account is active + require(buyer.active, MUST_BE_ACTIVE); + } else { + // Create the buyer account + Buyer memory newBuyer; + newBuyer.wallet = _buyer; + newBuyer.active = true; + + createBuyerInternal(newBuyer); + buyerId = newBuyer.id; + } + } } diff --git a/contracts/protocol/bases/OfferBase.sol b/contracts/protocol/bases/OfferBase.sol index 238c4ff97..a7a7f05d4 100644 --- a/contracts/protocol/bases/OfferBase.sol +++ b/contracts/protocol/bases/OfferBase.sol @@ -224,9 +224,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { require(_offer.buyerCancelPenalty <= offerPrice, OFFER_PENALTY_INVALID); // Calculate and set the protocol fee - uint256 protocolFee = _offer.exchangeToken == protocolAddresses().token - ? protocolFees().flatBoson - : (protocolFees().percentage * offerPrice) / 10000; + uint256 protocolFee = getProtocolFee(_offer.exchangeToken, offerPrice); // Calculate the agent fee amount uint256 agentFeeAmount = (agent.feePercentage * offerPrice) / 10000; @@ -258,6 +256,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents { offer.metadataUri = _offer.metadataUri; offer.metadataHash = _offer.metadataHash; offer.collectionIndex = _offer.collectionIndex; + offer.priceType = _offer.priceType; // Get storage location for offer dates OfferDates storage offerDates = fetchOfferDates(_offer.id); diff --git a/contracts/protocol/bases/PriceDiscoveryBase.sol b/contracts/protocol/bases/PriceDiscoveryBase.sol index e6aa7c569..10dc1049b 100644 --- a/contracts/protocol/bases/PriceDiscoveryBase.sol +++ b/contracts/protocol/bases/PriceDiscoveryBase.sol @@ -20,7 +20,7 @@ contract PriceDiscoveryBase is ProtocolBase { using Address for address; using SafeERC20 for IERC20; - IWrappedNative public immutable wNative; + IWrappedNative internal immutable wNative; uint256 private immutable EXCHANGE_ID_2_2_0; // solhint-disable-line /** @@ -42,28 +42,47 @@ contract PriceDiscoveryBase is ProtocolBase { } /** - * @notice @notice Fulfils an order on an external contract. Helper function passes data to either ask or bid orders. + * @notice Fulfils an order on an external contract. * - * See descriptions of `fulfilBuyOrder` and `fulfilSellOrder` for more details. + * If the owner is price discovery contract, the protocol cannot act as an intermediary in the exchange, + * and sellers must use Wrapped's contract. Wrappers handle ask and bid orders in the same manner. * - * @param _exchangeId - the id of the exchange to commit to - * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * See descriptions of `fulfilAskOrder`, `fulfilBidOrder` and handleWrapper for more details. + * + * @param _tokenId - the id of the token. Accepts whatever token is sent by price discovery contract when this value is zero. + * @param _offer - the fully populated BosonTypes.Offer struct * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + * @param _seller - the seller's address * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _offer - the pointer to offer struct, corresponding to the exchange * @return actualPrice - the actual price of the order */ - function fulFilOrder( - uint256 _exchangeId, - address _exchangeToken, + function fulfilOrder( + uint256 _tokenId, + Offer storage _offer, PriceDiscovery calldata _priceDiscovery, - address _buyer, - Offer storage _offer + address _seller, + address _buyer ) internal returns (uint256 actualPrice) { + require( + _priceDiscovery.priceDiscoveryContract != address(0) && _priceDiscovery.conduit != address(0), + PRICE_DISCOVERY_CONTRACTS_NOT_SET + ); + + IBosonVoucher bosonVoucher = IBosonVoucher( + getCloneAddress(protocolLookups(), _offer.sellerId, _offer.collectionIndex) + ); + + // Set incoming voucher clone address + protocolStatus().incomingVoucherCloneAddress = address(bosonVoucher); + if (_priceDiscovery.side == Side.Ask) { - return fulfilAskOrder(_exchangeId, _exchangeToken, _priceDiscovery, _buyer, _offer); + return fulfilAskOrder(_tokenId, _offer.exchangeToken, _priceDiscovery, _buyer, bosonVoucher); + } else if (_priceDiscovery.side == Side.Bid) { + return fulfilBidOrder(_tokenId, _offer.exchangeToken, _priceDiscovery, _seller, bosonVoucher); } else { - return fulfilBidOrder(_exchangeId, _exchangeToken, _priceDiscovery, _offer); + // _priceDiscovery.side == Side.Wrapper + // Handle wrapper voucher, there is no difference between ask and bid + return handleWrapper(_tokenId, _offer.exchangeToken, _priceDiscovery, bosonVoucher); } } @@ -74,139 +93,200 @@ contract PriceDiscoveryBase is ProtocolBase { * - Offer price is in native token and caller does not send enough * - Offer price is in some ERC20 token and caller also sends native currency * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) - * - Received ERC20 token amount differs from the expected value - * - Protocol does not receive the voucher - * - Transfer of voucher to the buyer fails for some reasong (e.g. buyer is contract that doesn't accept voucher) * - Call to price discovery contract fails + * - Received amount is greater from price set in price discovery + * - Protocol does not receive the voucher + * - Transfer of voucher to the buyer fails for some reason (e.g. buyer is contract that doesn't accept voucher) + * - New voucher owner is not buyer wallet + * - Token id sent to buyer and token id set by the caller don't match (if caller has provided token id) * - * @param _exchangeId - the id of the exchange to commit to - * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * @param _tokenId - the id of the token + * @param _exchangeToken - the address of the exchange contract * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _offer - the pointer to offer struct, corresponding to the exchange + * @param _bosonVoucher - the boson voucher contract * @return actualPrice - the actual price of the order */ function fulfilAskOrder( - uint256 _exchangeId, + uint256 _tokenId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, address _buyer, - Offer storage _offer + IBosonVoucher _bosonVoucher ) internal returns (uint256 actualPrice) { // Transfer buyers funds to protocol FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price); - // At this point, protocol temporary holds buyer's payment - uint256 protocolBalanceBefore = getBalance(_exchangeToken); - - // If token is ERC20, approve price discovery contract to transfer funds + // If token is ERC20, approve price discovery contract to transfer protocol funds if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).forceApprove(address(_priceDiscovery.priceDiscoveryContract), _priceDiscovery.price); + IERC20(_exchangeToken).forceApprove(_priceDiscovery.conduit, _priceDiscovery.price); } - // Store the information about incoming voucher - ProtocolLib.ProtocolStatus storage ps = protocolStatus(); - address cloneAddress = getCloneAddress(protocolLookups(), _offer.sellerId, _offer.collectionIndex); - uint256 tokenId; - { - tokenId = _exchangeId; - if (tokenId >= EXCHANGE_ID_2_2_0) tokenId |= (_offer.id << 128); - ps.incomingVoucherId = tokenId; - ps.incomingVoucherCloneAddress = cloneAddress; - } + uint256 protocolBalanceBefore = getBalance(_exchangeToken, address(this)); + // Call the price discovery contract _priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value); - // Make sure that the price discovery contract has transferred the voucher to the protocol - IBosonVoucher bosonVoucher = IBosonVoucher(cloneAddress); - require(bosonVoucher.ownerOf(tokenId) == address(this), VOUCHER_NOT_RECEIVED); + uint256 protocolBalanceAfter = getBalance(_exchangeToken, address(this)); + require(protocolBalanceBefore >= protocolBalanceAfter, NEGATIVE_PRICE_NOT_ALLOWED); + actualPrice = protocolBalanceBefore - protocolBalanceAfter; // If token is ERC20, reset approval if (_exchangeToken != address(0)) { - IERC20(_exchangeToken).forceApprove(address(_priceDiscovery.priceDiscoveryContract), 0); + IERC20(_exchangeToken).forceApprove(address(_priceDiscovery.conduit), 0); } - // Clear the storage - delete ps.incomingVoucherId; - delete ps.incomingVoucherCloneAddress; + _tokenId = getAndVerifyTokenId(_tokenId); - // Check the escrow amount - uint256 protocolBalanceAfter = getBalance(_exchangeToken); - require(protocolBalanceBefore >= protocolBalanceAfter, NEGATIVE_PRICE_NOT_ALLOWED); - actualPrice = protocolBalanceBefore - protocolBalanceAfter; + { + // Make sure that the price discovery contract has transferred the voucher to the protocol + require(_bosonVoucher.ownerOf(_tokenId) == address(this), VOUCHER_NOT_RECEIVED); + + // Transfer voucher to buyer + _bosonVoucher.transferFrom(address(this), _buyer, _tokenId); + } uint256 overchargedAmount = _priceDiscovery.price - actualPrice; if (overchargedAmount > 0) { - // Return the surplus to buyer - FundsLib.transferFundsFromProtocol(_exchangeToken, payable(_buyer), overchargedAmount); + // Return the surplus to caller + FundsLib.transferFundsFromProtocol(_exchangeToken, payable(msgSender()), overchargedAmount); } - - // Transfer voucher to buyer - bosonVoucher.transferFrom(address(this), _buyer, tokenId); } /** * @notice Fulfils a bid order on external contract. * * Reverts if: - * - Voucher owner did not approve protocol to transfer the voucher - * - Price received from price discovery is lower than the expected price - * - Reseller did not approve protocol to transfer exchange token in escrow + * - Token id not set by the caller + * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) + * - Transfer of voucher to the buyer fails for some reason (e.g. buyer is contract that doesn't accept voucher) + * - Received ERC20 token amount differs from the expected value + * - Call to price discovery contract fails + * - Protocol balance change after price discovery call is lower than the expected price + * - Reseller did not approve protocol to transfer exchange token in escrow + * - New voucher owner is not buyer wallet + * - Token id sent to buyer and token id set by the caller don't match * - * @param _exchangeId - the id of the exchange to commit to - * @param _exchangeToken - the address of the ERC20 token used for the exchange (zero address for native) + * @param _tokenId - the id of the token + * @param _exchangeToken - the address of the exchange token * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct - * @param _offer - the pointer to offer struct, corresponding to the exchange + * @param _seller - the seller's address + * @param _bosonVoucher - the boson voucher contract * @return actualPrice - the actual price of the order */ function fulfilBidOrder( - uint256 _exchangeId, + uint256 _tokenId, address _exchangeToken, PriceDiscovery calldata _priceDiscovery, - Offer storage _offer + address _seller, + IBosonVoucher _bosonVoucher ) internal returns (uint256 actualPrice) { - IBosonVoucher bosonVoucher = IBosonVoucher( - getCloneAddress(protocolLookups(), _offer.sellerId, _offer.collectionIndex) - ); + require(_tokenId != 0, TOKEN_ID_MANDATORY); + + address sender = msgSender(); + require(_seller == sender, NOT_VOUCHER_HOLDER); // Transfer seller's voucher to protocol // Don't need to use safe transfer from, since that protocol can handle the voucher - uint256 tokenId = _exchangeId; - if (tokenId >= EXCHANGE_ID_2_2_0) tokenId |= (_offer.id << 128); - bosonVoucher.transferFrom(msgSender(), address(this), tokenId); + _bosonVoucher.transferFrom(sender, address(this), _tokenId); + // Approve conduit to transfer voucher. There is no need to reset approval afterwards, since protocol is not the voucher owner anymore + _bosonVoucher.approve(_priceDiscovery.conduit, _tokenId); if (_exchangeToken == address(0)) _exchangeToken = address(wNative); - // Get protocol balance before the exchange - uint256 protocolBalanceBefore = getBalance(_exchangeToken); - // Track native balance just in case if seller sends some native currency or price discovery contract does // This is the balance that protocol had, before commit to offer was called - uint256 protocolNativeBalanceBefore = getBalance(address(0)) - msg.value; + uint256 protocolNativeBalanceBefore = getBalance(address(0), address(this)) - msg.value; - // Approve price discovery contract to transfer voucher. There is no need to reset approval afterwards, since protocol is not the voucher owner anymore - bosonVoucher.approve(_priceDiscovery.priceDiscoveryContract, tokenId); + // Get protocol balance before calling price discovery contract + uint256 protocolBalanceBefore = getBalance(_exchangeToken, address(this)); // Call the price discovery contract _priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value); - // Check the escrow amount - uint256 protocolBalanceAfter = getBalance(_exchangeToken); + // Get protocol balance after calling price discovery contract + uint256 protocolBalanceAfter = getBalance(_exchangeToken, address(this)); // Check the native balance and return the surplus to seller - uint256 protocolNativeBalanceAfter = getBalance(address(0)); + uint256 protocolNativeBalanceAfter = getBalance(address(0), address(this)); if (protocolNativeBalanceAfter > protocolNativeBalanceBefore) { // Return the surplus to seller FundsLib.transferFundsFromProtocol( address(0), - payable(msgSender()), + payable(sender), protocolNativeBalanceAfter - protocolNativeBalanceBefore ); } + // Calculate actual price + require(protocolBalanceAfter >= protocolBalanceBefore, NEGATIVE_PRICE_NOT_ALLOWED); actualPrice = protocolBalanceAfter - protocolBalanceBefore; + + // Make sure that balance change is at least the expected price require(actualPrice >= _priceDiscovery.price, INSUFFICIENT_VALUE_RECEIVED); + + // Verify that token id provided by caller matches the token id that the price discovery contract has sent to buyer + getAndVerifyTokenId(_tokenId); + } + + /* + * @notice Call `unwrap` (or equivalent) function on the price discovery contract. + * + * Reverts if: + * - Token id not set by the caller + * - Protocol balance doesn't increase by the expected amount. + * Balance change must be equal to the price set by the caller + * - Token id sent to buyer and token id set by the caller don't match + * + * @param _tokenId - the id of the token + * @param _exchangeToken - the address of the exchange contract + * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct + * @param _bosonVoucher - the boson voucher contract + * @return actualPrice - the actual price of the order + */ + function handleWrapper( + uint256 _tokenId, + address _exchangeToken, + PriceDiscovery calldata _priceDiscovery, + IBosonVoucher _bosonVoucher + ) internal returns (uint256 actualPrice) { + require(_tokenId != 0, TOKEN_ID_MANDATORY); + + // If price discovery contract does not own the voucher, it cannot be classified as a wrapper + address owner = _bosonVoucher.ownerOf(_tokenId); + require(owner == _priceDiscovery.priceDiscoveryContract, NOT_VOUCHER_HOLDER); + + // Check balance before calling wrapper + bool isNative = _exchangeToken == address(0); + if (isNative) _exchangeToken = address(wNative); + uint256 protocolBalanceBefore = getBalance(_exchangeToken, address(this)); + + // Track native balance just in case if seller sends some native currency. + // All native currency is forwarded to the wrapper, which should not return any back. + // If it does, we revert later in the code. + uint256 protocolNativeBalanceBefore = getBalance(address(0), address(this)) - msg.value; + + // Call the price discovery contract + _priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value); + + // Check the native balance and revert if there is a surplus + uint256 protocolNativeBalanceAfter = getBalance(address(0), address(this)); + require(protocolNativeBalanceAfter == protocolNativeBalanceBefore); + + // Check balance after the price discovery call + uint256 protocolBalanceAfter = getBalance(_exchangeToken, address(this)); + + // Verify that actual price is within the expected range + require(protocolBalanceAfter >= protocolBalanceBefore, NEGATIVE_PRICE_NOT_ALLOWED); + actualPrice = protocolBalanceAfter - protocolBalanceBefore; + + // when working with wrappers, price is already known, so the caller should set it exactly + // If protocol receive more than expected, it does not return the surplus to the caller + require(actualPrice == _priceDiscovery.price, PRICE_TOO_LOW); + + // Verify that token id provided by caller matches the token id that the price discovery contract has sent to buyer + getAndVerifyTokenId(_tokenId); } /** @@ -215,7 +295,45 @@ contract PriceDiscoveryBase is ProtocolBase { * @param _tokenAddress - the address of the token to check the balance for * @return balance - the balance of the protocol for the given token address */ - function getBalance(address _tokenAddress) internal view returns (uint256) { - return _tokenAddress == address(0) ? address(this).balance : IERC20(_tokenAddress).balanceOf(address(this)); + function getBalance(address _tokenAddress, address entity) internal view returns (uint256) { + return _tokenAddress == address(0) ? entity.balance : IERC20(_tokenAddress).balanceOf(entity); + } + + /* + * @notice Returns the token id that the price discovery contract has sent to the protocol or buyer + * + * Reverts if: + * - Caller has provided token id, but it does not match the token id that the price discovery contract has sent to the protocol + * + * @param _tokenId - the token id that the caller has provided + * @return tokenId - the token id that the price discovery contract has sent to the protocol + */ + function getAndVerifyTokenId(uint256 _tokenId) internal view returns (uint256) { + // Store the information about incoming voucher + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + + // If caller has provided token id, it must match the token id that the price discovery send to the protocol + if (_tokenId != 0) { + require(_tokenId == ps.incomingVoucherId, TOKEN_ID_MISMATCH); + } else { + // If caller has not provided token id, use the one stored in onPremintedVoucherTransfer function + _tokenId = ps.incomingVoucherId; + } + + // Token id cannot be zero at this point + require(_tokenId != 0, TOKEN_ID_NOT_SET); + + return _tokenId; + } + + /* + * @notice Resets value of incoming voucher id and incoming voucher clone address to 0 + * This is called at the end of the methods that interacts with price discovery contracts + * + */ + function clearPriceDiscoveryStorage() internal { + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + delete ps.incomingVoucherId; + delete ps.incomingVoucherCloneAddress; } } diff --git a/contracts/protocol/bases/ProtocolBase.sol b/contracts/protocol/bases/ProtocolBase.sol index 6ed2bf5c1..c8094f393 100644 --- a/contracts/protocol/bases/ProtocolBase.sol +++ b/contracts/protocol/bases/ProtocolBase.sol @@ -684,6 +684,21 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase { exists = (_exchangeId > 0 && condition.method != EvaluationMethod.None); } + /** + * @notice calculate the protocol fee for a given exchange + * + * @param _exchangeToken - the token used for the exchange + * @param _price - the price of the exchange + * @return protocolFee - the protocol fee + */ + function getProtocolFee(address _exchangeToken, uint256 _price) internal view returns (uint256 protocolFee) { + // Calculate and set the protocol fee + return + _exchangeToken == protocolAddresses().token + ? protocolFees().flatBoson + : (protocolFees().percentage * _price) / 10000; + } + /** * @notice Fetches a clone address from storage by seller id and collection index * If the collection index is 0, the clone address is the seller's main collection, diff --git a/contracts/protocol/clients/voucher/BosonVoucher.sol b/contracts/protocol/clients/voucher/BosonVoucher.sol index 3a35f95be..a4ff269ef 100644 --- a/contracts/protocol/clients/voucher/BosonVoucher.sol +++ b/contracts/protocol/clients/voucher/BosonVoucher.sol @@ -52,10 +52,10 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable // Map an offerId to a Range for pre-minted offers mapping(uint256 => Range) private _rangeByOfferId; - // Premint status, used only temporarly in transfers - bool private _isCommitable; + // Used only temporarly in transfers + bool private _isCommittable; - // Tell if preminted voucher has already been _committed + // Tell if voucher has already been _committed mapping(uint256 => bool) private _committed; /** @@ -368,10 +368,13 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable // If _tokenId exists, it does not matter if vouchers were preminted or not return super.ownerOf(_tokenId); } else { - bool committable; // If _tokenId does not exist, but offer is committable, report contract owner as token owner - (committable, owner) = getPreMintStatus(_tokenId); - if (committable) return owner; + bool committable = isTokenCommittable(_tokenId); + + if (committable) { + owner = _rangeByOfferId[_tokenId >> 128].owner; + return owner; + } // Otherwise revert revert(ERC721_INVALID_TOKEN_ID); @@ -386,11 +389,15 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable address _to, uint256 _tokenId ) public virtual override(ERC721Upgradeable, IERC721Upgradeable) { - (bool committable, ) = getPreMintStatus(_tokenId); + bool committable = isTokenCommittable(_tokenId); if (committable) { - // If offer is committable, temporarily update _owners, so transfer succeeds - silentMintAndSetPremintStatus(_from, _tokenId); + if (_from == address(this) || _from == owner()) { + // If offer is committable, temporarily update _owners, so transfer succeeds + silentMint(_from, _tokenId); + } + + _isCommittable = true; } super.transferFrom(_from, _to, _tokenId); @@ -405,11 +412,15 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable uint256 _tokenId, bytes memory _data ) public virtual override(ERC721Upgradeable, IERC721Upgradeable) { - (bool committable, ) = getPreMintStatus(_tokenId); + bool committable = isTokenCommittable(_tokenId); if (committable) { - // If offer is committable, temporarily update _owners, so transfer succeeds - silentMintAndSetPremintStatus(_from, _tokenId); + if (_from == address(this) || _from == owner()) { + // If offer is committable, temporarily update _owners, so transfer succeeds + silentMint(_from, _tokenId); + } + + _isCommittable = true; } super.safeTransferFrom(_from, _to, _tokenId, _data); @@ -458,7 +469,8 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable (bool exists, Offer memory offer) = getBosonOfferByExchangeId(exchangeId); if (!exists) { - (bool committable, ) = getPreMintStatus(_tokenId); + bool committable = isTokenCommittable(_tokenId); + if (committable) { uint256 offerId = _tokenId >> 128; exists = true; @@ -698,47 +710,36 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable * @param - this parameter is ignored, but required to match the signature of the parent method */ function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId, uint256) internal override { - // Derive the exchange id - uint256 exchangeId = _tokenId & type(uint128).max; - if (_isCommitable) { - // If is committable, invoke commitToPreMintedOffer on the protocol - + // If is committable, invoke onPremintedVoucherTransferred on the protocol + if (_isCommittable) { // Set _isCommitable to false - _isCommitable = false; + _isCommittable = false; - // Set the preminted token as committed - _committed[_tokenId] = true; + address rangeOwner = _rangeByOfferId[_tokenId >> 128].owner; - // Derive the offer id - uint256 offerId = _tokenId >> 128; + // Call protocol onPremintedVoucherTransferred + bool committed = onPremintedVoucherTransferred(_tokenId, payable(_to), _from, rangeOwner); - // If this is a transfer of preminted token, treat it differently - address protocolDiamond = IClientExternalAddresses(BeaconClientLib._beacon()).getProtocolAddress(); - IBosonExchangeHandler(protocolDiamond).commitToPreMintedOffer(payable(_to), offerId, exchangeId); + // Set committed status + _committed[_tokenId] = committed; } else if (_from != address(0) && _to != address(0) && _from != _to) { // Update the buyer associated with the voucher in the protocol // Only when transferring, not when minting or burning - onVoucherTransferred(exchangeId, payable(_to)); + onVoucherTransferred(_tokenId, payable(_to)); } } /** - * @dev Determines if a token is pre-minted and committable via transfer hook + * @notice Verify if token is committable. * - * Committable means: - * - does not yet have an owner - * - in a reserved range - * - has been pre-minted - * - has not been already burned + * @param _tokenId - the tokenId of the voucher that is being transferred * - * @param _tokenId - the token id to check - * @return committable - whether the token is committable - * @return owner - the token owner + * @return committable - true if the voucher is committable */ - function getPreMintStatus(uint256 _tokenId) public view returns (bool committable, address owner) { - // Not committable if _committed already or if token has an owner - - if (!_committed[_tokenId]) { + function isTokenCommittable(uint256 _tokenId) public view returns (bool committable) { + if (_committed[_tokenId]) { + return false; + } else { // it might be a pre-minted token. Preminted tokens have offerId in the upper 128 bits uint256 offerId = _tokenId >> 128; @@ -757,9 +758,8 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable start + range.minted - 1 >= _tokenId && _tokenId > range.lastBurnedTokenId ) { - // Has it been pre-minted and not burned yet? + // Has it been pre-minted, not burned yet committable = true; - owner = range.owner; } } } @@ -818,14 +818,30 @@ contract BosonVoucherBase is IBosonVoucher, BeaconClientBase, OwnableUpgradeable /* * Updates owners, but do not emit Transfer event. Event was already emited during pre-mint. */ - function silentMintAndSetPremintStatus(address _from, uint256 _tokenId) internal { + function silentMint(address _from, uint256 _tokenId) internal { require(_from == owner() || _from == address(this), NO_SILENT_MINT_ALLOWED); // update data, so transfer will succeed getERC721UpgradeableStorage()._owners[_tokenId] = _from; + } + + /* + * Override ERC721Upgradeable._isApprovedOrOwner to check for pre-minted tokens + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view override returns (bool) { + address owner = ownerOf(tokenId); + return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); + } + + /* + ** + * @dev Reverts if the `_tokenId` has not been minted yet and is not a pre-minted token. + */ + function _requireMinted(uint256 _tokenId) internal view override { + // If token is committable, it is a pre-minted token + bool committable = isTokenCommittable(_tokenId); - // Update commitable status - _isCommitable = true; + require(_exists(_tokenId) || committable, "ERC721: invalid token ID"); } } diff --git a/contracts/protocol/facets/ExchangeHandlerFacet.sol b/contracts/protocol/facets/ExchangeHandlerFacet.sol index 4db07d646..0c8c0f42f 100644 --- a/contracts/protocol/facets/ExchangeHandlerFacet.sol +++ b/contracts/protocol/facets/ExchangeHandlerFacet.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.21; +import "../../domain/BosonConstants.sol"; import { IBosonExchangeHandler } from "../../interfaces/handlers/IBosonExchangeHandler.sol"; import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; import { ITwinToken } from "../../interfaces/ITwinToken.sol"; @@ -9,7 +10,7 @@ import { BuyerBase } from "../bases/BuyerBase.sol"; import { DisputeBase } from "../bases/DisputeBase.sol"; import { ProtocolLib } from "../libs/ProtocolLib.sol"; import { FundsLib } from "../libs/FundsLib.sol"; -import "../../domain/BosonConstants.sol"; +import { IERC721Receiver } from "../../interfaces/IERC721Receiver.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; @@ -20,8 +21,9 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; * * @notice Handles exchanges associated with offers within the protocol. */ -contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { +contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase, IERC721Receiver { using Address for address; + using Address for address payable; uint256 private immutable EXCHANGE_ID_2_2_0; // solhint-disable-line @@ -46,7 +48,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } /** - * @notice Commits to an offer (first step of an exchange). + * @notice Commits to a price static offer (first step of an exchange). * * Emits a BuyerCommitted event if successful. * Issues a voucher to the buyer address. @@ -55,6 +57,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * - The exchanges region of protocol is paused * - The buyers region of protocol is paused * - OfferId is invalid + * - Offer price type is not static * - Offer has been voided * - Offer has expired * - Offer is not yet available for commits @@ -80,6 +83,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { require(_buyer != address(0), INVALID_ADDRESS); Offer storage offer = getValidOffer(_offerId); + require(offer.priceType == PriceType.Static, INVALID_PRICE_TYPE); // For there to be a condition, there must be a group. (bool exists, uint256 groupId) = getGroupIdByOffer(offer.id); @@ -132,6 +136,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { require(_buyer != address(0), INVALID_ADDRESS); Offer storage offer = getValidOffer(_offerId); + require(offer.priceType == PriceType.Static, INVALID_PRICE_TYPE); // For there to be a condition, there must be a group. (bool exists, uint256 groupId) = getGroupIdByOffer(offer.id); @@ -154,76 +159,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } /** - * @notice Commits to a preminted offer (first step of an exchange). - * - * Emits BuyerCommitted and ConditionalCommitAuthorized events if successful. - * - * Reverts if: - * - The exchanges region of protocol is paused - * - The buyers region of protocol is paused - * - Caller is not the voucher contract, owned by the seller - * - Exchange exists already - * - Offer has been voided - * - Offer has expired - * - Offer is not yet available for commits - * - Buyer account is inactive - * - Buyer is token-gated (conditional commit requirements not met or already used) - * - Buyer is token-gated and condition has a range. - * - Seller has less funds available than sellerDeposit and price - * - * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _offerId - the id of the offer to commit to - * @param _exchangeId - the id of the exchange - */ - function commitToPreMintedOffer( - address payable _buyer, - uint256 _offerId, - uint256 _exchangeId - ) external exchangesNotPaused buyersNotPaused nonReentrant { - Offer storage offer = getValidOffer(_offerId); - ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); - - // Make sure that the voucher was issued on the clone that is making a call - require(msg.sender == getCloneAddress(lookups, offer.sellerId, offer.collectionIndex), ACCESS_DENIED); - - // Exchange must not exist already - (bool exists, ) = fetchExchange(_exchangeId); - require(!exists, EXCHANGE_ALREADY_EXISTS); - - uint256 groupId; - (exists, groupId) = getGroupIdByOffer(offer.id); - - if (exists) { - // Get the condition - Condition storage condition = fetchCondition(groupId); - EvaluationMethod method = condition.method; - - if (method != EvaluationMethod.None) { - uint256 tokenId = 0; - - // Allow commiting only to unambigous conditions, i.e. conditions with a single token id - if (condition.method == EvaluationMethod.SpecificToken || condition.tokenType == TokenType.MultiToken) { - uint256 minTokenId = condition.minTokenId; - uint256 maxTokenId = condition.maxTokenId; - - require(minTokenId == maxTokenId || maxTokenId == 0, CANNOT_COMMIT); // legacy conditions have maxTokenId == 0 - - // Uses token id from the condition - tokenId = minTokenId; - } - - authorizeCommit(_buyer, condition, groupId, tokenId, _offerId); - - // Store the condition to be returned afterward on getReceipt function - lookups.exchangeCondition[_exchangeId] = condition; - } - } - - commitToOfferInternal(_buyer, offer, _exchangeId, true); - } - - /** - * @notice Commits to an offer. Helper function reused by commitToOffer and commitToPreMintedOffer. + * @notice Commits to an offer. Helper function reused by commitToOffer and onPremintedVoucherTransferred. * * Emits a BuyerCommitted event if successful. * Issues a voucher to the buyer address for non preminted offers. @@ -241,7 +177,9 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * - Calling transferFrom on token fails for some reason (e.g. protocol is not approved to transfer) * - Received ERC20 token amount differs from the expected value * - Seller has less funds available than sellerDeposit - * - Seller has less funds available than sellerDeposit and price for preminted offers + * - For preminted offers: + * - Exchange aldready exists + * - Seller has less funds available than sellerDeposit and price for preminted offers that price type is static * * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) * @param _offer - storage pointer to the offer @@ -256,7 +194,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { bool _isPreminted ) internal returns (uint256) { uint256 _offerId = _offer.id; - // Make sure offer is available, and isn't void, expired, or sold out + // Make sure offer is available, expired, or sold out OfferDates storage offerDates = fetchOfferDates(_offerId); require(block.timestamp >= offerDates.validFrom, OFFER_NOT_AVAILABLE); require(block.timestamp <= offerDates.validUntil, OFFER_HAS_EXPIRED); @@ -267,13 +205,18 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Get next exchange id for non-preminted offers _exchangeId = protocolCounters().nextExchangeId++; + } else { + // Exchange must not exist already + (bool exists, ) = fetchExchange(_exchangeId); + + require(!exists, EXCHANGE_ALREADY_EXISTS); } // Fetch or create buyer uint256 buyerId = getValidBuyer(_buyer); - // Encumber funds before creating the exchange - FundsLib.encumberFunds(_offerId, buyerId, _isPreminted); + // Encumber funds + FundsLib.encumberFunds(_offerId, buyerId, _offer.price, _isPreminted, _offer.priceType); // Create and store a new exchange Exchange storage exchange = protocolEntities().exchanges[_exchangeId]; @@ -592,6 +535,7 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * Emits a VoucherTransferred event if successful. * * Reverts if + * - The exchanges region of protocol is paused * - The buyers region of protocol is paused * - Caller is not a clone address associated with the seller * - Exchange does not exist @@ -599,15 +543,21 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { * - Voucher has expired * - New buyer's existing account is deactivated * - * @param _exchangeId - the id of the exchange + * @param _tokenId - the voucher id * @param _newBuyer - the address of the new buyer */ - function onVoucherTransferred(uint256 _exchangeId, address payable _newBuyer) external override buyersNotPaused { + function onVoucherTransferred( + uint256 _tokenId, + address payable _newBuyer + ) external override buyersNotPaused exchangesNotPaused { + // Derive the exchange id + uint256 exchangeId = _tokenId & type(uint128).max; + // Cache protocol lookups for reference ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); // Get the exchange, should be in committed state - (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); + (Exchange storage exchange, Voucher storage voucher) = getValidExchange(exchangeId, ExchangeState.Committed); // Make sure that the voucher is still valid require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); @@ -629,8 +579,150 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { // Increase voucher counter for new buyer lookups.voucherCount[buyerId]++; + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + + // Set incoming voucher id if we are in the middle of a price discovery call + if (ps.incomingVoucherCloneAddress != address(0)) { + uint256 incomingVoucherId = ps.incomingVoucherId; + if (incomingVoucherId != _tokenId) { + require(incomingVoucherId == 0, INCOMING_VOUCHER_ALREADY_SET); + ps.incomingVoucherId = _tokenId; + } + } + // Notify watchers of state change - emit VoucherTransferred(exchange.offerId, _exchangeId, buyerId, msgSender()); + emit VoucherTransferred(exchange.offerId, exchangeId, buyerId, msgSender()); + } + + /** + * @notice Handle pre-minted voucher transfer + * + * Reverts if: + * - The exchanges region of protocol is paused + * - The buyers region of protocol is paused + * - Caller is not a clone address associated with the seller + * - Incoming voucher clone address is not the caller + * - Offer price is discovery, transaction is not starting from protocol nor seller is _from address + * - Any reason that ExchangeHandler commitToOfferInternal reverts. See ExchangeHandler.commitToOfferInternal + * + * @param _tokenId - the voucher id + * @param _to - the receiver address + * @param _from - the address of current owner + * @param _rangeOwner - the address of the preminted range owner + * @return committed - true if the voucher was committed + */ + function onPremintedVoucherTransferred( + uint256 _tokenId, + address payable _to, + address _from, + address _rangeOwner + ) external override buyersNotPaused exchangesNotPaused returns (bool committed) { + // Cache protocol status for reference + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + + // Make sure that protocol is not reentered + // Cannot use modifier `nonReentrant` since it also changes reentrancyStatus to `ENTERED` + // This would break the flow since the protocol should be allowed to re-enter in this case. + require(ps.reentrancyStatus != ENTERED, REENTRANCY_GUARD); + + // Derive the offer id + uint256 offerId = _tokenId >> 128; + + // Derive the exchange id + uint256 exchangeId = _tokenId & type(uint128).max; + + // Get the offer + Offer storage offer = getValidOffer(offerId); + + ProtocolLib.ProtocolLookups storage lookups = protocolLookups(); + address bosonVoucher = getCloneAddress(lookups, offer.sellerId, offer.collectionIndex); + + // Make sure that the voucher was issued on the clone that is making a call + require(msg.sender == bosonVoucher, ACCESS_DENIED); + + (bool conditionExists, uint256 groupId) = getGroupIdByOffer(offerId); + + if (conditionExists) { + // Get the condition + Condition storage condition = fetchCondition(groupId); + EvaluationMethod method = condition.method; + + if (method != EvaluationMethod.None) { + uint256 tokenId = 0; + + // Allow commiting only to unambigous conditions, i.e. conditions with a single token id + if (method == EvaluationMethod.SpecificToken || condition.tokenType == TokenType.MultiToken) { + uint256 minTokenId = condition.minTokenId; + uint256 maxTokenId = condition.maxTokenId; + + require(minTokenId == maxTokenId || maxTokenId == 0, CANNOT_COMMIT); // legacy conditions have maxTokenId == 0 + + // Uses token id from the condition + tokenId = minTokenId; + } + + authorizeCommit(_to, condition, groupId, tokenId, offerId); + + // Store the condition to be returned afterward on getReceipt function + lookups.exchangeCondition[exchangeId] = condition; + } + } + + if (offer.priceType == PriceType.Discovery) { + // transaction start from `commitToPriceDiscoveryOffer`, should commit + if (ps.incomingVoucherCloneAddress != address(0)) { + // During price discovery, the voucher is firs transferred to the protocol, which should + // not resulte in a commit yet. The commit should happen when the voucher is transferred + // from the protocol to the buyer. + if (_to == address(this)) { + // can someone buys on protocol's behalf? what happens then + + // Avoid reentrancy + require(ps.incomingVoucherId == 0, INCOMING_VOUCHER_ALREADY_SET); + + // Store the information about incoming voucher + ps.incomingVoucherId = _tokenId; + } else { + if (ps.incomingVoucherId == 0) { + // Happens in wrapped voucher vase + ps.incomingVoucherId = _tokenId; + } else { + // In other cases voucher was already once transferred to the protocol, + // so ps.incomingVoucherId is set already. The incoming _tokenId must match. + require(ps.incomingVoucherId == _tokenId, TOKEN_ID_MISMATCH); + } + commitToOfferInternal(_to, offer, exchangeId, true); + + committed = true; + } + + return committed; + } + + // If `onPremintedVoucherTransferred` is invoked without `commitToPriceDiscoveryOffer` first, + // we reach this point. This can happen in the following scenarios: + // 1. The preminted voucher owner is transferring the voucher to PD contract ["deposit"] + // 2. The PD is transferring the voucher back to the original owner ["withdraw"]. Happens if voucher was not sold. + // 3. The PD is transferring the voucher to the buyer ["buy"]. Happens if voucher was sold. + // 4. The preminted voucher owner is transferring the voucher "directly" to the buyer. + + // 1. and 2. are allowed, while 3. and 4. and must revert. 3. and 4. should be executed via `commitToPriceDiscoveryOffer` + if (_from == _rangeOwner) { + // case 1. ["deposit"] + if (!_to.isContract()) { + // Prevent direct transfer to EOA (case 4.) + revert(VOUCHER_TRANSFER_NOT_ALLOWED); + } + } else { + // Case 2. ["withdraw"] + // Prevent transfer to the buyer (case 3.) + require(_to == _rangeOwner, VOUCHER_TRANSFER_NOT_ALLOWED); + } + } else if (offer.priceType == PriceType.Static) { + // If price type is static, transaction can start from anywhere + commitToOfferInternal(_to, offer, exchangeId, true); + committed = true; + } } /** @@ -1040,36 +1132,6 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } } - /** - * @notice Checks if buyer exists for buyer address. If not, account is created for buyer address. - * - * Reverts if buyer exists but is inactive. - * - * @param _buyer - the buyer address to check - * @return buyerId - the buyer id - */ - function getValidBuyer(address payable _buyer) internal returns (uint256 buyerId) { - // Find or create the account associated with the specified buyer address - bool exists; - (exists, buyerId) = getBuyerIdByWallet(_buyer); - - if (!exists) { - // Create the buyer account - Buyer memory newBuyer; - newBuyer.wallet = _buyer; - newBuyer.active = true; - - createBuyerInternal(newBuyer); - buyerId = newBuyer.id; - } else { - // Fetch the existing buyer account - (, Buyer storage buyer) = fetchBuyer(buyerId); - - // Make sure buyer account is active - require(buyer.active, MUST_BE_ACTIVE); - } - } - /** * @notice Authorizes the potential buyer to commit to an offer * @@ -1258,6 +1320,26 @@ contract ExchangeHandlerFacet is IBosonExchangeHandler, BuyerBase, DisputeBase { } } + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address, + address, + uint256 _tokenId, + bytes calldata + ) public virtual override returns (bytes4) { + ProtocolLib.ProtocolStatus storage ps = protocolStatus(); + + require( + ps.incomingVoucherId == _tokenId && ps.incomingVoucherCloneAddress == msg.sender, + UNEXPECTED_ERC721_RECEIVED + ); + return this.onERC721Received.selector; + } + /** * @notice Updates NFT ranges, so it's possible to reuse the tokens in other twins and to make * creation of new ranges viable diff --git a/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol b/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol new file mode 100644 index 000000000..8acabfdc1 --- /dev/null +++ b/contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.21; + +import { BuyerBase } from "../bases/BuyerBase.sol"; +import { IBosonPriceDiscoveryHandler } from "../../interfaces/handlers/IBosonPriceDiscoveryHandler.sol"; +import { IBosonVoucher } from "../../interfaces/clients/IBosonVoucher.sol"; +import { ITwinToken } from "../../interfaces/ITwinToken.sol"; +import { DiamondLib } from "../../diamond/DiamondLib.sol"; +import { BuyerBase } from "../bases/BuyerBase.sol"; +import { DisputeBase } from "../bases/DisputeBase.sol"; +import { ProtocolLib } from "../libs/ProtocolLib.sol"; +import { FundsLib } from "../libs/FundsLib.sol"; +import "../../domain/BosonConstants.sol"; +import { IERC1155 } from "../../interfaces/IERC1155.sol"; +import { IERC721 } from "../../interfaces/IERC721.sol"; +import { IERC20 } from "../../interfaces/IERC20.sol"; +import { IERC721Receiver } from "../../interfaces/IERC721Receiver.sol"; +import { PriceDiscoveryBase } from "../bases/PriceDiscoveryBase.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @title PriceDiscoveryHandlerFacet + * + * @notice Handles exchanges associated with offers within the protocol. + */ +contract PriceDiscoveryHandlerFacet is IBosonPriceDiscoveryHandler, PriceDiscoveryBase, BuyerBase { + /** + * @notice + * For offers with native exchange token, it is expected the the price discovery contracts will + * operate with wrapped native token. Set the address of the wrapped native token in the constructor. + * + * After v2.2.0, token ids are derived from offerId and exchangeId. + * EXCHANGE_ID_2_2_0 is the first exchange id to use for 2.2.0. + * Set EXCHANGE_ID_2_2_0 in the constructor. + * + * @param _wNative - the address of the wrapped native token + * @param _firstExchangeId2_2_0 - the first exchange id to use for 2.2.0 + */ + //solhint-disable-next-line + constructor(address _wNative, uint256 _firstExchangeId2_2_0) PriceDiscoveryBase(_wNative, _firstExchangeId2_2_0) {} + + /** + * @notice Facet Initializer + * This function is callable only once. + */ + function initialize() public onlyUninitialized(type(IBosonPriceDiscoveryHandler).interfaceId) { + DiamondLib.addSupportedInterface(type(IBosonPriceDiscoveryHandler).interfaceId); + } + + /** + * @notice Commits to a price discovery offer (first step of an exchange). + * + * Emits a BuyerCommitted event if successful. + * Issues a voucher to the buyer address. + * + * Reverts if: + * - Offer price type is not price discovery. See BosonTypes.PriceType + * - Price discovery contract address is zero + * - Price discovery calldata is empty + * - Exchange exists already + * - Offer has been voided + * - Offer has expired + * - Offer is not yet available for commits + * - Buyer address is zero + * - Buyer account is inactive + * - Buyer is token-gated (conditional commit requirements not met or already used) + * - Any reason that PriceDiscoveryBase fulfilOrder reverts. See PriceDiscoveryBase.fulfilOrder + * - Any reason that ExchangeHandler onPremintedVoucherTransfer reverts. See ExchangeHandler.onPremintedVoucherTransfer + * + * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) + * @param _tokenIdOrOfferId - the id of the offer to commit to or the id of the voucher (if pre-minted) + * @param _priceDiscovery - price discovery data (if applicable). See BosonTypes.PriceDiscovery + */ + function commitToPriceDiscoveryOffer( + address payable _buyer, + uint256 _tokenIdOrOfferId, + PriceDiscovery calldata _priceDiscovery + ) external payable override exchangesNotPaused buyersNotPaused { + // Make sure buyer address is not zero address + require(_buyer != address(0), INVALID_ADDRESS); + + // Make sure caller provided price discovery data + require( + _priceDiscovery.priceDiscoveryContract != address(0) && _priceDiscovery.priceDiscoveryData.length > 0, + INVALID_PRICE_DISCOVERY + ); + + bool isTokenId; + uint256 offerId = _tokenIdOrOfferId >> 128; + // if `_tokenIdOrOfferId` is a token id, then upper 128 bits represent the offer id. + // Therefore, if `offerId` is not 0, then `_tokenIdOrOfferId` represents a token id + // and if `offerId` is 0, then `_tokenIdOrOfferId` represents an offer id. + // N.B. token ids, corresponding to exchanges from v2.2.0 and earlier, have zero upper 128 bits + // and it seems we could confuse them with offer ids. However, the offers frm that time are all + // of type PriceType.Static and therefore will never be used here. + + if (offerId == 0) { + offerId = _tokenIdOrOfferId; + } else { + isTokenId = true; + } + + // Fetch offer with offerId + Offer storage offer = getValidOffer(offerId); + + // Make sure offer type is price discovery. Otherwise, use commitToOffer + require(offer.priceType == PriceType.Discovery, INVALID_PRICE_TYPE); + uint256 sellerId = offer.sellerId; + + uint256 actualPrice; + { + // Get seller address + address _seller; + (, Seller storage seller, ) = fetchSeller(sellerId); + _seller = seller.assistant; + + // Calls price discovery contract and gets the actual price. Use token id if caller has provided one, otherwise use offer id and accepts any voucher. + actualPrice = fulfilOrder(isTokenId ? _tokenIdOrOfferId : 0, offer, _priceDiscovery, _seller, _buyer); + } + + // Fetch token id on protocol status + uint256 tokenId = protocolStatus().incomingVoucherId; + + uint256 exchangeId = tokenId & type(uint128).max; + + // Get sequential commits for this exchange + ExchangeCosts[] storage exchangeCosts = protocolEntities().exchangeCosts[exchangeId]; + + // Calculate fees + address exchangeToken = offer.exchangeToken; + uint256 protocolFeeAmount = getProtocolFee(exchangeToken, actualPrice); + + { + // Calculate royalties + (, uint256 royaltyAmount) = IBosonVoucher( + getCloneAddress(protocolLookups(), sellerId, offer.collectionIndex) + ).royaltyInfo(exchangeId, actualPrice); + + // Verify that fees and royalties are not higher than the price. + require((protocolFeeAmount + royaltyAmount) <= actualPrice, FEE_AMOUNT_TOO_HIGH); + + // Store exchange costs so it can be released later. This is the first cost entry for this exchange. + exchangeCosts.push( + ExchangeCosts({ + resellerId: sellerId, + price: actualPrice, + protocolFeeAmount: protocolFeeAmount, + royaltyAmount: royaltyAmount + }) + ); + } + // Clear incoming voucher id and incoming voucher address + clearPriceDiscoveryStorage(); + + (, uint256 buyerId) = getBuyerIdByWallet(_buyer); + if (actualPrice > 0) { + if (_priceDiscovery.side == Side.Ask) { + // Price discovery should send funds to the seller + // Nothing in escrow, take it from the seller's pool + FundsLib.decreaseAvailableFunds(sellerId, exchangeToken, actualPrice); + + emit FundsEncumbered(sellerId, exchangeToken, actualPrice, msgSender()); + } else { + // when bid side or wrapper, we have full proceeds in escrow. + // If exchange token is 0, we need to unwrap it + if (exchangeToken == address(0)) { + wNative.withdraw(actualPrice); + } + emit FundsEncumbered(buyerId, exchangeToken, actualPrice, msgSender()); + } + + // Not emitting BuyerCommitted since it's emitted in commitToOfferInternal + } + } +} diff --git a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol index d78299c40..53f7be6b9 100644 --- a/contracts/protocol/facets/SequentialCommitHandlerFacet.sol +++ b/contracts/protocol/facets/SequentialCommitHandlerFacet.sol @@ -72,93 +72,94 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis * - Transfer of exchange token fails * * @param _buyer - the buyer's address (caller can commit on behalf of a buyer) - * @param _exchangeId - the id of the exchange to commit to + * @param _tokenId - the id of the token to commit to * @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct */ function sequentialCommitToOffer( address payable _buyer, - uint256 _exchangeId, + uint256 _tokenId, PriceDiscovery calldata _priceDiscovery ) external payable exchangesNotPaused buyersNotPaused nonReentrant { // Make sure buyer address is not zero address require(_buyer != address(0), INVALID_ADDRESS); + // Make sure caller provided price discovery data + require( + _priceDiscovery.priceDiscoveryContract != address(0) && _priceDiscovery.priceDiscoveryData.length > 0, + INVALID_PRICE_DISCOVERY + ); + + uint256 exchangeId = _tokenId & type(uint128).max; + // Exchange must exist - (Exchange storage exchange, Voucher storage voucher) = getValidExchange(_exchangeId, ExchangeState.Committed); + (Exchange storage exchange, Voucher storage voucher) = getValidExchange(exchangeId, ExchangeState.Committed); // Make sure the voucher is still valid require(block.timestamp <= voucher.validUntilDate, VOUCHER_HAS_EXPIRED); // Create a memory struct for sequential commit and populate it as we go // This is done to avoid stack too deep error, while still keeping the number of SLOADs to a minimum - SequentialCommit memory sequentialCommit; + ExchangeCosts memory exchangeCost; // Get current buyer address. This is actually the seller in sequential commit. Need to do it before voucher is transferred address seller; - sequentialCommit.resellerId = exchange.buyerId; + exchangeCost.resellerId = exchange.buyerId; { - (, Buyer storage currentBuyer) = fetchBuyer(sequentialCommit.resellerId); + (, Buyer storage currentBuyer) = fetchBuyer(exchangeCost.resellerId); seller = currentBuyer.wallet; } - address sender = msgSender(); - if (_priceDiscovery.side == Side.Bid) { - require(seller == sender, NOT_VOUCHER_HOLDER); - } - // Fetch offer uint256 offerId = exchange.offerId; (, Offer storage offer) = fetchOffer(offerId); - // Get token address - address exchangeToken = offer.exchangeToken; - // First call price discovery and get actual price // It might be lower than submitted for buy orders and higher for sell orders - sequentialCommit.price = fulFilOrder(_exchangeId, exchangeToken, _priceDiscovery, _buyer, offer); + exchangeCost.price = fulfilOrder(_tokenId, offer, _priceDiscovery, seller, _buyer); + + // Get token address + address exchangeToken = offer.exchangeToken; // Calculate the amount to be kept in escrow uint256 escrowAmount; uint256 payout; { // Get sequential commits for this exchange - SequentialCommit[] storage sequentialCommits = protocolEntities().sequentialCommits[_exchangeId]; + ExchangeCosts[] storage exchangeCosts = protocolEntities().exchangeCosts[exchangeId]; { // Calculate fees - sequentialCommit.protocolFeeAmount = exchangeToken == protocolAddresses().token - ? protocolFees().flatBoson - : (protocolFees().percentage * sequentialCommit.price) / 10000; + exchangeCost.protocolFeeAmount = getProtocolFee(exchangeToken, exchangeCost.price); // Calculate royalties - (, sequentialCommit.royaltyAmount) = IBosonVoucher( + (, exchangeCost.royaltyAmount) = IBosonVoucher( getCloneAddress(protocolLookups(), offer.sellerId, offer.collectionIndex) - ).royaltyInfo(_exchangeId, sequentialCommit.price); + ).royaltyInfo(exchangeId, exchangeCost.price); // Verify that fees and royalties are not higher than the price. require( - (sequentialCommit.protocolFeeAmount + sequentialCommit.royaltyAmount) <= sequentialCommit.price, + (exchangeCost.protocolFeeAmount + exchangeCost.royaltyAmount) <= exchangeCost.price, FEE_AMOUNT_TOO_HIGH ); // Get price paid by current buyer - uint256 len = sequentialCommits.length; - uint256 currentPrice = len == 0 ? offer.price : sequentialCommits[len - 1].price; + uint256 len = exchangeCosts.length; + uint256 currentPrice = len == 0 ? offer.price : exchangeCosts[len - 1].price; // Calculate the minimal amount to be kept in the escrow escrowAmount = Math.max( - sequentialCommit.price, - sequentialCommit.protocolFeeAmount + sequentialCommit.royaltyAmount + currentPrice + exchangeCost.price, + exchangeCost.protocolFeeAmount + exchangeCost.royaltyAmount + currentPrice ) - currentPrice; - // Update sequential commit - sequentialCommits.push(sequentialCommit); + // Store the exchange cost, so it can be used in calculations when releasing funds + exchangeCosts.push(exchangeCost); } // Make sure enough get escrowed - payout = sequentialCommit.price - escrowAmount; + payout = exchangeCost.price - escrowAmount; if (_priceDiscovery.side == Side.Ask) { if (escrowAmount > 0) { @@ -167,8 +168,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis if (exchangeToken == address(0)) { // If exchange is native currency, seller cannot directly approve protocol to transfer funds // They need to approve wrapper contract, so protocol can pull funds from wrapper - // But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw) FundsLib.transferFundsToProtocol(address(wNative), seller, escrowAmount); + // But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw) wNative.withdraw(escrowAmount); } else { FundsLib.transferFundsToProtocol(exchangeToken, seller, escrowAmount); @@ -176,8 +177,8 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } } else { // when bid side, we have full proceeds in escrow. Keep minimal in, return the difference - if (sequentialCommit.price > 0 && exchangeToken == address(0)) { - wNative.withdraw(sequentialCommit.price); + if (exchangeCost.price > 0 && exchangeToken == address(0)) { + wNative.withdraw(exchangeCost.price); } if (payout > 0) { @@ -186,38 +187,17 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis } } + clearPriceDiscoveryStorage(); + // Since exchange and voucher are passed by reference, they are updated uint256 buyerId = exchange.buyerId; - if (sequentialCommit.price > 0) emit FundsEncumbered(buyerId, exchangeToken, sequentialCommit.price, sender); + address sender = msgSender(); + if (exchangeCost.price > 0) emit FundsEncumbered(buyerId, exchangeToken, exchangeCost.price, sender); if (payout > 0) { - emit FundsReleased(_exchangeId, sequentialCommit.resellerId, exchangeToken, payout, sender); - emit FundsWithdrawn(sequentialCommit.resellerId, seller, exchangeToken, payout, sender); + emit FundsReleased(exchangeId, exchangeCost.resellerId, exchangeToken, payout, sender); + emit FundsWithdrawn(exchangeCost.resellerId, seller, exchangeToken, payout, sender); } - emit BuyerCommitted(offerId, buyerId, _exchangeId, exchange, voucher, sender); + emit BuyerCommitted(offerId, buyerId, exchangeId, exchange, voucher, sender); // No need to update exchange detail. Most fields stay as they are, and buyerId was updated at the same time voucher is transferred } - - /** - * @notice standard onERC721Received function - * - * During sequential commit to offer, we expect to receive the boson voucher, therefore we need to implement onERC721Received - * Alternative option, where vouchers are modified to not invoke onERC721Received when to is protocol is unsafe, since one can abuse it to send vouchers to protocol - * This should return true value only when protocol expects to receive the voucher - * Should revert if called from any other address or with any other token id - - * @return - the ERC721 received function signature - */ - function onERC721Received( - address, - address, - uint256 _tokenId, - bytes calldata - ) external view override returns (bytes4) { - ProtocolLib.ProtocolStatus storage ps = protocolStatus(); - require( - ps.incomingVoucherId == _tokenId && ps.incomingVoucherCloneAddress == msg.sender, - UNEXPECTED_ERC721_RECEIVED - ); - return this.onERC721Received.selector; - } } diff --git a/contracts/protocol/libs/FundsLib.sol b/contracts/protocol/libs/FundsLib.sol index 16077c373..77aafb6a7 100644 --- a/contracts/protocol/libs/FundsLib.sol +++ b/contracts/protocol/libs/FundsLib.sol @@ -60,9 +60,17 @@ library FundsLib { * * @param _offerId - id of the offer with the details * @param _buyerId - id of the buyer + * @param _price - the price, either price discovered externally or set on offer creation * @param _isPreminted - flag indicating if the offer is preminted + * @param _priceType - price type, either static or discovery */ - function encumberFunds(uint256 _offerId, uint256 _buyerId, bool _isPreminted) internal { + function encumberFunds( + uint256 _offerId, + uint256 _buyerId, + uint256 _price, + bool _isPreminted, + BosonTypes.PriceType _priceType + ) internal { // Load protocol entities storage ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities(); @@ -73,17 +81,18 @@ library FundsLib { // this will be called only from commitToOffer so we expect that exchange actually exist BosonTypes.Offer storage offer = pe.offers[_offerId]; address exchangeToken = offer.exchangeToken; - uint256 price = offer.price; // if offer is non-preminted, validate incoming payment if (!_isPreminted) { - validateIncomingPayment(exchangeToken, price); - emit FundsEncumbered(_buyerId, exchangeToken, price, sender); + validateIncomingPayment(exchangeToken, _price); + emit FundsEncumbered(_buyerId, exchangeToken, _price, sender); } + bool isPriceDiscovery = _priceType == BosonTypes.PriceType.Discovery; + // decrease available funds uint256 sellerId = offer.sellerId; - uint256 sellerFundsEncumbered = offer.sellerDeposit + (_isPreminted ? price : 0); // for preminted offer, encumber also price from seller's available funds + uint256 sellerFundsEncumbered = offer.sellerDeposit + (_isPreminted && !isPriceDiscovery ? _price : 0); // for preminted offer and price type is static, encumber also price from seller's available funds decreaseAvailableFunds(sellerId, exchangeToken, sellerFundsEncumbered); // notify external observers @@ -108,7 +117,7 @@ library FundsLib { */ function validateIncomingPayment(address _exchangeToken, uint256 _value) internal { if (_exchangeToken == address(0)) { - // if transfer is in the native currency, msg.value must match offer price + // if transfer is in the native currency, msg.value must match price require(msg.value == _value, INSUFFICIENT_VALUE_RECEIVED); } else { // when price is in an erc20 token, transferring the native currency is not allowed @@ -234,7 +243,8 @@ library FundsLib { } /** - * @notice Takes the exchange id and releases the funds to all intermediate resellers, depending on the state of the exchange. + * @notice Takes the exchange id and releases the funds to original seller if offer.priceType is Discovery + * and to all intermediate resellers in case of sequential commit, depending on the state of the exchange. * It is called only from releaseFunds. Protocol fee and royalties are calculated and returned to releaseFunds, where they are added to the total. * * Emits FundsReleased events for non zero payoffs. @@ -252,17 +262,17 @@ library FundsLib { uint256 _initialPrice, address _exchangeToken ) internal returns (uint256 protocolFee, uint256 royalties) { - BosonTypes.SequentialCommit[] storage sequentialCommits; + BosonTypes.ExchangeCosts[] storage exchangeCosts; // calculate effective price multiplier uint256 effectivePriceMultiplier; { ProtocolLib.ProtocolEntities storage pe = ProtocolLib.protocolEntities(); - sequentialCommits = pe.sequentialCommits[_exchangeId]; + exchangeCosts = pe.exchangeCosts[_exchangeId]; - // if no sequential commit happened, just return - if (sequentialCommits.length == 0) { + // if price type was static and no sequential commit happened, just return + if (exchangeCosts.length == 0) { return (0, 0); } @@ -298,9 +308,9 @@ library FundsLib { uint256 resellerBuyPrice = _initialPrice; // the price that reseller paid for the voucher address msgSender = EIP712Lib.msgSender(); - uint256 len = sequentialCommits.length; + uint256 len = exchangeCosts.length; for (uint256 i = 0; i < len; i++) { - BosonTypes.SequentialCommit storage sc = sequentialCommits[i]; + BosonTypes.ExchangeCosts storage sc = exchangeCosts[i]; // amount to be released uint256 currentResellerAmount; diff --git a/contracts/protocol/libs/ProtocolLib.sol b/contracts/protocol/libs/ProtocolLib.sol index 8205b5df3..d0c425db2 100644 --- a/contracts/protocol/libs/ProtocolLib.sol +++ b/contracts/protocol/libs/ProtocolLib.sol @@ -117,7 +117,7 @@ library ProtocolLib { //entity id => auth token mapping(uint256 => BosonTypes.AuthToken) authTokens; // exchange id => sequential commit info - mapping(uint256 => BosonTypes.SequentialCommit[]) sequentialCommits; + mapping(uint256 => BosonTypes.ExchangeCosts[]) exchangeCosts; } // Protocol lookups storage diff --git a/hardhat-fork.config.js b/hardhat-fork.config.js index 3157e7e81..45456a847 100644 --- a/hardhat-fork.config.js +++ b/hardhat-fork.config.js @@ -1,18 +1,46 @@ const defaultConfig = require("./hardhat.config.js"); +require("hardhat-preprocessor"); const environments = require("./environments"); +const fs = require("fs"); const { subtask } = require("hardhat/config"); const path = require("node:path"); const { glob } = require("glob"); const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require("hardhat/builtin-tasks/task-names"); -subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, async (_, { config }) => { - const contracts = await glob(path.join(config.paths.root, "contracts/**/*.sol")); - const submodulesContracts = await glob(path.join(config.paths.root, "submodules/**/contracts/*.sol"), { - ignore: path.join(config.paths.root, "submodules/**/node_modules/**"), +subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, async (_, { config }, runSuper) => { + const files = await runSuper(); + + const submodules = await glob(path.join(config.paths.root, "submodules/**/{src,contracts}/**/*.sol"), { + ignore: [ + path.join(config.paths.root, "submodules/**/node_modules/**"), + path.join(config.paths.root, "submodules/**/test/**"), + path.join(config.paths.root, "submodules/**/src/test/*.sol"), + path.join(config.paths.root, "submodules/**/src/mocks/*.sol"), + path.join(config.paths.root, "submodules/**/lib/**/*.sol"), + path.join(config.paths.root, "submodules/**/artifacts/**"), + path.join(config.paths.root, "submodules/**/typechain-types/**"), + ], + }); + + // Include files inside lib folder when it is inside src folder + const submodulesWithLib = await glob(path.join(config.paths.root, "submodules/**/{src,contracts}/lib/**/*.sol"), { + ignore: [ + path.join(config.paths.root, "submodules/**/test/**"), + path.join(config.paths.root, "submodules/**/artifacts/**"), + ], }); - return [...contracts, ...submodulesContracts].map(path.normalize); + return [...files, ...submodules, ...submodulesWithLib].map(path.normalize); }); + +function getRemappings() { + return fs + .readFileSync("remappings.txt", "utf8") + .split("\n") + .filter(Boolean) // remove empty lines + .map((line) => line.trim().split("=")); +} + module.exports = { ...defaultConfig, networks: { @@ -22,6 +50,26 @@ module.exports = { blockNumber: 40119033, }, accounts: { mnemonic: environments.hardhat.mnemonic }, + allowUnlimitedContractSize: true, }, }, + preprocess: { + eachLine: () => ({ + transform: (line, { absolutePath }) => { + if (absolutePath.includes("submodules")) { + const submodule = absolutePath.split("submodules/")[1].split("/")[0]; + if (line.match(/^\s*import /i)) { + for (const [from, to] of getRemappings()) { + if (line.includes(from)) { + line = line.replace(from, to.replace("${submodule}", submodule)); + break; + } + } + } + } + + return line; + }, + }), + }, }; diff --git a/hardhat.config.js b/hardhat.config.js index f1eacf161..42cd6d5a2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -196,7 +196,13 @@ module.exports = { yul: true, }, }, + outputSelection: { + "*": { + "*": ["evm.bytecode.object", "evm.deployedBytecode*"], + }, + }, }, + viaIR: true, }, { version: "0.8.21", diff --git a/package-lock.json b/package-lock.json index e0ac94776..49ed5db72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@nomicfoundation/hardhat-toolbox": "^3.0.0", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/test-helpers": "^0.5.16", + "@zoralabs/core": "^1.0.8", "coveralls": "^3.1.1", "decache": "^4.6.1", "dotenv": "^16.3.0", @@ -28,12 +29,15 @@ "eslint-plugin-no-only-tests": "^3.1.0", "ethereum-input-data-decoder": "^0.4.2", "ethers": "^6.6.7", + "ethersv5": "npm:ethers@^5.7.2", "glob": "^10.2.7", "hardhat": "^2.17.1", "hardhat-contract-sizer": "^2.7.0", "hardhat-preprocessor": "^0.1.5", "husky": "^8.0.3", "lodash": "^4.17.21", + "merkletreejs": "^0.3.10", + "opensea-js": "^6.1.12", "prettier": "^2.8.4", "prettier-plugin-solidity": "^1.0.0-beta.19", "simple-statistics": "^7.8.2", @@ -42,6 +46,48 @@ "web3": "^1.8.1" } }, + "node_modules/@0xsequence/abi": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/abi/-/abi-0.43.34.tgz", + "integrity": "sha512-wZ3JLA4kw2em8A7gFW5oESdo+F3G/WjIhCp/aZ0x3UgayBxrQjwBURoqDQPrY5k/BJ4R68LIEabLTrpSXesh1g==", + "dev": true + }, + "node_modules/@0xsequence/api": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/api/-/api-0.43.34.tgz", + "integrity": "sha512-YmV65zn9vZiprEXLfLVIWANK3WBag3d+N0Sc5Br19ezmCFBg52DdzumJIM+8S3maUE2JdL9RbgBLZ+9JOBKnEg==", + "dev": true + }, + "node_modules/@0xsequence/ethauth": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@0xsequence/ethauth/-/ethauth-0.8.1.tgz", + "integrity": "sha512-P21cxRSS+2mDAqFVAJt0lwQFtbObX+Ewlj8DMyDELp81+QbfHFh6LCyu8dTXNdBx6UbmRFOCSBno5Txd50cJPQ==", + "dev": true, + "dependencies": { + "js-base64": "^3.7.2" + }, + "peerDependencies": { + "ethers": ">=5.5" + } + }, + "node_modules/@0xsequence/guard": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/guard/-/guard-0.43.34.tgz", + "integrity": "sha512-U8uIjC8nifDgugo+4V3siu5fs86TqOmsb4Wvx0n6G/zbX2LaPGOYwHqCYkWrukETnk/FYiy8GoTuV11T9jIrSg==", + "dev": true + }, + "node_modules/@0xsequence/indexer": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/indexer/-/indexer-0.43.34.tgz", + "integrity": "sha512-u7dnbLGH447Utph3Ebvfmi98kTebdc8+we1L6FSYpodpvN3q/lb5de8BL1Jbmry0m9MSLy1iGwdGA0AivwNgtA==", + "dev": true + }, + "node_modules/@0xsequence/metadata": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/metadata/-/metadata-0.43.34.tgz", + "integrity": "sha512-ZJO+cerq2gQqktqyCsD1zfAAeOzsCDZXEDTO47oT5v42Bl4L50Vlj1PxNlo9iKzYooCA2LZjeWJkrvzfa0cvjA==", + "dev": true + }, "node_modules/@adraffy/ens-normalize": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", @@ -2268,6 +2314,216 @@ "web3": "^1.0.0-beta.36" } }, + "node_modules/@opensea/seaport-js": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@opensea/seaport-js/-/seaport-js-2.0.8.tgz", + "integrity": "sha512-uOjqXtXK49vbOoxXy/z54evxV3OtExjNe81E9CK6v6nOX0vDZH6dnDSTbC8ELL1RrAXM8dmDMtA0szepDyoaKQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@0xsequence/multicall": "^0.43.29", + "ethers": "^5.7.2", + "merkletreejs": "^0.3.10" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/auth": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/auth/-/auth-0.43.34.tgz", + "integrity": "sha512-dw58nX2gc5QkIkzeVCheFZrRQgHwp4ZlJdg2e5gk7jU8eEu48oWP6faz30MFfiJfUCaysbGZ0o9+mGPqwpPG2g==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/api": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/ethauth": "^0.8.0", + "@0xsequence/indexer": "^0.43.34", + "@0xsequence/metadata": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/provider": "^0.43.34", + "@0xsequence/utils": "^0.43.34", + "@0xsequence/wallet": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/config": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/config/-/config-0.43.34.tgz", + "integrity": "sha512-rOkNLB7z64ZkURzTXMF+4zTPo17VUei6vT5sp9Uzd5zamEneWGFdUJltzDc8sLdUWTEVdkyckaTSTS+8/sHuLw==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/multicall": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/multicall": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/multicall/-/multicall-0.43.34.tgz", + "integrity": "sha512-7gLlX3TOi+qZYe28DVdqkQJBeibl9JOdCcHaw9zkQYAZ+2WLouZl5Rlv0ZHEwX46gOiG1mCt/tZugoRkguKE0Q==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/network": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/network/-/network-0.43.34.tgz", + "integrity": "sha512-KH2k4zEiXBHBathU+T7AXxzSDRm0XJ2+bJSSKci+RWesLPT2TwZY7YLfSWjSyp20EPqeyuaG7Snn86e60Zi/eg==", + "dev": true, + "dependencies": { + "@0xsequence/indexer": "^0.43.34", + "@0xsequence/provider": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/provider": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/provider/-/provider-0.43.34.tgz", + "integrity": "sha512-AuMiP3budYbtql1L8eemcmxknuN5QJcPirr4DtkCnifCMGDoF/savSuue6+7K65HGj/8yzdFrRlt0MYavYWVoA==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/auth": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34", + "@0xsequence/wallet": "^0.43.34", + "eventemitter2": "^6.4.5", + "webextension-polyfill": "^0.10.0" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/relayer": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/relayer/-/relayer-0.43.34.tgz", + "integrity": "sha512-Kl6LitpG24i3ha6CxBRnFAD1/vAbC1+pub7yywhwH8jmnd7KncHAZNgYT48BZI6B2bOeQiY+tTevUcgYw0hSzA==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/transactions": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/transactions/-/transactions-0.43.34.tgz", + "integrity": "sha512-C6xDBqDOpx3+fuZ4OWStpAgAMKW7het1a6cwuQRalN8s+3n/SkjgzSK8Xc/5FT4FVExJuwo/D/AkvyOFz7AaCg==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/utils": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/utils/-/utils-0.43.34.tgz", + "integrity": "sha512-Rp0vVeBUeTmOSpXwy+Adlycitg0V4qjao1QvCqONgu9Rh1NIVpocVLx42iSopFQFIALhYB0ZrHp+ns6QsC08+A==", + "dev": true, + "dependencies": { + "js-base64": "^3.7.2" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/@0xsequence/wallet": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/wallet/-/wallet-0.43.34.tgz", + "integrity": "sha512-8ZojYXcLnItXfmBy1PRR4qf25GKV5E0bcGLb3tuw/7M6QlFi1CqgRcHuuXYZ4XYyLxLBaKUC1+3sNqcFJGAirA==", + "dev": true, + "dependencies": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/guard": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + }, + "peerDependencies": { + "ethers": ">=5.5 < 6" + } + }, + "node_modules/@opensea/seaport-js/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/@openzeppelin/contract-loader": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contract-loader/-/contract-loader-0.6.3.tgz", @@ -3933,6 +4189,63 @@ "@types/node": "*" } }, + "node_modules/@zoralabs/core": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@zoralabs/core/-/core-1.0.8.tgz", + "integrity": "sha512-KDqO4RXUAVHUYjtJlAZErX8hOYXLmmJzK3/gdhwKQITXy/SHzLzpE7adPSbHRCE2SncfFTW6tbCfxIvXno6MrQ==", + "dev": true, + "dependencies": { + "ethers": "^5.0.19" + } + }, + "node_modules/@zoralabs/core/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -5148,6 +5461,12 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-reverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", + "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==", + "dev": true + }, "node_modules/buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", @@ -6227,6 +6546,12 @@ "sha3": "^2.1.1" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "dev": true + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -8540,6 +8865,55 @@ "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", "dev": true }, + "node_modules/ethersv5": { + "name": "ethers", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/ethjs-abi": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz", @@ -8611,6 +8985,12 @@ "node": ">=6" } }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "dev": true + }, "node_modules/eventemitter3": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", @@ -11644,6 +12024,12 @@ "node": ">= 0.6.0" } }, + "node_modules/js-base64": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", + "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", + "dev": true + }, "node_modules/js-sdsl": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", @@ -12653,6 +13039,31 @@ "node": ">= 8" } }, + "node_modules/merkletreejs": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.3.11.tgz", + "integrity": "sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ==", + "dev": true, + "dependencies": { + "bignumber.js": "^9.0.1", + "buffer-reverse": "^1.0.1", + "crypto-js": "^4.2.0", + "treeify": "^1.1.0", + "web3-utils": "^1.3.4" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/merkletreejs/node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -13593,6 +14004,68 @@ "node": ">=6" } }, + "node_modules/opensea-js": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/opensea-js/-/opensea-js-6.1.12.tgz", + "integrity": "sha512-JRTFnvh/R1QGj5TOJbJeWnFcjvQEAK92748XmOdf7UmBR4y6DdFwP4kdK6mJAudfe82EsZDUBC0jm9RaASRR7w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@opensea/seaport-js": "^2.0.8", + "ethers": "^5.7.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/opensea-js/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -17791,6 +18264,15 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -19028,6 +19510,12 @@ "node": ">=8.0.0" } }, + "node_modules/webextension-polyfill": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "dev": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -19633,6 +20121,45 @@ } }, "dependencies": { + "@0xsequence/abi": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/abi/-/abi-0.43.34.tgz", + "integrity": "sha512-wZ3JLA4kw2em8A7gFW5oESdo+F3G/WjIhCp/aZ0x3UgayBxrQjwBURoqDQPrY5k/BJ4R68LIEabLTrpSXesh1g==", + "dev": true + }, + "@0xsequence/api": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/api/-/api-0.43.34.tgz", + "integrity": "sha512-YmV65zn9vZiprEXLfLVIWANK3WBag3d+N0Sc5Br19ezmCFBg52DdzumJIM+8S3maUE2JdL9RbgBLZ+9JOBKnEg==", + "dev": true + }, + "@0xsequence/ethauth": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@0xsequence/ethauth/-/ethauth-0.8.1.tgz", + "integrity": "sha512-P21cxRSS+2mDAqFVAJt0lwQFtbObX+Ewlj8DMyDELp81+QbfHFh6LCyu8dTXNdBx6UbmRFOCSBno5Txd50cJPQ==", + "dev": true, + "requires": { + "js-base64": "^3.7.2" + } + }, + "@0xsequence/guard": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/guard/-/guard-0.43.34.tgz", + "integrity": "sha512-U8uIjC8nifDgugo+4V3siu5fs86TqOmsb4Wvx0n6G/zbX2LaPGOYwHqCYkWrukETnk/FYiy8GoTuV11T9jIrSg==", + "dev": true + }, + "@0xsequence/indexer": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/indexer/-/indexer-0.43.34.tgz", + "integrity": "sha512-u7dnbLGH447Utph3Ebvfmi98kTebdc8+we1L6FSYpodpvN3q/lb5de8BL1Jbmry0m9MSLy1iGwdGA0AivwNgtA==", + "dev": true + }, + "@0xsequence/metadata": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/metadata/-/metadata-0.43.34.tgz", + "integrity": "sha512-ZJO+cerq2gQqktqyCsD1zfAAeOzsCDZXEDTO47oT5v42Bl4L50Vlj1PxNlo9iKzYooCA2LZjeWJkrvzfa0cvjA==", + "dev": true + }, "@adraffy/ens-normalize": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", @@ -21194,6 +21721,177 @@ "@types/bignumber.js": "^5.0.0" } }, + "@opensea/seaport-js": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@opensea/seaport-js/-/seaport-js-2.0.8.tgz", + "integrity": "sha512-uOjqXtXK49vbOoxXy/z54evxV3OtExjNe81E9CK6v6nOX0vDZH6dnDSTbC8ELL1RrAXM8dmDMtA0szepDyoaKQ==", + "dev": true, + "requires": { + "@0xsequence/multicall": "^0.43.29", + "ethers": "^5.7.2", + "merkletreejs": "^0.3.10" + }, + "dependencies": { + "@0xsequence/auth": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/auth/-/auth-0.43.34.tgz", + "integrity": "sha512-dw58nX2gc5QkIkzeVCheFZrRQgHwp4ZlJdg2e5gk7jU8eEu48oWP6faz30MFfiJfUCaysbGZ0o9+mGPqwpPG2g==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/api": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/ethauth": "^0.8.0", + "@0xsequence/indexer": "^0.43.34", + "@0xsequence/metadata": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/provider": "^0.43.34", + "@0xsequence/utils": "^0.43.34", + "@0xsequence/wallet": "^0.43.34" + } + }, + "@0xsequence/config": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/config/-/config-0.43.34.tgz", + "integrity": "sha512-rOkNLB7z64ZkURzTXMF+4zTPo17VUei6vT5sp9Uzd5zamEneWGFdUJltzDc8sLdUWTEVdkyckaTSTS+8/sHuLw==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/multicall": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "@0xsequence/multicall": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/multicall/-/multicall-0.43.34.tgz", + "integrity": "sha512-7gLlX3TOi+qZYe28DVdqkQJBeibl9JOdCcHaw9zkQYAZ+2WLouZl5Rlv0ZHEwX46gOiG1mCt/tZugoRkguKE0Q==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "@0xsequence/network": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/network/-/network-0.43.34.tgz", + "integrity": "sha512-KH2k4zEiXBHBathU+T7AXxzSDRm0XJ2+bJSSKci+RWesLPT2TwZY7YLfSWjSyp20EPqeyuaG7Snn86e60Zi/eg==", + "dev": true, + "requires": { + "@0xsequence/indexer": "^0.43.34", + "@0xsequence/provider": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "@0xsequence/provider": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/provider/-/provider-0.43.34.tgz", + "integrity": "sha512-AuMiP3budYbtql1L8eemcmxknuN5QJcPirr4DtkCnifCMGDoF/savSuue6+7K65HGj/8yzdFrRlt0MYavYWVoA==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/auth": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34", + "@0xsequence/wallet": "^0.43.34", + "eventemitter2": "^6.4.5", + "webextension-polyfill": "^0.10.0" + } + }, + "@0xsequence/relayer": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/relayer/-/relayer-0.43.34.tgz", + "integrity": "sha512-Kl6LitpG24i3ha6CxBRnFAD1/vAbC1+pub7yywhwH8jmnd7KncHAZNgYT48BZI6B2bOeQiY+tTevUcgYw0hSzA==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "@0xsequence/transactions": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/transactions/-/transactions-0.43.34.tgz", + "integrity": "sha512-C6xDBqDOpx3+fuZ4OWStpAgAMKW7het1a6cwuQRalN8s+3n/SkjgzSK8Xc/5FT4FVExJuwo/D/AkvyOFz7AaCg==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "@0xsequence/utils": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/utils/-/utils-0.43.34.tgz", + "integrity": "sha512-Rp0vVeBUeTmOSpXwy+Adlycitg0V4qjao1QvCqONgu9Rh1NIVpocVLx42iSopFQFIALhYB0ZrHp+ns6QsC08+A==", + "dev": true, + "requires": { + "js-base64": "^3.7.2" + } + }, + "@0xsequence/wallet": { + "version": "0.43.34", + "resolved": "https://registry.npmjs.org/@0xsequence/wallet/-/wallet-0.43.34.tgz", + "integrity": "sha512-8ZojYXcLnItXfmBy1PRR4qf25GKV5E0bcGLb3tuw/7M6QlFi1CqgRcHuuXYZ4XYyLxLBaKUC1+3sNqcFJGAirA==", + "dev": true, + "requires": { + "@0xsequence/abi": "^0.43.34", + "@0xsequence/config": "^0.43.34", + "@0xsequence/guard": "^0.43.34", + "@0xsequence/network": "^0.43.34", + "@0xsequence/relayer": "^0.43.34", + "@0xsequence/transactions": "^0.43.34", + "@0xsequence/utils": "^0.43.34" + } + }, + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } + } + }, "@openzeppelin/contract-loader": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contract-loader/-/contract-loader-0.6.3.tgz", @@ -22704,6 +23402,55 @@ "@types/node": "*" } }, + "@zoralabs/core": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@zoralabs/core/-/core-1.0.8.tgz", + "integrity": "sha512-KDqO4RXUAVHUYjtJlAZErX8hOYXLmmJzK3/gdhwKQITXy/SHzLzpE7adPSbHRCE2SncfFTW6tbCfxIvXno6MrQ==", + "dev": true, + "requires": { + "ethers": "^5.0.19" + }, + "dependencies": { + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -23650,6 +24397,12 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "buffer-reverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", + "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==", + "dev": true + }, "buffer-to-arraybuffer": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", @@ -24518,6 +25271,12 @@ "sha3": "^2.1.1" } }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "dev": true + }, "css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -26342,6 +27101,44 @@ } } }, + "ethersv5": { + "version": "npm:ethers@5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "ethjs-abi": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz", @@ -26402,6 +27199,12 @@ "dev": true, "optional": true }, + "eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "dev": true + }, "eventemitter3": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", @@ -28682,6 +29485,12 @@ "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", "dev": true }, + "js-base64": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", + "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", + "dev": true + }, "js-sdsl": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", @@ -29500,6 +30309,27 @@ "dev": true, "peer": true }, + "merkletreejs": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.3.11.tgz", + "integrity": "sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.1", + "buffer-reverse": "^1.0.1", + "crypto-js": "^4.2.0", + "treeify": "^1.1.0", + "web3-utils": "^1.3.4" + }, + "dependencies": { + "bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "dev": true + } + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -30231,6 +31061,56 @@ } } }, + "opensea-js": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/opensea-js/-/opensea-js-6.1.12.tgz", + "integrity": "sha512-JRTFnvh/R1QGj5TOJbJeWnFcjvQEAK92748XmOdf7UmBR4y6DdFwP4kdK6mJAudfe82EsZDUBC0jm9RaASRR7w==", + "dev": true, + "requires": { + "@opensea/seaport-js": "^2.0.8", + "ethers": "^5.7.2" + }, + "dependencies": { + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -33606,6 +34486,12 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -34575,6 +35461,12 @@ "utf8": "3.0.0" } }, + "webextension-polyfill": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "dev": true + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 597b510c0..ce69890f1 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@nomicfoundation/hardhat-toolbox": "^3.0.0", "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/test-helpers": "^0.5.16", + "@zoralabs/core": "^1.0.8", "coveralls": "^3.1.1", "decache": "^4.6.1", "dotenv": "^16.3.0", @@ -95,12 +96,15 @@ "eslint-plugin-no-only-tests": "^3.1.0", "ethereum-input-data-decoder": "^0.4.2", "ethers": "^6.6.7", + "ethersv5": "npm:ethers@^5.7.2", "glob": "^10.2.7", "hardhat": "^2.17.1", "hardhat-contract-sizer": "^2.7.0", "hardhat-preprocessor": "^0.1.5", "husky": "^8.0.3", "lodash": "^4.17.21", + "merkletreejs": "^0.3.10", + "opensea-js": "^6.1.12", "prettier": "^2.8.4", "prettier-plugin-solidity": "^1.0.0-beta.19", "simple-statistics": "^7.8.2", diff --git a/remappings.txt b/remappings.txt index e69de29bb..35f405f5a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -0,0 +1,5 @@ +ds-test/=submodules/lssvm/lib/ds-test/src/ +solmate/=submodules/${submodule}/lib/solmate/src/ +@manifoldxyz/=submodules/lssvm/lib/ +manifoldxyz/=submodules/lssvm/lib/royalty-registry-solidity.git/contracts/ +@sudoswap/=submodules/lssvm/src/ diff --git a/scripts/config/facet-deploy.js b/scripts/config/facet-deploy.js index acf3df832..7426e80fe 100644 --- a/scripts/config/facet-deploy.js +++ b/scripts/config/facet-deploy.js @@ -65,6 +65,7 @@ const noArgFacetNames = [ "PauseHandlerFacet", "ProtocolInitializationHandlerFacet", // args are generated on cutDiamond function "SequentialCommitHandlerFacet", + "PriceDiscoveryHandlerFacet", ]; async function getFacets(config) { @@ -81,6 +82,10 @@ async function getFacets(config) { init: [], constructorArgs: [protocolConfig.WrappedNative[network], protocolConfig.EXCHANGE_ID_2_2_0[network]], }; + facetArgs["PriceDiscoveryHandlerFacet"] = { + init: [], + constructorArgs: [protocolConfig.WrappedNative[network], protocolConfig.EXCHANGE_ID_2_2_0[network]], + }; // metaTransactionsHandlerFacet initializer arguments. const MetaTransactionsHandlerFacetInitArgs = await getMetaTransactionsHandlerFacetInitArgs( diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index ba0024044..e0e57d5f1 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -215,4 +215,10 @@ exports.RevertReasons = { INIT_ZERO_ADDRESS_NON_EMPTY_CALLDATA: "LibDiamondCut: _init is address(0) but _calldata is not empty", INIT_EMPTY_CALLDATA_NON_ZERO_ADDRESS: "LibDiamondCut: _calldata is empty but _init is not address(0)", INIT_ADDRESS_WITH_NO_CODE: "LibDiamondCut: _init address has no code", + + // Price discovery related + PRICE_TOO_HIGH: "Price discovery returned a price that is too high", + PRICE_TOO_LOW: "Price discovery returned a price that is too low", + TOKEN_ID_MISMATCH: "Token id mismatch", + VOUCHER_TRANSFER_NOT_ALLOWED: "Voucher transfer not allowed", }; diff --git a/scripts/config/supported-interfaces.js b/scripts/config/supported-interfaces.js index a3314f009..9ff8eb2aa 100644 --- a/scripts/config/supported-interfaces.js +++ b/scripts/config/supported-interfaces.js @@ -28,6 +28,7 @@ const interfaceImplementers = { ConfigHandlerFacet: "IBosonConfigHandler", ProtocolInitializationHandlerFacet: "IBosonProtocolInitializationHandler", SequentialCommitHandlerFacet: "IBosonSequentialCommitHandler", + PriceDiscoveryHandlerFacet: "IBosonPriceDiscoveryHandler", }; let interfacesCache; // if getInterfaceIds is called multiple times (e.g. during tests), calculate ids only once and store them to cache diff --git a/scripts/domain/Offer.js b/scripts/domain/Offer.js index 323963a41..8bb2142ac 100644 --- a/scripts/domain/Offer.js +++ b/scripts/domain/Offer.js @@ -1,4 +1,11 @@ -const { bigNumberIsValid, addressIsValid, booleanIsValid, stringIsValid } = require("../util/validations.js"); +const { + bigNumberIsValid, + addressIsValid, + booleanIsValid, + stringIsValid, + enumIsValid, +} = require("../util/validations.js"); +const PriceType = require("./PriceType.js"); /** * Boson Protocol Domain Entity: Offer @@ -19,6 +26,7 @@ class Offer { string metadataHash; bool voided; uint256 collectionIndex; + PriceType priceType; } */ @@ -33,7 +41,8 @@ class Offer { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ) { this.id = id; this.sellerId = sellerId; @@ -46,6 +55,7 @@ class Offer { this.metadataHash = metadataHash; this.voided = voided; this.collectionIndex = collectionIndex; + this.priceType = priceType; } /** @@ -66,6 +76,7 @@ class Offer { metadataHash, voided, collectionIndex, + priceType, } = o; return new Offer( @@ -79,7 +90,8 @@ class Offer { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ); } @@ -99,7 +111,8 @@ class Offer { metadataUri, metadataHash, voided, - collectionIndex; + collectionIndex, + priceType; // destructure struct [ @@ -114,6 +127,7 @@ class Offer { metadataHash, voided, collectionIndex, + priceType, ] = struct; if (!collectionIndex) { collectionIndex = 0; @@ -131,6 +145,7 @@ class Offer { metadataHash, voided, collectionIndex: collectionIndex.toString(), + priceType: Number(priceType), }); } @@ -167,6 +182,7 @@ class Offer { this.metadataHash, this.voided, this.collectionIndex, + this.priceType, ]; } @@ -280,6 +296,14 @@ class Offer { return bigNumberIsValid(this.collectionIndex); } + /** + * Is this Offer instance's priceType field valid? + * @returns {boolean} + */ + priceTypeIsValid() { + return enumIsValid(this.priceType, PriceType.Types); + } + /** * Is this Offer instance valid? * @returns {boolean} @@ -296,7 +320,8 @@ class Offer { this.metadataUriIsValid() && this.metadataHashIsValid() && this.voidedIsValid() && - this.collectionIndexIsValid() + this.collectionIndexIsValid() && + this.priceTypeIsValid() ); } } diff --git a/scripts/domain/PriceDiscovery.js b/scripts/domain/PriceDiscovery.js index 1a1c19284..3affcdae1 100644 --- a/scripts/domain/PriceDiscovery.js +++ b/scripts/domain/PriceDiscovery.js @@ -10,17 +10,19 @@ class PriceDiscovery { /* struct PriceDiscovery { uint256 price; + Side side; address priceDiscoveryContract; + address conduit; bytes priceDiscoveryData; - Side side; } */ - constructor(price, priceDiscoveryContract, priceDiscoveryData, side) { + constructor(price, side, priceDiscoveryContract, conduit, priceDiscoveryData) { this.price = price; + this.side = side; this.priceDiscoveryContract = priceDiscoveryContract; this.priceDiscoveryData = priceDiscoveryData; - this.side = side; + this.conduit = conduit; } /** @@ -29,8 +31,8 @@ class PriceDiscovery { * @returns {PriceDiscovery} */ static fromObject(o) { - const { price, priceDiscoveryContract, priceDiscoveryData, side } = o; - return new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); + const { price, side, priceDiscoveryContract, conduit, priceDiscoveryData } = o; + return new PriceDiscovery(price, side, priceDiscoveryContract, conduit, priceDiscoveryData); } /** @@ -39,16 +41,17 @@ class PriceDiscovery { * @returns {*} */ static fromStruct(struct) { - let price, priceDiscoveryContract, priceDiscoveryData, side; + let price, side, priceDiscoveryContract, conduit, priceDiscoveryData; // destructure struct - [price, priceDiscoveryContract, priceDiscoveryData, side] = struct; + [price, side, priceDiscoveryContract, conduit, priceDiscoveryData] = struct; return PriceDiscovery.fromObject({ price: price.toString(), + side, priceDiscoveryContract: priceDiscoveryContract, + conduit, priceDiscoveryData: priceDiscoveryData, - side: side, }); } @@ -73,7 +76,7 @@ class PriceDiscovery { * @returns {string} */ toStruct() { - return [this.price, this.priceDiscoveryContract, this.priceDiscoveryData, this.side]; + return [this.price, this.side, this.priceDiscoveryContract, this.conduit, this.priceDiscoveryData]; } /** @@ -93,6 +96,15 @@ class PriceDiscovery { return bigNumberIsValid(this.price); } + /** + * Is this PriceDiscovery instance's side field valid? + * Must be a number belonging to the Side enum + * @returns {boolean} + */ + sideIsValid() { + return enumIsValid(this.side, Side.Types); + } + /** * Is this PriceDiscovery instance's priceDiscoveryContract field valid? * Must be a eip55 compliant Ethereum address @@ -103,21 +115,21 @@ class PriceDiscovery { } /** - * Is this PriceDiscovery instance's priceDiscoveryData field valid? - * If present, must be a string representation of bytes + * Is this PriceDiscovery instance's conduit field valid? + * Must be a eip55 compliant Ethereum address * @returns {boolean} */ - priceDiscoveryDataIsValid() { - return bytesIsValid(this.priceDiscoveryData); + conduitIsValid() { + return addressIsValid(this.conduit); } /** - * Is this PriceDiscovery instance's side field valid? - * Must be a number belonging to the Side enum + * Is this PriceDiscovery instance's priceDiscoveryData field valid? + * If present, must be a string representation of bytes * @returns {boolean} */ - sideIsValid() { - return enumIsValid(this.side, Side.Types); + priceDiscoveryDataIsValid() { + return bytesIsValid(this.priceDiscoveryData); } /** @@ -127,9 +139,10 @@ class PriceDiscovery { isValid() { return ( this.priceIsValid() && + this.sideIsValid() && this.priceDiscoveryContractIsValid() && - this.priceDiscoveryDataIsValid() && - this.sideIsValid() + this.conduitIsValid() && + this.priceDiscoveryDataIsValid() ); } } diff --git a/scripts/domain/PriceType.js b/scripts/domain/PriceType.js new file mode 100644 index 000000000..2645d993a --- /dev/null +++ b/scripts/domain/PriceType.js @@ -0,0 +1,12 @@ +/** + * Boson Protocol Domain Enum: PriceType + */ +class PriceType {} + +PriceType.Static = 0; +PriceType.Discovery = 1; + +PriceType.Types = [PriceType.Static, PriceType.Discovery]; + +// Export +module.exports = PriceType; diff --git a/scripts/domain/Side.js b/scripts/domain/Side.js index d989349b0..211b92f3e 100644 --- a/scripts/domain/Side.js +++ b/scripts/domain/Side.js @@ -5,8 +5,9 @@ class Side {} Side.Ask = 0; Side.Bid = 1; +Side.Wrapper = 2; -Side.Types = [Side.Ask, Side.Bid]; +Side.Types = [Side.Ask, Side.Bid, Side.Wrapper]; // Export module.exports = Side; diff --git a/scripts/manage-roles.js b/scripts/manage-roles.js index 520f4655e..6a11d525b 100644 --- a/scripts/manage-roles.js +++ b/scripts/manage-roles.js @@ -92,7 +92,7 @@ async function main(env) { // Revoke role if previously granted if (hasRole) { - await accessController.revokeRole(role, config.address()); + await accessController.revokeRole(role, config.address); } // Report status diff --git a/scripts/util/constants.js b/scripts/util/constants.js index 53daa9163..af64e8be1 100644 --- a/scripts/util/constants.js +++ b/scripts/util/constants.js @@ -1,3 +1,10 @@ -const interfacesWithMultipleArtifacts = ["IERC2981", "IERC1155", "IERC165", "IERC721", "IERC721Receiver"]; +const interfacesWithMultipleArtifacts = [ + "IERC2981", + "IERC1155", + "IERC165", + "IERC721", + "IERC721Receiver", + "IAccessControl", +]; exports.interfacesWithMultipleArtifacts = interfacesWithMultipleArtifacts; diff --git a/submodules/lssvm b/submodules/lssvm new file mode 160000 index 000000000..3fc947819 --- /dev/null +++ b/submodules/lssvm @@ -0,0 +1 @@ +Subproject commit 3fc9478190cc44beee57de57128c59bb44f079ec diff --git a/test/domain/OfferTest.js b/test/domain/OfferTest.js index 8fc255e1f..e952fa084 100644 --- a/test/domain/OfferTest.js +++ b/test/domain/OfferTest.js @@ -2,6 +2,7 @@ const hre = require("hardhat"); const { getSigners, parseUnits, ZeroAddress } = hre.ethers; const { expect } = require("chai"); const Offer = require("../../scripts/domain/Offer"); +const PriceType = require("../../scripts/domain/PriceType"); /** * Test the Offer domain entity @@ -20,7 +21,8 @@ describe("Offer", function () { metadataUri, metadataHash, voided, - collectionIndex; + collectionIndex, + priceType; beforeEach(async function () { // Get a list of accounts @@ -37,6 +39,7 @@ describe("Offer", function () { metadataUri = `https://ipfs.io/ipfs/${metadataHash}`; voided = false; collectionIndex = "2"; + priceType = PriceType.Static; }); context("📋 Constructor", async function () { @@ -53,7 +56,8 @@ describe("Offer", function () { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ); expect(offer.idIsValid()).is.true; expect(offer.sellerIdIsValid()).is.true; @@ -65,8 +69,9 @@ describe("Offer", function () { expect(offer.metadataUriIsValid()).is.true; expect(offer.metadataHashIsValid()).is.true; expect(offer.voidedIsValid()).is.true; - expect(offer.isValid()).is.true; expect(offer.collectionIndexIsValid()).is.true; + expect(offer.priceTypeIsValid()).is.true; + expect(offer.isValid()).is.true; }); }); @@ -84,7 +89,8 @@ describe("Offer", function () { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ); expect(offer.isValid()).is.true; }); @@ -325,6 +331,43 @@ describe("Offer", function () { expect(offer.collectionIndexIsValid()).is.true; expect(offer.isValid()).is.true; }); + + it("Always present, priceType must be a valid PriceType", async function () { + // Invalid field value + offer.priceType = "zedzdeadbaby"; + expect(offer.priceTypeIsValid()).is.false; + expect(offer.isValid()).is.false; + + // Invalid field value + offer.priceType = new Date(); + expect(offer.priceTypeIsValid()).is.false; + expect(offer.isValid()).is.false; + + // Invalid field value + offer.priceType = 12; + expect(offer.priceTypeIsValid()).is.false; + expect(offer.isValid()).is.false; + + // Invalid field value + offer.priceType = "0"; + expect(offer.priceTypeIsValid()).is.false; + expect(offer.isValid()).is.false; + + // Invalid field value + offer.priceType = "126"; + expect(offer.priceTypeIsValid()).is.false; + expect(offer.isValid()).is.false; + + // Valid field value + offer.priceType = PriceType.Static; + expect(offer.priceTypeIsValid()).is.true; + expect(offer.isValid()).is.true; + + // Valid field value + offer.priceType = PriceType.Discovery; + expect(offer.priceTypeIsValid()).is.true; + expect(offer.isValid()).is.true; + }); }); context("📋 Utility functions", async function () { @@ -344,7 +387,8 @@ describe("Offer", function () { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ); expect(offer.isValid()).is.true; @@ -361,6 +405,7 @@ describe("Offer", function () { metadataHash, voided, collectionIndex, + priceType, }; }); @@ -391,6 +436,7 @@ describe("Offer", function () { offer.metadataHash, offer.voided, offer.collectionIndex, + offer.priceType, ]; // Get struct diff --git a/test/domain/PriceDiscoveryTest.js b/test/domain/PriceDiscoveryTest.js index b290793c4..0f6aea284 100644 --- a/test/domain/PriceDiscoveryTest.js +++ b/test/domain/PriceDiscoveryTest.js @@ -10,7 +10,7 @@ const Side = require("../../scripts/domain/Side"); describe("PriceDiscovery", function () { // Suite-wide scope let priceDiscovery, object, promoted, clone, dehydrated, rehydrated, key, value, struct; - let accounts, price, priceDiscoveryContract, priceDiscoveryData, side; + let accounts, price, side, priceDiscoveryContract, conduit, priceDiscoveryData; beforeEach(async function () { // Get a list of accounts @@ -19,17 +19,19 @@ describe("PriceDiscovery", function () { // Required constructor params price = "150"; priceDiscoveryContract = accounts[1].address; + conduit = accounts[2].address; priceDiscoveryData = "0xdeadbeef"; side = Side.Ask; }); context("📋 Constructor", async function () { it("Should allow creation of valid, fully populated PriceDiscovery instance", async function () { - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); + priceDiscovery = new PriceDiscovery(price, side, priceDiscoveryContract, conduit, priceDiscoveryData); expect(priceDiscovery.priceIsValid()).is.true; + expect(priceDiscovery.sideIsValid()).is.true; expect(priceDiscovery.priceDiscoveryContractIsValid()).is.true; + expect(priceDiscovery.conduitIsValid()).is.true; expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; - expect(priceDiscovery.sideIsValid()).is.true; expect(priceDiscovery.isValid()).is.true; }); }); @@ -37,7 +39,7 @@ describe("PriceDiscovery", function () { context("📋 Field validations", async function () { beforeEach(async function () { // Create a valid priceDiscovery, then set fields in tests directly - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); + priceDiscovery = new PriceDiscovery(price, side, priceDiscoveryContract, conduit, priceDiscoveryData); expect(priceDiscovery.isValid()).is.true; }); @@ -68,6 +70,28 @@ describe("PriceDiscovery", function () { expect(priceDiscovery.isValid()).is.true; }); + it("If present, side must be a Side enum", async function () { + // Invalid field value + priceDiscovery.side = "zedzdeadbaby"; + expect(priceDiscovery.sideIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.side = new Date(); + expect(priceDiscovery.sideIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.side = 12; + expect(priceDiscovery.sideIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.side = Side.Bid; + expect(priceDiscovery.sideIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + it("Always present, priceDiscoveryContract must be a string representation of an EIP-55 compliant address", async function () { // Invalid field value priceDiscovery.priceDiscoveryContract = "0xASFADF"; @@ -90,6 +114,28 @@ describe("PriceDiscovery", function () { expect(priceDiscovery.isValid()).is.true; }); + it("Always present, conduit must be a string representation of an EIP-55 compliant address", async function () { + // Invalid field value + priceDiscovery.conduit = "0xASFADF"; + expect(priceDiscovery.conduitIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Invalid field value + priceDiscovery.conduit = "zedzdeadbaby"; + expect(priceDiscovery.conduitIsValid()).is.false; + expect(priceDiscovery.isValid()).is.false; + + // Valid field value + priceDiscovery.conduit = accounts[0].address; + expect(priceDiscovery.conduitIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + + // Valid field value + priceDiscovery.conduit = "0xec2fd5bd6fc7b576dae82c0b9640969d8de501a2"; + expect(priceDiscovery.conduitIsValid()).is.true; + expect(priceDiscovery.isValid()).is.true; + }); + it("If present, priceDiscoveryData must be the string representation of bytes", async function () { // Invalid field value priceDiscovery.priceDiscoveryData = "zedzdeadbaby"; @@ -121,46 +167,25 @@ describe("PriceDiscovery", function () { expect(priceDiscovery.priceDiscoveryDataIsValid()).is.true; expect(priceDiscovery.isValid()).is.true; }); - - it("If present, side must be a Side enum", async function () { - // Invalid field value - priceDiscovery.side = "zedzdeadbaby"; - expect(priceDiscovery.sideIsValid()).is.false; - expect(priceDiscovery.isValid()).is.false; - - // Invalid field value - priceDiscovery.side = new Date(); - expect(priceDiscovery.sideIsValid()).is.false; - expect(priceDiscovery.isValid()).is.false; - - // Invalid field value - priceDiscovery.side = 12; - expect(priceDiscovery.sideIsValid()).is.false; - expect(priceDiscovery.isValid()).is.false; - - // Valid field value - priceDiscovery.side = Side.Bid; - expect(priceDiscovery.sideIsValid()).is.true; - expect(priceDiscovery.isValid()).is.true; - }); }); context("📋 Utility functions", async function () { beforeEach(async function () { // Create a valid priceDiscovery, then set fields in tests directly - priceDiscovery = new PriceDiscovery(price, priceDiscoveryContract, priceDiscoveryData, side); + priceDiscovery = new PriceDiscovery(price, side, priceDiscoveryContract, conduit, priceDiscoveryData); expect(priceDiscovery.isValid()).is.true; // Get plain object object = { price, + side, priceDiscoveryContract, + conduit, priceDiscoveryData, - side, }; // Struct representation - struct = [price, priceDiscoveryContract, priceDiscoveryData, side]; + struct = [price, side, priceDiscoveryContract, conduit, priceDiscoveryData]; }); context("👉 Static", async function () { diff --git a/test/integration/price-discovery/auction.js b/test/integration/price-discovery/auction.js new file mode 100644 index 000000000..698c5385f --- /dev/null +++ b/test/integration/price-discovery/auction.js @@ -0,0 +1,294 @@ +const { ethers } = require("hardhat"); +const { ZeroAddress, getContractFactory, getContractAt, provider } = ethers; +const { + deriveTokenId, + getCurrentBlockAndSetTimeForward, + setupTestEnvironment, + revertToSnapshot, + getSnapshot, + calculateBosonProxyAddress, + calculateCloneAddress, +} = require("../../util/utils"); +const { oneWeek } = require("../../util/constants"); +const { + mockSeller, + mockAuthToken, + mockVoucherInitValues, + mockOffer, + mockDisputeResolver, + accountId, +} = require("../../util/mock"); +const { expect } = require("chai"); +const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); +const PriceType = require("../../../scripts/domain/PriceType"); +const PriceDiscovery = require("../../../scripts/domain/PriceDiscovery"); +const Side = require("../../../scripts/domain/Side"); + +const MASK = (1n << 128n) - 1n; + +describe("[@skip-on-coverage] auction integration", function () { + let bosonVoucher; + let assistant, buyer, DR, rando; + let offer, offerDates; + let exchangeHandler; + let priceDiscoveryHandler; + let weth; + let seller; + let snapshotId; + + before(async function () { + accountId.next(true); + + // Specify contracts needed for this test + const contracts = { + accountHandler: "IBosonAccountHandler", + offerHandler: "IBosonOfferHandler", + fundsHandler: "IBosonFundsHandler", + exchangeHandler: "IBosonExchangeHandler", + priceDiscoveryHandler: "IBosonPriceDiscoveryHandler", + }; + + const wethFactory = await getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.waitForDeployment(); + + let accountHandler, offerHandler, fundsHandler; + + ({ + signers: [assistant, buyer, DR, rando], + contractInstances: { accountHandler, offerHandler, fundsHandler, exchangeHandler, priceDiscoveryHandler }, + extraReturnValues: { bosonVoucher }, + } = await setupTestEnvironment(contracts, { wethAddress: await weth.getAddress() })); + + seller = mockSeller(assistant.address, assistant.address, ZeroAddress, assistant.address); + + const emptyAuthToken = mockAuthToken(); + const voucherInitValues = mockVoucherInitValues(); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver(DR.address, DR.address, ZeroAddress, DR.address, true); + + const disputeResolverFees = [ + new DisputeResolverFee(ZeroAddress, "Native Currency", "0"), + new DisputeResolverFee(await weth.getAddress(), "WETH", "0"), + ]; + const sellerAllowList = [seller.id]; + + await accountHandler.connect(DR).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); + + let offerDurations, disputeResolverId; + ({ offer, offerDates, offerDurations, disputeResolverId } = await mockOffer()); + offer.quantityAvailable = 10; + offer.priceType = PriceType.Discovery; + // offer.exchangeToken = weth.address; + + await offerHandler + .connect(assistant) + .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, "0"); + + const beaconProxyAddress = await calculateBosonProxyAddress(await accountHandler.getAddress()); + const voucherAddress = calculateCloneAddress(await accountHandler.getAddress(), beaconProxyAddress, seller.admin); + bosonVoucher = await getContractAt("BosonVoucher", voucherAddress); + + // Pre mint range + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + + // Deposit seller funds so the commit will succeed + await fundsHandler + .connect(assistant) + .depositFunds(seller.id, ZeroAddress, offer.sellerDeposit, { value: offer.sellerDeposit }); + + // Get snapshot id + snapshotId = await getSnapshot(); + }); + + afterEach(async function () { + await revertToSnapshot(snapshotId); + snapshotId = await getSnapshot(); + }); + + context("Zora auction", async function () { + let tokenId, zoraAuction, amount, auctionId; + + beforeEach(async function () { + // 1. Deploy Zora Auction + const ZoraAuctionFactory = await getContractFactory("AuctionHouse"); + zoraAuction = await ZoraAuctionFactory.deploy(await weth.getAddress()); + + tokenId = deriveTokenId(offer.id, 2); + }); + + it("Transfer can't happens outside protocol", async function () { + // 2. Set approval for all + await bosonVoucher.connect(assistant).setApprovalForAll(await zoraAuction.getAddress(), true); + + // 3. Create an auction + const tokenContract = await bosonVoucher.getAddress(); + const duration = oneWeek; + const reservePrice = 1; + const curator = ZeroAddress; + const curatorFeePercentage = 0; + const auctionCurrency = offer.exchangeToken; + + await zoraAuction + .connect(assistant) + .createAuction(tokenId, tokenContract, duration, reservePrice, curator, curatorFeePercentage, auctionCurrency); + + // 4. Bid + auctionId = 0; + amount = 10; + await zoraAuction.connect(buyer).createBid(auctionId, amount, { value: amount }); + + // Set time forward + await getCurrentBlockAndSetTimeForward(oneWeek); + + // Zora should be the owner of the token + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(await zoraAuction.getAddress()); + + // safe transfer from will fail on onPremintedTransferredHook and transaction should fail + await expect(zoraAuction.connect(rando).endAuction(auctionId)).to.emit(zoraAuction, "AuctionCanceled"); + + // Exchange doesn't exist + const exchangeId = tokenId & MASK; + const [exist, ,] = await exchangeHandler.getExchange(exchangeId); + + expect(exist).to.equal(false); + }); + + context("Works with Zora auction wrapper", async function () { + let wrappedBosonVoucher; + + beforeEach(async function () { + // 2. Create wrapped voucher + const wrappedBosonVoucherFactory = await ethers.getContractFactory("ZoraWrapper"); + wrappedBosonVoucher = await wrappedBosonVoucherFactory + .connect(assistant) + .deploy( + await bosonVoucher.getAddress(), + await zoraAuction.getAddress(), + await exchangeHandler.getAddress(), + await weth.getAddress() + ); + + // 3. Wrap voucher + await bosonVoucher.connect(assistant).setApprovalForAll(await wrappedBosonVoucher.getAddress(), true); + await wrappedBosonVoucher.connect(assistant).wrap(tokenId); + + // 4. Create an auction + const tokenContract = await wrappedBosonVoucher.getAddress(); + const duration = oneWeek; + const reservePrice = 1; + const curator = assistant.address; + const curatorFeePercentage = 0; + const auctionCurrency = offer.exchangeToken; + + await zoraAuction + .connect(assistant) + .createAuction( + tokenId, + tokenContract, + duration, + reservePrice, + curator, + curatorFeePercentage, + auctionCurrency + ); + + auctionId = 0; + await zoraAuction.connect(assistant).setAuctionApproval(auctionId, true); + }); + + it("Auction ends normally", async function () { + // 5. Bid + const amount = 10; + + await zoraAuction.connect(buyer).createBid(auctionId, amount, { value: amount }); + + // 6. End auction + await getCurrentBlockAndSetTimeForward(oneWeek); + await zoraAuction.connect(assistant).endAuction(auctionId); + + expect(await wrappedBosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); + expect(await weth.balanceOf(await wrappedBosonVoucher.getAddress())).to.equal(amount); + + // 7. Commit to offer + const calldata = wrappedBosonVoucher.interface.encodeFunctionData("unwrap", [tokenId]); + const priceDiscovery = new PriceDiscovery( + amount, + Side.Bid, + await wrappedBosonVoucher.getAddress(), + await wrappedBosonVoucher.getAddress(), + calldata + ); + + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + + const tx = await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + const { timestamp } = await provider.getBlock(tx.blockNumber); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal( + protocolBalanceBefore + BigInt(amount) + ); + + const exchangeId = tokenId & MASK; + const [, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(voucher.committedDate).to.equal(timestamp); + }); + + it("Cancel auction", async function () { + // 6. Cancel auction + await zoraAuction.connect(assistant).cancelAuction(auctionId); + + // 7. Unwrap token + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + await wrappedBosonVoucher.connect(assistant).unwrap(tokenId); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(assistant.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal(protocolBalanceBefore); + + const exchangeId = tokenId & MASK; + const [exists, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(exists).to.equal(false); + expect(voucher.committedDate).to.equal(0); + }); + + it("Cancel auction and unwrap via commitToPriceDiscoveryOffer", async function () { + // How sensible is this scenario? Should it be prevented? + + // 6. Cancel auction + await zoraAuction.connect(assistant).cancelAuction(auctionId); + + // 7. Unwrap token via commitToOffer + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + + const calldata = wrappedBosonVoucher.interface.encodeFunctionData("unwrap", [tokenId]); + const priceDiscovery = new PriceDiscovery( + 0, + Side.Bid, + await wrappedBosonVoucher.getAddress(), + await wrappedBosonVoucher.getAddress(), + calldata + ); + const tx = await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(assistant.address, tokenId, priceDiscovery); + const { timestamp } = await provider.getBlock(tx.blockNumber); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(assistant.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal(protocolBalanceBefore); + + const exchangeId = tokenId & MASK; + const [exists, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(exists).to.equal(true); + expect(voucher.committedDate).to.equal(timestamp); + }); + }); + }); +}); diff --git a/test/integration/price-discovery/seaport.js b/test/integration/price-discovery/seaport.js new file mode 100644 index 000000000..542e0cb30 --- /dev/null +++ b/test/integration/price-discovery/seaport.js @@ -0,0 +1,194 @@ +const { ethers } = require("hardhat"); +const { ZeroHash, ZeroAddress, getContractAt, getContractFactory } = ethers; + +const { + calculateBosonProxyAddress, + calculateCloneAddress, + deriveTokenId, + getEvent, + setupTestEnvironment, + revertToSnapshot, + getSnapshot, + objectToArray, +} = require("../../util/utils"); + +const { + mockSeller, + mockAuthToken, + mockVoucherInitValues, + mockOffer, + mockDisputeResolver, + accountId, +} = require("../../util/mock"); +const { assert } = require("chai"); +const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); +const SeaportSide = require("../seaport/SideEnum"); +const Side = require("../../../scripts/domain/Side"); +const PriceDiscovery = require("../../../scripts/domain/PriceDiscovery"); +const PriceType = require("../../../scripts/domain/PriceType"); +const { seaportFixtures } = require("../seaport/fixtures"); +const { SEAPORT_ADDRESS } = require("../../util/constants"); +const ItemType = require("../seaport/ItemTypeEnum"); + +describe("[@skip-on-coverage] seaport integration", function () { + this.timeout(100000000); + let bosonVoucher; + let assistant, buyer, DR; + let fixtures; + let offer, offerDates; + let exchangeHandler, priceDiscoveryHandler; + let weth; + let seller; + let seaport; + let snapshotId; + + before(async function () { + accountId.next(); + // Specify contracts needed for this test + const contracts = { + accountHandler: "IBosonAccountHandler", + offerHandler: "IBosonOfferHandler", + fundsHandler: "IBosonFundsHandler", + exchangeHandler: "IBosonExchangeHandler", + priceDiscoveryHandler: "IBosonPriceDiscoveryHandler", + }; + + const wethFactory = await getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.waitForDeployment(); + + let accountHandler, offerHandler, fundsHandler; + + ({ + signers: [, assistant, buyer, DR], + contractInstances: { accountHandler, offerHandler, fundsHandler, exchangeHandler, priceDiscoveryHandler }, + extraReturnValues: { bosonVoucher }, + } = await setupTestEnvironment(contracts, { wethAddress: await weth.getAddress() })); + + seller = mockSeller(assistant.address, assistant.address, ZeroAddress, assistant.address); + + const emptyAuthToken = mockAuthToken(); + const voucherInitValues = mockVoucherInitValues(); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver(DR.address, DR.address, ZeroAddress, DR.address, true); + + const disputeResolverFees = [new DisputeResolverFee(ZeroAddress, "Native", "0")]; + const sellerAllowList = [seller.id]; + + await accountHandler.connect(DR).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); + + let offerDurations, disputeResolverId; + ({ offer, offerDates, offerDurations, disputeResolverId } = await mockOffer()); + offer.quantityAvailable = 10; + offer.priceType = PriceType.Discovery; + + await offerHandler + .connect(assistant) + .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, "0"); + + const beaconProxyAddress = await calculateBosonProxyAddress(await accountHandler.getAddress()); + const voucherAddress = calculateCloneAddress(await accountHandler.getAddress(), beaconProxyAddress, seller.admin); + bosonVoucher = await getContractAt("BosonVoucher", voucherAddress); + + seaport = await getContractAt("Seaport", SEAPORT_ADDRESS); + + await bosonVoucher.connect(assistant).setApprovalForAllToContract(SEAPORT_ADDRESS, true); + + fixtures = await seaportFixtures(seaport); + + // Pre mint range + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, voucherAddress); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + + // Deposit seller funds so the commit will succeed + await fundsHandler + .connect(assistant) + .depositFunds(seller.id, ZeroAddress, offer.sellerDeposit, { value: offer.sellerDeposit }); + + // Get snapshot id + snapshotId = await getSnapshot(); + }); + + afterEach(async function () { + await revertToSnapshot(snapshotId); + snapshotId = await getSnapshot(); + }); + + it("Seaport criteria-based order is used as price discovery mechanism for a BP offer", async function () { + // Create seaport offer which tokenId 1 + const seaportOffer = fixtures.getTestVoucher( + ItemType.ERC721_WITH_CRITERIA, + 0, + await bosonVoucher.getAddress(), + 1, + 1 + ); + const consideration = fixtures.getTestToken( + ItemType.NATIVE, + 0, + ZeroAddress, + offer.price, + offer.price, + await bosonVoucher.getAddress() + ); + + const { order, orderHash, value } = await fixtures.getOrder( + bosonVoucher, + undefined, + [seaportOffer], //offer + [consideration], + 0, // full + offerDates.validFrom, // startDate + offerDates.validUntil // endDate + ); + + const orders = [objectToArray(order)]; + const calldata = seaport.interface.encodeFunctionData("validate", [orders]); + + const seaportAddress = await seaport.getAddress(); + await bosonVoucher.connect(assistant).callExternalContract(seaportAddress, calldata); + await bosonVoucher.connect(assistant).setApprovalForAllToContract(seaportAddress, true); + + let totalFilled, isValidated; + + ({ isValidated, totalFilled } = await seaport.getOrderStatus(orderHash)); + assert(isValidated, "Order is not validated"); + assert.equal(totalFilled, 0n); + + // turn order into advanced order + order.denominator = 1; + order.numerator = 1; + order.extraData = "0x"; + + const identifier = deriveTokenId(offer.id, 2); + const resolvers = [fixtures.getCriteriaResolver(0, SeaportSide.OFFER, 0, identifier, [])]; + + const priceDiscoveryData = seaport.interface.encodeFunctionData("fulfillAdvancedOrder", [ + order, + resolvers, + ZeroHash, + ZeroAddress, + ]); + + const priceDiscovery = new PriceDiscovery(value, seaportAddress, priceDiscoveryData, Side.Ask); + + // Seller needs to deposit weth in order to fill the escrow at the last step + await weth.connect(buyer).deposit({ value }); + await weth.connect(buyer).approve(await exchangeHandler.getAddress(), value); + + const tx = await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, offer.id, priceDiscovery, { + value, + }); + + const receipt = await tx.wait(); + + ({ totalFilled } = await seaport.getOrderStatus(orderHash)); + assert.equal(totalFilled, 1n); + const event = getEvent(receipt, seaport, "OrderFulfilled"); + + assert.equal(orderHash, event[0]); + }); +}); diff --git a/test/integration/price-discovery/sudoswap.js b/test/integration/price-discovery/sudoswap.js new file mode 100644 index 000000000..9f52eab53 --- /dev/null +++ b/test/integration/price-discovery/sudoswap.js @@ -0,0 +1,228 @@ +const { ethers } = require("hardhat"); +const { ZeroAddress, MaxUint256, getContractFactory, getContractAt, parseUnits, provider, id } = ethers; +const { + mockSeller, + mockAuthToken, + mockVoucherInitValues, + mockOffer, + mockDisputeResolver, + accountId, +} = require("../../util/mock"); +const { expect } = require("chai"); +const { + calculateBosonProxyAddress, + calculateCloneAddress, + deriveTokenId, + setupTestEnvironment, +} = require("../../util/utils"); + +const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); +const Side = require("../../../scripts/domain/Side"); +const PriceDiscovery = require("../../../scripts/domain/PriceDiscovery"); +const PriceType = require("../../../scripts/domain/PriceType"); + +const MASK = (1n << 128n) - 1n; + +describe("[@skip-on-coverage] sudoswap integration", function () { + this.timeout(100000000); + let lssvmPairFactory, linearCurve; + let bosonVoucher; + let deployer, assistant, buyer, DR; + let offer; + let exchangeHandler, priceDiscoveryHandler; + let weth, wethAddress; + let seller; + + before(async function () { + accountId.next(); + + // Specify contracts needed for this test + const contracts = { + accountHandler: "IBosonAccountHandler", + offerHandler: "IBosonOfferHandler", + fundsHandler: "IBosonFundsHandler", + exchangeHandler: "IBosonExchangeHandler", + priceDiscoveryHandler: "IBosonPriceDiscoveryHandler", + }; + + const wethFactory = await getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.waitForDeployment(); + wethAddress = await weth.getAddress(); + + let accountHandler, offerHandler, fundsHandler; + + ({ + signers: [deployer, assistant, buyer, DR], + contractInstances: { accountHandler, offerHandler, fundsHandler, exchangeHandler, priceDiscoveryHandler }, + extraReturnValues: { bosonVoucher }, + } = await setupTestEnvironment(contracts, { wethAddress })); + + const LSSVMPairEnumerableETH = await getContractFactory("LSSVMPairEnumerableETH", deployer); + const lssvmPairEnumerableETH = await LSSVMPairEnumerableETH.deploy(); + await lssvmPairEnumerableETH.waitForDeployment(); + + const LSSVMPairEnumerableERC20 = await getContractFactory("LSSVMPairEnumerableERC20", deployer); + const lssvmPairEnumerableERC20 = await LSSVMPairEnumerableERC20.deploy(); + await lssvmPairEnumerableERC20.waitForDeployment(); + + const LSSVMPairMissingEnumerableETH = await getContractFactory("LSSVMPairMissingEnumerableETH", deployer); + const lssvmPairMissingEnumerableETH = await LSSVMPairMissingEnumerableETH.deploy(); + + const LSSVMPairMissingEnumerableERC20 = await getContractFactory("LSSVMPairMissingEnumerableERC20", deployer); + const lssvmPairMissingEnumerableERC20 = await LSSVMPairMissingEnumerableERC20.deploy(); + + const LSSVMPairFactory = await getContractFactory("LSSVMPairFactory", deployer); + + lssvmPairFactory = await LSSVMPairFactory.deploy( + await lssvmPairEnumerableETH.getAddress(), + await lssvmPairMissingEnumerableETH.getAddress(), + await lssvmPairEnumerableERC20.getAddress(), + await lssvmPairMissingEnumerableERC20.getAddress(), + deployer.address, + "0" + ); + await lssvmPairFactory.waitForDeployment(); + + // Deploy bonding curves + const LinearCurve = await getContractFactory("LinearCurve", deployer); + linearCurve = await LinearCurve.deploy(); + await linearCurve.waitForDeployment(); + + // Whitelist bonding curve + await lssvmPairFactory.setBondingCurveAllowed(await linearCurve.getAddress(), true); + + seller = mockSeller(assistant.address, assistant.address, ZeroAddress, assistant.address); + + const emptyAuthToken = mockAuthToken(); + const voucherInitValues = mockVoucherInitValues(); + await accountHandler.connect(assistant).createSeller(seller, emptyAuthToken, voucherInitValues); + + const disputeResolver = mockDisputeResolver(DR.address, DR.address, ZeroAddress, DR.address, true); + + const disputeResolverFees = [new DisputeResolverFee(wethAddress, "WETH", "0")]; + const sellerAllowList = [seller.id]; + + await accountHandler.connect(DR).createDisputeResolver(disputeResolver, disputeResolverFees, sellerAllowList); + + let offerDates, offerDurations, disputeResolverId; + ({ offer, offerDates, offerDurations, disputeResolverId } = await mockOffer()); + offer.exchangeToken = wethAddress; + offer.quantityAvailable = 10; + offer.priceType = PriceType.Discovery; + + await offerHandler + .connect(assistant) + .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, "0"); + + const pool = BigInt(offer.sellerDeposit) * BigInt(offer.quantityAvailable); + + await weth.connect(assistant).deposit({ value: pool }); + + // Approves protocol to transfer sellers weth + await weth.connect(assistant).approve(await fundsHandler.getAddress(), pool); + + // Deposit funds + await fundsHandler.connect(assistant).depositFunds(seller.id, wethAddress, pool); + + // Reverse range + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); + + // Gets boson voucher contract + const beaconProxyAddress = await calculateBosonProxyAddress(await accountHandler.getAddress()); + const voucherAddress = calculateCloneAddress(await accountHandler.getAddress(), beaconProxyAddress, seller.admin); + bosonVoucher = await getContractAt("BosonVoucher", voucherAddress); + + // Pre mint range + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + }); + + it("Works with wrapped vouchers", async function () { + const poolType = 1; // NFT + const delta = parseUnits("0.25", "ether").toString(); + const fee = "0"; + const spotPrice = offer.price; + const nftIds = []; + + for (let i = 1; i <= offer.quantityAvailable; i++) { + const tokenId = deriveTokenId(offer.id, i); + nftIds.push(tokenId); + } + + const initialPoolBalance = parseUnits("10", "ether").toString(); + await weth.connect(assistant).deposit({ value: initialPoolBalance }); + await weth.connect(assistant).approve(await lssvmPairFactory.getAddress(), MaxUint256); + + const WrappedBosonVoucherFactory = await getContractFactory("SudoswapWrapper"); + const wrappedBosonVoucher = await WrappedBosonVoucherFactory.connect(assistant).deploy( + await bosonVoucher.getAddress(), + await lssvmPairFactory.getAddress(), + await exchangeHandler.getAddress(), + wethAddress + ); + const wrappedBosonVoucherAddress = await wrappedBosonVoucher.getAddress(); + + await bosonVoucher.connect(assistant).setApprovalForAll(wrappedBosonVoucherAddress, true); + + await wrappedBosonVoucher.connect(assistant).wrap(nftIds); + + const createPairERC20Parameters = { + token: wethAddress, + nft: wrappedBosonVoucherAddress, + bondingCurve: await linearCurve.getAddress(), + assetRecipient: wrappedBosonVoucherAddress, + poolType, + delta, + fee, + spotPrice, + initialNFTIDs: nftIds, + initialTokenBalance: initialPoolBalance, + }; + + await wrappedBosonVoucher.connect(assistant).setApprovalForAll(await lssvmPairFactory.getAddress(), true); + + let tx = await lssvmPairFactory.connect(assistant).createPairERC20(createPairERC20Parameters); + + const { logs } = await tx.wait(); + + const NewPairTopic = id("NewPair(address)"); + const [poolAddress] = logs.find((e) => e?.topics[0] === NewPairTopic).args; + + await wrappedBosonVoucher.connect(assistant).setPoolAddress(poolAddress); + + const pool = await getContractAt("LSSVMPairMissingEnumerable", poolAddress); + + const [, , , inputAmount] = await pool.getBuyNFTQuote(1); + + await weth.connect(buyer).deposit({ value: inputAmount * 2n }); + await weth.connect(buyer).approve(wrappedBosonVoucherAddress, inputAmount * 2n); + + const tokenId = deriveTokenId(offer.id, 1); + + const swapTokenTx = await wrappedBosonVoucher.connect(buyer).swapTokenForSpecificNFT(tokenId, inputAmount); + + expect(swapTokenTx).to.emit(pool, "SwapTokenForAnyNFTs"); + + const calldata = wrappedBosonVoucher.interface.encodeFunctionData("unwrap", [tokenId]); + + const priceDiscovery = new PriceDiscovery(inputAmount, wrappedBosonVoucherAddress, calldata, Side.Ask); + + const protocolBalanceBefore = await weth.balanceOf(await exchangeHandler.getAddress()); + + tx = await priceDiscoveryHandler.connect(buyer).commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + await expect(tx).to.emit(exchangeHandler, "BuyerCommitted"); + + const { timestamp } = await provider.getBlock(tx.blockNumber); + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); + + const protocolBalanceAfter = await weth.balanceOf(await exchangeHandler.getAddress()); + + expect(protocolBalanceAfter).to.equal(protocolBalanceBefore + inputAmount); + + const exchangeId = tokenId & MASK; + const [, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(voucher.committedDate).to.equal(timestamp); + }); +}); diff --git a/test/integration/seaport/ItemTypeEnum.js b/test/integration/seaport/ItemTypeEnum.js new file mode 100644 index 000000000..1aff910a0 --- /dev/null +++ b/test/integration/seaport/ItemTypeEnum.js @@ -0,0 +1,23 @@ +/** + * Seaport Domain Enum: ItemType + */ +class ItemType {} + +ItemType.NATIVE = 0; +ItemType.ERC20 = 1; +ItemType.ERC721 = 2; +ItemType.ERC1155 = 3; +ItemType.ERC721_WITH_CRITERIA = 4; +ItemType.ERC1155_WITH_CRITERIA = 5; + +ItemType.Types = [ + ItemType.NATIVE, + ItemType.ERC20, + ItemType.ERC721, + ItemType.ERC1155, + ItemType.ERC721_WITH_CRITERIA, + ItemType.ERC1155_WITH_CRITERIA, +]; + +// Export +module.exports = ItemType; diff --git a/test/integration/seaport/SideEnum.js b/test/integration/seaport/SideEnum.js new file mode 100644 index 000000000..cf3a5e844 --- /dev/null +++ b/test/integration/seaport/SideEnum.js @@ -0,0 +1,12 @@ +/** + * Seaport Domain Enum: Side + */ +class Side {} + +Side.OFFER = 0; +Side.CONSIDERATION = 1; + +Side.Types = [Side.OFFER, Side.CONSIDERATION]; + +// Export +module.exports = Side; diff --git a/test/integration/seaport/fixtures.js b/test/integration/seaport/fixtures.js index c65b4462a..5ac10cab7 100644 --- a/test/integration/seaport/fixtures.js +++ b/test/integration/seaport/fixtures.js @@ -3,14 +3,29 @@ const { ZeroHash, ZeroAddress } = hre.ethers; const { getOfferOrConsiderationItem, calculateOrderHash } = require("./utils"); const { expect } = require("chai"); const OrderType = require("./OrderTypeEnum"); +const ItemType = require("./ItemTypeEnum"); +const Side = require("./SideEnum"); const seaportFixtures = async (seaport) => { - const getTestVoucher = function (identifierOrCriteria, token, startAmount = 1, endAmount = 1) { - return getOfferOrConsiderationItem(2, token, identifierOrCriteria, startAmount, endAmount); + const getTestVoucher = function ( + itemType = ItemType.ERC721, + identifierOrCriteria, + token, + startAmount = 1, + endAmount = 1 + ) { + return getOfferOrConsiderationItem(itemType, token, identifierOrCriteria, startAmount, endAmount); }; - const getTestToken = function (identifierOrCriteria, token = ZeroAddress, startAmount = 1, endAmount = 1, recipient) { - return getOfferOrConsiderationItem(0, token, identifierOrCriteria, startAmount, endAmount, recipient); + const getTestToken = function ( + itemType = ItemType.NATIVE, + identifierOrCriteria, + token = ZeroAddress, + startAmount = 1, + endAmount = 1, + recipient + ) { + return getOfferOrConsiderationItem(itemType, token, identifierOrCriteria, startAmount, endAmount, recipient); }; const getAndVerifyOrderHash = async (orderComponents) => { @@ -59,14 +74,13 @@ const seaportFixtures = async (seaport) => { }; // How much ether (at most) needs to be supplied when fulfilling the order - const value = offer - .map((x) => (x.itemType === 0 ? (x.endAmount.gt(x.startAmount) ? x.endAmount : x.startAmount) : BigInt(0))) - .reduce((a, b) => a + b, BigInt(0)) - .add( - consideration - .map((x) => (x.itemType === 0 ? (x.endAmount.gt(x.startAmount) ? x.endAmount : x.startAmount) : BigInt(0))) - .reduce((a, b) => a + b, BigInt(0)) - ); + const value = + offer + .map((x) => (x.itemType === 0 ? (x.endAmount > x.startAmount ? x.endAmount : x.startAmount) : 0n)) + .reduce((a, b) => a + b, 0n) + + consideration + .map((x) => (x.itemType === 0 ? (x.endAmount > x.startAmount ? x.endAmount : x.startAmount) : 0n)) + .reduce((a, b) => a + b, 0n); return { order, @@ -75,10 +89,57 @@ const seaportFixtures = async (seaport) => { }; }; + const getAdvancedOrder = async function ( + offerer, + zone = ZeroAddress, + offer, + consideration, + orderType = OrderType.FULL_OPEN, + startTime, + endTime, + zoneHash = ZeroHash, + salt = 0, + conduitKey = ZeroHash, + numerator = 1, + denominator = 1 + ) { + let order, orderHash, value; + ({ order, orderHash, value } = await getOrder( + offerer, + zone, + offer, + consideration, + orderType, + startTime, + endTime, + zoneHash, + salt, + conduitKey + )); + + order.numerator = numerator; + order.denominator = denominator; + order.extraData = ZeroHash; + + return { order, orderHash, value }; + }; + + const getCriteriaResolver = (orderIndex = 0, side = Side.OFFER, index = 0, identifier = 1, criteriaProof) => { + return { + orderIndex, + side, + index, + identifier, + criteriaProof, + }; + }; + return { getOrder, getTestVoucher, getTestToken, + getCriteriaResolver, + getAdvancedOrder, }; }; diff --git a/test/integration/seaport/seaport-integration.js b/test/integration/seaport/seaport-integration.js index 86d4181c6..c129cf23e 100644 --- a/test/integration/seaport/seaport-integration.js +++ b/test/integration/seaport/seaport-integration.js @@ -1,6 +1,12 @@ const { ethers } = require("hardhat"); const { ZeroAddress, BigNumber, getContractAt, ZeroHash } = ethers; -const { setupTestEnvironment, getEvent, calculateContractAddress, objectToArray } = require("../../util/utils"); +const { + setupTestEnvironment, + getEvent, + calculateBosonProxyAddress, + calculateCloneAddress, + objectToArray, +} = require("../../util/utils"); const { SEAPORT_ADDRESS } = require("../../util/constants"); const { @@ -14,7 +20,14 @@ const { const { assert, expect } = require("chai"); let { seaportFixtures } = require("./fixtures.js"); const { DisputeResolverFee } = require("../../../scripts/domain/DisputeResolverFee"); - +const ItemType = require("./ItemTypeEnum"); + +// This test checks whether the Boson Voucher contract can be used as the offerer in Seaport offers. +// We need to handle funds internally on the protocol, and Seaport sends the money to the offerer's account. +// Therefore, we need to use the BV contract as the offerer to manage funds. +// Seaport allows offer creation in two ways: signing a message or calling an on-chain validate function. +// Contract accounts cannot sign messages. Therefore, we have created a function on the BV contract that can run arbitrary methods, +// which only the BV contract owner (assistant) can call. // Requirements to run this test: // - Seaport submodule contains a `artifacts` folder inside it. Run `git submodule update --init --recursive` to get it. // - Set hardhat config to hardhat-fork.config.js. e.g.: @@ -75,7 +88,8 @@ describe("[@skip-on-coverage] Seaport integration", function () { .connect(assistant) .createOffer(offer.toStruct(), offerDates.toStruct(), offerDurations.toStruct(), disputeResolverId, "0"); - const voucherAddress = calculateContractAddress(await accountHandler.getAddress(), seller.id); + const beaconProxyAddress = await calculateBosonProxyAddress(await accountHandler.getAddress()); + const voucherAddress = calculateCloneAddress(await accountHandler.getAddress(), beaconProxyAddress, seller.admin); bosonVoucher = await getContractAt("BosonVoucher", voucherAddress); // Pool needs to cover both seller deposit and price @@ -85,15 +99,13 @@ describe("[@skip-on-coverage] Seaport integration", function () { }); // Pre mint range - await offerHandler - .connect(assistant) - .reserveRange(offer.id, offer.quantityAvailable, await bosonVoucher.getAddress()); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, voucherAddress); await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); // Create seaport offer which tokenId 1 const endDate = "0xff00000000000000000000000000"; - const seaportOffer = seaportFixtures.getTestVoucher(1, await bosonVoucher.getAddress(), 1, 1); - const consideration = seaportFixtures.getTestToken(0, undefined, 1, 2, await bosonVoucher.getAddress()); + const seaportOffer = seaportFixtures.getTestVoucher(ItemType.ERC721, 1, voucherAddress, 1, 1); + const consideration = seaportFixtures.getTestToken(ItemType.NATIVE, 0, undefined, 1, 2, voucherAddress); ({ order, orderHash, value } = await seaportFixtures.getOrder( bosonVoucher, undefined, diff --git a/test/integration/seaport/utils.js b/test/integration/seaport/utils.js index 365259318..6bd0d526c 100644 --- a/test/integration/seaport/utils.js +++ b/test/integration/seaport/utils.js @@ -1,7 +1,9 @@ -const { BigNumber, utils, ZeroAddress } = require("ethers"); +const { ZeroAddress, keccak256, id, solidityPackedKeccak256 } = require("ethers"); +const { ItemType } = require("./ItemTypeEnum.js"); +const { MerkleTree } = require("merkletreejs"); const getOfferOrConsiderationItem = function ( - itemType = 0, + itemType = ItemType.NATIVE, token = ZeroAddress, identifierOrCriteria = 0, startAmount = 1, @@ -11,9 +13,9 @@ const getOfferOrConsiderationItem = function ( const item = { itemType, token, - identifierOrCriteria: BigNumber.from(identifierOrCriteria), - startAmount: BigNumber.from(startAmount), - endAmount: BigNumber.from(endAmount), + identifierOrCriteria: BigInt(identifierOrCriteria), + startAmount: BigInt(startAmount), + endAmount: BigInt(endAmount), }; if (recipient) { @@ -31,72 +33,92 @@ const calculateOrderHash = (orderComponents) => { "OrderComponents(address offerer,address zone,OfferItem[] offer,ConsiderationItem[] consideration,uint8 orderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 conduitKey,uint256 counter)"; const orderTypeString = `${orderComponentsPartialTypeString}${considerationItemTypeString}${offerItemTypeString}`; - const offerItemTypeHash = utils.keccak256(utils.toUtf8Bytes(offerItemTypeString)); - const considerationItemTypeHash = utils.keccak256(utils.toUtf8Bytes(considerationItemTypeString)); - const orderTypeHash = utils.keccak256(utils.toUtf8Bytes(orderTypeString)); + const offerItemTypeHash = id(offerItemTypeString); + const considerationItemTypeHash = id(considerationItemTypeString); + const orderTypeHash = id(orderTypeString); - const offerHash = utils.keccak256( - "0x" + - orderComponents.offer - .map((offerItem) => { - return utils - .keccak256( - "0x" + - [ - offerItemTypeHash.slice(2), - offerItem.itemType.toString().padStart(64, "0"), - offerItem.token.slice(2).padStart(64, "0"), - BigNumber.from(offerItem.identifierOrCriteria).toString(16).slice(2).padStart(64, "0"), - BigNumber.from(offerItem.startAmount).toString(16).slice(2).padStart(64, "0"), - BigNumber.from(offerItem.endAmount).toString(16).slice(2).padStart(64, "0"), - ].join("") - ) - .slice(2); - }) - .join("") + const offerHash = solidityPackedKeccak256( + new Array(orderComponents.offer.length).fill("bytes32"), + orderComponents.offer.map((offerItem) => { + return solidityPackedKeccak256( + ["bytes32", "uint256", "uint256", "uint256", "uint256", "uint256"], + + [ + offerItemTypeHash, + offerItem.itemType, + offerItem.token, + offerItem.identifierOrCriteria, + offerItem.startAmount, + offerItem.endAmount, + ] + ); + }) ); - const considerationHash = utils.keccak256( - "0x" + - orderComponents.consideration - .map((considerationItem) => { - return utils - .keccak256( - "0x" + - [ - considerationItemTypeHash.slice(2), - considerationItem.itemType.toString().padStart(64, "0"), - considerationItem.token.slice(2).padStart(64, "0"), - BigNumber.from(considerationItem.identifierOrCriteria).toString(16).slice(2).padStart(64, "0"), - BigNumber.from(considerationItem.startAmount).toString(16).slice(2).padStart(64, "0"), - BigNumber.from(considerationItem.endAmount).toString(16).slice(2).padStart(64, "0"), - considerationItem.recipient.slice(2).padStart(64, "0"), - ].join("") - ) - .slice(2); - }) - .join("") + const considerationHash = solidityPackedKeccak256( + new Array(orderComponents.consideration.length).fill("bytes32"), + orderComponents.consideration.map((considerationItem) => { + return solidityPackedKeccak256( + ["bytes32", "uint256", "uint256", "uint256", "uint256", "uint256", "uint256"], + [ + considerationItemTypeHash, + considerationItem.itemType, + considerationItem.token, + considerationItem.identifierOrCriteria, + considerationItem.startAmount, + considerationItem.endAmount, + considerationItem.recipient, + ] + ); + }) ); - const derivedOrderHash = utils.keccak256( - "0x" + - [ - orderTypeHash.slice(2), - orderComponents.offerer.slice(2).padStart(64, "0"), - orderComponents.zone.slice(2).padStart(64, "0"), - offerHash.slice(2), - considerationHash.slice(2), - orderComponents.orderType.toString().padStart(64, "0"), - BigNumber.from(orderComponents.startTime).toString(16).slice(2).padStart(64, "0"), - BigNumber.from(orderComponents.endTime).toString(16).slice(2).padStart(64, "0"), - orderComponents.zoneHash.slice(2), - BigNumber.from(orderComponents.salt).toString(16).slice(2).padStart(64, "0"), - orderComponents.conduitKey.slice(2).padStart(64, "0"), - BigNumber.from(orderComponents.counter).toString(16).slice(2).padStart(64, "0"), - ].join("") + const derivedOrderHash = solidityPackedKeccak256( + [ + "bytes32", + "uint256", + "uint256", + "bytes32", + "bytes32", + "uint256", + "uint256", + "uint256", + "bytes32", + "uint256", + "uint256", + "uint256", + ], + [ + orderTypeHash, + orderComponents.offerer, + orderComponents.zone, + offerHash, + considerationHash, + orderComponents.orderType, + orderComponents.startTime, + orderComponents.endTime, + orderComponents.zoneHash, + orderComponents.salt, + orderComponents.conduitKey, + orderComponents.counter, + ] ); return derivedOrderHash; }; + +function getRootAndProof(start, end, leaf) { + const leaves = []; + + for (let i = start; i <= end; i++) { + leaves.push(i); + } + const merkleTree = new MerkleTree(leaves, keccak256, { hashLeaves: true }); + + const proof = merkleTree.getHexProof(keccak256(leaf)); + + return { root: merkleTree.getHexRoot(), proof }; +} exports.getOfferOrConsiderationItem = getOfferOrConsiderationItem; exports.calculateOrderHash = calculateOrderHash; +exports.getRootAndProof = getRootAndProof; diff --git a/test/protocol/ExchangeHandlerTest.js b/test/protocol/ExchangeHandlerTest.js index 1e54981c9..1963a67ec 100644 --- a/test/protocol/ExchangeHandlerTest.js +++ b/test/protocol/ExchangeHandlerTest.js @@ -92,6 +92,7 @@ describe("IBosonExchangeHandler", function () { adminDR, clerkDR, treasuryDR; + let erc165, accessController, accountHandler, @@ -841,7 +842,7 @@ describe("IBosonExchangeHandler", function () { .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); - // Attempt to commit to the not availabe offer, expecting revert + // Attempt to commit to the not available offer, expecting revert await expect( exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), ++offerId, { value: price }) ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); @@ -863,7 +864,7 @@ describe("IBosonExchangeHandler", function () { await offerHandler .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); - // Commit to offer, so it's not availble anymore + // Commit to offer, so it's not available anymore await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), ++offerId, { value: price }); // Attempt to commit to the sold out offer, expecting revert @@ -893,7 +894,10 @@ describe("IBosonExchangeHandler", function () { }); }); - context("👉 commitToPremintedOffer()", async function () { + context("👉 onPremintedVoucherTransferred()", async function () { + // These tests are mainly for preminted vouchers of fixed price offers + // The part of onPremintedVoucherTransferred that is specific to + // price discovery offers is indirectly tested in `PriceDiscoveryHandlerFacet.js` let tokenId; beforeEach(async function () { // Reserve range @@ -1364,7 +1368,14 @@ describe("IBosonExchangeHandler", function () { it("Caller is not the voucher contract, owned by the seller", async function () { // Attempt to commit to preminted offer, expecting revert await expect( - exchangeHandler.connect(rando).commitToPreMintedOffer(await buyer.getAddress(), offerId, tokenId) + exchangeHandler + .connect(rando) + .onPremintedVoucherTransferred( + tokenId, + await buyer.getAddress(), + await assistant.getAddress(), + await assistant.getAddress() + ) ).to.revertedWith(RevertReasons.ACCESS_DENIED); }); @@ -1385,7 +1396,12 @@ describe("IBosonExchangeHandler", function () { await expect( exchangeHandler .connect(impersonatedBosonVoucher) - .commitToPreMintedOffer(await buyer.getAddress(), offerId, exchangeId) + .onPremintedVoucherTransferred( + tokenId, + await buyer.getAddress(), + await assistant.getAddress(), + await assistant.getAddress() + ) ).to.revertedWith(RevertReasons.EXCHANGE_ALREADY_EXISTS); }); @@ -1450,7 +1466,7 @@ describe("IBosonExchangeHandler", function () { await offerHandler .connect(assistant) .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); - // Commit to offer, so it's not availble anymore + // Commit to offer, so it's not available anymore await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), ++offerId, { value: price }); // Attempt to commit to the sold out offer, expecting revert @@ -2417,7 +2433,7 @@ describe("IBosonExchangeHandler", function () { // add offer to group await groupHandler.connect(assistant).addOffersToGroup(groupId, [++offerId]); - // Commit to offer, so it's not availble anymore + // Commit to offer, so it's not available anymore await exchangeHandler .connect(buyer) .commitToConditionalOffer(await buyer.getAddress(), offerId, tokenId, { value: price }); @@ -5755,6 +5771,249 @@ describe("IBosonExchangeHandler", function () { }); context("👉 onVoucherTransferred()", async function () { + // majority of lines from onVoucherTransferred() are tested in indirectly in + // `commitToPremintedOffer()` + + beforeEach(async function () { + // Commit to offer, retrieving the event + await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerId, { value: price }); + + // Client used for tests + bosonVoucherCloneAddress = calculateCloneAddress( + await accountHandler.getAddress(), + beaconProxyAddress, + admin.address + ); + bosonVoucherClone = await getContractAt("IBosonVoucher", bosonVoucherCloneAddress); + + tokenId = deriveTokenId(offerId, exchange.id); + }); + + it("should emit an VoucherTransferred event when called by CLIENT-roled address", async function () { + // Get the next buyer id + nextAccountId = await accountHandler.connect(rando).getNextAccountId(); + + // Call onVoucherTransferred, expecting event + await expect( + bosonVoucherClone.connect(buyer).transferFrom(await buyer.getAddress(), await newOwner.getAddress(), tokenId) + ) + .to.emit(exchangeHandler, "VoucherTransferred") + .withArgs(offerId, exchange.id, nextAccountId, await bosonVoucherClone.getAddress()); + }); + + it("should update exchange when new buyer (with existing, active account) is passed", async function () { + // Get the next buyer id + nextAccountId = await accountHandler.connect(rando).getNextAccountId(); + + // Create a buyer account for the new owner + await accountHandler.connect(newOwner).createBuyer(mockBuyer(await newOwner.getAddress())); + + // Call onVoucherTransferred + await bosonVoucherClone + .connect(buyer) + .transferFrom(await buyer.getAddress(), await newOwner.getAddress(), tokenId); + + // Get the exchange + [exists, response] = await exchangeHandler.connect(rando).getExchange(exchange.id); + + // Marshal response to entity + exchange = Exchange.fromStruct(response); + expect(exchange.isValid()); + + // Exchange's voucher expired flag should be true + assert.equal(exchange.buyerId, nextAccountId, "Exchange.buyerId not updated"); + }); + + it("should update exchange when new buyer (no account) is passed", async function () { + // Get the next buyer id + nextAccountId = await accountHandler.connect(rando).getNextAccountId(); + + // Call onVoucherTransferred + await bosonVoucherClone + .connect(buyer) + .transferFrom(await buyer.getAddress(), await newOwner.getAddress(), tokenId); + + // Get the exchange + [exists, response] = await exchangeHandler.connect(rando).getExchange(exchange.id); + + // Marshal response to entity + exchange = Exchange.fromStruct(response); + expect(exchange.isValid()); + + // Exchange's voucher expired flag should be true + assert.equal(exchange.buyerId, nextAccountId, "Exchange.buyerId not updated"); + }); + + it("should be triggered when a voucher is transferred", async function () { + // Transfer voucher, expecting event + await expect( + bosonVoucherClone.connect(buyer).transferFrom(await buyer.getAddress(), await newOwner.getAddress(), tokenId) + ).to.emit(exchangeHandler, "VoucherTransferred"); + }); + + it("should not be triggered when a voucher is issued", async function () { + // Get the next exchange id + nextExchangeId = await exchangeHandler.getNextExchangeId(); + + // Create a buyer account + await accountHandler.connect(newOwner).createBuyer(mockBuyer(await newOwner.getAddress())); + + // Grant PROTOCOL role to EOA address for test + await accessController.grantRole(Role.PROTOCOL, await rando.getAddress()); + + // Issue voucher, expecting no event + await expect( + bosonVoucherClone.connect(rando).issueVoucher(nextExchangeId, await buyer.getAddress()) + ).to.not.emit(exchangeHandler, "VoucherTransferred"); + }); + + it("should not be triggered when a voucher is burned", async function () { + // Grant PROTOCOL role to EOA address for test + await accessController.grantRole(Role.PROTOCOL, await rando.getAddress()); + + // Burn voucher, expecting no event + await expect(bosonVoucherClone.connect(rando).burnVoucher(tokenId)).to.not.emit( + exchangeHandler, + "VoucherTransferred" + ); + }); + + it("Should not be triggered when from and to addresses are the same", async function () { + // Transfer voucher, expecting event + await expect( + bosonVoucherClone.connect(buyer).transferFrom(await buyer.getAddress(), await buyer.getAddress(), tokenId) + ).to.not.emit(exchangeHandler, "VoucherTransferred"); + }); + + it("Should not be triggered when first transfer of preminted voucher happens", async function () { + // Transfer voucher, expecting event + await expect( + bosonVoucherClone.connect(buyer).transferFrom(await buyer.getAddress(), await buyer.getAddress(), tokenId) + ).to.not.emit(exchangeHandler, "VoucherTransferred"); + }); + + it("should work with additional collections", async function () { + // Create a new collection + const externalId = `Brand1`; + voucherInitValues.collectionSalt = encodeBytes32String(externalId); + await accountHandler.connect(assistant).createNewCollection(externalId, voucherInitValues); + + offer.collectionIndex = 1; + offer.id = await offerHandler.getNextOfferId(); + exchange.id = await exchangeHandler.getNextExchangeId(); + bosonVoucherCloneAddress = calculateCloneAddress( + await accountHandler.getAddress(), + beaconProxyAddress, + admin.address, + voucherInitValues.collectionSalt + ); + bosonVoucherClone = await getContractAt("IBosonVoucher", bosonVoucherCloneAddress); + const tokenId = deriveTokenId(offer.id, exchange.id); + + // Create the offer + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + + // Commit to offer, creating a new exchange + await exchangeHandler.connect(buyer).commitToOffer(buyer.address, offer.id, { value: price }); + + // Get the next buyer id + nextAccountId = await accountHandler.connect(rando).getNextAccountId(); + + // Call onVoucherTransferred, expecting event + await expect(bosonVoucherClone.connect(buyer).transferFrom(buyer.address, newOwner.address, tokenId)) + .to.emit(exchangeHandler, "VoucherTransferred") + .withArgs(offer.id, exchange.id, nextAccountId, await bosonVoucherClone.getAddress()); + }); + + context("💔 Revert Reasons", async function () { + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to create a buyer, expecting revert + await expect( + bosonVoucherClone + .connect(buyer) + .transferFrom(await buyer.getAddress(), await newOwner.getAddress(), tokenId) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("Caller is not a clone address", async function () { + // Attempt to call onVoucherTransferred, expecting revert + await expect( + exchangeHandler.connect(rando).onVoucherTransferred(exchange.id, await newOwner.getAddress()) + ).to.revertedWith(RevertReasons.ACCESS_DENIED); + }); + + it("Caller is not a clone address associated with the seller", async function () { + // Create a new seller to get new clone + seller = mockSeller( + await rando.getAddress(), + await rando.getAddress(), + ZeroAddress, + await rando.getAddress() + ); + expect(seller.isValid()).is.true; + + await accountHandler.connect(rando).createSeller(seller, emptyAuthToken, voucherInitValues); + expectedCloneAddress = calculateCloneAddress( + await accountHandler.getAddress(), + beaconProxyAddress, + rando.address + ); + const bosonVoucherClone2 = await getContractAt("IBosonVoucher", expectedCloneAddress); + + // For the sake of test, mint token on bv2 with the id of token on bv1 + // Temporarily grant PROTOCOL role to deployer account + await accessController.grantRole(Role.PROTOCOL, await deployer.getAddress()); + + const newBuyer = mockBuyer(await buyer.getAddress()); + newBuyer.id = buyerId; + await bosonVoucherClone2.issueVoucher(exchange.id, newBuyer.wallet); + + // Attempt to call onVoucherTransferred, expecting revert + await expect( + bosonVoucherClone2 + .connect(buyer) + .transferFrom(await buyer.getAddress(), await newOwner.getAddress(), exchange.id) + ).to.revertedWith(RevertReasons.ACCESS_DENIED); + }); + + it("exchange id is invalid", async function () { + // An invalid exchange id + exchangeId = "666"; + + // Attempt to call onVoucherTransferred, expecting revert + await expect( + exchangeHandler.connect(fauxClient).onVoucherTransferred(exchangeId, await newOwner.getAddress()) + ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); + }); + + it("exchange is not in committed state", async function () { + // Revoke the voucher + await exchangeHandler.connect(assistant).revokeVoucher(exchange.id); + + // Attempt to call onVoucherTransferred, expecting revert + await expect( + exchangeHandler.connect(fauxClient).onVoucherTransferred(exchangeId, await newOwner.getAddress()) + ).to.revertedWith(RevertReasons.INVALID_STATE); + }); + + it("Voucher has expired", async function () { + // Set time forward past the voucher's validUntilDate + await setNextBlockTimestamp(Number(voucherRedeemableFrom) + Number(voucherValid) + Number(oneWeek)); + + // Attempt to call onVoucherTransferred, expecting revert + await expect( + exchangeHandler.connect(fauxClient).onVoucherTransferred(exchangeId, await newOwner.getAddress()) + ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); + }); + }); + }); + + context("👉 onPremintedVoucherTransferred()", async function () { beforeEach(async function () { // Commit to offer, retrieving the event await exchangeHandler.connect(buyer).commitToOffer(await buyer.getAddress(), offerId, { value: price }); diff --git a/test/protocol/FundsHandlerTest.js b/test/protocol/FundsHandlerTest.js index 4284f7d78..600620a2d 100644 --- a/test/protocol/FundsHandlerTest.js +++ b/test/protocol/FundsHandlerTest.js @@ -1403,7 +1403,7 @@ describe("IBosonFundsHandler", function () { it("Returns info even if name consumes all the gas", async function () { // Deploy the mock token that consumes all gas in the name getter const [mockToken, mockToken2] = await deployMockTokens(["Foreign20", "Foreign20MaliciousName"]); - const ERC20 = await getContractFactory("ERC20"); + const ERC20 = await getContractFactory("@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20"); const mockToken3 = await ERC20.deploy("SomeToken", "STK"); // top up assistants account @@ -4594,11 +4594,12 @@ describe("IBosonFundsHandler", function () { totalProtocolFee = 0n; for (const trade of buyerChains[direction]) { // Prepare calldata for PriceDiscovery contract + const tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: voucherOwner.address, buyer: trade.buyer.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: BigInt(trade.price), }; @@ -4609,9 +4610,10 @@ describe("IBosonFundsHandler", function () { const priceDiscovery = new PriceDiscovery( order.price, + Side.Ask, await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + await priceDiscoveryContract.getAddress(), + priceDiscoveryData ); // voucher owner approves protocol to transfer the tokens @@ -4630,7 +4632,7 @@ describe("IBosonFundsHandler", function () { // commit to offer await sequentialCommitHandler .connect(trade.buyer) - .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(trade.buyer.address, tokenId, priceDiscovery, { gasPrice: 0, }); @@ -6212,11 +6214,12 @@ describe("IBosonFundsHandler", function () { await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(fee.royalties); // Prepare calldata for PriceDiscovery contract + const tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: voucherOwner.address, buyer: trade.buyer.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: BigInt(trade.price), }; @@ -6228,9 +6231,10 @@ describe("IBosonFundsHandler", function () { const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); const priceDiscovery = new PriceDiscovery( order.price, + Side.Ask, + priceDiscoveryContractAddress, priceDiscoveryContractAddress, - priceDiscoveryData, - Side.Ask + priceDiscoveryData ); // voucher owner approves protocol to transfer the tokens @@ -6247,7 +6251,7 @@ describe("IBosonFundsHandler", function () { // commit to offer await sequentialCommitHandler .connect(trade.buyer) - .sequentialCommitToOffer(trade.buyer.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(trade.buyer.address, tokenId, priceDiscovery, { gasPrice: 0, }); diff --git a/test/protocol/MetaTransactionsHandlerTest.js b/test/protocol/MetaTransactionsHandlerTest.js index db5723d27..d79a88bd7 100644 --- a/test/protocol/MetaTransactionsHandlerTest.js +++ b/test/protocol/MetaTransactionsHandlerTest.js @@ -3462,7 +3462,7 @@ describe("IBosonMetaTransactionsHandler", function () { message.from = await assistant.getAddress(); message.contractAddress = await offerHandler.getAddress(); message.functionName = - "createOffer((uint256,uint256,uint256,uint256,uint256,uint256,address,string,string,bool,uint256),(uint256,uint256,uint256,uint256),(uint256,uint256,uint256),uint256,uint256)"; + "createOffer((uint256,uint256,uint256,uint256,uint256,uint256,address,string,string,bool,uint256,uint8),(uint256,uint256,uint256,uint256),(uint256,uint256,uint256),uint256,uint256)"; message.functionSignature = functionSignature; }); diff --git a/test/protocol/OfferHandlerTest.js b/test/protocol/OfferHandlerTest.js index d09fd8f79..2e67cbe9c 100644 --- a/test/protocol/OfferHandlerTest.js +++ b/test/protocol/OfferHandlerTest.js @@ -222,7 +222,7 @@ describe("IBosonOfferHandler", function () { // Mock offer ({ offer, offerDates, offerDurations, offerFees } = await mockOffer()); - // Check if domais are valid + // Check if domains are valid expect(offer.isValid()).is.true; expect(offerDates.isValid()).is.true; expect(offerDurations.isValid()).is.true; diff --git a/test/protocol/PriceDiscoveryHandlerFacet.js b/test/protocol/PriceDiscoveryHandlerFacet.js new file mode 100644 index 000000000..401f38dfb --- /dev/null +++ b/test/protocol/PriceDiscoveryHandlerFacet.js @@ -0,0 +1,1239 @@ +const { ethers } = require("hardhat"); +const { ZeroAddress, getContractFactory, parseUnits, provider, getContractAt } = ethers; +const { expect } = require("chai"); + +const Exchange = require("../../scripts/domain/Exchange"); +const PriceDiscovery = require("../../scripts/domain/PriceDiscovery"); +const Side = require("../../scripts/domain/Side"); +const PriceType = require("../../scripts/domain/PriceType.js"); +const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee"); +const PausableRegion = require("../../scripts/domain/PausableRegion.js"); +const { FundsList } = require("../../scripts/domain/Funds"); +const { getInterfaceIds } = require("../../scripts/config/supported-interfaces.js"); +const { RevertReasons } = require("../../scripts/config/revert-reasons.js"); +const { deployMockTokens } = require("../../scripts/util/deploy-mock-tokens"); +const { + mockOffer, + mockDisputeResolver, + mockAuthToken, + mockVoucherInitValues, + mockSeller, + mockVoucher, + mockExchange, + mockBuyer, + accountId, +} = require("../util/mock"); +const { + setNextBlockTimestamp, + calculateVoucherExpiry, + calculateBosonProxyAddress, + calculateCloneAddress, + applyPercentage, + setupTestEnvironment, + getSnapshot, + revertToSnapshot, + deriveTokenId, + getCurrentBlockAndSetTimeForward, +} = require("../util/utils.js"); +const { oneWeek, oneMonth } = require("../util/constants"); + +/** + * Test the Boson Price Discovery Handler interface + */ +describe("IPriceDiscoveryHandlerFacet", function () { + // Common vars + let InterfaceIds; + let pauser, assistant, admin, treasury, rando, buyer, assistantDR, adminDR, treasuryDR; + let erc165, + accountHandler, + exchangeHandler, + offerHandler, + fundsHandler, + pauseHandler, + configHandler, + priceDiscoveryHandler; + let bosonVoucherClone; + let offerId, seller, disputeResolverId; + let block, tx; + let support; + let price, sellerPool; + let voucherRedeemableFrom; + let voucherValid; + let protocolFeePercentage; + let voucher; + let exchange; + let disputeResolver, disputeResolverFees; + let expectedCloneAddress; + let voucherInitValues; + let emptyAuthToken; + let agentId; + let exchangeId; + let offer, offerFees; + let offerDates, offerDurations; + let weth; + let protocolDiamondAddress; + let snapshotId; + let priceDiscoveryContract; + let tokenId; + let bosonVoucher; + + before(async function () { + accountId.next(true); + + // get interface Ids + InterfaceIds = await getInterfaceIds(); + + // Add WETH + const wethFactory = await getContractFactory("WETH9"); + weth = await wethFactory.deploy(); + await weth.waitForDeployment(); + + // Specify contracts needed for this test + const contracts = { + erc165: "ERC165Facet", + accountHandler: "IBosonAccountHandler", + offerHandler: "IBosonOfferHandler", + exchangeHandler: "IBosonExchangeHandler", + fundsHandler: "IBosonFundsHandler", + configHandler: "IBosonConfigHandler", + pauseHandler: "IBosonPauseHandler", + priceDiscoveryHandler: "IBosonPriceDiscoveryHandler", + }; + + ({ + signers: [pauser, admin, treasury, buyer, rando, adminDR, treasuryDR], + contractInstances: { + erc165, + accountHandler, + offerHandler, + exchangeHandler, + fundsHandler, + configHandler, + pauseHandler, + priceDiscoveryHandler, + }, + protocolConfig: [, , { percentage: protocolFeePercentage }], + diamondAddress: protocolDiamondAddress, + } = await setupTestEnvironment(contracts, { wethAddress: await weth.getAddress() })); + + // make all account the same + assistant = admin; + assistantDR = adminDR; + + // Deploy PriceDiscovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscovery"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.waitForDeployment(); + + // Get snapshot id + snapshotId = await getSnapshot(); + }); + + afterEach(async function () { + await revertToSnapshot(snapshotId); + snapshotId = await getSnapshot(); + }); + + // Interface support (ERC-156 provided by ProtocolDiamond, others by waitForDeployment facets) + context("📋 Interfaces", async function () { + context("👉 supportsInterface()", async function () { + it("should indicate support for IPriceDiscoveryHandlerFacet interface", async function () { + // Current interfaceId for IBosonPriceDiscoveryHandler + support = await erc165.supportsInterface(InterfaceIds.IBosonPriceDiscoveryHandler); + + // Test + expect(support, "PriceDiscoveryHandlerFacet interface not supported").is.true; + }); + }); + }); + + // All supported Price discovery methods + context("📋 Price discovery Methods", async function () { + beforeEach(async function () { + // Initial ids for all the things + exchangeId = offerId = "1"; + agentId = "0"; // agent id is optional while creating an offer + + // Create a valid seller + seller = mockSeller(assistant.address, admin.address, ZeroAddress, treasury.address); + expect(seller.isValid()).is.true; + + // AuthToken + emptyAuthToken = mockAuthToken(); + expect(emptyAuthToken.isValid()).is.true; + + // VoucherInitValues + voucherInitValues = mockVoucherInitValues(); + expect(voucherInitValues.isValid()).is.true; + + await accountHandler.connect(admin).createSeller(seller, emptyAuthToken, voucherInitValues); + + const beaconProxyAddress = await calculateBosonProxyAddress(protocolDiamondAddress); + expectedCloneAddress = calculateCloneAddress(protocolDiamondAddress, beaconProxyAddress, admin.address); + + // Create a valid dispute resolver + disputeResolver = mockDisputeResolver( + assistantDR.address, + adminDR.address, + ZeroAddress, + treasuryDR.address, + true + ); + expect(disputeResolver.isValid()).is.true; + + //Create DisputeResolverFee array so offer creation will succeed + disputeResolverFees = [new DisputeResolverFee(ZeroAddress, "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); + + // Create the offer + const mo = await mockOffer(); + ({ offerDates, offerDurations } = mo); + offer = mo.offer; + offer.priceType = PriceType.Discovery; + offer.price = "0"; + offer.buyerCancelPenalty = "0"; + offerFees = mo.offerFees; + offerFees.protocolFee = applyPercentage(offer.price, protocolFeePercentage); + + offer.quantityAvailable = "10"; + disputeResolverId = mo.disputeResolverId; + + offerDurations.voucherValid = (oneMonth * 12n).toString(); + + // Check if domains are valid + expect(offer.isValid()).is.true; + expect(offerDates.isValid()).is.true; + expect(offerDurations.isValid()).is.true; + + // Create the offer, reserve range and premint vouchers + await offerHandler.connect(assistant).createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); + bosonVoucher = await getContractAt("BosonVoucher", expectedCloneAddress); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + await bosonVoucher.connect(assistant).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + // Set used variables + voucherRedeemableFrom = offerDates.voucherRedeemableFrom; + voucherValid = offerDurations.voucherValid; + sellerPool = parseUnits("15", "ether").toString(); + + // Required voucher constructor params + voucher = mockVoucher(); + voucher.redeemedDate = "0"; + + // Mock exchange + exchange = mockExchange(); + exchange.finalizedDate = "0"; + + // Deposit seller funds so the commit will succeed + await fundsHandler.connect(assistant).depositFunds(seller.id, ZeroAddress, sellerPool, { value: sellerPool }); + }); + + afterEach(async function () { + // Reset the accountId iterator + accountId.next(true); + }); + + context("👉 commitToPriceDiscoveryOffer()", async function () { + let priceDiscovery; + let newBuyer; + + context("Ask order", async function () { + let order; + beforeEach(async function () { + // Price on secondary market + price = 100n; + tokenId = deriveTokenId(offer.id, exchangeId); + + // Prepare calldata for PriceDiscovery contract + order = { + seller: assistant.address, + buyer: buyer.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: offer.exchangeToken, + price: price, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + + priceDiscovery = new PriceDiscovery( + price, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(buyer).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + newBuyer = mockBuyer(buyer.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit FundsEncumbered and BuyerCommitted events", async function () { + // Commit to offer with first buyer + tx = await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }); + + // Get the block timestamp of the confirmed tx + block = await provider.getBlock(tx.blockNumber); + + // Update the committed date in the expected exchange struct with the block timestamp of the tx + voucher.committedDate = block.timestamp.toString(); + voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); + + // Test for events + // Seller deposit + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(seller.id, ZeroAddress, offer.sellerDeposit, expectedCloneAddress); + + // Buyers funds - in ask order, they are taken from the seller deposit + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(seller.id, ZeroAddress, price, buyer.address); + + await expect(tx) + .to.emit(priceDiscoveryHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), expectedCloneAddress); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await provider.getBalance(await priceDiscoveryHandler.getAddress()); + const buyerBefore = await provider.getBalance(buyer.address); + const { funds: sellerAvailableFundsBefore } = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [ZeroAddress]) + ); + + // Commit to offer + await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price, gasPrice: 0 }); + + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should stay the same (funds are only moved from the pool to the escrow) + const escrowAfter = await provider.getBalance(await priceDiscoveryHandler.getAddress()); + expect(escrowAfter).to.equal(escrowBefore); + + // Buyer's balance should decrease + const buyerAfter = await provider.getBalance(buyer.address); + expect(buyerAfter).to.equal(buyerBefore - price); + + // Seller's available funds should decrease for the amount of the seller deposit and the price + const { funds: sellerAvailableFundsAfter } = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [ZeroAddress]) + ); + expect(BigInt(sellerAvailableFundsAfter[0].availableAmount)).to.equal( + BigInt(sellerAvailableFundsBefore[0].availableAmount) - BigInt(offer.sellerDeposit) - price + ); + }); + + it("should transfer the voucher", async function () { + // seller is owner of voucher + expect(await bosonVoucherClone.ownerOf(tokenId)).to.equal(assistant.address); + + // Commit to offer + await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }); + + // buyer is owner of voucher + expect(await bosonVoucherClone.ownerOf(tokenId)).to.equal(buyer.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.getNextExchangeId(); + + // Commit to offer, creating a new exchange + await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }); + + // Get the next exchange id and ensure it was no incremented + const nextExchangeIdAfter = await exchangeHandler.getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + // Commit to offer, creating a new exchange + await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + it("It is possible to commit on someone else's behalf", async function () { + const buyerBefore = await provider.getBalance(buyer.address); + const callerBefore = await provider.getBalance(rando.address); + + // Commit to offer + tx = await priceDiscoveryHandler + .connect(rando) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price, gasPrice: 0 }); + + // Get the block timestamp of the confirmed tx + block = await provider.getBlock(tx.blockNumber); + + // Update the committed date in the expected exchange struct with the block timestamp of the tx + voucher.committedDate = block.timestamp.toString(); + voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); + + await expect(tx) + .to.emit(priceDiscoveryHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), expectedCloneAddress); + + // Buyer is owner of voucher, not rando + expect(await bosonVoucherClone.ownerOf(tokenId)).to.equal(buyer.address); + + // Buyer's balance should not change + const buyerAfter = await provider.getBalance(buyer.address); + expect(buyerAfter).to.equal(buyerBefore); + + // Caller's balance should decrease + const callerAfter = await provider.getBalance(rando.address); + expect(callerAfter).to.equal(callerBefore - price); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(ZeroAddress, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("token id is invalid", async function () { + // An invalid token id + exchangeId = "666"; + tokenId = deriveTokenId(offer.id, exchangeId); + order.tokenId = tokenId; + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + priceDiscovery.priceDiscoveryData = priceDiscoveryData; + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); + }); + + it("offer is voided", async function () { + // Void the offer first + await offerHandler.connect(assistant).voidOffer(offerId); + + // Attempt to commit to the voided offer, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + }); + + it("offer is not yet available for commits", async function () { + // Create an offer with staring date in the future + // get current block timestamp + const block = await provider.getBlock("latest"); + + // set validFrom date in the past + offerDates.validFrom = (BigInt(block.timestamp) + oneMonth * 6n).toString(); // 6 months in the future + offerDates.validUntil = BigInt(offerDates.validFrom + 10).toString(); // just after the valid from so it succeeds. + + offer.id = "2"; + exchangeId = await exchangeHandler.getNextExchangeId(); + let tokenId = deriveTokenId(offer.id, exchangeId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + + // Attempt to commit to the not available offer, expecting revert + order.tokenId = tokenId; + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + priceDiscovery.priceDiscoveryData = priceDiscoveryData; + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); + }); + + it("offer has expired", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(offerDates.validUntil) + 1); + + // Attempt to commit to the expired offer, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + }); + + it.skip("offer sold", async function () { + // maybe for offers without explicit token id + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("insufficient values sent", async function () { + price = price - 1n; + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + + it("price discovery does not send the voucher anywhere", async function () { + // Deploy bad price discovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryNoTransfer"); + const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.waitForDeployment(); + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + order.tokenId = tokenId; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + priceDiscovery = new PriceDiscovery( + price, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.TOKEN_ID_MISMATCH); + }); + + it("price discovery does not send the voucher to the protocol", async function () { + // Deploy bad price discovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryTransferElsewhere"); + const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.waitForDeployment(); + await bosonVoucherClone + .connect(assistant) + .setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + order.tokenId = tokenId; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrderElsewhere", [ + order, + ]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + priceDiscovery = new PriceDiscovery( + price, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.VOUCHER_NOT_RECEIVED); + }); + }); + }); + + context("Bid order", async function () { + let order; + beforeEach(async function () { + // Price market + price = 100n; + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + order = { + seller: await priceDiscoveryHandler.getAddress(), // since protocol owns the voucher, it acts as seller from price discovery mechanism + buyer: buyer.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: await weth.getAddress(), // buyer pays in ETH, but they cannot approve ETH, so we use WETH + price: price, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + + priceDiscovery = new PriceDiscovery( + price, + Side.Bid, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Approve transfers + // Buyer needs to approve price discovery to transfer the ETH + await weth.connect(buyer).deposit({ value: price }); + await weth.connect(buyer).approve(await priceDiscoveryContract.getAddress(), price); + + // Seller approves protocol to transfer the voucher + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + await bosonVoucherClone.connect(assistant).setApprovalForAll(await priceDiscoveryHandler.getAddress(), true); + + newBuyer = mockBuyer(buyer.address); + exchange.buyerId = newBuyer.id; + }); + + it("should emit FundsEncumbered and BuyerCommitted events", async function () { + // Commit to offer, retrieving the event + const tx = await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + // Get the block timestamp of the confirmed tx + block = await provider.getBlock(tx.blockNumber); + + // Update the committed date in the expected exchange struct with the block timestamp of the tx + voucher.committedDate = block.timestamp.toString(); + voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); + + // Test for events + // Seller deposit + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(seller.id, ZeroAddress, offer.sellerDeposit, expectedCloneAddress); + + // Buyers funds - in bid order, they are taken directly from the buyer + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(newBuyer.id, ZeroAddress, price, assistant.address); + + await expect(tx) + .to.emit(priceDiscoveryHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), expectedCloneAddress); + }); + + it("should update state", async function () { + // Escrow amount before + const escrowBefore = await provider.getBalance(await exchangeHandler.getAddress()); + const buyerBefore = await weth.balanceOf(buyer.address); + const { funds: sellerAvailableFundsBefore } = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [ZeroAddress]) + ); + + // Commit to offer + await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + // Get the exchange as a struct + const [, exchangeStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); + + // Parse into entity + let returnedExchange = Exchange.fromStruct(exchangeStruct); + expect(returnedExchange.buyerId).to.equal(newBuyer.id); + + // Contract's balance should increase for the amount of the price + const escrowAfter = await provider.getBalance(await exchangeHandler.getAddress()); + expect(escrowAfter).to.equal(escrowBefore + price); + + // Buyer's balance should decrease + const buyerAfter = await weth.balanceOf(buyer.address); + expect(buyerAfter).to.equal(buyerBefore - price); + + // Seller's available funds should decrease for the amount of the seller deposit + const { funds: sellerAvailableFundsAfter } = FundsList.fromStruct( + await fundsHandler.getAvailableFunds(seller.id, [ZeroAddress]) + ); + expect(BigInt(sellerAvailableFundsAfter[0].availableAmount)).to.equal( + BigInt(sellerAvailableFundsBefore[0].availableAmount) - BigInt(offer.sellerDeposit) + ); + }); + + it("should transfer the voucher", async function () { + // reseller is owner of voucher + expect(await bosonVoucherClone.ownerOf(tokenId)).to.equal(assistant.address); + + // Commit to offer + await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + // buyer2 is owner of voucher + expect(await bosonVoucherClone.ownerOf(tokenId)).to.equal(buyer.address); + }); + + it("should not increment the next exchange id counter", async function () { + const nextExchangeIdBefore = await exchangeHandler.connect(rando).getNextExchangeId(); + + // Commit to offer, creating a new exchange + await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + // Get the next exchange id and ensure it was incremented + const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); + expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); + }); + + it("Should not decrement quantityAvailable", async function () { + // Get quantityAvailable before + const [, { quantityAvailable: quantityAvailableBefore }] = await offerHandler + .connect(rando) + .getOffer(offerId); + + // Commit to offer, creating a new exchange + await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + + // Get quantityAvailable after + const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler.connect(rando).getOffer(offerId); + + expect(quantityAvailableAfter).to.equal(quantityAvailableBefore, "Quantity available should be the same"); + }); + + context("💔 Revert Reasons", async function () { + it("The exchanges region of protocol is paused", async function () { + // Pause the exchanges region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Exchanges]); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("The buyers region of protocol is paused", async function () { + // Pause the buyers region of the protocol + await pauseHandler.connect(pauser).pause([PausableRegion.Buyers]); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.REGION_PAUSED); + }); + + it("buyer address is the zero address", async function () { + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler.connect(assistant).commitToPriceDiscoveryOffer(ZeroAddress, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.INVALID_ADDRESS); + }); + + it("offer id is invalid", async function () { + // An invalid token id + offerId = "666"; + tokenId = deriveTokenId(offerId, exchangeId); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.NO_SUCH_OFFER); + }); + + it("token id is invalid", async function () { + // An invalid token id + exchangeId = "666"; + tokenId = deriveTokenId(offer.id, exchangeId); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.ERC721_INVALID_TOKEN_ID); + }); + + it("offer is voided", async function () { + // Void the offer first + await offerHandler.connect(assistant).voidOffer(offerId); + + // Attempt to commit to the voided offer, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_BEEN_VOIDED); + }); + + it("offer is not yet available for commits", async function () { + // Create an offer with staring date in the future + // get current block timestamp + const block = await provider.getBlock("latest"); + + // set validFrom date in the past + offerDates.validFrom = (BigInt(block.timestamp) + oneMonth * 6n).toString(); // 6 months in the future + offerDates.validUntil = BigInt(offerDates.validFrom + 10).toString(); // just after the valid from so it succeeds. + + offer.id = "2"; + exchangeId = await exchangeHandler.getNextExchangeId(); + let tokenId = deriveTokenId(offer.id, exchangeId); + await offerHandler + .connect(assistant) + .createOffer(offer, offerDates, offerDurations, disputeResolverId, agentId); + await offerHandler.connect(assistant).reserveRange(offer.id, offer.quantityAvailable, assistant.address); + await bosonVoucher.connect(assistant).preMint(offer.id, offer.quantityAvailable); + + // Attempt to commit to the not available offer, expecting revert + order.tokenId = tokenId; + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); + priceDiscovery.priceDiscoveryData = priceDiscoveryData; + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_NOT_AVAILABLE); + }); + + it("offer has expired", async function () { + // Go past offer expiration date + await setNextBlockTimestamp(Number(offerDates.validUntil) + 1); + + // Attempt to commit to the expired offer, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.OFFER_HAS_EXPIRED); + }); + + it.skip("offer sold", async function () { + // maybe for offers without explicit token id + }); + + it("protocol fees to high", async function () { + // Set protocol fees to 95% + await configHandler.setProtocolFeePercentage(9500); + // Set royalty fees to 6% + await bosonVoucherClone.connect(assistant).setRoyaltyPercentage(600); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); + }); + + it("voucher transfer not approved", async function () { + // revoke approval + await bosonVoucherClone.connect(assistant).setApprovalForAll(await exchangeHandler.getAddress(), false); + + // Attempt to commit to, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); + }); + + it("price discovery sends less than expected", async function () { + // Set higher price in price discovery + priceDiscovery.price = BigInt(priceDiscovery.price) + 1n; + + // Attempt to commit to, expecting revert + await expect( + priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); + }); + + it("Only seller can call, if side is bid", async function () { + // Commit to offer, retrieving the event + await expect( + priceDiscoveryHandler.connect(rando).commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery) + ).to.revertedWith(RevertReasons.NOT_VOUCHER_HOLDER); + }); + }); + }); + + context("Wrapped voucher", async function () { + const MASK = (1n << 128n) - 1n; + context("Mock auction", async function () { + let tokenId, mockAuction, amount, auctionId; + + beforeEach(async function () { + // 1. Deploy Mock Auction + const MockAuctionFactory = await getContractFactory("MockAuction"); + mockAuction = await MockAuctionFactory.deploy(await weth.getAddress()); + + tokenId = deriveTokenId(offer.id, 2); + }); + + it("Transfer can't happens outside protocol", async function () { + // 2. Set approval for all + await bosonVoucher.connect(assistant).setApprovalForAll(await mockAuction.getAddress(), true); + + // 3. Create an auction + const tokenContract = await bosonVoucher.getAddress(); + const auctionCurrency = offer.exchangeToken; + const curator = ZeroAddress; + + await mockAuction.connect(assistant).createAuction(tokenId, tokenContract, auctionCurrency, curator); + + // 4. Bid + auctionId = 0; + amount = 10; + await mockAuction.connect(buyer).createBid(auctionId, amount, { value: amount }); + + // Set time forward + await getCurrentBlockAndSetTimeForward(oneWeek); + + // Zora should be the owner of the token + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(await mockAuction.getAddress()); + + // safe transfer from will fail on onPremintedTransferredHook and transaction should fail + await expect(mockAuction.connect(rando).endAuction(auctionId)).to.be.revertedWith( + RevertReasons.VOUCHER_TRANSFER_NOT_ALLOWED + ); + + // Exchange doesn't exist + const exchangeId = tokenId & MASK; + const [exist, ,] = await exchangeHandler.getExchange(exchangeId); + + expect(exist).to.equal(false); + }); + + context("Works with Zora auction wrapper", async function () { + let wrappedBosonVoucher; + + beforeEach(async function () { + // 2. Create wrapped voucher + const wrappedBosonVoucherFactory = await ethers.getContractFactory("ZoraWrapper"); + wrappedBosonVoucher = await wrappedBosonVoucherFactory + .connect(assistant) + .deploy( + await bosonVoucher.getAddress(), + await mockAuction.getAddress(), + await exchangeHandler.getAddress(), + await weth.getAddress() + ); + + // 3. Wrap voucher + await bosonVoucher.connect(assistant).setApprovalForAll(await wrappedBosonVoucher.getAddress(), true); + await wrappedBosonVoucher.connect(assistant).wrap(tokenId); + + // 4. Create an auction + const tokenContract = await wrappedBosonVoucher.getAddress(); + const curator = assistant.address; + const auctionCurrency = offer.exchangeToken; + + await mockAuction.connect(assistant).createAuction(tokenId, tokenContract, auctionCurrency, curator); + + auctionId = 0; + }); + + it("Auction ends normally", async function () { + // 5. Bid + const amount = 10; + + await mockAuction.connect(buyer).createBid(auctionId, amount, { value: amount }); + + // 6. End auction + await getCurrentBlockAndSetTimeForward(oneWeek); + await mockAuction.connect(assistant).endAuction(auctionId); + + expect(await wrappedBosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); + expect(await weth.balanceOf(await wrappedBosonVoucher.getAddress())).to.equal(amount); + + // 7. Commit to offer + const calldata = wrappedBosonVoucher.interface.encodeFunctionData("unwrap", [tokenId]); + const priceDiscovery = new PriceDiscovery( + amount, + Side.Wrapper, + await wrappedBosonVoucher.getAddress(), + await wrappedBosonVoucher.getAddress(), + calldata + ); + + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + + const tx = await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery); + const { timestamp } = await provider.getBlock(tx.blockNumber); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(buyer.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal( + protocolBalanceBefore + BigInt(amount) + ); + + const exchangeId = tokenId & MASK; + const [, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(voucher.committedDate).to.equal(timestamp); + }); + + it("Cancel auction", async function () { + // 6. Cancel auction + await mockAuction.connect(assistant).cancelAuction(auctionId); + + // 7. Unwrap token + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + await wrappedBosonVoucher.connect(assistant).unwrap(tokenId); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(assistant.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal(protocolBalanceBefore); + + const exchangeId = tokenId & MASK; + const [exists, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(exists).to.equal(false); + expect(voucher.committedDate).to.equal(0); + }); + + it("Cancel auction and unwrap via commitToPriceDiscoveryOffer", async function () { + // How sensible is this scenario? Should it be prevented? + + // 6. Cancel auction + await mockAuction.connect(assistant).cancelAuction(auctionId); + + // 7. Unwrap token via commitToOffer + const protocolBalanceBefore = await provider.getBalance(await exchangeHandler.getAddress()); + + const calldata = wrappedBosonVoucher.interface.encodeFunctionData("unwrap", [tokenId]); + const priceDiscovery = new PriceDiscovery( + 0, + Side.Wrapper, + await wrappedBosonVoucher.getAddress(), + await wrappedBosonVoucher.getAddress(), + calldata + ); + const tx = await priceDiscoveryHandler + .connect(assistant) + .commitToPriceDiscoveryOffer(assistant.address, tokenId, priceDiscovery); + const { timestamp } = await provider.getBlock(tx.blockNumber); + + expect(await bosonVoucher.ownerOf(tokenId)).to.equal(assistant.address); + expect(await provider.getBalance(await exchangeHandler.getAddress())).to.equal(protocolBalanceBefore); + + const exchangeId = tokenId & MASK; + const [exists, , voucher] = await exchangeHandler.getExchange(exchangeId); + + expect(exists).to.equal(true); + expect(voucher.committedDate).to.equal(timestamp); + }); + }); + }); + }); + }); + + context("👉 onERC721Received()", async function () { + let priceDiscoveryContract, priceDiscovery; + + beforeEach(async function () { + // Price + price = 100n; + + // Approve transfers + // Buyer does not approve, since its in ETH. + // Seller approves price discovery to transfer the voucher + bosonVoucherClone = await getContractAt("IBosonVoucher", expectedCloneAddress); + }); + + context("💔 Revert Reasons", async function () { + it("Correct caller, wrong id", async function () { + // Deploy Bad PriceDiscovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryModifyTokenId"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.waitForDeployment(); + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + let order = { + seller: assistant.address, + buyer: buyer.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: offer.exchangeToken, + price: price, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + + // Seller approves price discovery to transfer the voucher + await bosonVoucherClone.connect(assistant).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + priceDiscovery = new PriceDiscovery( + price, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.TOKEN_ID_MISMATCH); + }); + + it("Correct token id, wrong caller", async function () { + // Deploy mock erc721 contract + const [foreign721] = await deployMockTokens(["Foreign721"]); + + // Deploy Bad PriceDiscovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryModifyVoucherContract"); + priceDiscoveryContract = await PriceDiscoveryFactory.deploy(await foreign721.getAddress()); + await priceDiscoveryContract.waitForDeployment(); + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + let order = { + seller: assistant.address, + buyer: buyer.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: offer.exchangeToken, + price: price, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + + // Seller approves price discovery to transfer the voucher + await bosonVoucherClone.connect(assistant).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + priceDiscovery = new PriceDiscovery( + price, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + + // Attempt to commit, expecting revert + await expect( + priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }) + ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); + }); + }); + }); + + context("👉 onPremintedVoucherTransferred()", async function () { + context("💔 Revert Reasons", async function () { + it("Only the initial owner can transfer the preminted voucher without starting the commit", async function () { + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + + // Transfer a preminted voucher to the price discovery contract + // Make sure it does not trigger the commit + const tokenId = deriveTokenId(offer.id, exchangeId); + await expect( + bosonVoucher.connect(assistant).transferFrom(assistant.address, priceDiscoveryContractAddress, tokenId) + ).to.not.emit(priceDiscoveryHandler, "BuyerCommitted"); + + // Call fulfilBuyOrder, which transfers the voucher to the buyer, expect revert + const order = { + seller: priceDiscoveryContractAddress, + buyer: buyer.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: offer.exchangeToken, + price: "0", + }; + + await expect(priceDiscoveryContract.fulfilBuyOrder(order)).to.be.revertedWith( + RevertReasons.VOUCHER_TRANSFER_NOT_ALLOWED + ); + }); + + it("The preminted voucher cannot be transferred to EOA without starting the commit", async function () { + // Transfer a preminted voucher to rando EOA and expect revert + // Make sure it does not trigger the commit + const tokenId = deriveTokenId(offer.id, exchangeId); + await expect( + bosonVoucher.connect(assistant).transferFrom(assistant.address, rando.address, tokenId) + ).to.be.revertedWith(RevertReasons.VOUCHER_TRANSFER_NOT_ALLOWED); + }); + }); + }); + }); +}); diff --git a/test/protocol/SequentialCommitHandlerTest.js b/test/protocol/SequentialCommitHandlerTest.js index d57fed78e..e643c7280 100644 --- a/test/protocol/SequentialCommitHandlerTest.js +++ b/test/protocol/SequentialCommitHandlerTest.js @@ -72,6 +72,7 @@ describe("IBosonSequentialCommitHandler", function () { let protocolDiamondAddress; let snapshotId; let priceDiscoveryContract; + let tokenId; before(async function () { accountId.next(true); @@ -264,24 +265,27 @@ describe("IBosonSequentialCommitHandler", function () { beforeEach(async function () { // Price on secondary market price2 = (BigInt(price) * 11n) / 10n; // 10% above the original price + tokenId = deriveTokenId(offer.id, exchangeId); // Prepare calldata for PriceDiscovery contract let order = { seller: buyer.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2, }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Seller needs to deposit weth in order to fill the escrow at the last step @@ -304,7 +308,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event const tx = sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); await expect(tx) .to.emit(sequentialCommitHandler, "FundsEncumbered") @@ -331,7 +335,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // buyer2 is exchange.buyerId // Get the exchange as a struct @@ -354,7 +358,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // buyer2 is owner of voucher expect(await bosonVoucherClone.connect(buyer2).ownerOf(tokenId)).to.equal(buyer2.address); @@ -369,7 +373,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // Voucher after [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); @@ -381,7 +385,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // Old buyer cannot redeem await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( @@ -399,7 +403,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // Old buyer cannot redeem await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( @@ -419,9 +423,9 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); - // Get the next exchange id and ensure it was incremented by the creation of the offer + // Get the next exchange id and ensure it was incremented const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); }); @@ -435,7 +439,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // Get quantityAvailable after const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler @@ -450,13 +454,12 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(rando) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), rando.address); // buyer2 is owner of voucher, not rando - const tokenId = deriveTokenId(offer.id, exchangeId); expect(await bosonVoucherClone.connect(buyer2).ownerOf(tokenId)).to.equal(buyer2.address); }); @@ -473,7 +476,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); @@ -489,7 +492,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); @@ -508,7 +511,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); @@ -529,7 +532,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), buyer2.address); @@ -544,7 +547,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -556,7 +559,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -570,14 +573,15 @@ describe("IBosonSequentialCommitHandler", function () { }); it("exchange id is invalid", async function () { - // An invalid offer id + // An invalid exchange id exchangeId = "666"; + tokenId = deriveTokenId(offer.id, exchangeId); // Attempt to sequentially commit, expecting revert await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); @@ -589,7 +593,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); }); @@ -603,7 +607,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); }); @@ -612,40 +616,81 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price }) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); - it("price discovery does not send the voucher to the protocol", async function () { + it("price discovery does not send the voucher anywhere", async function () { // Deploy bad price discovery contract const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryNoTransfer"); const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: buyer.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2, }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); + priceDiscovery = new PriceDiscovery( + price2, + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData + ); + // Attempt to sequentially commit, expecting revert + await expect( + sequentialCommitHandler + .connect(buyer2) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.TOKEN_ID_MISMATCH); + }); + + it("price discovery does not send the voucher to the protocol", async function () { + // Deploy bad price discovery contract + const PriceDiscoveryFactory = await getContractFactory("PriceDiscoveryTransferElsewhere"); + const priceDiscoveryContract = await PriceDiscoveryFactory.deploy(); + await priceDiscoveryContract.waitForDeployment(); + await bosonVoucherClone.connect(buyer).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); + + // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); + let order = { + seller: buyer.address, + buyer: buyer2.address, + voucherContract: expectedCloneAddress, + tokenId: tokenId, + exchangeToken: offer.exchangeToken, + price: price2, + }; + + const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData( + "fulfilBuyOrderElsewhere", + [order] + ); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Attempt to sequentially commit, expecting revert await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.VOUCHER_NOT_RECEIVED); }); }); @@ -677,11 +722,12 @@ describe("IBosonSequentialCommitHandler", function () { price2 = (BigInt(price) * BigInt(scenario.multiplier)) / 10n; // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: buyer.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2.toString(), }; @@ -689,12 +735,13 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [ order, ]); - + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Seller needs to deposit weth in order to fill the escrow at the last step @@ -748,7 +795,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2, gasPrice: 0, }); @@ -785,7 +832,7 @@ describe("IBosonSequentialCommitHandler", function () { await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: priceDiscovery.price, gasPrice: 0, }); @@ -820,22 +867,25 @@ describe("IBosonSequentialCommitHandler", function () { price2 = (price * 11n) / 10n; // 10% above the original price // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: await exchangeHandler.getAddress(), // since protocol owns the voucher, it acts as seller from price discovery mechanism buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: await weth.getAddress(), // buyer pays in ETH, but they cannot approve ETH, so we use WETH price: price2.toString(), }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Bid + Side.Bid, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Approve transfers @@ -856,7 +906,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event const tx = sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); await expect(tx) .to.emit(sequentialCommitHandler, "FundsEncumbered") @@ -883,7 +933,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // buyer2 is exchange.buyerId // Get the exchange as a struct @@ -906,7 +956,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // buyer2 is owner of voucher expect(await bosonVoucherClone.connect(buyer2).ownerOf(tokenId)).to.equal(buyer2.address); @@ -921,7 +971,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // Voucher after [, , voucherStruct] = await exchangeHandler.connect(rando).getExchange(exchangeId); @@ -933,7 +983,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // Old buyer cannot redeem await expect(exchangeHandler.connect(buyer).redeemVoucher(exchangeId)).to.be.revertedWith( @@ -951,7 +1001,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // Old buyer cannot redeem await expect(exchangeHandler.connect(buyer).cancelVoucher(exchangeId)).to.be.revertedWith( @@ -971,9 +1021,9 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); - // Get the next exchange id and ensure it was incremented by the creation of the offer + // Get the next exchange id and ensure it was incremented const nextExchangeIdAfter = await exchangeHandler.connect(rando).getNextExchangeId(); expect(nextExchangeIdAfter).to.equal(nextExchangeIdBefore); }); @@ -987,7 +1037,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, creating a new exchange await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery); // Get quantityAvailable after const [, { quantityAvailable: quantityAvailableAfter }] = await offerHandler @@ -1008,9 +1058,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event await expect( - sequentialCommitHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + sequentialCommitHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ) .to.emit(exchangeHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); @@ -1024,9 +1072,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event await expect( - sequentialCommitHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + sequentialCommitHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); @@ -1043,9 +1089,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event await expect( - sequentialCommitHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + sequentialCommitHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); @@ -1064,9 +1108,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer, retrieving the event await expect( - sequentialCommitHandler - .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + sequentialCommitHandler.connect(reseller).sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ) .to.emit(sequentialCommitHandler, "BuyerCommitted") .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), reseller.address); @@ -1081,7 +1123,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -1093,7 +1135,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.REGION_PAUSED); }); @@ -1107,14 +1149,15 @@ describe("IBosonSequentialCommitHandler", function () { }); it("exchange id is invalid", async function () { - // An invalid offer id + // An invalid exchange id exchangeId = "666"; + tokenId = deriveTokenId(offer.id, exchangeId); // Attempt to sequentially commit, expecting revert await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.NO_SUCH_EXCHANGE); }); @@ -1126,7 +1169,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.VOUCHER_HAS_EXPIRED); }); @@ -1140,7 +1183,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.FEE_AMOUNT_TOO_HIGH); }); @@ -1152,7 +1195,7 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); @@ -1164,16 +1207,14 @@ describe("IBosonSequentialCommitHandler", function () { await expect( sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.INSUFFICIENT_VALUE_RECEIVED); }); it("Only seller can call, if side is bid", async function () { // Sequential commit to offer, retrieving the event await expect( - sequentialCommitHandler - .connect(rando) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery) + sequentialCommitHandler.connect(rando).sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery) ).to.revertedWith(RevertReasons.NOT_VOUCHER_HOLDER); }); }); @@ -1206,11 +1247,12 @@ describe("IBosonSequentialCommitHandler", function () { price2 = (price * BigInt(scenario.multiplier)) / 10n; // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: await exchangeHandler.getAddress(), // since protocol owns the voucher, it acts as seller from price discovery mechanism buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: await weth.getAddress(), // buyer pays in ETH, but they cannot approve ETH, so we use WETH price: price2.toString(), }; @@ -1218,12 +1260,14 @@ describe("IBosonSequentialCommitHandler", function () { const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilSellOrder", [ order, ]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Bid + Side.Bid, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Approve transfers @@ -1273,7 +1317,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { gasPrice: 0, }); @@ -1308,7 +1352,7 @@ describe("IBosonSequentialCommitHandler", function () { await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { gasPrice: 0, }); @@ -1343,7 +1387,7 @@ describe("IBosonSequentialCommitHandler", function () { // Sequential commit to offer await sequentialCommitHandler .connect(reseller) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { gasPrice: 0, value: sellerMsgValue, }); @@ -1405,35 +1449,37 @@ describe("IBosonSequentialCommitHandler", function () { await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: reseller.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2, }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // buyer is owner of voucher - const tokenId = deriveTokenId(offer.id, exchangeId); expect(await bosonVoucherClone.connect(buyer).ownerOf(tokenId)).to.equal(buyer.address); // Sequential commit to offer await sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }); // buyer2 is owner of voucher expect(await bosonVoucherClone.connect(buyer2).ownerOf(tokenId)).to.equal(buyer2.address); @@ -1450,33 +1496,36 @@ describe("IBosonSequentialCommitHandler", function () { await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: reseller.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2, }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Attempt to sequentially commit, expecting revert await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) - ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) + ).to.revertedWith(RevertReasons.TOKEN_ID_MISMATCH); }); it("Correct token id, wrong caller", async function () { @@ -1489,32 +1538,35 @@ describe("IBosonSequentialCommitHandler", function () { await priceDiscoveryContract.waitForDeployment(); // Prepare calldata for PriceDiscovery contract + tokenId = deriveTokenId(offer.id, exchangeId); let order = { seller: reseller.address, buyer: buyer2.address, voucherContract: expectedCloneAddress, - tokenId: deriveTokenId(offer.id, exchangeId), + tokenId: tokenId, exchangeToken: offer.exchangeToken, price: price2, }; const priceDiscoveryData = priceDiscoveryContract.interface.encodeFunctionData("fulfilBuyOrder", [order]); + const priceDiscoveryContractAddress = await priceDiscoveryContract.getAddress(); // Seller approves price discovery to transfer the voucher await bosonVoucherClone.connect(reseller).setApprovalForAll(await priceDiscoveryContract.getAddress(), true); priceDiscovery = new PriceDiscovery( price2, - await priceDiscoveryContract.getAddress(), - priceDiscoveryData, - Side.Ask + Side.Ask, + priceDiscoveryContractAddress, + priceDiscoveryContractAddress, + priceDiscoveryData ); // Attempt to sequentially commit, expecting revert await expect( sequentialCommitHandler .connect(buyer2) - .sequentialCommitToOffer(buyer2.address, exchangeId, priceDiscovery, { value: price2 }) + .sequentialCommitToOffer(buyer2.address, tokenId, priceDiscovery, { value: price2 }) ).to.revertedWith(RevertReasons.UNEXPECTED_ERC721_RECEIVED); }); diff --git a/test/protocol/clients/BosonVoucherTest.js b/test/protocol/clients/BosonVoucherTest.js index e382ce321..adc0a5994 100644 --- a/test/protocol/clients/BosonVoucherTest.js +++ b/test/protocol/clients/BosonVoucherTest.js @@ -1429,7 +1429,7 @@ describe("IBosonVoucher", function () { assert.equal(tokenOwner, await rando.getAddress(), "Rando is not the owner"); }); - it("Should call commitToPreMintedOffer", async function () { + it("Should call onPremintedVoucherTransferred", async function () { const tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); @@ -1448,7 +1448,7 @@ describe("IBosonVoucher", function () { // Update the validUntilDate date in the expected exchange struct voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); - // First transfer should call commitToPreMintedOffer + // First transfer should call onPremintedVoucherTransferred await expect(tx) .to.emit(exchangeHandler, "BuyerCommitted") .withArgs( @@ -1462,21 +1462,21 @@ describe("IBosonVoucher", function () { }); it("Second transfer should behave as normal voucher transfer", async function () { - // First transfer should call commitToPreMintedOffer, and not onVoucherTransferred + // First transfer should call onPremintedVoucherTransferred, 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 + // Second transfer should call onVoucherTransferred, and not onPremintedVoucherTransferred 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 + // Next transfer should call onVoucherTransferred, and not onPremintedVoucherTransferred, even if seller is the owner tx = await bosonVoucher .connect(assistant) [selector](await assistant.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs); @@ -1583,7 +1583,7 @@ describe("IBosonVoucher", function () { bosonVoucher .connect(rando) [selector](await rando.getAddress(), await rando.getAddress(), tokenId, ...additionalArgs) - ).to.be.revertedWith(RevertReasons.NO_SILENT_MINT_ALLOWED); + ).to.be.revertedWith(RevertReasons.ERC721_CALLER_NOT_OWNER_OR_APPROVED); }); }); }); diff --git a/test/util/constants.js b/test/util/constants.js index 738eb72a1..b5f72c1f3 100644 --- a/test/util/constants.js +++ b/test/util/constants.js @@ -5,7 +5,9 @@ const oneWeek = oneDay * 7n; // 7 days in seconds const oneMonth = oneDay * 31n; // 31 days in seconds const VOUCHER_NAME = "Boson Voucher (rNFT)"; const VOUCHER_SYMBOL = "BOSON_VOUCHER_RNFT"; -const SEAPORT_ADDRESS = "0x00000000000001ad428e4906aE43D8F9852d0dD6"; // 1.4 +const SEAPORT_ADDRESS_4 = "0x00000000000001ad428e4906aE43D8F9852d0dD6"; // 1.4 +const SEAPORT_ADDRESS_5 = "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC"; // 1.5 + const tipMultiplier = 1n; // use 1 in tests const tipSuggestion = 1500000000n; // ethers.js always returns this constant, it does not vary per block const maxPriorityFeePerGas = tipSuggestion * tipMultiplier; @@ -17,4 +19,5 @@ exports.oneMonth = oneMonth; exports.VOUCHER_NAME = VOUCHER_NAME; exports.VOUCHER_SYMBOL = VOUCHER_SYMBOL; exports.maxPriorityFeePerGas = maxPriorityFeePerGas; -exports.SEAPORT_ADDRESS = SEAPORT_ADDRESS; +exports.SEAPORT_ADDRESS_4 = SEAPORT_ADDRESS_4; +exports.SEAPORT_ADDRESS_5 = SEAPORT_ADDRESS_5; diff --git a/test/util/mock.js b/test/util/mock.js index 8e1dfe3ac..7be5072a1 100644 --- a/test/util/mock.js +++ b/test/util/mock.js @@ -23,21 +23,13 @@ const Agent = require("../../scripts/domain/Agent"); const Receipt = require("../../scripts/domain/Receipt"); const Voucher = require("../../scripts/domain/Voucher"); const Dispute = require("../../scripts/domain/Dispute"); -const { applyPercentage } = require("../../test/util/utils.js"); +const { applyPercentage, incrementer } = require("../../test/util/utils.js"); const { oneWeek, oneMonth } = require("./constants.js"); +const PriceType = require("../../scripts/domain/PriceType"); let DisputeResolver = require("../../scripts/domain/DisputeResolver.js"); let Seller = require("../../scripts/domain/Seller"); const { ZeroHash } = require("ethers"); -function* incrementer() { - let i = 0; - while (true) { - const reset = yield (i++).toString(); - if (reset) { - i = 0; - } - } -} const accountId = incrementer(); function mockOfferDurations() { @@ -83,6 +75,7 @@ async function mockOffer({ refreshModule } = {}) { const metadataUri = `https://ipfs.io/ipfs/${metadataHash}`; const voided = false; const collectionIndex = "0"; + const priceType = PriceType.Static; // Create a valid offer, then set fields in tests directly let offer = new Offer( @@ -96,7 +89,8 @@ async function mockOffer({ refreshModule } = {}) { metadataUri, metadataHash, voided, - collectionIndex + collectionIndex, + priceType ); const offerDates = await mockOfferDates(); diff --git a/test/util/utils.js b/test/util/utils.js index 2ca93b96e..4a3f644ef 100644 --- a/test/util/utils.js +++ b/test/util/utils.js @@ -143,6 +143,13 @@ async function setNextBlockTimestamp(timestamp, mine = false) { if (mine) await provider.send("evm_mine", []); } +async function getCurrentBlockAndSetTimeForward(seconds) { + const blockNumber = await provider.getBlockNumber(); + const block = await provider.getBlock(blockNumber); + const newTime = block.timestamp + Number(seconds); + await setNextBlockTimestamp(newTime); +} + function getSignatureParameters(signature) { if (!isHexString(signature)) { throw new Error('Given value "'.concat(signature, '" is not a valid hex string.')); @@ -331,7 +338,7 @@ async function getFacetsWithArgs(facetNames, config) { const facets = await getFacets(config); const keys = Object.keys(facets).filter((key) => facetNames.includes(key)); return keys.reduce((obj, key) => { - obj[key] = facets[key]; + obj[key] = { init: facets[key].init, constructorArgs: facets[key].constructorArgs }; return obj; }, {}); } @@ -384,6 +391,7 @@ async function setupTestEnvironment(contracts, { bosonTokenAddress, forwarderAdd "ConfigHandlerFacet", "MetaTransactionsHandlerFacet", "SequentialCommitHandlerFacet", + "PriceDiscoveryHandlerFacet", ]; const signers = await getSigners(); @@ -454,6 +462,7 @@ async function setupTestEnvironment(contracts, { bosonTokenAddress, forwarderAdd const facetsToDeploy = await getFacetsWithArgs(facetNames, protocolConfig); facetsToDeploy["SequentialCommitHandlerFacet"].constructorArgs[0] = wethAddress || ZeroAddress; // update only weth address + facetsToDeploy["PriceDiscoveryHandlerFacet"].constructorArgs[0] = wethAddress || ZeroAddress; // update only weth address // Cut the protocol handler facets into the Diamond await deployAndCutFacets(await protocolDiamond.getAddress(), facetsToDeploy, maxPriorityFeePerGas); @@ -486,6 +495,17 @@ function deriveTokenId(offerId, exchangeId) { return (BigInt(offerId) << 128n) + BigInt(exchangeId); } +function* incrementer() { + let i = 0; + while (true) { + const reset = yield (i++).toString(); + if (reset) { + // reset to 0 instead of 1 to not count the reset call + i = 0; + } + } +} + exports.setNextBlockTimestamp = setNextBlockTimestamp; exports.getEvent = getEvent; exports.eventEmittedWithArgs = eventEmittedWithArgs; @@ -500,8 +520,10 @@ exports.paddingType = paddingType; exports.getFacetsWithArgs = getFacetsWithArgs; exports.compareOfferStructs = compareOfferStructs; exports.objectToArray = objectToArray; +exports.deriveTokenId = deriveTokenId; +exports.incrementer = incrementer; +exports.getCurrentBlockAndSetTimeForward = getCurrentBlockAndSetTimeForward; exports.setupTestEnvironment = setupTestEnvironment; exports.getSnapshot = getSnapshot; exports.revertToSnapshot = revertToSnapshot; -exports.deriveTokenId = deriveTokenId; exports.getSellerSalt = getSellerSalt; From bc18cc3e6f0aee4a0ea45bf5893b9aa45127c1cd Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 20 Nov 2023 19:09:02 +0100 Subject: [PATCH 47/47] New chunks + bump line coverage --- test/protocol/PriceDiscoveryHandlerFacet.js | 31 ++++++++- test/util/test-chunks.txt | 75 +++++++++++---------- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/test/protocol/PriceDiscoveryHandlerFacet.js b/test/protocol/PriceDiscoveryHandlerFacet.js index 401f38dfb..d2a44c867 100644 --- a/test/protocol/PriceDiscoveryHandlerFacet.js +++ b/test/protocol/PriceDiscoveryHandlerFacet.js @@ -284,7 +284,7 @@ describe("IPriceDiscoveryHandlerFacet", function () { }); it("should emit FundsEncumbered and BuyerCommitted events", async function () { - // Commit to offer with first buyer + // Commit to offer tx = await priceDiscoveryHandler .connect(buyer) .commitToPriceDiscoveryOffer(buyer.address, tokenId, priceDiscovery, { value: price }); @@ -424,6 +424,35 @@ describe("IPriceDiscoveryHandlerFacet", function () { expect(callerAfter).to.equal(callerBefore - price); }); + it("Works if the buyer provides offerId instead of tokenId", async function () { + // Commit to offer + tx = await priceDiscoveryHandler + .connect(buyer) + .commitToPriceDiscoveryOffer(buyer.address, offer.id, priceDiscovery, { value: price }); + + // Get the block timestamp of the confirmed tx + block = await provider.getBlock(tx.blockNumber); + + // Update the committed date in the expected exchange struct with the block timestamp of the tx + voucher.committedDate = block.timestamp.toString(); + voucher.validUntilDate = calculateVoucherExpiry(block, voucherRedeemableFrom, voucherValid); + + // Test for events + // Seller deposit + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(seller.id, ZeroAddress, offer.sellerDeposit, expectedCloneAddress); + + // Buyers funds - in ask order, they are taken from the seller deposit + await expect(tx) + .to.emit(priceDiscoveryHandler, "FundsEncumbered") + .withArgs(seller.id, ZeroAddress, price, buyer.address); + + await expect(tx) + .to.emit(priceDiscoveryHandler, "BuyerCommitted") + .withArgs(offerId, newBuyer.id, exchangeId, exchange.toStruct(), voucher.toStruct(), expectedCloneAddress); + }); + context("💔 Revert Reasons", async function () { it("The exchanges region of protocol is paused", async function () { // Pause the exchanges region of the protocol diff --git a/test/util/test-chunks.txt b/test/util/test-chunks.txt index 3799251c6..831cf237d 100644 --- a/test/util/test-chunks.txt +++ b/test/util/test-chunks.txt @@ -2,59 +2,62 @@ [ "test/protocol/ExchangeHandlerTest.js", "test/example/SnapshotGateTest.js", - "test/protocol/FundsHandlerTest.js", - "test/protocol/MetaTransactionsHandlerTest.js" - ], - [ + "test/protocol/ProtocolInitializationHandlerTest.js", + "test/protocol/SequentialCommitHandlerTest.js", "test/protocol/OrchestrationHandlerTest.js", - "test/protocol/DisputeHandlerTest.js", - "test/protocol/clients/BosonVoucherTest.js", - "test/protocol/GroupHandlerTest.js", - "test/protocol/clients/BeaconClientProxy.js", - "test/protocol/ProtocolInitializationHandlerTest.js" + "test/protocol/DisputeHandlerTest.js" ], [ + "test/protocol/clients/BosonVoucherTest.js", "test/protocol/SellerHandlerTest.js", - "test/protocol/DisputeResolverHandlerTest.js", + "test/protocol/GroupHandlerTest.js", "test/protocol/OfferHandlerTest.js", - "test/protocol/BundleHandlerTest.js", + "test/protocol/DisputeResolverHandlerTest.js", "test/access/AccessControllerTest.js", + "test/protocol/MetaTransactionsHandlerTest.js", + "test/protocol/BundleHandlerTest.js", + "test/protocol/PriceDiscoveryHandlerFacet.js", "test/protocol/ProtocolDiamondTest.js", - "test/protocol/BuyerHandlerTest.js", "test/protocol/TwinHandlerTest.js", - "test/protocol/AgentHandlerTest.js" + "test/protocol/BuyerHandlerTest.js", + "test/protocol/AgentHandlerTest.js", + "test/domain/AgentTest.js" ], [ "test/protocol/ConfigHandlerTest.js", - "test/protocol/AccountHandlerTest.js", "test/protocol/PauseHandlerTest.js", + "test/protocol/AccountHandlerTest.js", "test/protocol/clients/ClientExternalAddressesTest.js", - "test/domain/AgentTest.js", - "test/domain/TwinReceiptTest.js", - "test/domain/SellerTest.js", - "test/domain/OfferTest.js", - "test/domain/DisputeResolverFeeTest.js", - "test/domain/BuyerTest.js", - "test/domain/DisputeResolverTest.js", - "test/domain/ConditionTest.js", "test/domain/TwinTest.js", - "test/domain/OfferFeesTest.js", - "test/domain/FundsTest.js", - "test/domain/FacetCutTest.js", - "test/domain/ExchangeTest.js", - "test/domain/FacetTest.js", - "test/domain/BundleTest.js", - "test/domain/OfferDurationsTest.js", - "test/domain/RangeTest.js", - "test/domain/CollectionTest.js", - "test/domain/ReceiptTest.js", - "test/domain/OfferDatesTest.js", + "test/domain/PriceDiscoveryTest.js", + "test/domain/BuyerTest.js", "test/domain/DisputeResolutionTermsTest.js", - "test/domain/AuthTokenTest.js", - "test/domain/GroupTest.js", "test/domain/VoucherInitValuesTest.js", "test/domain/VoucherTest.js", + "test/domain/AuthTokenTest.js", + "test/domain/ReceiptTest.js", "test/domain/DisputeDatesTest.js", - "test/domain/DisputeTest.js" + "test/domain/SellerTest.js", + "test/domain/FacetCutTest.js", + "test/domain/ConditionTest.js", + "test/domain/GroupTest.js", + "test/domain/TwinReceiptTest.js", + "test/domain/OfferDatesTest.js", + "test/domain/RangeTest.js", + "test/domain/DisputeResolverFeeTest.js", + "test/domain/OfferDurationsTest.js", + "test/domain/CollectionTest.js", + "test/domain/OfferTest.js", + "test/domain/FundsTest.js", + "test/domain/BundleTest.js", + "test/domain/DisputeResolverTest.js", + "test/domain/FacetTest.js", + "test/domain/ExchangeTest.js", + "test/domain/DisputeTest.js", + "test/domain/OfferFeesTest.js", + "test/protocol/clients/BeaconClientProxy.js" + ], + [ + "test/protocol/FundsHandlerTest.js" ] ] \ No newline at end of file

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 z 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%0D*&rrQb#lz~UncL-7K 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�r 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≧1bkM~7c1`8y#2cWxCxT@iLqbSLPD-G7r)E2 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�*lft#p!e|8Yi>xxo8TZN3fxy%%*X8?JNlS;2x`xBiJ)Du$Ua?ScNy|5iw*Wid7GE=# ze2pL*IlhU^6)b&L20#L}bqtvBhk|>=9@PTm23Zn{Z7G;-^w=O|RoiPpQF7Q+XXnYj zY<|cs#1~?Y&8aYBcod`)s0FUTS_HW;$-iBTCt}MZnkNV;H0iX2F`l`aAP2)=Jz`j< zIdi}Xj+d;dWzz`Q}#xUWV1QjX0-!XM6iSbKd7AR$P#qQ;7Icw77tu zGQAB&0O-|2mcK39fJr!qho_P0fFNXW&p#XjKpSIk8?>6uQgjji>g!wBlIZq&>=zVA zW#!4fQ|%&ewu|6+3qU$@B`2?iL140zE zCK>RrBryV*{*le6oNXe9L%<0djjhlX)G7{+VchOM7FS_?u9irO6yG$x-j8O^ z!Pt6#X^M)S9W9Y*$REZjjkwmG{VnlPf=01Z(M+VMN$T)r?(HISEGh_qBnd0W6+%L# z>XC3LxeI2_42f4FvGASR)N zN(v$%AsB?D3eu^BfPi$zn1Gbhl1d5)NHgRZfV9#LN=bLuZ|xaS@B4n=&u{*?m+O@| z=j^lgif66o*)J}=vhIE_$7I%rk1MTQzFTg8PPO0~Z;Gn_(sSKJM^!*d6$jhGyypA>-sf0{w#? zBl6-PbtZ*g!W3Yf`c`dRjSK0~*McXJFPfedxX8~u>&YS?aWeLkV8e8`J``pu1>s;r zRi)`s`KRp1cUjxRC;@CguA-X|0fPlKAXCvXG5Oj*%?VxX!d&RiIbqloQm=sz?g8Mx zU?lvTt&ou4*)=q+_$5Z-Oh@eCCpQj>Iy6g(dW+K5Clp~Y6SYg^6U*NHB*$6Ops};i zi@IUb_dAn0!nPu?>DfTxgG|4~D4Vd+Z!ma$>Za2)$dOcKo#0k$4$gUa)A_-?2TNdr z4{QqL?as){u~++cIebZ|dyWtaE5k0v^_BV_=>jU)Gq(wIR)tiMjiAEh!8Gy$89akr zl;iV8>f`n14$!XQD>!V9x<0UxjMN7Jm*g@<3rFVds8+WzHH@p331BQepJ`p5`RFtx zcFb15abacfIVL1zmZmsJAYrt#fVH58q)}n|(c8}R%w~zV`f>M*dTQruP|pt@^-jN^ zq~sAaxOvoh!h<fT;OW9NHo9t`)d%Xy?jw}Fz2S4uTk=ry;N zfxv@BRRrrotK6&{w-kN;`%K>RFeH^Ez>PL2f>excyKoiI=jgL79`G;7KUHU*C@k$I9mnCkCS`#ur~^xZ z4lQ~C7n}>nJxX_D!k=4dVla7!$c}^r)<{a;I?4+3D5sQ;XBU1W-M!bsATL0|tTi1T zY|88c%tk1~sHF~!h(U5f4=K;R$fGd+5DbWLuEjr4@6g^9oO}n4SxmMhLpt3vl_j<9WI$We zL%kyh*&k+`RP7GD$z=^jgvNg($?iu_m&>`SA7cREy?i-XItT2_ps*;uM;zdnt=qQW zP?rvnIC__v$DBWB6MaJ=g-5>bL1=gYGyC^FRVhz6N^@dhOZ0+mGX=6*e=V+{tgWxr z9TP$D+aa=muh26Juj9;__&crkrnt!-Zd znu3K<-16leFs==7!dCzwzy8oVo0O{#rh9Yo*mmUYDp`QZn0AR(nN>YYDvco}7g3X% zc!eruG9xrNLe2r{6teX^V**1b=W=X+loHT}Bk_mmw@*v=Oj_+yG=B9U<}&Ipi#y3b+L7O zBKZyGh&;dOnEs|x|Iv`C`*+%o;u@~ZCRg%>Iju_BascYmJ25F+-k%B(d2ZGh?}TzZv0Pqz4T zRaIHt$e)aEs`&mrcnp-an2+x|I$zUZ!%-*O2crlY5GGnFoMY$5fz50l9@IBIFak<# zx*@jQ4?BV3cK~KOKBw2Le{ryn4e;K^X0F~bExO6iW-Hydm$b~eVER?nLhQaodQJw! z4TeuP>Rpeo_q{Y5bLP*Lb#VAa{(O)V=-}(q{dwiM9Yy(KfSzZP9)!PZ-u{vHEHqyl zbXOv%T17~B{O)u;i=`{B=->AsgVabIYJTCABCYb`E!Dlw?PFS!4X742H(NdLv~<2M z&A|0V)IYDI2dGic6 z>3kpECHk<@BDNmDgFJ}&KRm&<2Pm+AaV$|ygby76H&rtJ=0}dY5D{BRcQLidZhr*>kqNTCI=N0%N>HqvmQJMgcSeEnL92;sQS+? za{jzB4QZ|NINC@T@!aa@fzU@&nZD$_jF-2EhxmZMF$x8>owmXRzS^AEb;4g^Mtlu$ zFVi(eZRrXLHBD~c_3m^Qq3P7s&no>5rf6>DqmfERCjtcYu;jlL2Jjg~f+kCGj6auo zskGl$L4h2(;REavz%iEbI@+t_u3pomNw~@Y3c4`dDGs?El+xt~4Pbt(Vkq!9ey z4Z3=_4)WlHiEYLlm~+fvz_X*l4KzUP=IK_;YXM)Uhxqx}7O-WZ;RDUw_zILV5{q6; z_i4m$r)N+Hrp#|XCb}A;UGo%zcY6Hf+ybElvHr%per>ok+rmimTfPq@7DsG_F#Z4R zhV{{1AtCbdu!DYKuM(KQqA@4A3iB_eE-4IboQMb? zM7p}}-S2$^&_pzD|83jp$htuyf=Y#bn7g?sSQU^wK#XwU4z^SRFW4vVf-JhzAqO>I zGk!2+!p0&{^4a!4_AU;sn#Q~E>-Z9f5ELwq6&1bfg%^j#hzVW2 zgNU^opMQVBNq`WbZ^<+u?e7Qj1o__xL`S4&F+BShXS{zWT20~ytD+MdcditOA3$V+ zCTO=XE4XX9=#c(s{73|5S85fHT90LmVN`AwX; z+)<)F#i!5kUSzyPpFR@@iXrhHt&BQ9h;ThrjIumNxLy5{-M?i^G(~#*qnSz*tDD@v z>%o+q4I9*q1n%mb7t`jIG1ifQTW)+RUEx*0z`%Wu4YD2>3f22*@odnhU>pE4-{0a7 zPkz&7F&K_(|K`%|w;B<0Gs_2l{U7!Lx<>!0f=1-};8S@92KJ7hpbGFxPK0q7%NUSN z5PfK#h>4Yjr>l$@XPS0&q-VY;1UygJ`7tt@vCI{PvM}0bRI>5fXg*yB6QzoUHwd=w zZ~oe9cRnTuW&nvEK{enal>w;y{wk4{jsrGYOj;kO+WuEGBRFg87Ae00<+<2cXGD@JAvJ>BM_r z6Gn}ENqu_ZOL%5zp2sZ~gn}^5v&1Q}sqO9O1kcJUy_Ef13+2aYuqm0Y`h?Jm>KhHf zK-%oV~h__WnH29bj^SLd=hWhDJHY@3|RP||CUQ(8$RaA z9rG3^M@q>PZ+Q;Wt_l4b_?h9XbRglv!G3Qnk#r+`)IA^ipjV)IV%zquUj;8`ROyD2 zF5^cu*^c#^+)ho462!K|w=VSEx8I?J2&5r{Lhfz=-Jg3=E02yoWU#5o;T`UFEpvv0O=?pX|^>yj7rp_53D$ zVU=P)3utY5b@gx$#=H_K12jggp?|Paa+_P*1mAKm6vT4UjH}F`5;)CE>3(QS zI4j+yTS7uIzGT|Kg|12p>;-lo zq9_D+k3PN?b2t6;`N-Esso~19RiIaUd#l6!(eaqNUA&md!(54j zsThIFRx$)v*^GbxrfK+hTm~*3!`IiJ#VZ~Ve0gNk^z0EfN*LXNku@c_uZpy>!_?HB z*4XS9kSGCvUMavbj)H+P07QuL=3~wg-6x(i;{lQ1eG5IAJvF6;O@!32LItET{`V*} z10b0L%HXN>g>J{Rb6l-stKeN?V8(9P_1_qNBD%0mJIb}jj)p6x}pVGGO4Ed^c6&OCIW|keHAK=bvBrwm_q?y z$^->$B0fikuI})5|864-?RCQ5<0nv+(T`*q>V|EcQ1 z=ol|t`)9;L|Dm1%C?r&#vQM!Fkp!SOLBk(EoC!A$p~2rA9+_V7>~-KXRUQBi^J00U zrCMzAEW6G1Z*VBMcM36Dytr3P2l+UENXx%+JAA!00MENk50zDPR36db{L) z$X|NTSKO&~lvf3HqM)lWdN{(6Gy9k;ey^X^+JCM z=2T*Vpaqp|O6UeFOTFgy^dlAxvse$)*Q?hCW!)pKTj?(!ux#hTtyX1%x!F9HFLWjl!6# znf*M3T1m;BCJPJtwlmb(?r5rwjU*+&*Z&N8Lnc?xhCUKZ)xmYzwKpY_5(5U18rH0y z00w^$AYAy)0BeDTkg9eFnW2pFFF(nF;?ORe%qODKHyoozyYkj&zH#j${mD<*@cHCO z0RY_~PmLzqS5P@9$HDFUgyujUKw^WD^$9MAFM_`R14^3iSyTuRgs$4Yy|=V6iAtE> zN6^DLbofzQy+A8{;A|&7`N6C+$Cd9-3MhS3E z+B7vhx`AtRF_eY~i1md3P0P`tV_k;~AP5IyXp#Z`To* z;j@^Du4;$KS!`!nd!G?0!V~^m5y~bB z_8dc2`qJU59hhccSboq(gWgS-?%0>X3t}D`bat2H&tMaGEA&+)^zm@@JR%`cMkhD` z0Y|r4PdZSCR&cfB;<(=9YxCDh=q6`I)?<^Z(` zQA#cLHbgg_Pbsc9>ZUhW9XtLY8XtRNCZG^B4J_Q15bTonidDFb^Ej&v4@Sbg0UYfnlyb=~l^FO`TZ*&hn8BmP~oTy+dx zi3C)guxt~D;3{5+$)S4aZh(!TW2%~AmYhEXih6fX)N@^ADy98&4Wx~3A^Vb3R+oY+ zWLK4nKD3tm2I({G`tO@tp~^H9q_NSm@yzk!mX?=!B$Ibw-6-he)qpp&;dKm{rqcX- zK&0h@fHBC7wLb@WApjBXR42hK3c3a-+a?+kz#^i6}IUGFyYboL;GUx8GuHN zU?M7-Vn1liL8^~jT|gB161W;o#w=syI0|a2ImevF)5Gy;$@BfzD!YUXPG$XMdMr6-uIb|X6 zoI@&>|LI!@(iUhz`#s=(K&T}lG`(q(920tQ)0`kF@TtePy*yTQg9nv`P@ThTm;HvI zpOFbMZ8GWyuv)*kAD}XM7`L_4>vK~7| z;RBA2Amd2%&6xAqZ$IG2+EG+PE2f?j2qj2gJCySP~=9iG-5xfQzJ#HU0 z(d$DW>~T?o56_owYB27zQB-to1U+B=JDYB&@ks9r@BLxTB$D3X0<3)NVKt4ib11M{ zzaa#P6<^b2q)7TQptnsBW6}V*uU`-3F=h$T*EW$7T?)c- zPw;|XMfoJ43Q5Ex84F?7X20{C33h)8R8!6{>wHI@YuwgJSLCSGO-Hrg||{Dym#&) z2T3ioGbh*}nYP-)lg=SSPZ#vm1~ZmQuur1nn^4B_3=)OGE}noLL5esu5AOSa02CJf zb3)uJc)y`?kuoHvySKBgIylUvWt~#sY3gV@Yw5`XUY>AqHm#i9D7{HRqHbOh{E49w z%DE`eVyM2V*XBXGj&wC)mQI%)2IbKA@rXilY*{AJz!c>LNG!;YKz_ajcBOyVjIhw5 zo}-{m*lh_Z;|(#7K&VzAUt@ERIdk>~IF!D2$W!jI9fSRe(T%AXRU>K+Y7%fpO!p)Z zE@hgUX%NKTu^f%eToQwI`ELh^Rbu5a-6dv#zgRceFLqTu%WMqfCIIkzq;*!@uV3- z30&!k;5Ka`(|x+@AA8`)w0V69LpCgsZTBreOU)-(=rbCp#LNE5AT$Jm5+E4CaZgDE zW!S^`O3-#P?EFyDuYs_`wSll0cmJus?VA(eh)w-u&Les~1QeQv*{@_9*8Sh6z2o1e zeT#XYtE+{Dnm<%3Ae6TR?^Y0@I&9_WAr1}~3i;!&O~0H6)@86HCN9VUVVsj$y;E)WcS+m3Cm zqxfFdyL+zD(b3&NMGPBW17tzZeImhJvVchQe3Z_ib+~>vhJyjH8?YI`!m4$XAqtqK z)zYDy4ob1NoANYC8L#=|WLS4e{LCOVtwu_mJnAn4t9S&u|A8Bv*bdS7#&z?Mv}4wP zTRCN&bp|N8>Up)Hd*$|`_XL_k=-*37qS)dQdhYh39_b~H zocLFkG2KUvsks9Fv*3?73Fuiv1FV7f3s2&2AbF4hrM%>Xdjgb2N#!Mzi&uEY>*29c zUob2&sYvVr^T2}fmKi&l?C$2v!}1lsepv#@RQF>Q%H+@)0S+C|z9baLn$_R*H$>;~ zqpi*pIcDu$T{kyK1Z*7i@$o@E^_WXHQ<~b;zGr6SiwsZnU$n1r{;iaafAGx#gXM-~ z@a4bj<2SECmJuXcJgWrPxJtC4UYgoJZ~U^@^g9uFyz&TIQ|Qy(0E{qDR=LTO4Iv2H zrWbbM(~$>C0Xeq6a3@I@i1Gx@W_Xb(hB2ZH*=QuGofA;eIB`bQILh~lKQ{oPq)FFJ z#hd)8fHw#q-OaoX|KRxE?L3f5^Be`pm{_u5{BA&XdK2OgG&SU^<-#6*|<7b4Fw zx5K;TpXqjlKezEDZp}F6dLumpT2sgEnQB9~oRZyKK|U4~Yd2o$M#jp6s^5*Q^@hPA z=Liz~=(pk7Lq(V5p%#AeQS&8Gfxr#lO8#)Et4ZP0wd=YUx!{g8le<$ii1%g*k=yLo zSFyfOV#d((^9YG%KLVGi0bJ6KiMs)bDv16G@7u=1@e4^q{b|nFJcFf1rfvr=Mg~A- zK(4vWAwd)a*Lf(5L^BxBnq8#1v#(FUD;+k0D`I(s!^#9%q34yL(gjRDDP$iruu5j&#LbcuVnOS^|0xK~BY26Qa)RV+ z17{ZO8~l4vhx}dn5^&n0?gX;S zgkE!65Cqs79)x9tKK_rPy&656_U_(7Vr!V2h$+1^^#kV7nBJP?#IQx+F|K0OA7zvT^_b zHA}rwBdS?fmlB%@c`m+L0r!(xox`FzLQgbeVwcf_n}< zuxaa1%feccCscEvLlDrY({qxA#|8f5E32#8&=EmCAlo$KpXMI~K!9KtCMT4b2tVLy zA<*O{_MoQ7Q+iYMOvXCr#f$(Xfrx;RUGm>1-qUk9tAljY!so)RF&dEWrM5X}8eL6Z zSoj5y3XCp5&k5zc=8$S2FdQ}v;UoRq4(UWGZ*w0$0^g0v34_P^4b$R#ieY*JY}zat zH|x{*!!{F|g{U5lfU^_Pt$hFQL=Iz|ZKadE(6-1o3T;;qK|@A>K3K8e%P3878X&|j z53FoR-k@T|dnjh|mjd*(QG_BwJ!7dLI-}rXioEjeD2f`50EP{_Z{0MM7M0WcF}g^3 z_=yU2 znL+Nr&-~<`JwkCkW}wi48XbUVoE!KPbwvLSovN30PFpGHo)AmNZt>-$hIJH0Ubw{Q zL%O(^>({@H%ylmuFos(5P;Wx&WVi-Uk3QvzZgO~!9a}SnQ@;}xyGgE)scFDMk^x4I9=Ug(#>dwm9XugwUcX^`w%21giGme-|)Ft%wZ5DuOo7Gb(N0TRJ&qM%()XoH}Y*TJ$_}VTemhd81NxI zcN_J$u+a!}NpP|UWTg{|fVMzw17?L0LOO)ZFXU46YzPtS+#3Sgj|mqgh~J{CgGwzy zETK(N@f0uRWntY`#=UT59pN(gMn3j1W#d>9Kh5fxx zhm;g@WQM=#76AMJji*;&(|_46Ze&k$Iy5cPb}qnS2--1cr@2e%2ordJs7_S+Y5iLW zxZ}@I_Dh%Ono3K~7HHhMg{_1^P1|MoPi+&EW&^{*6z{FuZkzNOsXGs=g``rpWlt$r zWA{DV2IB<+GSv_(1Cac~My`P+FMLhx{{_Pkh!t5qY%GaSK9`cNhjM^ML)lH|wY0i< zZ(#Q7SnCj^vROQ>hpg|kt$&}ck;LJ5wEh|$4NM|CRXWLA=CFfP>iqygeixFh_lfX0alzcUWV& z+>cjmi-{Dm@D+#SGpZ=W46s_zmV|OUM)6gzEetBH^gK~P4;IECcQzFU4bv|i)pUvK z-vSi@9ie^t9JZ~I!)=XHDxaorf18qb%XCJuO3xVd`%pn zu-O|%9CN}OM#7*0C&R~n4Z@j5Bvc^OsR50De{<+j0H`?cm3_%RSv-9Gdy$x!{!gQm1 zV;jARMnm3X_02(Z4)Mz()>d?a4`4V>5w;=pxG5p`0LQtBx4OEz639Jp8~FD6&D7EJ z**4I$7yTkT;N4x!wi@& ztK$_?3{q4~Pd14G0BU}wj2fh*!Dd=ks!u*FIx9gIfMzfo79<3nZx45n==uXxgzB@a zV94mW0QV;#8BwP8I$qi37kjYEd4|$0a1IH8-B4|K3r&-Z9|Q%}mK)uvqE?6j*0Nmf zQZBMJWxV4o&_c2sny2#f^u#feQGjH^&`J{X-ekyeA%jD`Ez2On4QqOBri7I_@Jv<% zT!dsM^f0t~(3em!@v@9N<#g$;Wv)A@6s}ih_j&OZ-nitjnq+M>mJQsCypEU@Z_hHM z!=0R*D4#e5V+%g--rZpMs-Y`8qa`+L<9#_zcKZ&O=owXikbZfcrvZM?=e@_pvg%yf z{^h0MaY68SM~6?EnPhwe%U`Te;5ay2hAd5@0mlhe zX^nTve`$ z(%(Z>J9NEaJlgih;&}P`VvS(y#nZQvcDFXSL*A7x&?E)oFUERrkWz*8kw9_L0rF|B zptU(^Nktl_J!Yff)RI=lCWToa^E-h0q-3S=JUWI>{F6_y{J?h4sw5eeE;>)C?6n8H z-e1da__l{K$JNj#dwYuMG;Gt?&(n5Nt;<3ziAZ5~c=XO6Twdg10kHvGi6|bxj8~DL zzk*U&6#7mO{#8>6V0W?RhV%;r@6As<1)k)SMU^A0IwgDP}g ze_`gV6j}#32K80PerL;C>1K$(pjcVR0$fxs^H?J{SM-a?E@+AdC%|Q#eiPW6X+@oL zIKcNVjLU)m8~>!I3+0)>j-0!GTbu=B9je=i!2ovlg|mep&7qBgfEt+cVHHop(RPNQH3cce~BN zBzw7woUhwDX)b2ZpN*jM-H~l{`$$QSnXzVT|JoK&IuI`}oUU^A;T!wLP=jNxp(Uka z4W8k@CKMf=aSb`?5Yq}Hw{`=O37|k(g`9X-&BX0IXl&~^E}(P!4g?&eC4@8>w`g?! z{9vPJVsZmVd2o2TBCIWYH@^$0q=Czvn*6B6Wll`37~{ou!N3Y(tM{|D5>F1n9*4GP zMjJ~jz!MNuq46U-Gn?Wl{Fq!!E|AqyQs2Jxg>)NB$?IPuI7UL)ILYlH-Jv1pp`{NH zvD4CYpw9Y7S|mmA`G=#hG5t?}Kl$dcE_mfDGXv+Dd`ItiuOfxw+3ZeBJLk^zU%p}y ziC`u`xm_0pLuMG*0ZSEle!V(=pxtj~d(Dr{cp~d_0;2+z3d7e5`D&P8gsjrrWOHtK z#?G#ggzUS6rd9CxFcYZoMnW<;Xl`rIZG5b6wqQ9_K+pzw_((F2Csk*ldLJmH^a`#` zXiP1sj7wM8etGD;{ygniX{T1vMBRGRkHtEnl>E8aG}n?^#KqNc&Y->y0XHB1r*-Y9 ztE<_pf*unJSZV3IYf)DGX96kc^X3>GN9@wIg1_t9-JmAdq%akr)8fIMnSsksD(ROG z^nC*&B@|~grSelaLRof##Oy+>xC|3Rl8HXwnfo{Za-LTB|ki;Fd;mjU4p=J6CZ8_qotVXe}$X>`{!AFw_gE?Qd%mwk3R0Hr&g z(ubA!?2T`!bVaLHm3DHChKV+-nCf0Y5e@d4#nNP$DQrKp5TsnodhGFlS;nWJDE~5o zv0&OgnTcm9vC*PYSkMY1&bYyPG@ky+MbE6H&Cze@6Nsb>@>j~&<*JcpWFGZe0{9)! zItowpxq8+&Q*y+pqG6d7y5iuxLL5!~7RaH}hfr%p_$x!xmaL=R9BrWs6pmJ)7;rBX z9Ae*)=mbA(%H-+!kiQO#th=-LbUo!YOy2dc@4;TXZq5W=n5SV8o5=K-iitOgUleK7 zz)nDE*XY{iaf7!-iO!`rt*F>jNj}~`TXTuI#5CjbD;1`$W)<7}32pof^F8u{UEm$S z46zyEop?ETSd9&s9%dBxxlLOf26hLRA$ zcr*7=Yf!h?jc-rXPKWrlSk1jIHkQ*Pf2apSFbX|r&lous4I5-{<#}b1wfrLmWx*G~ zP;%{;de1NKZ(*TQ*2s~`YEPF2-f!Pha}eamw~n-CWT>SLg|3D`;?=GWF3q8DP0H-P zL0Y@1i}Z;QmA-}h#pR`45csD)O?QROEbf6B%~ESYx?K`SDk!agA@Ob`>`}fI_h8GD z{(*OSaW!KGLMRj72%N_4Y~m7a!nOkiMQJ`>JD~Rfb9ouB5gPV8Q^1y@M(C1Ao7@gK zNl*t`f(r|k1u00rMMf!#b$4dq-aVB%+n;?a%*B?J5893Ww2&LKJa`pTy=WOOF^c=7qA36^!q^Vrgv6q>^)C+(Ko{PG@MsfjxZJk>j3QGO-4q}KmeT0t>oQa zk5)Iu`xc@xtc{GbM~|L2cz-3;9@;BjWO%}H2(}}y!lt4IB3`ZNDESmIS0o%O zr8~={wzv%O+E<`NVXRAg+Ij`#07*>N3WeK(#_p52cZHLy+f&wR+p`PwJ z4+{{!-sxN3U>B-(dlrhd7Y$rMAJiExI0?_DY8m(I%ke%(b(^)kG|TDj(>!kn`DdOD z6b;|u|Mnb3KPv&*Jo{ys>S!ot`G+G83MD|02Q7;zjq zeE1!68oZ%9$r^rZG)Yr;JI+EQfq0n>4ITC8W2}JbkI|ITe(hd4qBs76B$k$ystbRgB~~3&&D3$> z-oV4`-Wml}j0qY$CjggGi};Z!Z_IAb1=jd9F;qY-R~t(uNkx;=5!!><6?>tzKM^97(F@ReJI$33{t=#>}IE)?13F z9$+kzEkLYJ+}3-M8bfTu#?y4E?f_3 zSZ~|lhkZ4vZ@)Q3{ve{2{9e%cNUN3gGlzqKp-m-1$#*-FaHu78{m}dY0HqDaJ}eMC z1F!N8&_NZNLr79U9BIuTmHEc2DqU*eV;O5`L7Ja(P5subH(Qx@KM`rqxF?kUGgMN0 zYWo)VpZD4i?T@_)Fba%+$X7RTXNiNQz++3>PgI0_LFb0uMdpGr-lFh`;5o5vhr@Uu zSXbXp16^sTS9Om5h_~{=!9aAU2;V@J^uR z;@<-x%`j@w|2c6w?XHW#tV*8={5K+zY(}!Pt?#hD$DY_RS9Uh_L1N7i9ak|%c$;G}& z0^v#<&qB7@6=H#tUE#>6L^ETcSi-DgZjP``!myTB+I-l4e{jTcKr&> z*)$0G<>oa`qE2&B>!rqz{m&|9+~|zkuoOAhi(~?vfD8Pzk+P z1G$=C52Cu8FUv0?fpXCrL0Y6UpJd&3y217fR3a4NqJFLVxd$lwmJ$-GU0rv|dPOj^ zS}xebo;7ycQ6~Y^01>{3OsFDc9N(%#a5#vQYj^%>@!E`)xjr*e8ogNT{Y}1&Bp)o7 z!<7Cjv+Cb9MIFjLRz?QalbOFYLp($QPNI!Tz`k0S8A7AeS{?OQCy*a})j%4o+*GtoEzZ5duV>P|qe)FI(+3vu8Sipp@5?v+m(NJZJb?bDsXKloFv1s3 zuzk33zO;EFVN@Yr@aV><0vyP;3Tv|kOsYH;GJL2>{HM`bJ5zHnq??cQLb)sFv=p^2 z3Qorl%6UY3ehzE~>#oz_H@!|AaIjJ?cx()Hsmi?!Yu2H6Bp(5ffK#JdStd1?=$YMo zg(EbmgMPtgMn`P|F}ZYhX0F1ys7T|vsP4QiA?{RKZUE?5eFew2onwJjDuo&H7QC!t zHi?kwBQ8_Jz;G|rO8y%8Gt}kxLN{`RindPYZr7>>6hVC@8|)_28gD+ke)Z}j&|401 zwFPl74jG(( zRZnDb82<3zFyRPVa<7OxXD>&?R5`#oz`Aq~cU(Kmy6%KxI=7jZe{(8(T{OC;JNgz#BZ)Iqwt+6f#$=D2UC9g7p5os)g6J;?PhD z@_$wghY0{W5u#w19q%Td?WpByAUO>*#{V9{dvF92YwXuD(_fDAx#}ha8f$ z=jp)i|A5e8UobG2ctA72EHmz=m!tf>`SEQBU*#zbFrCB^7C3+ZhJ({o<{7%8oWqNV z(7NDErr?+aX`Cy9Frci4L!$O5@?T~L-DkrL*hUS;3ZqcqyDW1wz~Z$?$Z)(~u){A) z21Ex$_%Z-!j;1eYKC$;VZ12PkR4kq~*u^Nf2FISCaIW{!`G>fqX;*YwQ&QCqAt99711J>Y{_u_} z%h$&P@D>Pwe36d8V#W=s)zVRzehm2@1VwF|nx}O|b_(TBs}6YaSxl|Whx9#}S5T0ka*9V`wWB-oOsGISCCB-;Mr?=4+Td{KQ2?WM1|pZv4s zJZPtc)9SUMRT-8W(Fi;CECP!Q;RL*0R%y%AsI$`L+}~zAe#8hBK=~6Z)~JAqp5F&` zE|mJ!iiSZ{v2>h>*{!eF$aQ+x#6`q2_1d7B!YF|})FIHjAFrLpR0=~tup+ZjK&|tM z%GJ^5uQO*m_kk3W(|FbkQ%Qu)nGNNi0vm^-G^2A?Y}>YNHi^Il}aK66c!@WM(e?Y5a;-z%F%HQFjK^?dIf{+l>pk_9F1hnv1i-pzJ zb9aoKq4n2Lqr(00<>M6{v)%fqo|t^p_Ykt}>)DF+fD z=ioB6m$gk6GA2KNK5pAhe#3zA%{wXm<~*zdaavQ(HUy7mn9jPv!29DSRA0i)?dsbi`PKfgrIKuDFAT|mEAyueBOjI9Ny|7(K2*~g4{;vtNI&+vw zAs*@$Ck1E8n(@;!gx75CM$KFvK$rhY4x~-y#v7nXpmgjYD}X_V?w|drK@SgH)ip#$ zJq^~{KmGWLi@TwG2>=K5OjG7a1Q=lMti_PFDb{abzrDAd&RZj9`7AUw=@%u$0U(C66s5N@9tYdtjDkVn)e?0#f^Y{!409Vh-11Q0l{ZfL%O+v@VEH76%! z7n?Q!Ql^|c9Y6^aCK~-sc8Gn$tHRLzz3fv1YgNO<nPt5KgMJ2<&8{g1*vQc|~~ zqIIK`$RX+USzJU#wVzxWgq*(i4cfM`{fNT+%6A7odWO!i1cU5hIHhka%kjWL9yp;- zZUnIw;}lkApO>1?tXo*$faisAwf5Yb1k*IK9cw@%zcAm(`9eA@lTU#d2Kr!sax?9@ z2LA#Aw?_ITTo|NMbRYpXLk(J(>p+dkW8i}bbJ~f#U2iOH>NX_-&HU1(sO4AvoiT(4*feXc{UC3|!IXm!3&zGL zAgcnQ>g70yloD3Yms4adVVoT1;j#_+I2rDWXx&7d!UCs8{&3=Crg2M_ddgAYj2OEU z=6t(fw_6{9@oD(zUj92*POJ4rlSXWhh}eBOUFOF4p1se}ZWb71KmqePxiAy3b$o(H z3da3mj7W!F`p>W&Eljp&s(7T+oDaE2ftk1hgVc0D(;Nyh6fE+N;Abjxn>0Qj>_2YU*V+R#rJY(F?9CbB!iLRk*_Neb4UJFqRx|J|~s zLXrcE6&L)?Bgg~wDLah_!mXe+@Im~@DPL}CXo8~wFf8S>#OaZ0@-W^+g77oAzayua z2!j`~AklA$Y>|TB>>8RgpBf4mEQiA+&JlPp+a>d02+3?En%C_#Jj(_h82@PBvu#!b zFr0}d?AdE&$^3h0X`_(f(C|cTbiwoskUwJ@Ks6#7t1*GG8m+q|Fyx1Svbek`2By)c z3Kbiy7o4>XPusm-)7iB{)G=ayZQWhxks942vBbH49u+;`*NHlKhlYN4&o^Y>$fxg*p@r`bO@=cjbQp|aO%o#xEdnoeTaZcd1? zqxG+R1LwnVW?Q|(WnM2>-C`BDrIOK^On6m_W*cU!i_YzXt-*m>9`F|wj|^}5Q8H*@ zpd}y6Zj~?>+A(>m>F-FRosr|>aK}L4Z9}A)kE~GU%&{NcFIlPw$TeE zq*MuhP4+cohY2|hp8v{qBAT;PQK6>Pz<89cL6$K>#owi0o|4`sso2t_EAq{f2P2kvbjV-O4*$E z`B>iQ@!LJc{TS~J=jr~O82%0%NSP0ZJGofWZXB4_6xR|)15z9mq%L!chmXw9?SI#2 zCW;l^+8+m0$>L(6lUjkwtZiZHSE|+Y*po|(g_yW>aUFvsS<~DOnh(^y0)&N|3Eyz& zr^30*^kv8RFscx$}TTG^ztwm*{jcpsx3hm;M$al7mhMT$I&sIc6!#q@DC>Zid!)&kB>!8TI<*h z_RR-_K3U}YJ%tl&9vu!vOE+_jQCf zOeDxBIo!LUz4fjK`O*H0QL}9}LYOOTWi=g|O3`y`&J?jX_PzA;$!N_PoAh{>zWlsn zaIvAq@pBr^*iqyKx|kNEh?N?oF4uR=jj@#&+l$oOq_%8W;uY7()5IwX$@1ZXxF;@* zp6uCeF>p(0t%J_Ee13bmcw5OaVFSFQbe!zABY9$B3779%+c|~tJzS&e4Dx4)tJAl( z!zLXQz+k2|uC45jiK9ENRhnSXdmN_)djNe-ZT$`hx5LN0yE-h-Dh?wl(NnD3G3Rki z3-0OV=Z~U$F>KVE7U4m|*crb%n< z!g+NZx%Fl(IaEia%_f*otHOOHgjJIw3byyr1yUy|GWcp zYUTg-3RiZ%-FO#@LpiPS{f(qr?{nBRj02hQwEtkPU3D7U{-PcuD4*mYF2PbL%6c&q zOqiC{T9xB(6DHia;Qx5rzprr2wqV|3p;=6;a@2~!-Z2S=)Yu_C2pcIilS7?oxS*yM3|<98Whqt(j#c%*8{aZY)(r}7CC7Uuws z9^!JsM9lFQlXl~S-EYOU zJbrA!9H3emecJzU2Pff+u`aiH2u6i**h{)EF*obuF^QXEGGKvdTU9cR$j%9C9Y^YfE~)_Vv8YL}_o zz_X#xP7;S;qJMF?b4OmKWJT)0HcaLDib$G_hR7dS8qO=_$`Qt@aBN7t%9GDo{Zrl;CQVlwvT|wS`kOcFVS&%zW4pWz(+~F-gD&X@-Z3{ohscx9H`9^v--^hNO|9%m1~4TSfA zc^65vC!c6I3{p--Qs(BLm)-12!-;S0Z=Wrh4PFwlE$HN_DjuF4vat+f&vI<*=W)`( zc7L}?eGN8*q5U84S;ira_@3#+$o(4v*qw!&!Npv8bwZQHD9m;TgZF(Qg=EVrebx?c zyS`LC-Wy`dmj_bz9oWlHYi;qqyq&)sJO-9d=l=}&R}O5t*>^n#amniATqSd(tUhgh zyC+;R*hO^vl|K~U5bX5J;1W+&A6TpB@HSf>$Scg(S}`B-UDC#2LYNyq|Ks&O-FUsH zDmS+_Glc6}nZ{PINE*tFEn!bd!1KN`C~EG<1eq4j!j*w9V~V{bX@@P5PT zM8Ha6cM#%wasKp=)PxhZB4a4n6Sl;Ki@k&&4)hxgpR{b_xaTq2rbY*G9i}deL}11G ztypf`B`n%UWv#7*_TF+Hi!^nVx|a``wV}NcCJ)))#H|?ieycpc0)Z-IJXcG)s`b~b zREZn??~+}W0T78Zu`)bZw2foXgD=&lJ-$+Uv(1Pu0mR6|I5Bl> zx1$@|`&DKf-Y19*bI9~sOtD`CZR71q6-W_+_bzerQz$xQzwp3U^cVHADEr&YcJ~2d*I?i3lM6~8zz2J>OzUZa81oGtuTJIhpIPk zO8K;9ei;k=C&=7#_Nd43AcL5=tHxbxJ3QHO>iHqPKCyzO*5RR%ppu|z@ZGKiq)gRLTZAMEk0Gwhu?o!5SlJbo(o zsI@Vlfvau>8JBkR>LHA| zG01I09ahqu#e#!p<9{`2nzA-O+||%F?tgg_9qcnZ^dO~1NmjBrm9b#5pU-=R$12}0 zS^a{h2+u|Z#MfryMnbGYp7NX^sEukqSCVR9{iRKr*Ne9bf5jKD%^)N#A`9J`tJx(A zFdbtLM~k#-cp{ZAuw4j2YV2Bt2Y<(E7Lk$Jk#7 zM7eeEP7;-F$wjhb}@$ zaJ+GFX3HBj$`92+pA?|<>(9{t#8h8xBSL5RRmyRQr6IeUW1rHq>iT0%OSO0TczJlD z;~OA-o84sr#<$MIu{C~lY3!QLP)|j{3)VaIo++PP?T#^iG8R!!Xt$f(A8x0%uxog} z&g|^PiyyNsZKu5LZg$I-SRTjs3FkRc%->PTGAa+@Y_~O-&8S!kpV~+D<|24vzk_iT zD&}Uxm6+=xU2+p)Qg|qJK_%CNef`dI<$}wT4$b+dsj$APzcnUu*(lxB*G;AH7Zk{`lPWkyJ1+V#B zDSRtgO~v!9GA!on3VmOrk6d<#;Rat>;LNP;kr0yhb5+t3X;_lm50 zNBsokPQRL&QJdfyXq^+^w)ZAlYg4t87uWm6K?hxdEnLgx2Ny#(X_ytgo94l)-&?=d%36~X;guv7y2+=Y1^ z=av0v<7nHzzwX2Rt)8A<^q?8m#>T4pNpu|*D>}oN%#~}fGR8d9vgei%x612@zR~_A z$Zc0dWjb_~*;;kVhvqNdub6FX>$TU<%%9A|*M6%U^L^6f+!N$H*hI0-W}HkRE?joUDD7RUKg*}97<08G$P`9 z*UjFqx!_u}omyUMd~WGI*B8fCF7d9mHM${xIupM5a=e97czZtC67UPzUK9z+-jUU4 z&fWAV}!zVzun5?q$!-?-#Urq-I}(C{wb zZv1eL90fU#zz8=aZDWr${Q2=gC)Iyc>W7_5U+Bk85+`D>KsRrc0dqy{D;Coj!mC&1 zU)IF@r6h%a3ocN}d8!8Q)VYwqr1?CFKDuc`Fcb<)H3f^o74x(2lsXo^2NM_29H{L$ z&oO1LCJiGqIgKU0obbIk*H^7W5?%I|Y7fsHIYPGI7_!MS)7}lEi>)Vf!kM*$`E5%- zS8`O93`=gIY9xFfafw$-ehVoJ>9g(nLkwRF)K%lrH*R`+#ZO7TDRd*wHTR_-lc!JQ zu@m6)@}@p8r8_pAR%i&pqJN81pTkP5`SM(YY0C*l_yfZoatSe%6Io9 z!PuIE!s8RB3)<_3?CMTf+f2{4{xsHF(s-35Za+2y=d@G?FWXtRY}=+}B;I~mEsPiE zno0ui;xqAuOg1CJ?ips~VG}9kAvp%>Uha<-O*VT{QfZ0%OE@)9Gsv@QWt5KWJlS(o zFcG{2x7g`0FKAExuskK8ZyQAVc7-0RrAx2SWuv#U&lCr5>86A4kH!mum6;bP>8Y;} zS+wN9Bi}0s7&%qN>0sLrZY1`IAJf6CVaVUvc+$^R|2VCbKjENd*KLv6ZCdM}>uHo* z88zy>5+jf)=$JO(-4YzO0&&xetLuvF&6}>8_TPQ%m-q$kZsw)NizWu}JDP1P^@UU+ zM-Jyw4`9mFNJWWLJz#~$1#R!`d-9cDBu_ILfJz?X#?0R4B`7I4qag5DEPb~O(@woP zeuMRKUI%UFVYYgv19T}|v6Cd9iwj#vN|xD0LnLE<2AC*WT+iQoiYp#~G$_+D_*rpO zbQd3J+&mgL^?ZlU^#*Ve`)5C~g+JIQHt^2gshs}y4#mVY$I^spx~t0R)%v2r4{a&l z+AoU-WLszd9P*v~FDOy1TvM-@?E!&Z0!y@bJNdU36ay^b6T|DIa@3#pO>`$9N=PJk#q9U}0d9 z3Ehl6cW0K?zTrGusnMl{L*Xr4P)@Cc4O`Bi#Ysi>b!m3loBgyeS-5ohGA+G^YD=$k z=G*-(6HK1LHiiAe^bmv7%v5$R`#MQ;^i`~XQ!RL_=3Q7WPZcB#Eqwbn`4*fI2N=-t zMp67=bnR0)ho89?=N+7~_dQu}BPdd+D8Bm?Pg2K^;l3H_UQe}q7vU@L3=Cy+EZxJt z6XDK%5`6r|8%h9~$=s)hmBV!2bbq%xyK5I;QA}4!)|KzSr}G?XYS< zg(`>~?4apfYOkpMF)>!oX{oFBzE`X(`a#G@7(KKle;P8AyHxG^I1_hM1_9$tF8Qr~(eAiLgRbl%&Pe|Zmb?2m2kOvxp74r~ zH&4s0m@YTlvUSTsLX1SUudHQnNXoZ_rQ$lK@{U?sEJ3W$xb{7Z<_mU~n^P?6x~sbV%GNA7Z2ula1(d|dMe zo39tXG|8mZy_>9|H_v}L7~1C3UNC+8VX;8$Q>y~+KBxz)SUP64m>1r3zBiTV6Y)D3 zdV1?&14GmB`bk5htWwGIR4RbQ`Il80o)9BDK>B8-Or$H1qrMD|YvaUij}(AtC+tr= zxiAKqH2dzT{c-2w>Pz+ueAYlY%veB?N0Gfb>xV4XG* zCH1l*G{+YGTZ+YT&htI*S5B$qHqI_~IIX-$d1DG$@XErB`Hz+5C3>Sg6SjTidRHM` z(Y>pxH-8oxY&p1OkHP3@vgC$%xd;2V9*etWoYW?@o{HIchmv7ji>^l~M{XvCOB47{ zr|E6ZMYIcp<}0C36vMc}C?N?zCOfsn+kU!z=BH`Jy%pZ!G(Jd6_FcL)Yf$HF$vZs4 zmy)xM+D$Sfi5In^Qt6LlSL}MZp)H2XifsY^kph$jRZ|S^{cRCM}4Je^x;I9u1&|61MMD+{{ z&U9LOEw+JofKs(=WsY^1dB^M4p=s6KyW*b2H(0|ry4Ubc)-o6(N3HlN#|iJgt)=T= z<0g*ul9#;;G|)LhM?HeU{B3N|B!O^at1bso;$DH;@Qru1!>E)YA2Z%sL~h!Vq0+M} zzaRh_Wyd*9L;Xd@9sY?p!f{A*F8Hm2B{9=mx3l#y9q*YkEtT>5^T!Goq0{Q z^5@a$XMKl70(cparX`Qf&jihBmc%{W`+Q;0;fd8cwk4 zCPGsa+K2Ez=tjdK4#8ICgm0j>f-;J(AoFNktw%>_m&dB^3xd4*O$yh9s73T zC2{(dSwQuf0?hp|4Dk(NTwQ3pEf^Q{R;Mlcix;ayu3xruQT-DgibTR&SC2 zv#w*h$y@Kc@I!PJQlHHzt$efX9pWa}6PFXu0mKSgKQ;SEZe|Ts-j}5z4lU`Rmb10c zTLi>b9Kv@!@5mUNPDE5uZkl+-n_+ zlgY;4g(pRA0e>Td>3+yFpXcT1ahtowxD^|fD2rL3*64+@eyi6Er;2Ee&vZm>+lG%S zrr{>eLM-G9mCOkzr%rntCfOjHd8eV`qhH;GeI9DH-UKiKp@_BUhitb*3MZ>6o69$VGw6_5){i^`c_Og?&ca_fJ2@mbvm3Uk^`) z574tI_x1q{d!^@}@P(B+-en1>*H!Yj!c7<`SbRTf&{F?3X6Wo5w%TY=uP-A*NeSDUVx3Zww_f%VoI-*-l@+6>9b)W+pQz#010WJyB<5yFOl<~2Cjf|Utz z`w`xm6O8s*9bJ+kY9UF+>_iw5!ss;{pC5+vB$^JnxVN^e5}BmslkQjQXZ2f+j5 z?~J~fpLe3)0QvcRXoY~Fp$;8k0X&`xPv-NXlf_Eq8`@uz$&FwCEQ*mf@@HGZ0R$|x z*-Pe9^|9{)O&?heU%{BIrtaC~-~pX$A*ts)CU>oiht}Zy*MeMg57W|Od5i16n*0pk z)A&gnpdF4nWA;YRQm!OUT=J}zYbYMReg3`oT~R3WkKgt`c!Z6|p13jlz&_(pa}zLl z)g24nKDU@g1bsg1Z`yNKZ)%Rh2S&ngm=L|A&huwhc~Gol(7*T~+g>2UWUpPLEVyu~ zn3mAf)Te*$+@tJlt{|^Cm{olq?vF%^L^$Ovf4f!rAY-`-L0VeTx%ORt?Xc{3e@_H1 zqmRoMAFno2dOIl$C6aE!+t>97#k`;g#d446YmXG}$KzL3eY9=egucvuN;;e<<}u?C z3~o_in%=>{j)MG~)!Fpqs4stbCLHz6Ak}8phucgbj7c(Cnqym0n7m3B5DG-qjJ|i) ziI}X!i;$HMNp8ncUt(sCXI;g;U5^6h_|wk>18ZT$)#Bl73MQqubL7?wOEza8Eg4Px zieM#y1n*P^(HK=bH-uPbYKtoBmljK%P%kwyf z)@cATvl2hjvV19#fs_II<%dd!RU-ogmgupLcsbl+H$$0hSvu-(NWq7&(OEK zO;wEg&wV-th7z4w6NzV6XGRe?jy}|`u~8&bfEN3YLMAs) z4pJh6T1tg_n#oGoF2jaS+)jm^2;_x!nr|B81!&sJs|Sj5Ze_og!cpQE7If1i^tEXv zbR$r|qH$;~WYedfMjd_l?K9iSGNdzBT$oFak(F6 zW}4E=%>Z*x>P~-*Ht@}bUva9WKPrus^9TM4y6d(0tj@dgH-5y2I4QW@=3eP@^big^s6n0KK0w*Id_Dp+`o)S zV%W~O>|4#6zHNu(WdH1?6b|T|;#~J&d7uIjUD!KWD(Hs_3Ej}~XF1Oe1S^G>7vM_J ze+)c=C^5J4ztXk@`hA7gGy1TfScU%xUf@k4uWZ4+fVyX3u)xq`xGC!z%7WmFGHAo; zoz#IYH(E^Kk-weWy48*o&I9_UtU5d6$O;#EffbO&Uk)Yru|%;qsD~Q%2VYcN-E*Az^ar&4 zfxE#;^swB4@&UN)`e`|Q{Ck2gb z-1Vqe!)_SP>tJK%^D(6mTO-H6j^z5^j^xwYkuWN#2b$BpgwyTy0+TIiv#38xWHG$v zf>y!)l5}tAbby6Y=iHNxE$zx57sd(^0?>zwgR2|+7lIgMcWJ3#T755q36>sQ4mv5m zIK%*Za$H<3APQ9^Ljd|gLXCb{G*Fu)0MqeE`JdeB!rG5J@zwrpC_0X!za7UeFvAj! z+a#ZeSqxX`UykE9G3_5@D0sM~&@?$sg!@-qQuY>^pC0n{uVNVKbYhI?t&DGB$%Pa; z53EM{!{LR|uHH87VxUwK=HmW#2G)O`ft~Q*&cJoe6_5*~ojQfe@|>;&F%PBcpmoU9 zwI$DlN|tC`NVc|#Un;gn|7l%p+^e?RvxPrRgL|ybg6G5b+?9RxstO5VEBdU(srxvb zQ3B+XUqbU8qa18iQY;yqM#rV?25^s6Zgx4XJ7Bng!=i(L4}n^AjYx`o61yPkxBWXz z!3!^dxcO61{juRY|FYqKa8fb%HV?;L>?Kd91DC6(2dJiCG#QUPRg$J^j{+`bdJoVO z>@w~6n4N1OS2uBbcH@gb3k$SaKVV{qb5EwK3CVEhe%{ zt=W7Swx%H1g_E{LDn7KyQQZGvKO_;%y(j4fGWObPGjusOU1J^g|45l(-f|LA6G;q2 z?XWe6aV`b;539b4aJL}(>kh#(f8a{^tnE4!jTm_kT!$BwUp36^x(aJ@gE9Oj!o$DpKlRRE`w!c0j{aEDU;Izuxr=LrPk&h9s;l~@ z^v5v(z|oAn1r_sJpa*vYSqB#A7mNSmeoD{$TId`sRG|gn>6!Zzm)?jXZTea#R3(s9zTi}ANhxQ*o0((ZzKrAi3at-5}xjci1 zOn29 zNwtUlApm_n0f~(x{H-1=3h&Kj(HInJj}rP4hMMmoXMJ>9hlR zA3S*A1g-zpJY!o`Dq%%IhqXkpmt0A8_1Cn>scVPqWkuvT`y?wbSo)#So7SQ2&ZAXr zvC!7#6ruN`#n1|--;)pN!Io%--f#`Jg?9xVw2CdwAykouQ^5UlE zXQKDkJ154y3Pl7x_!KG*kq9q1y^#N87!2L%=~^lL>s<*LY_OVB30(pEQ^ZIcx!H)i z2zS+$%jUy-d53w5$fu+q|H|dQYgY{E)QAWJ`=dhMU-RQ zZXgJ;-^pFnp|lQaDb4XiwHqn*$5Fq9%)QpD!@fhmt&6S-I^oSKzHqgvggcyvPYp{R zI=XpFTLxMqP0Szw;VRdzkj0EqAcBM8=SvUvJfY*`eSW6*(_BsE<`DC9 zAszAvi4|p$8fd{!ZcAO7IP|G)?>@zkbBq}M7A;?!EoIFMaFd)+RIg&k{=L8ON13B{ zpIZX0gF7TA*0w{?py`b);HF_sIpUl4NSZ2^!Pjw`Ezv<;bXp+9Cx-HG^vz~7yjld8 zva+J2*t19+5C=NLL3#P7F+jBlw}T!nV)^(NCBfh6dnfUItJJS(0+tmbDX@0~PCFaI zhP=4iLo<3CgX1iMuPdChYe#l+R3L}ksqFVeK8%YiXCeCR{CJb?fzLpKz&0QWkep4v9VkM>(zDe;`Nh(>V*+m!&%6 zm5wO>BIue$6njbb&uBV-HIHZpOhRyB?n23p%ztREu>)W=|FUD{neNMqODQeDdr~4t+&0oj8(x5Y3Z2w$pkk=Z~ zP%ueRkl8;pP&L>wcNYQB;mcn~7fd6ep;tNMJR1!_?c#ydp?e#l zn+$nJiI|db*2y&t%~0?e=c|v%-5!4fLlv!Nsv~MpNQ*_A^`wXxU2$ z6!Z4Z&*1`iZ4j~3e7%_3PO&ZItjy>*&20)F=j(-S#Luia4`rSiwt&`jbjDAX`RYE+ zzzH(}f>l9N)m6+}Jfwr!gqJ{f=`T)agTlSGZm`WJW?i8dKp3~P{EzF+oqlyRSq1|V zK5YI1enn$x{q)amv!76IpiylE+>kl8=$K4%?P|S{htQY*anN~IYW@^{_AN)dU4&T2 z(y|mq@%Q1GVy>a_Sm>H@6ewvqI29KG;jcbEIWGyJlQFr$+Zfso3fw|zozAR~9n@)G ztq(D{-qJupZF2jl5x8FJYCv!l#aniTn&#fLWyXEO5P7+_L$>;2f> zv8o2V900PnHK?dAcxml{M7jP!jGW)J)!Byoux-?DW36D@gsvDFnP#3G+CUuyZTxyT zUuyDQuzW{fd*(v(cSK|f)6GsWw#NqjVwb3~)^1OxR3#R2| z_0SjtfHFQnaK+_lKaiy`eryXPK-o(UZhce6559f>w|CcwFag>g?2O=XRWj;%nCp?( zXn8Tvya6hrI5aoXup_7D1I;5hk4n@^zJ3e*c#oc7qOJ^=#)Wy56;%&IR@8sb(iifZ z(!PC%pr1)8kRpC3`0X*VbYOsfi+CxJ2~^6em_vP{f=fyzMWJWH-&M85fCr#^xHEUU z{L}$Dqg%NHzT>$+KKF_HWu8z2P-pMnz4a-t<(L;Mu9XoqJMY z3gFsQztn!aO4=i0Ic}M8z}`*H|CwE3YTlkb{o+s`>HE_wr117>7i zCz-zC--Sy>V~Aym`+?OYqiMEV#Nbfut&+>>Y1Th7uJ=8F8fktgMjVpr3*W|PMis+Y z+hV?GEv*Cy%($Rkt$s04+^{n19YIdhC*ZY>+|IF5sOvss*Vx_1*U7exI?81iT+%(L zILlRa^iy&rz~$+{jA?@=`WZ)U+wlz1@*>EQ<>3#VKYwr`cLQ{ytAWmA_+7Z@NZ;wc za;*tI-kUSqk4v$_$>78;U68LFQ$%QS`Pkh#uR-C$#hEed2#WCI^#FD>g`$^C*>$m( z=Y+0|!5|Mb1G=JQaFaEr6_1wZ+D;Dxq7Inr&ZsgPRDCcl$VENqf8_32A5btGiN4!F z5T}6Li(`gUBe8HGj?w2F4loDpdv~YAZn1M%&GWt%VE6WOlc#<_VCLAJA~qAFAjoDe zmoc8=53SqM@y2nZ@GkOg^#9IFA2C%zw*d`8?zW`}3Ge*BfOE<#W)4n2oMIzk7zjL}Omw60r*|yDVgL74|yg~{6+cERp3M2*Fvc zW}Qw0)5a>RD@b5Y;-5WQy=2gd1^2cy?s`3lstiJ(%_=Wd4c0Of!))6TSe?ZG%qnZi z&Eh<-Ltc*)&@5_e=;8!KES_0GiW1(tR}AGDzzl6(x_$jX9EO~ZbikOO9r=DG{%QAZ zPu4jWOzm`2xvO2nd#mQ{qsFLAypZ5xf6n|=>O$b={Wng_`Tzs}{h9YGkb2i2ORSF+ zQ4jmiIx{?BmVs#*b6dZFt77Nrx-t`dcX>+6S*!ehIc1K6 zKWlO`;D=;}`p!L;;v~fSiAER-4 zmmvM_C++r`CRb@9baecUFX{#DP)d{rMi1Y|pn|Hvdk>#T##g;sD&`L7#;-S`ktjPeW0`F_Q{uv232A#T z&*X>>U{1Sq$46=24_dWlosha)-t;sg9TAh=l(RJ0a^xu4A46{{-CDgSYg3ue?kIH9 zLAQmaAupL9R~ubOa5#75EQ~quyHCI+KlB=Uh0DVoB{Anr3cDe0Ry5z08k%qjKjywi zjVRG4RVV>75$zMUj2|g`nPA@0Psx$%mEOLjO=J)PH zaa5`h#^sENVlY0XIkEv8syR)-omB0Xhl$iq%`!ay6Wp2SATTrSJ9V~Rv(P{AP3({AWzwg?0ePr%yMI_m zz!eKZLjlxN7)&Kp$;=|yI}<4m8K2)si^z5g_HD2qwXm3C2{o)c{nY?QZ00}1tVTEI zF|HohcO(E9ahINK-5GaC8l1t>RN@R{p2l*t2^e+#i}1*lp)Jn^?wbL17+tFZpNPZ+ zN9OW(VNg|kdGtz7K=SHDbCdlXb*E}`QY^R{G_0m)lP@%=T2O|7 z4B;t7_}7X#X*6d73F-t*kN>mWdZtVu9q2|SZu)VCgQG-$eAlJptA$MAqV*4hx~PWx zFrXH>oidP;F=>-&u`~RdqD9R;%*nHl28A&*Qul;PM@SmNcT{{sVkUD(P{7N?;M8GB zn!?R$yLUzCZyQp)wsfZ?&HxPwfa{0x<)%=!XhTwA1l@_dh6T6l8gKdl{bXTgL|$Fp z16ocAXxw9{Vx@RACBf#S;ThYi+kIh(>iQ#Q^a@+mU)mG%&;KQF}8-?h+17=E0nq?l~BQ3u9@6$v<7&*El^yD<>vwK9-~!F|$ok zdBopxO!}By#6{H1SoQo4h+zj#8F_v~iEFTT0>;qdVQ$Y?SU(rMGtcq)ZZ<2_$nO9i zXz>-bJ!3c*Ap3F;jT`7!DTYtd*NmJ6rU*v(xJ0pq(Q+X}pXl}Y8(@?WD%oplo6Y7_ zBCJ-jrD3S|0S#`l6o$agD`ku7(+8iV+@+q^o_J9-KCR$pPFp|1Dg@4mqs^{_r!nGm z6zYAh;|G1hU(ito76H)(7{2x9BOSVTf>S*MYAw=hM&voJ!LT*}S{^Bn4|8iXx7+va z5iovL;rpXR%Tea>KjYgrz6S_2bV}L6f`d4X=);b!gQ)^gb#*K*(W4>{Ra|*L6AS8q zJLT2N!+HL5pEKem+nwuQUN`i18w+OASwYcPfQ%Ch{+#*%RJyD>uNG{ZLl2ODn{Jo8 z^I6p2=Y%jN_nGUHTlqB8q0pvX?8T z=6g!~=|*Qtj?@b#Lz&^&Y3UD+TQyAdx-&v^E!^<|n-2zt1M$eyGl2$-yc(`KR~K%w z5o}_qo=NaJ9n58X%SI39y6n(@jclD6X0Gy94nAbbF zLouGCG#$>#63I@3=5b_OxGmRO z9KHo@#W4xwO;6=c<>oIeh0lKiWXEIBi^VCr`#oFKv#Fj_j+gP{11sZMZJ)AgE>S$~ z&+S1})Zvx`!_4iymMg9bES_E~KOJ;|?{->D7Qz~u9xE?4-nP*C8x@?ClQYhNqRk@( zrfXJZPkNJQG#2fk=F_m>9R<>!p25$|t$bP-iz_CjT6s*~Mo!)izK z7N1E?mIf4z(G|?3Fu!nzf)_(Ox~`#763&UehtuE*jQF(x>y~!06uSN;jWJt&4XO_3 zhY7udiOaKZY#(y)$PgY>$^bC$uR3He4}}fff^J2DUHa;1zy;NRnn0I~ZuQ_iWbE|7 zLa8ziRoCrpEeBQHJv=H)!tw7X1i75*7PQ_1 zCE!-X#g#chrzdUFA2xra{7~_@M&G*S4a}ey55OeJqd1pKSwu@8N7iUQVM3wh=DD`Hy%pkm|LF>X5zL)967?Rf{{s} zRR~)P>vn0B4;p(35Q1OF!Utd5e3f^V5kT&Iy|NsG=jy+63ET;=hO6)k%}R?~H#I)X zl)+DX3I)&r1pm^E%dMUN5GZGymRV6rvbV$|;;nAy1nIuDEeSc5S`$XS*Sx%@7&t zzdZ%tV>Pmv>@Ec$1@rZ~;uGARfoYl}(knWGOfV1R7^!n;7Mu#IHJo%1;0UY}U;&XC z0ElPuWv*-~#}^Hj>?B)vH`?FE?;yuYLZg!GC5zisXkWZKPOluOfJzH%**&7Wd3N+^ z^h~>BXCTa882(Voxe0Kt0;HGKvOWm#=$aD(E;v!clo1BSxo=JSL!hhjY|p3clcgzg zK6}Kpn)tj35x!u6XuQ?rt>fbP761C5qCqt}H~bg&yAdjH14M=C4V=oGI`KfdloKTg zOYIi~DA%#a;hi%}Z_lI&+RcgBNI^r3FpO0hgkh*(`jr(tIW8aCOLz!B|3_l@(nAi# zrvWcngJXF9+5O%fdgX8Q6bNsC%C-l;pt6x#_)BABVEzoTh1dv_kdPrFsf~5potG;P z`Y{=?+LF(9n&aJI+{Bgj!8X@}c#4gX(a2ujd#wmJGC9M4_&m4Rf_Mgr-uhvm4m_}p z(`JD}u|zKfJWh}W?%ZLx1^p*_zs7*m-Md)ls_dFXiZfh9wBC9KEgjtVI7DIC`vbD$ z6U40?P@SzY@EE@Mj}mvc-0}QQRy{kE?!@cxSz1VXDpeYx$nUoDTrra^fTOb56tg0MuQMUiEtW7!(?eBfut70 zK2#d!w}}uNA#DPo9-tTmMnl_owb|WU3*A6WL!o+!AA0Fz0DP%Py4Zj^0V&M6_!f|- zL23iyUF1)_gAx$ECSmc4AHW*uH}@jX=v6?r9(aAU(zrqhQGdDUoR3JW`>-aM@=LUd z;SrS7h!~?YG?wBcaWR;3aaSDB+JG-4)cB73OacT{BILRfA|kR0JFlv;*`ixd3+-J7 zQ1BQc3%sUCsIR?RV}E?SLAso7~Ze#dN#W(QB22 z^Y5}zcV7*(bm~prH<^N{(+lb?1kjxNcsn2js_nXZUihne1cmSSVHO8^qKIVBdkIXW zYaWPh_tkh%(ZCMs1uBjiXAdEj2Y^4IU#>*>3DI(6rSKJ$fuTMoaQdKoiRfp6)|(Gf z2`+Eg38U%>BJLs3@n!e|kwlk{UqXi;)?*^eReyzapRsr=_laX^t!yboe^2iLmTSjd)l^Ai?>#epat ztqurJrJZ!^@4!V9|DPtGaDCO}JD%*pjlJ97!c$^e*QhCTY)y2Gp_G($@YMl2s(VNk zXuhp%N+1aLJKQD!%y%sj#4;WkB8`-|`^g-Hx&D9A8z18A@@7slU4Rklo<1{lPKTmz zi6`lj?Y;MpusBecFpA&A5rw?~%A!-WSUtsVP&*JJBM?BvZa9UuzP?591=K8L2M}K> z@BqeRVb_P4cGfPon$~hn>;oq(4de8nZ^BDN{eTpXje)|U1&7mtCjz9!KwDrb#eaA7 zA#pvtce31n20wIQ_#itG*hgQ!f77wY-Bn5Z61RVSuqVd`8rK7O&x~EpHZ(Yb^k?Wc zoo*ty?B9LQ4p$fs9Xl z*GwJY0g_NlAS2ini4cGe5T1b0!05d&_)*f2_<`i#l5wB3zEj~OMEdhJh}N(%VINrO z(`!M>5J+KPNvyO|Du_9IHtfU&UPF3V+-PdUZ1~Nh;}*0nh8P`CTT(tOHU*^XdcfZi z?i6at7&kpclZ%{%THZkl7btPA&;G-^r4stDaqFwdyMjarFew=UkRYXj# z=?0=Y0C74A7zQ-Emx9U^^&h)HzmUY&UROAnr_c9KGf1bw@G*pwKql~ws=Hrs1Tv%@ zO%$sHitL{*Jq(CKAGT3!#Fef`dV0IJI0H?|D64flQuo3U3Ncb4L8WK9z2^NzKa^cU zLzN4-gX~^WYrg!YkM&;qmxL3wFs;I}N{kZq-b;9D4T57>Dgu*NZf;S^Cw$Y?M-Zch zBO~UW&{oQWWfXyQpofwEQahD{sO?hOWu8gcD44SuC-eq&ov*{4%Yy7mCZ6Wx-WHbE z@36L;N|6G3>$mhZQf@T0h!_SLubq)(fik~bdgvLadLUR(By8jcW+BFGv&W5HDlYcg z+whAyMvX(cMrZ18NzC5ozik_OTpUC+Qq;nW>OX3Bl)@l$GG5->vW;ZeDnu1FScsnI ze+|1g0tyvOZ4l65!&?SG|Kzy#>&hTz+=YB1P8ba}VSHQ20sMiQ#xJrC5#4o5weEs_ ziz54;h7biYMncofA4^^4yde)jq;LWOu3N5$rzZ#8xDbSDCMtRkC+_zT$6j^gyo*KQ z(!F@FDUmxsr`A3IB2rIVA_;LrAAy4OkWL!XAzd)^2uj?#mwz$zHTlns-W~&$oizny zrB6U|^jFyh9HC~QOmfsib_1*zlpsWd6yEnDtt0HnprSz))4$<9NQHJ!wAH0$ttfIl8A1kY06!8og>E1>X>G)7ijVwB(qFE3 z!65$azctpP1j&h0+5f>Xjf=g4f*+EP%rHo5CS@&MWTi=8H_%8!ow_)DSo*vE3=kT3 zLtHrpc{%uNI-SkOaqLQf0ba3{r-fN}+?;``t@qsGH`i|w%HuLgbsAbx=KBF7M|qbvD)09eh?)9{@46WP+x-v8yi?~pk3rGcQ0_%G)jZT@F& z4467(;*8MwgfK>rMi!_p|JQH-k~k&F$udZ+u1ieS=LOJzh{|yHNi{s~T;XqPc6kC# zg9Gjl3fq^-($z<(K(-GIhntj@96?zLpum3!+PS9upH>Ybp}2KEYs3ePRCx6B)rhF^ z8>wb$TuQ^>KR^(53^&Blq)iT9$-SU$J@ewwRp1VK!p;8-89&my@j$!f|G$2l?qi)0 ztgQ4=^ZaF~T0nbD`q73$oBuWHF{MU;kO{Q+R(4<%4B10xyIDLPow z@ssXum5TAipY>ZyD$o-N1R+wEYdD!Xh`Voqa}oQySs(ukNVaCyFr&CC&Z%u9P2jF++v|Ktn=QM97}I%gqT1S3HM$&l~G zAL`BLzF}@!=FOjOdh9r6*khm83>zU9+I|32!qyTSMVSVsNZ;hQj7^J1rY0~tO?nWE4HY7sk-c8$x8EXHzqQfd6{2)I#2 zCS`Tb*w{|20~39pk4`_P>_=0)9XH3BVFNE(eGkkSFKTd}A=$A6h5iTh`wy?#F(mlZ zLm>Bj9A+yyY%<25K_dwzjhE1Ms`@|NF$-HURCBm7ck;BkBd2)9|6MFn(V$3$BafD^#+pJYB4rzyttmA?fqY8Oih{sAV;&%(ATBcS+Ln7^JoLZ zQG|*-aVaDIkx0Xy;Bp`{{A1LL8iyn)|1Ci-@eeFU6b5KuWRw?`7pvnJr9hg->U)#2 zk}9OM2l<#7DEtkl7%&VCACxZXc|wvnCtYaVUvy6;R4%K&1*QSL&cV=xnnv375)n_p z#n}9R8LSLHp;8DlSYC2`jv7Uw6;v0w5YUR*eSOghI!m6x6<>@dE^;f8lzET(Iqt z5G$&a)W~Z$McTDdBJS@hH!xVCV(@=#m`lZ56)1bC7?b+<)XrZ`*&VPwh|UPGS~F-k zmoJ>BcR;OIxCg=$g7z@Th8d-J-yV|;`eZJq*{_GfDhN`NhLwDv3DOTB9#I+KJV5o* z5nPh-uf!q+1hGDG!E6uYcCV^rAcc?a`(yrX*DkRX+2e+}jR`Hg%M?gj>qUb&;b2(i zuT18RPl3ip=6EBp%|a~aL7>N~+I|fw!zgXo3;^0MrDtmLcQ;VtEC#rq>h4I`!AlSz z?!2_>VC$L(Zl7d`fHBGtf6WOAJ;K-}|4$c9p!=STOU#XQd%y0`QJ|uN_%T3bY7c?d zg#s!y7EsNF-h)CpG(ZA*7kM(mWB>Vf1uULQDdZ4j9fiq|BZERA2K1p9XwZIf{3YE$ z*cAy+agF_(<-54AT0ULR|5*Oew^LrEnLGmZ8a}jY_MCcvP-LiTa2gCtV-oaehZson zd5aU7YJ>upEjTKL|1o=*AA_S4f#^Z_|105OcnA2bUfr$V#{YXvoIs7z`i}&1+;>Ls zQ$5xZpyv;$0;w)s$AJZP?YMty533tcu&?~Zd3PsYNdy37HJzch?6k^*I$tNjYDE%- zSdfQj z5z=A5VDM*xT|PpO(Cl@y^a4*|pr5aFLxek8P}}l(5s;Ii;U?RM-k1OGnyL%`n56}} zF%TfJw4KzvC8%Vsxq8sN2G)ycY3`&F@ zY-wavN7hV!E&JuT295#ZJd8sZR{tem0Rx3*kVnd|o(nF*5W8F4iJXB9QESzu(}{<004nSku=x{&W{-gcpFL1J> z;9aPcc0heM2C+QNDaRdU#Ti#;hTnSZPf7qyW*BEy7BG{QyWvxPab)LHmpbMsW;} zZuKJleledtk*h-SjmRNmkTLWHkq826)cv;vxDW}=&56F{-w?`JgH?a>7&-0(j0a;E zAt9y)gTA*ePrM^Fc&K(Lexy9AG7|QnvIk~~bQF3cZ^NRfY|R2nZfAZ)I`g103!o+h zryUsPCG>1XI1Swg0^n!GrZR={Yps_XL7?b?-uuCBgwVe_n@FW(I{|wXBCmz^4>+%Y zRM%qb+X(^(TOA`Ck?4dKNPZ$9ihTLOwjq`iH)adZb(=q=L>q>*_9wC{0b+(C2V&Dv zEFLhdV4136)nQm$;lD4#LvYqX|Ain4Wgx>APz1XFei$p9iU7QbhhCw7zALouK0+qf z8;q_LU59Mw3k(FDKoS=5=VaiGpexW-Vh@+VB7!Wsaf1VM%4LbY6`WAB<7OZy`r{#^ z)uf2}#akFC06c=_k~@r{r}?G>azJMTl6B3^F^cjBfb`Ox9>0qwRh=c=O%60f+xjUo zim!;*-;d@&62do5f;8~eYNP1;58lD_xT+7Wxrsb0q)oGaQidu!1vlNj1=?0fGuk5s zVdN^5uJAM`{8v`hcIT0F+*NpnO5&aG_NX^kfq&B;-_$-U8vu z#I{(j9(Z^#@71Qigk-kc;;!$N=t}{Cwc~!B-&Fdb-UeWPW%;{Icq5>5z~=z^#ZDl{ zs_oXo+r7J5aiH=1D4vp>9a1JeK1XdWF>g2m^j!hs#fp7eij5zNyqqU8bcaAonUv@- z9EXVxX-M@!LAgWpVEk(t)vM21la3+16daCT<{0Fa)V%_)lTuG!YRSxbt|yf5nhuAm;oiGJjq`99>+z==8Iu z;JvRjL#f^6%TuXZ`!G=kY*ZX<^^X(`lb3f(9avRF=HmH&Zsi8o6 zLnqiAm^*uAZKJ>Y00z+t=UT9ntv87H3<5hq={T#X&_#pv%!1}8o3r^^M!3jIQ{=4s zJOsOrflY}4n*g#0&V=zBmde|X7cNZ-;~O3;COKXO`sk1(4b@(N=8>HCvY8KN_*CX0 zB9JY*rod9sRGkO(5IB}L3LxGhqEu#^S=`-CMfpcdOB%q+RqQpku+w-5bhWL`$?pbu z0d~co-#JQ@|Jl?0t;jZi#%sPe9IvryPj#2qm~VK_g+4Z}5}m-KDw;B{_Jj*EAdPp! z8+*ZFp^+H?O&o8)2>G69Amg68o!2FuU0ORzaiZk;AgSMQku9LwSPCe!G1T+AO~implnS;k8Z>~ zK5+{KlH>|fL{}Ego^uF{bL++I(_ZFpY3$C7oA9X^(nCIP)L0;#a_2W0AMRFo9cU0t z5JX{zCAkgIU@~vJW8a@|Pn14`eBpJRIhV7f9T3Ky`%)_`QHz&BLgqXW8>oQ_-r^}j ztX>jMLzUugNr3l3G_j>m3Rnr_KLHz>Z>reV9Ll3j|M25=zC&pwFbzd&q?-=I9sc7` zqQbP{%Dqr>!W|GAwO$&F5ll59ssL(b)lrV~+Q|PO{Gv1Ao#o7kZ;%E0b?|gqRFMNv zF>OjW@3?p|6NbzaCWuS#*O|G8DnOz`dR~r01a1SX?Rviv-kwFZrt27#%}h}F1}_Bw zB4)0088BSP&KpCS^+-?8apTS4tsy~kv{|6D9L<$*{jG-82Mtiww}~O}_1-#Tr9HY; zXXwu~ohPCgx#G>9o}T^Cm?SnjvR%5D?vZBgFP8(u>4U%{mC;wKI5LQ)2;sSb=Fqd+ z!B8ea|2*0FflDn^$ghqj>bV&_LS)UD@U|FRyr!84%)ZzU>aRq=BQC&|0Xj++Q=#K6 z=x>7N05N=Yo5Odxg*m>1H%NKSdmsQt-dV6*3}5c)IO(1U8U)5G?V)b&?p2_Pdko_x zx8}Zr5i&H1XeS8!wl$z7xiR!ZNKc-n=v_1~_cnv=c?DZ<^CxW={K{CYlu0Cx6@Wc3 zakmVUaz*`_EhV8{N!B)lOy(=v$#mfv^@T+rJFsm|IjY)->MT9l=6+HLJF@x24q%xMU#gdK(ncLm9zW z_tGkPKtl7O64)gl-%@XpVgxzQg_)gbSMS_(0#v7~tNo6QD=!eu!@L~oI`DFX&l;HR z=ibseXkNq~A&`e@8XLiDr7nMS<%z0uzL>C9Wbp(K63&4aB!Ee%2s8?3FY-gLLHW8o z)SO3C8s7>hqo3u&;8OEDWsADApqU7R58x~(RlMNz_GsE`sth`9>f6I+bsV^4;B@7m z7KkCab%C-u8h$+iv3tvxK1$u}uBN;uqzD=Z(6PP(qi7(PEi3MS^7&7PX*3d+nYr?w zBS1uidE%df@PmS0cGY-Mn!e3&Vqc*52rSxyWF*SG56Aa=F+o=%DzBN!fo`ufq0=Q zaq%4gAm*qEv^s)OfeDM_PT)h$yL7yakb+i^_VGSUtuRCn&=dr?m&MM=1Ch{YXg?&M zB|MKnL>T5YXhNCAHpx1(LQ=WEL`DRbF10*Vg<2yS8MFn|tuo z56zURr#++8Os@TA-$!Ny&>5g7L5P`G5VyCHM;tXpB~n^+J5O5}nOM-f#ke7W5DpV6srLE7`JmF^F$AQ|i%Ol>T-S7I}DT+T3E| zrn^NT>{LetM;sUMU*XjQMF*}e?QH=Vn*c;80N$sd5$mx)?v^j7_AEZu`@P^Sz(0Nq zx|(a}w#EVrO}S_aU47_O_GOKWT?$RzN(X_>CxjhDjW3jXoa-6a2$C!H$|6gzp*9S$ zW#>^pU0h!>G?%}l@@A=G&4wWS4S3g=!fd`S^kbce-Y${or#9HoB z`Xc3FiruQD=N$YxMzflyf zCxHx*&tGuh+HF7{8{-UK?wOv_dC+{{^sdUsL+AQ#^U-;Odzt6va+#QKF>GC)Dd#{6 z{|xQ()~;uLr5_eU4Gv%;9}52dMIChS<3`n;{#A5UQELLX*0Fl+U~3sgC_}Z1Dt!vH za3lc|L(kul#yA0xpotf?j@+DGl9XpU^M1~DMnSY}Nb5N2doiEO;GKJ|Rp#vIX#Gk! zn^-b*&BgV*^#`QYj@fuV`fZ;J-3ZgF=!+!P!Zbx8(dSuDgVfnR+(%GuPeaH>9fPJv z567h?=C$UqGUeK;vYvOEINX0ZD{)k#nyVAeNI>V!&-8OWhDg)6ueQ42`x17eGCF8m zkYdgPT>~Zf1`>Y4O8pOo-gAdpm{B$ip#!|~!GQ6bUe~kfB@TScBStFh8 zA<+WxNGslOfEKy6Q**n2i}jiHUnw5i|B$U4OS36|zy`In7=P(76Y?rL`d;cPU(6tCIx3N#^rHUr zAeVCE@>6o?v-AiQF7c;IM!b#dD(i5ipjC0VMR}k5^UP-W z#vwkr037tlo&=DMdk@@ojKNLuxb=5Q$%@vC=aqI_4ZJ1Bpc%F8`U4-U4|i1_T$Z9w z*pUwP1Z5rBs|Q?Auk#+~MUR=j@=B;NPtQ7N=KgyMI@u^XZyUM?k~80)y=h;i{G)h7 zh{SUyZTFKP$mc^Od1K8>*6sdQKrkAW4HACrQ-{ehh4Uk~p_ul!gL8%)#Gt{I41NxA z9q`f!-t5426(OT8mIR9+e5V?m zE7f}rMnq7F8it#DgxIJ?c`9Gw?*OvU1|p3&qj{g3e}tko^WP+ z--iePij4970G|9dwv&X;n-XTYKtpq^vqib&u6% zuNUdJKT$qp{0MpI4^&xExORr}4K+JPbNyNqPk`xP@9R6{QU8_T8KR@e%Fg_k+xh+B zzo(H9fml`2T?P*F&t7^eN@&5l>KUZ_k;#ztzfmO3U2V;u;bAA`G$L}U7}F#jzRd;` zMf2l3z3Hj2LodMezWQR6gPQt$laUqhhp|)ALbZ6X+iL{S9?2 zyJ$Ci5-*|&UTB(4K!Lpvs)B{ey@Tb6u6r0o^PQVvMBH^k_DL=%`hjv&qm?fYzpjcL zb$k!u15VOx5wCsD7mclg1cMilenw86#PKVJhq)7Be(U6WOwDJY!ymM$ZGjf|@=LV$ zz_kjLi~*%RIFWjA^MPfBYu{SxjmT~~F9_tJK`ak9PJ2~U#J>9o8kF27YN-RH8PEW- z6=;%zpx=7Ic*9501+j1$C;>s$^gcs$@Z7^WPGjGS{X#DKIXZrrU(O+_t1tSf6)pba zO#>IEYl&9EI=?P8lF|tQE7s&NoqW_J3d$vZrD;RC5V;|?`@k#NRs}y^tD>(WRsmP1 zd=nRIgi#Oup)s4pxXo|AGG7Cho$pDJEZ%AD+mi3&5wJ@WHai)c#iGgo#m zT%6(xJmKb;)3n?MmmLI6!s@uz=de>!d=tK?)CAlE{*34B(AE}Br~%*wDUh=c9TaqV zK#4ysBue-a#WLpwiA8Wxe&yW^PC&YwGfn%r92$5qAP{1>I63R2BntIA$i)xz6SAuN zHKkSeyRIwWd1=EF-;V}su!d_wr?FPAVzEI2oh^(qh&;FPF=b6p3x9w5(nhyN3F01s`BzjUKlP%>yHqeamC3#jIpI*= zrhgXPtH3K(z0{AHpiK;vz#cPP`7rULEYu6;F%h7IpDj1|wzds^s0hT`kH22a%!K#Q zHS!T?mApk$7~KX}ZbO0nV(tb2ePxr1m8vPX-b;;4-^+m`U=CCPE}33SAfLgAkps+M zJ16;hJyG$RSzRm4+_H;KP#8L&bvv2jDU!6k;G7({q%l!) z;b3ERt{9R@kjdczpwkw@}$%!ITnov}oEmxmt`c;1CroS|hJn z+;|h_FDwi|dF`($7<6Epx0syH`@47OK!wu}OK}$|@QYUtgrY`hw5%>>odq_1wVK5i z+o5v^5!ErG!s|N_!34Qz9qb&ZOzSBv-7mkw@Y`}vPH*zocmQ7fW(Tq8!{QmWg;vqx zn;=gRC171~=7!2KXjo(1)<45_&D11!{TIpb_|$H24Ds60@R50!^TqQOPMv536X4vb z;+epD;wWh0j1;}Ok7X|Tud~IQc4mPivAIxqL7#?J$gx)TKvb0BBhVZ8hEy$~;w$Q}KP`cv#tzJKm z=HzH|gW*nsRDlwj?)3}Go{P(Q0vG;M_ z;t4M+&HO~~YA;nZ_d)-omCiw!uO&Xa1f7H2Ec^l52cEtA0W>8}{c5`i?t4Di0amU) zzRI5W3Ve!;Kt$Mrs0X+pMLJS8MEjZWT@0A!XiOn22;p^@JJ@U!qZIjeKXkMIAV*1c z0XRIbUgq|wBPi>9n)_;X&=uZq*vJtW%1Rzm2$`1*qsH}3qt4uh^>ygC=KS&H2|8B4 z@*InnPjjEqJPoM%Za(}f9uEHO>v&cR6({K9KQiK-!8IW6s6?sY;4n%e|f#o$%Ier_3FPJb2q9A zEGnWg=9?&*54^JWFT$Egwt$|zcxDMJD41<8KR1jq(14zCJuX|L`c6Y@$;RrS87CFS zS)r&Xa={EwIbdLcRLiMi4ndUa~O32J#I2OQ)cg> z#zx6#NGHS&s7JDR7af%qL96)-{vK}&oU+mJhxda^EFqkgsT(q@7)m7x(`QwbdyERW zkt2aZgL5v7P(>FR)}zJ(jt$~cS4;nb`jH_waB4Wf^5I-&gmDXD*%k?@MUCnzNR)6v zEY1&3Ps?jUAt;y;*Enxz7B=^*Pj+m8rhiws`K~awmHd%oig1l}+$I*7YK2{729pa} zsoY@@1N26kpJH28beB^{{=&qubN!YHN<>IEu_f#7t~Y*obE2JI*}xb zfR_nHN_|j$Y2{Y=7pc|^UMo=4v>W2W%Q{7lYpT`#sDw8ljbv4lZx zN%w~QMgZd|L~yS~Nu%a$Xgpn8U>bPGxL?DMJ2I?F-&=;kMu7vCr|zN(sQ)$!l}Q)m z+QJAfEAiTTmoEhhKk?7Qp#;8|S{OwxNEAe)u-K@OE08i$_o`xBKF28Hqn5&{gXwC|2O8Y7G7Td?4~KRdJY^nE)(C z?{tv6s%tUoj7wLpp1lKgGWED-JGs@GxmeVDF8Y+rx3>wm4C4;|ww%2SkkyIDr``4_ zfSWSIck}YP$?0FXoi-U9zZav}CZzP#AW9C!jfljfr;~(oM>4l^Y_SeUypD@W91<$T zTACKm{wV)yazXuzp`hfI-@lON~^R)d>Y-1{Qkd{u*2Hh_?potEiJ% zBQ#4G2_wyE!o)oOem~MhLY*LaRwwGX(BDFeL1A>%HUdI&dqW5Pp{J!=tNhS2S|~}s z2|z_+EbjYvFUYhyerR)r3U`AGw>+R_{@`=K3ozIR#2ez&HZbVav>*`aG4Sh0Y$txo zZeG56R)DqKBq+iQ);Z@g`7p10?sK$o`#t4%1G(Q->ij&);i)L+#A^>A1^h`!#Uqt^ zfK`nZx%^NGX$TAnlou8ifBFXcB8-)A){qRK>xF2N9}5Gs`Pp}gslqW3{yTgg%?iC= zzjxRAbr=Nmhe)vv!($0Kwj{uYWG*Kh5QsJtftwEo6pI^~(f)vjkI=bq{%&?V*MmHu zW!6=iJ_vfYmjz`p+?wVB!8u25iBO=W<}QH)t)r08X8>K*?5XEcveH;*IL%n#o^%1`|?|-8pMA_&ScK_@Q}4GaTzm zGA(si>pS(i6Y0SC;G1o$-x!oY>vemOG8>}|<1~hCIsoXNOw;cF3T=b>__{>kF7Elv za|Yg(+~|U7&l418!G*A36rutFWUzRStjDKE=WbkU{CMRe?{ za_Fmk%~uFjjfpdHfLEb2fS^}eU?vpryG5?ahs(m`O~_gN1ybK6S-4^8?$C#P3vjGE zbo~8W?EEL6qLw{PofGn_IS}-h5N7@3eh)mIL}xjtaF0mfgtCEnnDs`cLI7+(oR13zm@(gs zZY3}oOPJCVKCr13xFx0kGP$$9NB?aHG@I)?Qv8mJVI`Lw3>SO;@;y}fJI}1_fJp%4 zp#0p`|6~h(AM2ln=Cu0C&Y-ZNp$TkP!o=yH&bg&+!E8X+1brSK5YsX#Qm)jhE{KEZ z@PY$rs{FcwTob@XRD)j2p&aAIzcKd$oJ3FQOcKDLF_6tX`@$PY{b@^W6mpQTi<{yX zN9PJiB;B)rH67a>@tNkxP7y0Q_BWrUu5Q1#S2UZUXujBnim>%5%f>OpKoWMPtkar zlGh*aZ5Pclr8c%CxxjCC6fPCrk3Dd=*mnwKWhCTh*E4pgWN?okZk(RB*hkA8_POuA z{-mJpl`B`6aQSI?hrXYbKD97T^ygRYL`UoU)Sj_reuvY-;xg@Y@{$8>21g2a?C@Oe z_3L@$y`$eMSF3%L=Vftzwo>i-*IgK?634>q3{zZg%`EIXnHHQmVX@o`_;!=51-9*l7 zx0WgPP#ea5qk?@4^C%<*+Dzc%a?>_`vYocPG8v4eSY%5Kn|9j~;b8L0Nn^2SBQuXz zY}{r8KmFN#L-!7T=wpo%P1_KIuWCv~U)iTWh_utm`)!I{x%4gY``9~JNM zerGk1pUqq|tCrGG zkfT14B%D0D!%N`qm1m|?-PM&}!sW}kI>u(zMuIG-rj0q)mWVoPmHK|Cu`PSfT~lfq z{-mZIls24Cnq?bhV^rx%hxVwk!~XT_k^K)ImQ=ng+nd27eurUSt^b{T)Au3iH}~&9 zTe}V8sJ>9HG?I63^^@(I?UA4}Vus3Pcsy#K)$RD@_s=+06uMQ^yezeL)LO6R$?@~=CcvEK2nKdbzPPkf^5P<{`a&7RkU5nj9U!_7bKu7DR7 zz_qeZElnder;V}P?((f(jgy=wj$d@aI3r5!16la>M zu8K|8aa~(UQayG0^!4;~8#c$e=36pf8wx#{CNJ8>SxK*S-xF~fzBj9=c%4TPT0OQ# z!679&evyYQJ1LjH)N)aA_j#@SDl3JTFLMbVi%C5NQ}p?hOic;2p&8B(^)^X>kvavt zJ)Yb-^M%t%_UWq_npzTeCR5Vt$QS$a;`Lwg5EZ9Ay^DwpyKQ%_0^~9MFo=YEGUvgMvE-f{&FwW2K?oxKU_RLf*!B)93 zquDy#=Z5?>p*A7go)V1g_ z2w<`QI3i~KJ$--52iLC?^K>84B{7wBvuUb#N&MhigWF3?y&y=qQ~ zM-Xp+hiIbtQ$Gsyw0@^l*sVu$S6b;m@rR>ynpl06nKq$StkSc&Mb+=tUon;HP*aW{ui2Ly6T|we4rtO(x9#G| zF&$gaI^+qAxi) zQkLfaxb~tW`&{4sd82P8`pHlhCSIs!4j8kd#C!V?SIKEU>?5^NIt-@n1S+mpb&C5FQHck`qmWPFu;NAHJ-=C|m zRdv>=dSAFSkb4(D7BDs0+T*_F;tZcrbmsH5UE4{JQrhY3HY9Ar-WD4?Q@+yjJkTdkK#ayoXw4}nY`j}21}$Lz2NUY(UKG>ia#BBHteSUuOW$znvbtf)Qf9dIrK44 zv+tQxtD5i?#3$MlE5-^RBJgGKad9p}1e*?ppgp&fE3VPJ46?u-S{WxCTwSd`FVQgQ zBWmg)xzV)u74jP!NUYrx&kcHPJ>ju(baw%S5 zZ0BXM)5hiXll4XO(j`LKo%@-Y`4Scgm*;Zkgm;Qe6&%cklpPM&?A8yT?Nci*2|Zu3 zE)WGA)6T=E5%1`Pwv6ZjS&V z@E!H)>$Hu^r#fqwh%e5&FYG1c?LKE}e6>XA_)*(rjkgo(4DFdybX!2-JF52ZplfVv ziks|~&^J?V6ak#jFv;IrI2&}HNvOZ`7N}R<&AvJJsRdYCl#gF(m@UmMo572RY+vmm zDh{)PvQUA~AkDy(EjL!@(tX6kxR1(`JLrZc%bs6dg>EJiL<{l`A4yvZw4V@t;C=EW z{*0(i`osZGMYu?tORsfXZIsmH9ZSEXgMPGNg#U7g6n6L+UD#@!G8JuSZX8D?zFgV z)G(~(hsXAdpa+t_Dx^pdBNV%BzZiQudD>}~JK;>hjdfle+2gj>LQ1l9->4_=&3WvM zliS7s!-4~LLk;~iKrDsjOtuF;M*S`uprgpg+~j|QmoHa~+Z+phH|)FkKCZ4Qh5_NU zq()$@HAQp19oj?il=^sk50^_~Wr^b8r+@t6=r-Da5P<+DM#g5J-Y|N1*XHmF%Frin z`i|-h+&WFq_69Y#w?@~Te4Y8|Ug+&X&NG67SB0K?3Gh5}tFgJSKjf>qFVWQ!`jB>s z47n*Q;ES!v68Y z5r#$>-R?10=$T=&6>sb!#HUuPHM?(q;MU!ow$K?d=F)f*Il5c`D0ekgXD)YgzTVT= zjsLA(Woh#9$@(Qvyn&OLjLBF|)Z@pO9&+DJ&b8^%hzt`Lsh{j#lgn1BsYP&mLu`00 zz=Aa8@}<`^pH$H4R1XinuHbYTq|HtC1-Ax6WcW1u2~k`5gu#@@U<}#q{Ao_G@@}_q9)H? zPwyrhe^%$lT%qa7r)f*}EH{;toKAHNNvyo(5}i2&_>bU4t`0aFwW?RB7%pP!?Y2nI z)>v@Sy+Xz)2V@ZeWC>#Ndf8Aw_7e`)oyDdbo?AB9`pboklyP2!Sb2`m)9xFUO%q*@ z2n~rg*Ia?d=}(>&Q$WwH&3`7)*}Owp~;d=Z4zt=9jr95;k!J(+I(~PDa+& zY*KGev(w0I@Yq46K!vf9_!8T>ebghAk}CncSw@!kZLjaRJc+dwCZ<}FN{d&-IJgC`R@;0vkKZF-QINwZuJO5@5w#Cen!2;K zT9u;g`YmfEjwulbisupi7RoU*n7&uyO_1an&SB!@beO%5kOk;tx zxgx-lAQ=r}VYf7&FrbxRv@+VWh6a3kU)gI`Os#hvO!|LY5U&IG$U{ZNj>KQ(&lc5S zCJuEd(mI}sBRra1n(;Fuj{mGVTvBr@xcD};Vd(FfeJek@Hz&9^Hsd1$s|Bjag4qY@MX~AZc=$OJCcFSR~W@3`D`;VqPoy!GrZN z3VYSs_~Q8+1_tIByOkuf_-*p9st1}>y}mEo)xS}6Yj}VJ^+{_*#S5AvA}0S>Ff)1&w(IMz%Z-e{q6M*qw8fb6>!_GV2et*-2i{bA4f97W?=gTVJmdvb|t3DY%+J_DXifsc4ybBV7{i>&awX*|$q0IMT#>A}PV& zaQHd;GOFv8e&G#vrTn)>?B_zN%N%4dR9C?^bg-eh2EiacFK3)Bjp42FuuXb5%CasL zPKOwBEQBMNw%~`=1?7}Qpo~)hOJRM1&pW++)gOG74Dy&hR=O5BB!N}B$2G337<7zm zQ%2$jf;%!0R3x2F!KV>Y$|d|xb=*d&@dX8j?j6Hh490a>$AEB*j@Zv#dz#Ol*F+83*U=z=Ak=WBM6 zmKBbdWZlYF0nhAL83v;oC%vO&U&e!(?dRl`1n`S3&6Rw%rS?#jqHZ@`v0GaxoP)E= zLc%6@IfNNw@E1Ij?5;Dy6v7g9hz?A&J5(_0OKro{$ip3~s;uF`n~W(Xf-*$Uzy$9+ z9RNT0DpElOsBcLphV|xu$fG*ww@>u(b%&9_oEGN#2EXY+`6hDph5ioc2nNM#Y~X35 zVlwGBAaoXz;vjMuDv9J8_-XJWvOCGd#8?QgnpV>SsugV+D}(|5rDKV))okskbnwjS zo({u;Nqbq-{#92bx2)0-N7hd^S=xsFq^C_PiGsBs)(44#<|`gdXD?ik2Ic0HKF5&X z>|R9JdLwqh-|+P{P~vs&&O|CN-stiUkA|C|DJ>i_c(eL9Oq>d{k)|lgFflV$cCf_p zW+fO!0BHjKr6bkkuu*{7S$@2c)DBDuY4`3;MOAp(zMmnl1Ku-Qdc%IcKw0sUec!g! zOw@{&sE-kbu1||QC`%HCx&Cp(x4wdjiK+3ijI0lWM*{}u)8UZWF%ZD9KO!#$!8P{q zOx#p6l0)^C!26@p7`pB7n0_u{fK-W+?p0fFgdxAt&fK?e_m>l_$nWzGcAu-4a2AR5Zy=AP`T~!KQ-Sz^u>zVVjhZwXAH? z1N~@T)CT89kR(}SDkshsy5Yy*yaZG0vtih;-kxP(!;$w3P(vcDm^aWrB>MNqok*It z|4%uX_9Wk=OI2fqv58#*ct&(c?f)Zo#&hzpW_xp2hoQImX^r@b3M%x>I1iv>@kedL ze1A#s*j`E6y$9nFat_>qcc^4fdyVE6s=uPTZ&m@XM0LA%sGWtmF)Q!@+OwlI1J=fC z{ZNB67I`#cHL4Pw;?B;0YKcE8Nx7m&Z~Nf}r~)nN@puBt0E{a8Q^4Kj%GN00hFyP; zG#BQV$JV%N-c+iEoS9 zm1GQ8lc&$uSG5u^O%4tOabu0hZ}8#&OTb~&!v;yf0SHIG`S;<+$pOg%zK~ge=)8Tx zhHE@|!yd71U77uWCcu4W)CXAtcN(b~p4$H*1JgJEmkhiW6$z!_$4IGL9lpuFBFP1* zrO+>Y3}?7_yBRzFc2C($$$Egj$u+ereZRS>+*>W~qM~ikks6}Vo68*f<9UYH;w6m8 z`kmJ7!Gp_`G%NymkfVUG!XHR9_L48p-mQ(}i-N4%o9Y4UBex&@6V#d)AG9-+jAs(ATZ;>PCj-OB)D6)2U88o`iK&Z_&;JUm~^DL zN<{wqyy;H-kMrg^>T}L~baH|ZFL*Eo6&j+izT|-p`_p|emU_qj`<%(J5>p*opD8N% z0YJdJ>M}J!Oo{p5I8l3P-$*m10=f=*TK^Rq2YtJryi3oa2;AePb*`4L%$|SV@$al% z&;uBA2%PGRk-lFcq7CH`NUyV30QCD1Ub9c%OjjX7OhyPPT@PI{Vy%C}t9q;GCwZGt zvWERV^p(W$+yztbYZv_w5Gtjvk780DSMn20S@a<&iVkyU0&w-q)fc{rAp8336ISmg zr(%S_KP@#>f*U~}&2JvtF(sbUMA4fo-4bL-`BZ>>!_=Gp^EzWL{x5}oRgrr8zqL7( zdP6{S!f+UZ2;ZKd?Vvo!#MJ9(Z3M;`?jd?V3|$r(_31q(y4--UzBey;LC;psATvdU z9FhO;FEGZW$0|S^2j3R=!S0o}H+kpK7yTnL=b;O*0VC!O2JrfI=u-oJNFvx&r0+3L zPo54(+s}_Pbuhc!p>g{}&}%osqgLxw?ZQH}a;+Qk-fFE)-c~pl$n?}_#@;w|2BdMZ zBlI@7rJ`=v>NwaRTtkwPI2N^h2usf}A@E#+x2qGdy-j z2{s9PyRx!Z-)Kx}o)5G*9ojIor|p+@u1Zm?dKzgoTLM`t7SWZ*t!bJMMHN6+K9{~? z1-%^#c**ux!!3QYd(Mf0XLa)U`J@ zdnvKrki%HMx9k8m@q<=+mOthCwZC%#=1VWZ^CG#`3K6ixmmuH?vwKN`pa=vJ~mU&!(S%*d+71$?9iCYwI47@ucU`W)OyP^ zc)yp9!Fg{+a+{x7n3|7GJJloxRDX=k`brrN$(W0+&j2%;OpFeY9o9Ie7$O+dxV!KW zxUfurUxgYTvXQ#X8A4rQY>A4BI6C+Pez8CUeW6JA+=d1BK^eR85r2H!Ny{drV+d;K*QLu>uYPj9kFMcj-?E!s&* zpY}8$0_3cW%j+kh#bCbgA>)9!#jageR8psw-n`F>*6 z!@F6^J2=er_JnGHi%2<8XwYQ3L$TIK;aoU)exHX*BPIG50QB(V?%{)9O|8Vl=s>d8 zQ?NK>x?KvCHyH?b7nyE-^W5j~J7;Xn;wj`A?V^9Vn(LuWSp|$e>umWGP%(zx?B)#Q zHG(<7Lx?DDd+2?VqvmS_>#t7`o`DeAov!rfyu_e^2dB?1><S3>79AEvQnetdL(f75_EsMq5DH^%*5aF|5H#;%nCFx!|)DCw${HkoB zh;zXua~1}E9-dG5L7P=Pk>py{O(`t{FAU(J1-YuN;Fv)K0x7WX4ZFMq-YA!1==M=i zP-LbJe)f&Hm{RAWm{&NbO=|q^Ishpy*Uj!x858p{Mw^K_oLik^kLXh8qGQ6LkMWA9 ziXpKl@^kv}VMQFv*GQBPg~h?^EB!R=pKo_)wT8qDupW+;FLXvM3=~c#@1IJZfcWUm z2yL|tbJLd)iwOC$^rYcUSm9-blp+}_5pJ#9$cIO!<>FaG_uC&PnlZ9}f_W`$ri?bT zzdT!?T?jhcI{1zn%vDCS?7T`D{i{$uL+KS1tA%8YMu!N#Idu;)l^m>HSsvtCGm{rc za2b<^*r_|wUUBTosI()U!}9~dt(QHCY03ze2Xky)5DxeY=Cq*)1{DK+zod8K;0y~_ zQ?N&r&2ov5|BWy=X^9+_fw)pl&dWjkv;xjYEJ>AH`60mqa*iD{ku8)oV@cTcHTTzt zK;$+?BUsv%-6|N&d&hkxt<@^GxZqX~N3R%f|QZ zj>;uUJaW7~Dy^7ip14<-I+8DiAz^Rty^nDS>0>s=W|X}<{K#=*GE$;&me^x` zHm?(VUU_EdQkT=j%kHm#lQuwCC=JgX(4OWw6c^8!rZkU(e#hHu6OKCT9HHJuB_TVi z!fgMj9M0R>VMs^c%(gj6x8P0!c6A9SZxnH;LZAU?29oPD%=GRP zbvJ?*5Lp6bmGhCq$gkcQ2pR9Nk(Fgmb68sZsR+2m$2)jTdUE(wN5VD&9HqNiM!)U*iLH>3qIXZ3&p1$4hQD~osZKC6F+W}j;H^ViVZKdT&v#kp_6d4vU=FB8O{58% zxlTY9IfGkLX=ZM4q9F%bVA3vi+vr5k5r2ct( z`;o$kwe5?!`}XZyE$9(V(@akppTr3TnKi`q$u0mxraJ!HWJJ|zx6QBc-Wn+JkCM<= zYD4JXbi#Fb@{$~$r#rTNLAz(deypGEXX=vuogVWjuuhF6_nB%3qTt;deDlBi!xo0H zD+;(F2c0~zXlh|dp+{3+89N|g=nEolGIlWy-9rh#eA^z5V}#0R2kZ6WsX$XFi!6hB z`w;?e`4deGYm4vF5`~%d{P}&RQ7{{l7l(rRoAWo$e*(gk#8@V}+hCsPH9A~ZUe}+o zyka3Wd)0AdB;N#GG!J8jioAyBAM7OTq9G8^__c9Rsd)Ygf(eovYf7%OkQo!3blCip zFd$8~k04^#;+!sXPbpyC{7lyPc5ho3b&b2mGr@Z z8})3q?>4Wv3#W{7Aoun4x4%PEGCW55otHJ?e=t-hArq0fUlHLSy)ISsN1Bblr9G|NX&VI#qtRW&SDq8gCmXGv|oU7 ziA~prwmzG4kgiaYDEc|HI{uT}ZT)zMTPl&ol+oezeGq(<-7YscBK9T~#c{V`Jgl^b z$G=2u=fT5=Q_icSEcYv&^-+fRa4{s;n)XU~Qoyz+G<>Epx#(u!NrY*^jer0giiheW zCvLYk?E_lJywiB0{|8ywLVRVT738rYXMPG;a$RCEgEi>GtL?arTO|_A$~K+ll3%(m zKJA@3=f^Z<1*uglT`%CAFp3u<5U?{o16fxL#|fZT^V&rN>jJNA`Q>XMBTf>s&Yi?l zPUDU6_n(AqpnTD_dASWT6Aq;O1i|B7-8}O^tk55o26cln<#)vH#z`4j81w016Z>}K zOd+?iqko?~T0YyxxA>plo|G3HyVj@Ju#_OZ@@$GIHWY3r1U?j&xE*Z|5Gz8 z1x4za)0IwN6@VV+>++rM*Ms5bPNfXY1>i~J#}=vv`b24;D8UvMt~aTj=!#73v|D+V zm+#U7X?FmTw63%A@SBIz)Wq%@bUw22B9wSeD4E&20>j~Zj%m`%VK7xXSa9Y>GjT;L zH8+g7Sbw}r9D(sJn?xA0h6rSMeDsXEzhpM zb|YQ}=7rH+#kl&=;aPFdc==4GN++Z669Z`)y*AE6p2L%q0y@~0F)UxgS|?*sg1Y1k zsGntOHrg6v87-9qBzd%Z3|nrL|Bt6m__1?CBJ;y}cl-;Oc@|I->E^_(2}zZu1|O{@ zo6~1eW^5@Fl;?`pnIrKx(?NbgWN14tI+Vwzp4qmsFZ{t-4W4spo(;CCU0UI`tD+D; zNeJ{0h=#ZmZmiIah?h^__2f!*u04ym`)T&D)v1MR*6(JV*TqOZgmA^(3q)6rE{TA7 zpX}3Yr-zb=c*zZZIyf!H>mHPg_xy6@E{KR!*_&W66%*4-x8`zb0uC{>E7DH$f`Q%) zEd|olj$+RptWOCe=fS`YEoI7QhJYZtEBW7j6M6W}(OoGtW9d?wUmSZg3TUL2HOsH` z+ieMt#2+AQMI*UyX{ybh$p(G=E3@n!3;eiJa8PkNa z73-g#)H{ZR0Jpipc}dOoZ4Gk=4XcF6gZ;$-$4mMa6rB{ zKgl@IzW_93xQE{Ry`fBFu2KY?)x(U2h4!QKKfT;1e*9SWDDcYi-3unN79Wpqdxv{vdScIDMn%r&c z`%jJ?Ynz9VEsyr{AlL^U9~3$XrztF6`~(3To9i@BzSoIUdJ_TdBlii!iT^ye>brTh zp5=Vw+FIMDTEJNFb*jK4#t&tLo6=s}&vk~c)bKCOKg<}yDicPLh8V_tEy&<(Mg&Cv zyifk9=hInod=Bi?Xa2l&2VkE>E$|6I90e-kDS?6g@Bsosp9Fj7xD)5JzqC0PZmN3< z2AMr_?cS8JqupqqnX4gfx&uQ1H;pG=)64PrE*bN zEOQ-y0uS~i0E=h-G|WHX4ZyfWgKkw%+8Y}y(f5^N44@P6vVA$dbjHW`T+=>fQ+Xek zc?^ET|2biB+b-~i$Y`29`STYD-v(a|p;2gLt$zcqIeu3<`MA;{~X+>6P0r2x}Co>8Mx)=as_p? zhnqvL!nsEdD(&ta)*wZNYSfa>a#!Cq`H#TKf&KfVKRju{w?U@HsgQrNG!?j?_=J1R zuUc2M_Wt-WP-z`Xv(c{9(|o==vAN7e@C1kukruoDd{KpMPlaikPG+a$#)1%*PgmSD zA0Pxq?(!v-LA$9>K)@=R>zW#?C(VWd8%t;xqkKYFOD2Bo^`VbdEG!RU8B)R3x(~bG zpj3`I3!$fGM`wLT?!z_uFaejvw!f;Ly(UOr(_b_T0uZ~ZH3c4BQqQU^@L;SQs7ip~ zbf+j=vu7iSMAbINkg7dklLEw4-)7wO-Tr;+4vIaF3!;c$&y(u!AvFSga!4TK8EXw8 zsMVLoodNg9qWcJ>9lmRU!Uir1e63sRM7t*6#ENi>LAPVlZRE!33zl~#>;*qwls&ER z(WTIt-&e49(a7B!9!Mim+Z~n9m67#jPUGa{cyu44!X^B#Kco1&R8FPl+Zc$POAy5) zj|zkvw}uvIb#NW?z9JjKyhIBy5zq&q;nCq4io)z95qA0@PKX3wLXcCaJ8}NyM1sUe zfQkt0K)gYJ+sHjc5k&AxXKf@*KNTQqa`DFE5==#oc~{QbvM|Pqf+2IOz*|)v$j>LU z^pa!4RiBgqp={Vuq%MZ04B9WmDI+K2%Z29VKG94YHMAV&!CNmV6;oguD_EoGHXpEB~@M5t^hE#QJ;5Q{M?my+aq;BpRF#- zW=X1OBQCy<)0s~j$3BbXr=qL}H}=_9{E=oW{um-EQkBk>vZ@zh{pCM(hL63I26N*GI5A;AJ-A!5iv2_fBR& z0V6uCmC|~k(*){KC4;$xtaMfND|19s({@m-!G3)?#W( zCg*k%4*I)y1B3Mm1OzoInWC%t_&-A453jDMN+AQL?2;*j(S6ga5(^`S@4|R{(ePyoc`x7w6fU=%_g>2ZU(O@-9LE?q>+=k{ zYyP{bIUOfQUU{Sv!R!T3o8HJ4>VLo+z@S{x!_JJ|@d#FU?%1*KRvwCos34gAQ*r;j z-}&0&SDf>ZKwg7m|gn&Via6NfUx>eusyQrcA8pn)SO(qCKLJL*(@}$-`Vr}7jyu|6C*K8)eXKX6_c5CzT9#r{j%CsGaN-yAwnE)t)v`R~xtK!Cv zY|g;=_}EjB+z+rgjeMes6p4>*!MXa8YyzCtHCWuW{pCGt*KFHVc!0)TFQFaX_hCB< z1VZ(})Zk&zEg;Amnbsk#18MKd`)`?Qdi4-53^+}>~i2_&qy$1O{+!IHZJLZZLimbZjJzF38Qza52BI_Se=?7@t{Cv z7PEeA*)vEr!r@>r#b1I9hKFbPAfY?=aw1JUF_vlXK_)q7T32y-ZKDrk{y@+cHg_pd znVRSv%I0o{C|J;W?8U)cT4jdNn#138%?B09*u?y_JO3|JA%dxcy>@OsD|9To^{xF* ze=Ef8LQ6InZ^DhF3peNjE?wZn3HZbJ7T_+822H_qD!)U?tubS)$uMw_t{paOw79#& z)H@*P;29xPGX0DP(hnm(Fzu1K0a7g#fN8x!E9tD&>sISCn(+}?QhG?u1nXRbpNjP0 zLqm}Wuw#qO$tkF&`-c;^VTwRW4yzSA^)Q|}$K4D>)~#EmsUiqC`Rw%@A!*@f=iYxT zUEs7!!6JH>2Rk7C%<5j-@ZUdnK3oeaqQrQZ10sf1G3x`&8ui6xp#(D(m^ncU;G|9{ zz5mQaiQ&=zQHD2C=)Ryo{(IyqisQ2Bq^XJ8PYU4&^I|}(%TWRhbI*s&N(o#Mm2l3M zDHXu5ySA`MPPDyH)lro{4sdiBZYCeFCwcBgMjySymdu9-`}H1t4)p8tRrm!{ej?Pd z-BF#0_}v8r0$<1lgCqOrv5+!T(J0`&F{mj)3y%*+n?&#ZukyHFA-3=k% z53sZPX4r{aT?NATfw>|@2SpHoBBsgdb}70?xdz5-^e6Gb<$NDO9ix}{oSf4Cf!o#| zh`A5TUcGoh=5G1FE95Ut-{$iTBCda%S%BPCnvwd555s!BEbDeZa;L!FB1Tln1!xD2 zcxhw@k?JpFuDJmJfP2upTQULY*63Z-`~GqG=WBz1L#u<&^#h>;j@M}rYY&LS12d2T z?ZBOSi#Wq@CSva)Je5+ZY2Y0p)^!a?ir*1E5=eYBWN&=uer);mDSM+I6qu()|1vH> z;=$CY%e|aIC$K!dk2(R&HSn_nqW_p$c6Py3-I!d+FD8F6NkjHM5U~`1cBx0MlcBSc zl}*TKE8rU1(lc8uozw-|(MRMGtur$4aP&sMqRq~6S$j8nP{26jt&@&|ljc*neEBJB z$LBi6v5~?={GcF`ia4gNl^@z#c}T&4@O4Fb9#jTa1*0z!5y)pf;>}>%VqLXKe}o<8_1;3sU&opVY>%U$<~W(QBNbT%s$(N6{z(c!QTbOnQtAwmZxIG4;7q5A=L;FK`$H^z=T z+Gk+e0j#Rzz_i0(p~Y7pf)I;ranw8-TA+3lM&RPr0l0QH-a?|Kr}5k1oK^B(WIe)! zWXTWT{+|yA7~@-O^bfUJg48^SEHCK0WQcl@&oK7y)%yWG8LYlfItc#2xq8Ht!VnVg zYbMO(fiCcr^(8Yj^|rp_=hh4SMy#;^ca3+xfCq7V5v<yBlj6$K%B1L4DktBp{viV(~@10KP)bl*Q{?Y54-1qnU8Si;r*V~*9o!3P9OFQU6 zA*-}=<^_ONy_x97!i`={V)z~>iCRsk8HfMt9(&K9TB8Ow_PqcGfg#DS2;)Kwm)8=G zs-(9%K+$R*NTUi!G)x{v>QtCdhr)7^ut}l+)EhwxCGZ#RBe!llXL-X2#^0!F`rzfr z$K<9~Ywz6xu#)$~wosS+{lNZ_5{<#{Zt;pgsavctow@+Vr@uzC{pS{UGmV8gHFw<- zMW<>BS&{1B+}{1c*V3M+{FFQ5$tV-VB}xHa4SZmyH-=wq{O9(r*!`Q^n?S&{3PHcw z+0Bvv+1YL{dbl7(2e1FV3T!eS{%5SxQ~*AkEJVmESbE(Bv5l*E)~t0k&0E#QNRu~- zBwwNSn|r&N`#<-VjSM(}+oT&Hk-|y}Z0qY|1^07X+M^o8+D8c?fS<^}iy|L-_Nd7Y za8~?Nt|$CKaX)Y(Hy>#n5esbx_k!=vH`FX?LQ2$$WE7yE6+>9YeR`S}{xD>Fu_EM* zuOU?liWflKIY80m_MeumaAWTqd+I0ETLQPRk_U9Al(LLPm=Pl+bZYS5#V!1kU1tS}_wHy4%B?5B&m zr*4xwu|$&e0OAW}G;{w*PrLO}IK3wFEeFj&zpke&Fu(+k3eaiVM}tSRL=i{IAM5?q z=NfE#_s}B%(ON*t?M-6eqZ?NGnYW{NnP;6neR{X^ffQkfe=PDtbQ(4zhA{QVn?Wq$ z1Lga~z&H%na3yOX6qgw9=nsHA0RhoOw*BmRF)@Q2k4`eZh~`y7$C9!Mx|;gHbor+s z%2HOGj+nG-26h)E1h1Mrb9r+MtNSpGA6EcIQjW5(KWgVxl?i9c_)=}cY5+s<22)Pg zmHrB;^jGk){qMD%IQW}uYdixlFlv6T6oJF$r@z^1ws_oLqr!T5E?RCsQKgDA5Toxk zjoNDk`CmOR(>H3b7e5R7$1Za{+9-Ad{&vcLU;NFchg<;LHg%2lRY2aW69I9+n#sT8 zu$JGv*(02sAsQ2n9@H(O;##a3Z4{BZgT)s9_53lwVRM?avz1zjlmu=Uc~h?ti9O z*NCQc6iUPvIZL>{psGLwXoWSkb)bkrVdxp>WE|MFtX)CF)ht!ih})if$4$W*$`S;0 z9XAh)?!B1mxG-}6FdNHdj@RP&GdcXrY%tFKL?EO%n3N;>R<)v{=o-Q0c_ov0+o;9E z$Rbf%+xayRXfWqH;@@euvGz%gd1dN{lT%kQTcw!pS8Ite6hb6Kr>Z{gJ9EmuhCW=dLXV-u!Mf2YvhK ztRL?iIE_ee?~t;^_dpR6O3xia85Jl1h)9(K`(|sq^7i{|vme&ei%8Kw7m?22vl(X> zdyvk-QG&#ehcI;2+z&rJr}jr<*D>_LEMmRbZjjtV^WKHBSBO3Na9}J2>Tc9O%F5^Y zeh11=8Enxt`P5nOkIFcna_hXk!6|F&h%u#wOrziKJwM+Ba*Rs-Px<+OZs*Dy>9^%P za9Srt&Yp`&6(JgYH?H&*6EboQqBbz*@R-SY?m+>?X>bHcTOeAJ65#5x_3JCl z+cMpb6mC-A+uVNP^QyagX*y^HE4PKiwHbA?fbb3!ky-1-7duZ!#6ol=ujPB|@^WLV z+#m zuGTA+5QG@@orP|{DR6k70)1)x%fN$x6!br;(+5CY{%b~n77NHX7mVs&6pY(1JCn(D z@)}gww$8k>q$Rl%%a>cYy$LlbE^neAfZ$n0AX{z%>2%zf#%rH4+@N-x#ze3UQUrG62Gx0~I6`ur4vSWH|+n!QuQf?rJh22PB6!=a;BNclfc z?Db>wzfrv<*qMAw1r9Vcw7R~Q03Me&bht_bPY5(s0*dT*kFKl&9;2M-B^@{M0U zERW_-jka!F>d>D2hG~wl0vqb5@EY;lM}*@c^Z)px%{piN@tmXs6FFUDLG@yJIDr%> zvDV)^tHJA421I*mGP_SL5054d7@k(nz`4vX$6l@cc?(^VvRAOSfyXK zx*-UU%JhkzIewao(|;7Z7EMD-EoArGCOzzyO1^QD{^R>brGrquv0ImYU8?MhNSd<@ zgpB$D#5$|8G2{};Mp70bvb=RxTXubS`-X*a=8r|0;RKH=3*V}Mdh zHfs^2aQHgg!XDC#wTzH6&ZgUN360DDM8Z!Al3GCfyqP~`zrC~8XtQKG77=4XkZpOR zxT1Cm89SN>a6S{2tn)IWK<4S%rx`$@M00Y!W%l&Im$`Jd&dsmZn1sUs%(Jw3S>y}2 zrwtTT*O2?E0|IwihzsmLOh&~yJK5rn@`gmCzqi=A~kVT@w`d?EvuSbx}2lhhT%N-LQ3mnmW4KLZ(0yazkaIq|p52zX$n~E@zB`7#~)c{(5>1u zN+4rO53V&TpL52lTV}_$k}eYIbF(Fvz`6sbzz-q;ey_ECea_lehp2GVA}1b??FwU$ z0xte|xPwwaE=iN|w>Q>F)r_wFm3myCzr!n81N5lwHvkd}n)tLX?pL$8SJp}Ng zktn&L{<_Q($O`#?sLQB2`k43>SeMXEqu>5$?P*hhepLcslB<-81T04eJT-#Xbg*^d>W(}wmUi#_)cg8a>Uo|xrtX2q#t zq_`S1rZrR94HA?n)NoaM19L3_ssP?I`xX(O0W2M7W?RH)zlh^m!HWc=380&QagL)@ zKVpl#zX}M>9c%}k_IZ_S&E717Xd9q^iA2A_XvGz@)md^zPG!vu20BEIduyhyq~GJN z?cD^xS8&v$xFy9g^zABM2+IJqw-fSd;vb>$VkZ%P{nE!ic1B(J`K)EC`AVwFyT6tG zLGo3m%P6}_<&A||%pPLK)5uP~Z|NVnqW?&Gco+?8k3!>RN|1lKjU|;Wwr+nY)Lg6@ zbVGkUZ}Kf+c%k=?7VM=oQAKp&gpI5F!-# zo3Ix|;z`P7?9waX1O0dhm)dvQk7sU2_@z8FKJZO1__Fqpfw0lPip{jkwQS1Az%-56 zk5q1Muhxj0?~FB&AJ|O5sYa@^AH!ImIMI%fb8B7Qs~lZJ zPT0j;+voMA$w<^3ovwV=P|6yV7PKG@9_fQKE9?A0Zh-A!#BV(G`F-ajVtqtZ@-lbB zO3(#$-t7*U9fVzmWY!YEbF#BMu3G56Y)vge`M^Y*f23YX#K?|4+HbTa2pVK6?iF$b{z0;$a(W5=M;X=M}t} zfRIv+Q(dd~p4M;j-lQp-|Ik!G6MzOMm$ltDey-ioCa%2}+jVf4#21OK&Ym+Qs<1Vm z4YI-|fLh#3-ra=0qwH?oxQ5g&S^#-b<{e#4NrH9*q0YNGfbY79m>75=KBGOER82!D zfd1OX$M0YMwN+bC_57Fy9N`KjRrWy}2qNZ>4Sz>R%h~;XrFHn4xQx@v;^5A3-jQ2g zlPN?wjugfS;KKWH&n)H=ucaU0Z%dJn8BQQ?Mgi9Qh^;6;EBnSl3Dqwr?p$!6~N3|(bpnhL9qFNmjWz2 zJ3FF`eIDbge<}ydHOpZD4dYNl^5`F_p@2gu9_U2BP~I?Ki{9K8if@5RC}Gx2M241h zLMm(zLMWreO@%R`XBJ=Z@_k!rvxh|rlbU<)9Re}(>ns`1A0he&=G%gE5dQIdj?ktiTS_QTKfZd$&a2US#qzv1Aao0x&XyihXu-SfwU6awdR}6g?kWh6|-CJDwqZ0TFt~4kW7AU zk9cjyzxq$vgVRKP)>HM@8Q;7rPTkue;#R2)D8u*y-fk_HmytX>A-Diy-%48$u`TQ1`_WG%6QlrV%5?E+is5|;SK03ENUL>IL zgEZps4cI2n#n7UI(ftbK3oxwhAD#krOfIM#ldt z>C;a7Qvwx+8>*$=a9k@U_g*)$L!@Yg;Fm~ZJ88)m#lP0Az4AI81lWi6(i5fYMPE`K!cJ|BBo41#Xj<~ zZDWL8P>RV3$R={n%yBvDi~UE0a$UUix}}*0l#&S7s{J9fi4z(=Gq));kcjg~*@joM zl$^I;d-cN8AxXr>Q7AiJyWM>*^HB(dinMP#l^x}J zO5`I}U_IwEiHmP%&Z!$`O#k}5*vG-l&$dfKywN_&n= zhM_V3E)69h+$0Vo6bmu(j_9YNxHcA0oQ90qO}r6c(Bs$H-Sfe3Kr}O?(}ip_xU1va zfd`DjGh`?Djn1)6Hr}H(I_NE@-!AYZr}uBWk8Lz^yc?&O)?9wT4x zC+9xwwyd*>7@fbczC1|HB3t?tA>JhXL?BN`u&S<@A@T@%I)O#O#U6(_f>r_3vDYy> zLM(k~0ay1Qb(jMxqdLN92w7guqgBDeRh5Z)twZkh1=cXK4GlnQLfxXGTFm*Pm4scG zc~^-VNlcz;L20xue`o2drj2<62h?}f)Y-oVgmb*NW4X)tm7E^u4*c$`gBM%}-4Wm2 zoId;9kWA-LfryktZ>p8^V?U^nW~46|uL{(y&dRB58GyA}EbRI%q$Nw|zTMZ)rV)cg zS@=qlo73px5A7>f^YDzwu!!gb^|U;0lNsrbm>Igf`fP&_C;_q6v zq&W=CZByXp+if4Y5aCzag;C%$%>v^xo0E$(KTlL$7daZ3A}A{3)@Kz4#M74meGRig z+4?~tP>f^wR^((YIFPcgCZur`Xf7bB4;^Z51MLH6ZYSn~)tH&*+`zj@dGz|%XP$p8 zRx`x5mu2ok@ZfI8cHI{K_SP?-bEy0P4o{)mqI72b^wRxNm+t(LMn2`ej#8Ys-sBs{ zoFt&4!EbgNoN?_8p|)yxCOmogJ`$-b#0gsmhdC!-eiNSg4C14F5IYVb>`h0W_^Z$W z;(Ob0E2#X^_GJ;aEoopOmmJ+GZk+w^!eO*HkUr~v`1Neh2(6CzxAZb=m#yUMBE7cz z*>NEAo_Nx}bC>&bn@x?Y=t$m%hm}1KjLE1X*1NCu?FWkq$g)Ci!ksF0^t~el(Y%Af z9#tAwG85MxN*}I20pFLWzPD>!CVQgytl!R?i+C@QLj60_759X30RTUtqUxaC5WG#8 zr&J1QHp`k3sH81=eldtSpvtoH6v_Y`_t_3@cDZe&`vqd-J>C(n`o%quc~-+6=d}oz zpHd*_KrAg(O@X8ZGQlubw1&TLqd_Ad0!|#OYN^Cp>u)os%(|q_?dIC8n13u3F3hW zA~t`BMSp0OeWVZp(19yJ^>mOKQ4L+1!h8*5th@<|V`f&CRrZRuTaQ%eO{t%bj`bC{ z!QnJM+ zLr_xp;5o3e|EHWto3#&S%ho*WO`5(?_(K}kL(#M$)l?nOT!!ylHs4&m(&EjW>15dt z3lx^23TGQx>SCFR6KT39j3tcjO?mnHf1j zq|6JI%rKeBue;VOo6J95^6|eNn$xDoT6=vnkEQGqXnAu7()|unv^<+hm!oKV-O>DP z(r|JgE_m}G|Bj1ud;7|d#T4vkxm~Rur6%)OSMW+ZL#O|-Zo>0)@2kjJXrfCJXwQM( z$x;-4+UGk=+fq1BCpI-F5;tC8~>$tezEC)mBFmUckG`()nYL z2}Z5RqbXQRFC~n$8NWhaCsexXZe@C(tT1#gUKF2=ViS1oB%=rde@TnWf!1yC?`my& zwul*t()?eC(jiRbe~uUQUeA#x7EzuJd-K)8ua=m08 z1fQnZv)2d!@2A$`sFvN;36C19;0Km`;|FUJ?G3h`y8}4A_lL=g{-^k~b2M#Rw%uv( z^<`g??JNOE^UXB#5dS571mpIgtBb-#rkZU<+-~@U_*w`}R=a*YO%yP-mg7E=6#GAcSQ!j0|8?m@=73hy4O}Z1i`L7%?ty~w%r%;f>9P3`mV6K%^ z4k2`y`7B(}&62p8FB<-2#0g+vkT zvhlioC@z8*llPqSu<8`fLTK#p3(JOgWg1#lCLF*>HLa)y33qHH=u1(8iw{jZz=F+i zh5Epv`$x??LS>Gu)4Or-fz`%J8&u;;h=2wpEW5MVK22nnO2F;iwi4Z#rVU0cE_lEEfTT!R$$o6Z^He?a=amo8-}#O?uvO zl(x`m-j4$x_Cn_dDu^jzbG>*0w!ND#o}g~EwBGSqT}@71lT&pc2IASGM8WOYrl2Tc zwbnz=K*F{ze%{vYTk#5QkzFb%xc7WWoNi`jDUl~TSa5D_-~g~zwC4o!MXM!cW;vH} zOG*xmqCGC6C6LPNq>FVD0mdo!DX zebnWW##@q_dWOPJZ}<*|_K3Y;WPGR_h)l9iTb+3o8J~vaWbRfq_MsO)X$D)tI@g=5 zuntH;t4TY@DwvL-ST#-LCXk^t?w>JJ-$6PC)dYKaI}sXe6mkQZXUH136(Uh=D6)Xb z7^+<;&I+r(bN*2A{H}&4BtVQ}zMN+bYt$kEH)T5!RU8v>xpckwbF0d3LB1A*(l0ur zGlA5UvbxWH?YKe^uqnV|kuu$wr?gix{kV!n%pIOx?5RUUI(Yw*hXCeCQC61y#HXQa zL7W2n?-u%>ve-M*7<1)i)6(^iEt9leFW%&x1buz{J6Hddivg#!gRT$^X~Gp5sMn9;0x;>|IF(mBY!2tHH%=vC`R%5Sp_{Hj?J3e4 zc?im^qIu!tFU8qypUJ~U>PE=&q_-YiwM8Vxag)5XNW24!@O{d!_KcS zG0CYx6fq7mWsOEjSQ#_VoO_oA)5FB4nS3{mkh)bX6tPpB)6-`nKZ|K8VF}20>@iA6 zeYksGnL6qawA<1SJ8>q)Wt-ogtQe`Yyq4x<7z%KhW$Ygx89+KR->mJ2^e1v+V)H=1 zuD#pWt9hCN+95ITSXD>oOZ4=8vLhb9vTO99N&YF-yV_TyUIOlFIxY87O~&Aj#`<7b zchc%7xH&9~&l_(1U?pv5LPjO{0U$hcFPW2$OjtAT6Cl|9ZqSzZVpClDItIsNV_@)P z2YTjGf`Woxv||}MuZr|;s=1FjVzx^~j|~(frH-dQamZv$_TSp7otitAS6uUA&9P_KC)MZ6MrZao;~iR;qqEQ%TVSduq0lDuo`KxdDO%GFgmxWax85-h3NIY zse|~H+M;-AkI-$YLRB8w>M8=wN={ae{T+G1U%n`PSdy#!>39aA;o}=0jo0Xk#-9r5 zVK0HNC#FRG=|5yKAG%gF!@jgpug2~ZPk|da&_oI>1DoMQ+PF6;Lz$N*nq(AMe{sLLzbdu(WZ4yp;1k? z%v*?MAd1Jr6NTzE?_iY|d0rz*swR8qZN>uM{#IV%HH&MNMpe0V-cIqQ0K76lI9MIh zp=?IDD;gm=rTF@oGZ3cay!)=rX|F0K2!M)Ht;$?h*c2L#x|P2$SQto4h$Uligsc&; zD?gV}n4Z&@HV2h%Iv8Ewbsr1}xf|2W9PRHK`EN_@-VJHj#?&v6d<|t`A+^`C1R>98 z=oqmZVO>`MuZVL|QCm$&1B`LsQ9TO|Ord%faZUIvd)8mU5VJ?f&G($2&a{`p zxy3r~6$&tEAUSxND$lAT#wfoM{?dL(1%juZVsyRhd1Su+bZy)|fKM6OegV!v5BS+^ zC?TH-l0P9Nwq9^s)4{^_Fe1_Xt`rF1{U4JU>go$zXhLPE@NHZ`hs5olWd|r+t{m0L zH_(~F#S+{IJUqVtVL&XsF zeSjZw{eRAUC`m&hZptj>dIQY)C1pPF-2@|P)j@bgGRdU8`BjuRWLs*o(1r~+@#gGC z1aJ68#{x%dv%#Y^X(L1x;WUl^WwDVEgV%YD@9<8^Gt%7eAWwaG2y`^G``2Pt600=I z3zS;)Y18K8MWvvX{Cpo4enq_q64fMJ`yt=%L$Q2lYu2IpP62`cf%A6DkEzVXhRR?L zltR^VRkCvHuAGh7O{;BAQo5Tyu}Z~^8^jv=y~Cn~6#RnjXJmrrD>bl1*o~GuJ{A_D zL1~meIf=mJRl$aT--3@bNOg0@+`3ix5pv1TGCEM$Q%+}^d@{(^#5pNF5xsfJcS`2^ z9|8UuY@S=4vzdLvDfh!w!AjQZ>dRli#0(H^Ui@q}O?f|b`k*#XM$1^3;t;LtmPmeC zf5w%@lmL?nne4?&5;WmSsDM@I@9ug0c%n6{>5UgQBk)szh$V+lIfQDIMV41jyh3CyJNJ&Y|gN@QS=vm5YG(@BEObM7T zm$cglt@9lVB@h_}FTEzwB$}3f2Q^bXh{4m}o_$vOtt|aPrw`mfF)(aQb-n za#nzwv>cib!5}amY7~pF;FJRzcEWf6)7YuqFQ11~JJeu=an3KNrf=ujmiPU;9*m@i+_4p{`Sr>Pk4p8%Iq zc+cwwgQBTx)m`W)L+*0vt>{86)yvNFb=;Fx!y_X}svEr83=3$Gd8PJ#B;@}YO5nxz5?&|#qV1IA_-R_7LaK`ak?@ONM{Yj_WZX6W2c9Y6D@QDGz7UYI0Yvs_(-KSbRD z7~~C0qX}0Zn9gMsv!c5M|HT|x6~;XLxDq>h<%q-TK1K#yI7)ICn114?DQ|%s8>RFL zaSp^ttaPRtlk)%K5q^yDVkk7g2%Xm*$=r`2ET3NX_e_c~dqi)#7H2}xQ4b&Bg z%(+N**o?ZY^72EYPfF_-hC#vSzmd^@RI)=FhE$j7(TRWZLO3qUjr%L__l zdjPnCQhu5)ZV&DwQy%QF!$wgBTvyraYtl1+$fhe0VcdV~;x96o5H|#QHaDG+0VFL- zeuy({@%nYF0wV)t0SLRxpYAchae%%1&eV;?6QC4|=!DB3aT!2~!h{;GaX#WGq6&aA z7%7**Wq=AWo#WjRf1#>9E$lBZA&q~crGM*ndof)I%XpWfWQp=8rPaHLLG%MPAK~5a z{DtG65jjOs5IbMebSMeNf;G>b4j?pDQ+=i+*Hs3K2H>#p2}4?bd6kQCrP-; zMCt42Ka0L!LR+!zKbHiO^E0kd=>y7lJI~H*3JfHH=wXZ@$ojT?12u*Kiw+B+P?9$= zd|cXMM-rwMcJv!;)_&#IbogpjE+~Va+1NutAOF~J>?DbsV(1JwrehNxFZ&0lucgh< zrdU^^YE&?Xv()xH{(jy&NECd}Zn$67J01!xS6B#=;K@_Jx{H_f-6xO>9nV-hb2{TA z#LgJSanv6qp&wr3BfQ2SgZvG+gC98qbvqc>d7J_bQa+cYzHtU=Khdffr&DM@afVWI zJ3>Fyb(&b}L_kV~#yvGML>LdnOdbk?U;hoC#^!Ur{4s#ZI~lX2{;e7hTSrM0=rIGI z_dSC0W&o!X0WQTP#Nj8YF`0EtRZEr6oE($+30Qy>lIuBUW=#$1!UQee4I7@4&Co%F z5?cP05szkp!Dj!a)DsV3Q(Uj)PGeg8za4&|reC6^m>HDW6mNMxvGe3{=C%Xl^}lh@8e=LQX9hHYCKOEf z>%2`}vsFFi{HNvqeRI)#L=8WUJbgiIL`ctISO^va;}*PH3HknCl&{mYUWq9~pF@jL z`<KrH^(tPgR>{xd6uQSpDa=xb2l*P`=#8M$$5fPC>M%=INx95C)c z$`pkhM;Dah3nN8F_JcyG{OKEu=KmV*EhjsI~*xVm%~}*c(x6^8aPB!NVoacN#)?8=2)t5xyK*x051D z(w@>8qe24zj%yD8CF$j>xRQOrij9%&3wTy-R56?B)q3grCHWw2R<>@+DF0(2AS(%C$_;l#gygp1_=;G_vLV-A4{W}q1*V5U0u8fe0q6SG!h z2vWDuFUPoyK6xobd@AK5=8@h4G3KI_$o%HKFkR&CWNeVg`Kope)TO4n_1Gf^U71I(!8)>1ucw(XQvFBJNHttQ0ux@spTZqX|>v!^OG8dZ?h z5HbZBwypKCnLjc&igimX^P}G(&7aaaz-oPA8wIbov6&>wQBX=oVs3KH2l;b5_vy_JN2=ee!Pt3qv3JlQ?{*1`2C} zXDMO5VbQYO=R@XOK<-8L$1sMZ;8bi;{&fNEiPxI6?hTl~L67P~xPJuRQ=HbnmKbh4i1m#GB(M%^Z5oZrW+YUdH!+7fR4pgYUBDW>)Mu zN7ieA=V^7C<}*F$Ec^sv9B>j9`2exD;o3i{MMXGU7%O(lV`A#?dY$6ij_4wCIq z1sH)kE=q}mN;=8yY5bq_&Co9p5J7rD>?l(o$v2G>=TIDXnmEOAr&WT6Ynu3MOE&`A z*K~t>zPIXhI&v%&gyhlRrG|X|6FUfTe_tQ4ZU5n){h**Sa>WMql6{J?k$MZ2RwxQx zPYzqL?&ZPosP94y0`}arz^TxI%nqc{EWYoO4GJFSt+Rg0?idv&*x&xpbqYCzusINt z79c;;3&#d~QG8zyV-7m4LFri7O${KRq&LmjZM2I6md)EABgC{HZ>XY5i$+Fb3ReI; zM}z1m$s{gDdQD{k#mP6|ph6e}QemW1qJG#frnWJ$n-C|s{ya=botj~{Q7mn`ybuS9 ziPXeHMzS-g3LGL+J-w8sw8SVMOd!xR$lOJ0JSnLYD3jqVwF@{(?@UeLR=$r$<3FMg za^?m!qmFOt%#>yW&4Lnrq9ybVD#qvJxzb?QR%%aa%sgLAQ2sGX`{br0R5H_BR&<(X zyapv6AV)EMWZF7`BiZzMinZA0A8Hg-40)PH-9*#SK7QqV0+SwnCKO@Dqi%vx0If1??wlxOvm@kM~IAFq4Gr9YiuQHv!feT;+ClU z?^KZ{vluIfX*|(N0?#pnoe~OrW-yI*5vdBLTSGdvbf-M+W$P9hcy-1l6RIQCFV3tr zjFZl_pPnCXelk^e{?YHr{R^gvs3K5`N6IOvZK2prG<)ci z+?beDRm`R;m;Foz^h2Nf)-}s)8GX=@A`OL794+~m*QSg&G^q1^%)JG1FfsQL@WmOA_RAXugpc4#N(P-mV=^hZ=NVz8X~<_RHi#0DaA#_k$DI&9Ris?+D{tNL5} zJef5a3}Sk!ZaIKHLv^nfF+lz4N437IYa?-&m#<4!pyZ9&lPD@6dqRlDuz~al7R=9w z*;g_pbTYh5 zE76|m@hxV7 z(89}$m=&t3i8=t!1TY!}G0V98?SRBVwR(9YDh`y?g362?$cd($*lS!CZeG|%oEwO< z)QPdPu|XUv<^ZHrAQZN|(K@?t#rLPM#{gc)|8j`35$QoM19}=cBMcP|?Nsz2vhMT7 zlE5mI7E=Nh3Ky(0nC4%GLXOJb3;6lsKpF@&*A5&R#FZDQ1BdWus)B+g{k9GyQ ziQ$^IubEVT8#w{y@4nln~3iU6u@D1Kx^U7}6~C|W-nNEA-TgglPQB%-UO z9RFfoS%7+WCm8fI89I{OT_F{P5K2bC>8kB>CHPc)7YS2z8L3NO7+;QkB#1ou0j^}%YLs> zlNXJCexamQ96~xPg8HqMy_lg^)G#?uQAzvN%C^kj$cf3XEMs$B(tmk=fr-#@Ca!hoFYNagGEqFV=;B+SRoc7C#wNPunZ`E?%vlwtIoP+S ziBGbtEVjN){GzP;hpxevypPGkvXY_+>hoK86{eqjKJ;+=z|l9YTBCQ$Ypivh(w{F$ zRJ$Stn9psmiRVv^h1ZAb+^9^-n!g8Ci3uHmnjBz}6Tb0mi6^^!OJ#|*MrIS0SESVz1GH`la1n18Ef zz0IlitHqaIFUfw_SuXtd?Q<0e7eqRD+D2&FEuNW2Q&1d@zB6)ex-VwU9=R4A9B$_5 zm;B^oN%9AmK`$%z$|r-LvYCYBGPZBC8{T=Pt@!%2Qujy=r8y&yHyx>^S2=G|s4@oY z+Y%%FsNm&alI;ZXp(}jr))jWHXV{!@XEd{pw@`Xzlu=t-b|QaOX26RVnmhe^ z)56#Ip9grbGTZUno!F#$p627H+J}qfxP6})I)>7Ojsh#I;T)I88nr_}QYDSICGg?9 z=$+arI&)rsN;BCLa>(d%_4sP8(*pwwO)M;GJ4Ktr63@AoS9vOGNyUEa(jK|h(_7Xg zZJIY{1BytWTMnDnG#|=>Mcp{?+VAhna-5T&hhv-UN=+8|)vk;JLTGO1JZ>%ygoU8Em5x+~%2sFXQ;`$c7x>%5Ja&Usb2u-a>ggSflxAUwz$U-S`3} z2UOu~JC9mxvBvrcV&yZ6H>q9#n_jZ&)=DvZhggQ(S;{9Cs0PjFgfejc)$WB{pKDo{ zRYC!!iuJX!+S|^10{Dn)$MhK)Epr|m_1&wvx;hM6A2gx(Q}si664{2-G0Q#t^t$Si znAleh`)}ZkcBszIT~>f!XKp&j>tLE%7jF>gB(tK!wPll$#gRiN#S_;j<2omA?R~W+ z%=FPmTesTAw+~yE&YZj1Z1|p2bdsv(!O(+w7^vpHp6Y;;g#ug;B`^Gx#Q zh!#p~@`&&{qFtlXgPj z>oD?Bfrn#azc@NtR~`3|Ac7?_o9W9LFmvA0wWcqQZwd`wGx+kfje?}tMujPKGj_Gi6HUu$f732U$vMJXnhtbNkyR@Cbk`8Ay*mtDqZO+Wp%Z;moM zt(|Oe;_~{lE!j*=(=I=JIxy6ADPE{+k8C4K__>enef-u@^Pq!5>N+u1WhS+Ezh!P~ zZGRxWgeG!}OXA?h_pN?sbGHk3sy>Tmq)pf0M6mdb)s?!a`@HLTr+MA2Uawo> zxeQ#z4!)X%g#LY5>RV{6%h)wF?C-)B+#^r43RY!!;{olIu|-+x#5k>Br03s*INzCW z1EFsu(rlP#g&f7320vJ%&L8w={|=o$W}0v#>%8mU^;JUrK&DQE%S4~r%Z6j`!q(8< zCB{eV2K$=%3;UHT_*S_dtgCV@FLeO&bN#!p1zx7w!Iy4qzh9bqXn3Wc->o?-7n4e0 zL{PNlwX+=ri1c(l|AMxivBM%0a?TT`veM6WSDf3fmE@jKcC^@5pOO9~7cSrQPd(g8 zIqV z;Iw~k;OPV6QsOb;;j;F6?V6*}{Mt)7V^{Ce(GCj*R%W&Kqtg*hxy$8TvlJe?PtRlB zY8GjF%w&HlPuA(3r2!m@eu06h(gL)})qEQi7VXkGE){nC)cSPk<%KB)J_}_A`y1!Z zYdkvW_O`4>uE~~V{ap3KrzPr^C(0(Kf7`_zXj!9uv%iAZadHB?J}lFDU+uE*m^sUB zt3MXBBYZaZ$E7uKe7}@z9Skee&?h6K#Y>w%XA^+I$v-1Hap<_>B*Rp-?+rhFp5F~uV9z||0W5mhFT;mVTuK4^`{-*-G)aQExly109m*OqXy7&%sZb-Z%4 ze6Vh>^nTr47ql5O??0O*ZAp{=XqxfP&;q*4cUQNYLsc+1eI7Z90uc~|p=F6s_K)Qsb zxi>@34Q2|DO*7!}ts?{>>{^LKa%SZQO-=1XjxHgq131N64 zADnh+NoRdkoylmjz*kXPU{-2>|G=H5L;0df)SJ_UTusRACHzV6cCkLFde$N1sfJF7 zk)vP8%*nANIgPsb`wQ&@<`r3v? zK&TY|z&e*l^MFQF>ZRc0Qp1d_G_Q=j?h_?@vtyN4zc6PjTA*L+}EQQT->PV+9=S}vg!7VvrFHPH!-C1)}x6l zhB=FWJe#fX`reS_i~JWu%j)rK2PLdaswSEb%BGxib?H#$V06MEs@{DmX<<8u+aVM~ z`2-6!sH;Oi7u~GSU0t+*XQwgJc8EU;IK~Psg`gsHuS<|+%+%SeXBi{m9Jt|*W2c%e%8fhq`cBY z$v#%rSqCvA)Dte<<5Oa5jrZU)>egocwf4fjuK_iyMby9?6X!&cKA+RZ4eR zr0qVN-0g9}0ka$Vy1lJ4+e4!2Q=0S6T`HBYg8A1)FhAjz;!}V%NA&QSIf@+6jQnnf zTJr`4Ux!Op<&kZT%z+Qm?Ujc{brLz*<;+UAvuKx>AK!xw4}N$k<4@+xriwO^$Eyyj z44bzje0}k4my~Pg$xv-UGg!R$S537qBrCViwLNOTywoK{Q7chLojh(anJf6UQ1q^{ z_LGl3TzhmvqV98wz3y1ib4Ek6;!7*fAi@pAR+vI71ZeDy}Tug2dt|LLg(kGo$Kw*^~!s{O6_jIUlj2F6Et>He$XEo6<) zd5&CGD=f#nmzpHckt&ID=espL4v`rhR_eR=9rn&1Dh#?j(|l08xAwx7Ac4E$o&hnB zIM>%lznmPg)vxy|-e?+MkXce|T)Qc*v;6&>fGwV5_1W^9wV7&Cvv&)ytS5I)o@Ova z>#Qn2Zc}R8d}#dqm7u$R&3q99iY;kY#AD#b;&IF7>5qM_%Ucn2@WG>z7fIhnMcI^c zbVY~-!ydJL9dX*G{~G@e(&l3_a=0XY0T$^? zB&hynwfw7GYmRHDQsMhDT$ObuV@b#-uO0Q2j2!gNPE<;H7nRb3E`7JFx>+>)`nwW4ccca!TYa#_+7s)TI!1_h;V z5ZTWL8UCznJ`!=8%+I$W*gJvcHd=WBp@X$di8P1wHN1XgTHYL zJaROKS60wiyXtD^u%mKxLGzaJtGp78we6}Gu9`jwlivU{td%pWh%s|#7mi8&m3-vL zt+uEbaldjJEqM+sLlw+RGz_)5_PGOEA<`|*s%aOF;CSZ@Rd~36E9jk*zdb)Mrh-W% z2sc)?#J2uGV{n(?wZ7}Z_hAS6M~Xqtf>ki1obq>j;U}F;e|*)4+qc=@ih0BOk4al+ zYL@ZEwy=K7m+%iBx}u^z`wxVL$~3JBdcNd!uv5M5!uj(@1#X=6^Cw+Xn|o>fO6qTX zJ}ERM7KFagvN5|SQf3#KQM6&A%sr=Ef2_67A@)i2yskWoWr+n8}ViJth zKKI&|>wbf```-l$Xg@M}n~)t7qWgcC$J0~hkq6n~fhqI&a-U>y=%do!v9ldlV#Ewb zhL1*srgHO0i>T;3jm&9u_MAMI#KuV;CVZ$)7vZ?#JQk^P*pcmll^^PswCKq>_bqoH zkIuOl{gNNSRK#$<-go;RANxQ*%|6y&dP2vdSBHPi$}U7i-zSX%O~Z%uqKdTWaf(8u9jPNsroWt)9K_OE_^-9$~~AkMea7 zT%S`;FgBu=-`D6wohjwjyqKP?T|*rMq4q?3eJ!-lsPr6*=@5IGYv~fUNS5rO14bfm zN=lMbcCW+2tal%H{Yp2;-jUA^=KgVaZJEnR!SRT`*}gvZO>JTav$M&?O3RM>XeF}S zO~?P0phE{Cf?h!SQIeZMm-eTE~xbovgaBrk>wNr%4A3RELK^sQR>cDo?X7s zGjewK^RCX&WNEd%hfcm4yu?=A(Ka#E8KD+2{?h5qS^eRaIQqv|uU@m`;!;7owT>#6 z^+wvRz$py-f37X9?zCNp4QZd-=N6cs|02%mZdcEP`Z~F@Nqo(I&l*Zp&vwNl<~B=d zttTK!NN!nv{chxqF_iw`Xnm4+9^x-Vf$zfpvgo!^>$<*4tM&4HKYvjPcYFOV;T826 z?)RGp{3@GG>!p{-<|pZ7h(|uO6ui{Mn^hxZmxE^6>cst>WycV3Cg>p*-dMzJ+oF1* z8tKhf<}CGFm%rQ083Bmod=`2?B)2Ohxv<4a{q@#HtH_J}e^-4d-=#l(uSx1ix=d(r zgY7yo=ir-vt#mv7*pcO8rFh?DV|>A#brI%+Z%R!&+{~iyj$bTJ!55DY#WtHeI=Fwe zEo^ho9SaJ*ek=IQoXpbqP0V)^Ztu~4wR2cJdpg6^C}{!C1Qm4s$HvQW|zX_h!e3m`74c_8g8MywoHs}1?cBq62r^9qpb&Jy*lih|_%I3GJhE*Wc zX&i1pJGVbfYvM|=r{`c!j+Kn3z1h9`mO&N?m4oSg`?9Jk1774MnI__p4F$-B23yI< zN{P$1WE`>z4!*pfnAB<_+`T!n>5wn6`@%j8xT1NK$ zF)ze?ywV~?o&AfvZ$|x<8o9Qf)!eFMHv)?9A*L78)7V_<(G4p1ao)M_Kg_>$`>_t%I$!s`YFumonu}Ni5)jGTUt9gCu6H-TmG;C^@FTaxX##xo|kHwgq zCzSQWzC2#KUi;Q^pD@vD+Je<#)t4{|oAq3T?C6F1{D8m!HpfZ^V};+R8r)A!9qF+z zm!xicb|P{jBMZen*?4*^w(TnVSrnBNm1!7)XWo}8u6E&q`lx4v){~8v8GY8_P}x-fs~!ao=;IxF>Q{?@DXjUFqoFx+9l+{`M`} zdQ)?&J)_`@QyWP|rnYlY#Qq~10pT9Ula>X}bclsa_oL6&`5WQnJ!0yIqt+alO_o|m zPN(B*Xx|vK);g`0w9YyCx8;UPy0mXar@3zn&vLR1FSfb`mp&#daWVq;|4r$i|D51IJ#|L-q0xo0z*`BYhk4(5Ok995*v6Y?`ZuMXKXHv_L@TO9@Mab1AE9Q%(If zyDKb8wVaEy2CwXUZEiXjTf#Od1)2P9Yh5TJEPa| zn4&Ny>~Jeawe!NBsWgk*YswWivKLFV#~5y(rBproPIKcjw^fVG5J|_A#p*%)<=z*p zW&d#Gp^aHT@kzZMUGbkD^F{1O4q$Vr-wr4iRvvXuM2ko|trfVd;3N1J>?77N&%CbY zJToV!xlD_MuWZO}Nr$xiudUUK4kOVT@SN;Pn{(0seX9WQ#emONbAFRA5x~qja}oAWEpvqDjsTy zNIxLov%C75l#Y8XPd*;3SaR9N&4+KrvY(s0{mI=(UG>EYzVQyn>?E!1@l9!ZUxmcQ zxSfV-E+mZX539rE4E*t8`j{$B&v==1P*alHYxMsZd+$K3*Z+U~u2kAOMMfE?6BUXG zWj3_TtdK$?n~;^+(jcdll#mh`k&MV}xK&6I%19+7*~#YjxL&ur(;4s2_jmu3&Z)cC z>$;xTb3C4p=i^%A`Ak)1_a}L6zlDbR-PuB<1UY_+9`42 zpQwes(;nuI`?}dVe2wITRft~7HhnI4hO-)L?AlxAN%TH69se({=jU;2(*KaWb1on2 zrj)#%P?VZ?trXWNc0CB{MRLJYel@87$?+Y=5v1&i5)6M7{V~TT8$ZvRKd;>V{LVA8 zR__mQP2-i-@iq!b2o^@Dhe1~s%ja!8(o)~h+Lc>I*AC*|dZNj?^u|Cr0e*L=lSW5?q^Hy<@gISzNpyl7x$m~NoDqbvsfW`JA-lrGHu(Bm%RRtyXYwRsGVQuNpI{O`)5`_sR4HirDR zNF>}T=AD6}YR$oE3$esyhUbk3>lcN#KLd8**%9=B6zM%f#DkPpe988GBzEZR9OutY zCwa_}4uqnZ>4X#~SBIOgU{r@-UJn`Go6^o;e%% z3fb9h0A>=`YO2e4baJH}YJ*jbQtR)Fb z>x=~Fk#xgyYq>^SVA8Gg+P=#d<$whowEr<`lNI5LVi+?E%6=Bjr9Ey zZzZ1$bRd7c!g%Q0Hi<>ZufCAdHOjl~oc5Yt&+&J0-xc?dN|Z!+erh@p>yp@ z?q`RyKk`~h6$9YoFx^bty!j}qZ* zAV#W;dPRcXmBc65ccVSv>YrG_M$aJ|ZSH$)>|7t+geqowbfy%1-=jTVi$;eiqPd}! zxZfv4cbreM_vCZzQe`Kjx>0hplxs{1?}XG$m)YKCM@^RFXR4R5uT<>Xy)WBSje!EJ zsEXKlRGJq#Zc<(zfvni zH(jdy{=(Mr_Pp;^Yi%Y*0;(9OgpiT&TA{s2EL^gN2h;o1RrO<+L>@I1`da?wTlO4! z%^LE5XZNk#d_1O+BYs~SQIXfHS}AIN`?|Wqx*4<&KG+-l7fv%I$uK_>Y7agP5(8j~ z{=?;aUhghdOJv*J|9JBgsid8hrd*p{*P6)O9w7&482X!Wf zLm=^9n!dQ>{m$3ZV(l$0_19bxHaQ!I()5LM$t|QoWzRC|?FTMh^XNQsb3BkZs#(Rz z{Muk*k&d=5wyGB`{@BA3$8-EisIv9dwpApfF?ogSOy2$3my`2Ac>RXwFxVJgmoYYL zk00NE2wPaW6ebCnMlH~YtO9&Xe|w0x@iu!2A3BNDld&Bf+=`hGeB{!>h#iwMPI^q2{Pf?O4SJ@i5F?m791sL>Ypmz8<;7N1@M`O6d!|iX}^WQPu&Y#g#8xpTU`lCMzjO_x^ z7<#cc#1uQ?H!>)!)&5r&;rHRQ!(id5n_kM>r;oo}0R{?Cj7wKT4+JiYj}JL|@D1}@ z*?xZOJ$hf+!!}-V`VWOn-T5>5a5pjkE;aRv7r2&>;LyCm#pEyDtHP}l4AZ2UM7ua& zFfVO;>DMUAB?>XA*-t-%N<>r_IAbgi_FA16ZCGALsoz| zaD7Mi<0rZg3^|^#oq6ol0E4A}+si_^?Ns*>fhqJKdnrvT^t#BX1L-86Mu9+f zL`+DKM**(^G#{e}s~e0Ca`ipHb(7>%sG}*sz&&G2<<})FU*B@zBo6ipL6J1Fl+*sc zna+=~(o_OM^YeJd&YN7JRz%kaq5xpw!Q?>{3+{;*r@lLEoo~pNF}0=BX)KwHy^|*G zkvDbHgtI^^O+Y6qMo@^51U^h@zWe;o5_bDQge=2fdLey_slFd7*Ii8TLhoU`esXs= z$)eeIz(hsPy?H-Yew-YzW8B2d+&3(p-{EbWDDGM*e-lf@G_#gf%sq+8eP35Q;gcbb zKE1Gm+9|S1ycZcIgIOR-S|u?u0?bnI^o02fRTXji`8RK#!!b-cMH5~IR{(c%CIAk! zf4Ds`H80ri@{>o~=hE2bGLXbaX!g&eu?3d71>n_Gh-`kwQI6vJbj3vK$ z;+G|#yPBMzo8<3$G5+rVSm*NYS?j2E?k+Lz$tZY!ljb+HYSN{l$q7ZK6JYeRdEzuY zYo0Alhc6MG1Y_{m{5(!DyG-#-PH`0AjooxQ8KBVzGa0&(B}Qs{rB`GjPVmB6;SJZ* z@vgskt3tWWleS1MYi)<@Jv|IaaPE38vgTncNV7>rg|uApG#?BHj(}CDbb1%!C0^L< z+uuWb%dE-m@!LJtwynV>nA7YiU)}0}V}S!v?Z6{Q5?dWqcsn{A*~QtdbBd;*{0W76SQ$2}95rgb>`*nmF!q>sJGw zMPoZlGSgBwoI)ypqzpjHS2kv-&VWcn!FE~0v~9`V zjtGZR7tJAJ{zvSJtyYTSm#kYBaA0K|0XPaI~UH*pE3Vq zRcXvseRDWl`$`G~Npf6mnPHb@IkCCE3k4DNKBlFNC75 zQPRvlQi54&9);qjrURYh9>_LtnKdo?bm4*usWNjaX9jjIjvxC}(>3yx;hqf8Y$PBO z>n1F~KN7MJBP2C6UoaNfmZU1XQ#|-d4N=xo&j2d>|w*4 zl1wU(#dF`|ssc8P&k0U&25X0Bn2AV;o;<*b`$R9VJ+j}<_Z5->6H4!X|V8p zaVD6-G%@pEPArf=W@%Yy;q{3MTS}*`+OrOD2@8v+VlcE&t|?{5-OT(=lZm6i=g)1y zfyEMNAOq*_{EkDYimV4YEd6a;+7dLZsb_i{$UGi_y8gAuU{9U|GXS?~E*KKl>Z@U$ zjbQ@fTr`Xe#4`_jM-$kl1#~(_6nZJS--O0MZ+6Sce8ZQ+nKe)H!mR zKtDRGYu2hZz^Z*~3eEY`&7x#mj}2EVb3v6{L`V>FKE$&jsE`lOZYwIzE3sz3a(eA9 zw_NmH=Nnpqr{~C#)Uk{9?_ClB;a6ydWE?!BlBgX5kVT@+tSiHPcNofZL8Av_73`u0 zpR`^i{INpBSgLJ(m-J5>P&N81mqM>44G!wxPTKWw#3-$Ky!Bpf#OxR#QlA&l_VftZDRTd9!IbO0*P2YBlbM(}Vs^wR9~TBU>{8nwUlsp+QMKqe z@6iHTn>^W+hQc78ukAdqhHoA-7~3c|VIH$0u~##wya)9eRg!vA>su%&`I-8GZF91# zD^IZEZ%b_4to>dS{PG8V@7G+Zo{0tkab4pZ4uhN5_oizVQ0X4-Mz7|oS-xw5<0%e4 z)*c=&7}Y0alVF1Et@Y|Wv-4cZwqpAC%BTsa%?bmp4{o{KlDe|EY|uvHa_f0OJXa!p z>Ae|418%Fmcqe*Q)>`-66eEBsXjkr^dStkrJ{Ui~?{2o`<>kbEa#MBYI7lZ5dPPi7UkspPkxAzcKcaLMcPDWvuLIJGs(dsk|zO8-|kUrUcN z{LXsUyp>>9d~fg_mW~?jJdNi7GVP?dKk9kp-fLbBt`mlu7xVjGOlSe~)Xm+q{-eyi z4Pk*uWl_-WYx{0Dz7(hEjmQERG5PqPVV=8ZY6GXs#jgk(nVNcS^((COOnnk}x-_QG z@WKAE&IP()&NSzg2+I3f4#qZGENu%pkO9JuXIr9i=Jg#7gS$g~dRt_D<~;qQO|$td!`R|plED-J&|;4ve_FFv2{ zMSHL5@qW%8$kUFV7zs{MYctBpU&9jcW#!pd=y!82&Yn`4D!IDC-D~xP z6a#y7{F#+}>~a65K4nvK+ymc=iBqQ>)IB2zlVil!tL*-&KhSIHO_h+37y_QGVOj(3 z71-X;5Hr#QPg?dYi^kJD*AP*__7pvE0Kes@$B(3Dvc>; z_T6_GT#o5eySvfxo*`&I-Y;{Uny*#tiK`zQ(eNEtDJa`as2Y+R4bg4~f?E}sPuD9S zQ=Aws$KE>*bp1Rc&I?9cPLodTD*2l8U}qU=xeTmF%Eo%|+>*8TWv8>Gf>dDf&20qE zX=tf1j()jIKweJW4qvVix1xPh7%AH#WhKkND*_*6}j>dXPNv{D9P%uV2 z`L!9Wvh|Klr&hmDy!M*DEb-&CBAdSNC(Grdi_R$wNsoi*Q1!g>kasUReeFf(XW2*# zll>Tp?^taZ4w?!ciF|lj+$tDRgH7||Z;QSM6$~X1&?BcZx`1>RVxFGJ2@*KBM6?31 za$s=5TOX^6R^kLm%NTtes2^-54Nh=u8iQIh8=Ui^qfLQ%wmYjMBn(%V7*`T<#xAr3 zfUyIBt!y0YN*RgxB)5$8omzd6h6qYF)qBiS>}$(YXswReD0};P0~Y)HJE0Px+?GO} zz~D3X zN@>s|0YZVo643YA38oEb*Lj~uhhPUbDNt>#Tm9Z7*Oa?F=Yooi!1YMS|2}eOcyLRB zpuD^{y-C{PA;BzrvYsPshII1lmC>jZh7%?Ye~rO&p)O zdr^daMzZ{tS;24K`t;%odK38VwRz6xkjiX(VDE9n%yGwdYXu#fy#b^T^6ZGS^ZlUW zKBxO*QlE{ooyy$3%${#7DMp7+&avrLUJ`FXSHxK^54FyfQj5E$Xjj!Jn#e_7yME9O z)f~TD*L?1Q;jFmes4cAeO=eABv%GZq_F3)*cbC5GKrA{*WilcrJvWsJLoO$F-QAPu z4U0^8@6-Yfh|B63MK($zu?0wQK>m+By<{WtTQyrO+j4)THmst~f*)6hS>zW>M>ITW+fE;dYXn}*&ELGSy=Up|p~ ziQkoX-e>R}{~r+V0ieH*e)IPs zRcagjG?kf;{Rbe>RDrm$3SIWk7U+VtWcMpi4~cXJ({dHNAudMsPSd(B#S>d0Ci%@( z@q3?$!KZ5>DTk6HXFe)!?@ws{w8G5x-SWdEePwmhD|&^V7dPL)N|C_2o9>QCzlf{8 zEpP~rOIXA#Z$3=%-L`F;ko+ObNH5XCCtrrMiru|*wT}JFM0=h~P)jRt&|K@7HCUx+ zQrhbv9S1@Nj8Sr3W%FgiR_x;&cm^irhWw}OYxWEt$f=x0JKW9y5(#qG=2lFc$A%A+&gwg znZeNxSM-&y1bG28-I&8@H`wl4bmx#3>6r8^U(c6-Gb=8oJ>6O_ND|rB<@}B2>c%q* zpC%}YVr+w~-sY{YbZew?=rkCH53Iz?@aKZB6=g=;y!+6aqQ*1}MI&#om(C?G^yNFI zu&mGMm*}m>G7d?8cG=%v{qS*aj*U{`nx-b+^$(74nsHer6;qC$)!fU8k&Lh&-WaiZ z)eC9*`Rd3^7~z#wK9PmkUOO>v8r4KtW|(KWs(HGqqE?}GNYwX__wKpF#}`z2zJ4GR z2jbaGALoPHB4sVC(t8HO9mZ)Y3b4pnfbLRsY-jLlC{k*ca`4;+xk*Os^RP1qejQPhlghf z%99f|?@)R97sCSy)$A>8;FXRfJqM$R(BR?Ww9%*Cm+hoB(x@o6T-N`6kEPzJ-+gUfgK2#pK1*g0e?gnFc&mINZSmng*KliDk zV{c6zh)(=Z4<@POde|LWn8t}**=XfG^g?V^@^6%(pO7ZZ>V|S&{5$o}^B21M)GE z_&IQqy(d?yW}V(XZ9o#Nhh=w34=>HBbiCgcF?%mz1I#WWL<`G0XT^zc+6%tFMKk)v zz9;kb!whi1N*WGh{Lzw%jOYm*K(D7ZGy5%PPQ|<1KUDSr0TU2WP^iv2Y67=L(%fo` z>$1o^?w~I%S{?ziCBClG#|X@9LN64ZDMwq75P!P?m`G?)`)5ZHGIhmx-k(TNa(59L zmx#EaP`^c|^0CiEPaggE^&#<*(v>8WLoH*do|WIIQy%`+*|)tp1;jAB7s0F0aqVrW zTx&Z`P@BM>lPHSP=@;IT+A2mZ`xF=G)x3aeMa1u$-@rt^-)@b%T9FNxmU-8WFh_D|%G#(e7C1e2R+ zS*dJ&uM8A+LTc@m<=TKGeWEXA!ie~sy57LxbFU}dd&<9m4{7!}R|KYp(L_^`V)*OU z^B5T?*<^@<8!{`x5U`{_O2RtUcALM_)h2d4Hyw( zRYCr_q@Z`ZPxy~>Xacvd9lLygonBpN>+sE~-dLNV2HcGD@Eb*yD33r#o=_|34ccA4 z5#z1K6WLf$+oKU|xwy1l6f9Rx;l@0W?5I6a^3!)5!EBqW5F?NNb{J61`*Gdd;-pVB zJsUvaX&M4uY;<%ug&+!ge*=jLK1*on-rSX?PS<)OS|7gk*;yepPw@5b3vT7o&A)MF zZ$-}iaAW}7=%{u&g`>ES zRf>*gkK5DTdp_-K3<>a1KOqIBm8gO=V+3=BRM`L6$JVGzPf`mzB>}j0Dpk$2wqGwD ztT2$RA-wTy%kDwXu*M54NG z)#F?*+ey)rU0malERk*}OBiGw%*aI%@3PKYFpzVxDNKY}1}TPJovr-~EBkiMLp8+| z!pyH*l@XeHzv4HNe3%>ytXg$8I7pl@HW}xK#;+6@eAFfVM~pZJ#uBMPLl(+req!@C z>bFZ|JSAPniL%|KVQ?`9J5B}c6&U^?7%)!0prZ_je&@v|raj|nPeZs3lO*Ew7j6z| zlirNYk2yo-B*S6Z6}Kx${K3(!hZL;|MZvEOLQ$(1fbxOxyLxpgqCJfkNM?&AYW~<|mox-7t2Ml8LypzE9yFm3oRSj#`kO3&p zx@@~!RG*ub=64%i+R)U5uZCoC-X`7bz1K7N+ny{;h}~m8?hWSSx}iUl83>7Y5&#MX ze|prF=DIh@ZhycQze&DdMt^(xv8S0{`-7EM(J16UY#|+i}jv&7G~deK@e%}K4MXYA~x1m z50)z2GjH0sAwr!TEb{52O&+AeXUtupzO*%E=zeMWOB~tKnOuq5i}`rW0+%HvU#5~# zd>M}I7t4;FW@O;kpp7Kh)YtvhsJ+cRKV;zHQ6jQZL`1s&P7sKHUycNi<`?kqEm`p& zTVle?B&g{BJUnDeWQbPkKenXwH0jW(YKYSnk{n)PC&+7--usVZqdJHA-YH-TMH92x zp;EU3Tpvy|*#~x%7e?DFOdL&W5i__Gs&Qb-emNkh_)6B}3@dMcCS8Q+(%F~{X9Dkb0I(7y1Cp-g26KGdXe>PMbVj ztR=?ZZ)FGt5&a7D+i~QJ$tSFp`C^sSiv|2g^k^Yw=0x+alfU_}vXvYPG3Ea_0sO4Y zQy&|I%?q3Ok9c9b5Gnv5%hBJWE=6HMT62CoA-tgXBwNrdiC;P#J%92s*~a`bURHGD z#R@Xg_knNjC!XnCw~ZP&2TQ5$n}}#?AezBsz`^9V|FU|1hR^{Nl}+H#3P)jk|L{VY zF!<1%WBc2_X=$tEkhK!|&%p4@H-)grGlR%9U$qc(pKGGHX>`R;2Xt3H9*QC4t2!nA z+tyhYGGoG)o;@)b)z-r;DCArX{F02OV2GG_27Z2-rE^4PvSp$2grQU4>-_Dh734_h z`a_*|%$n=i+)3m55Ur{gqvtk!o&3@9bCBO^j)p?w8DS%vK0YdLPu;}~3iDd8xtKYk z0GL9XXhwA4ez~5@45}*Nl~5lE=E!dME3n?Z^&6Fl{hwyqf2OUGP+TQyEO3pkQxW^@ ziznoP2G{B}hWx_?@o#1Fz7`^npFAj=-w$RLJGou|#V)5jr_Mkv1TBzLVF~>Hta(%M za`G&SwGKWdr*cyflpFBGpzf~NNu8D7{6@gW+IbPKdvWU1+oxBkP^FJ(sh=!iFyt>ZguTR-AN6U{zb=lD8++YQe!cSuax=ZSBprRuE@9eR)5$Z22^(td{N6o{dyJpL z20TY*U^v;}?vk}U=YFTLgrXl9MOGO!jq2X*pR8V9ZN=&$a#i(3`EJHg(~nP!6g|Bg zw-`4;xBw*T$DM1Zv$`|gA%Zr0QNb+_vI1W-f{#Qu-|TS{runZ$nA;E>E4*A(8Rea8 z-0@W;JwS*Z`_?f#Dhvl0S)UD2Ua8#b$H~oIT~MIi^Qy+Y>OGTK3h8n}(q>@U~WYt(s1GwND z<_^Bnr4`d)%*yuR7f(=|o?qCxSX>!t#YVU2{?0Bn@R&d%1l_ap!nKWeckR+Nu$onN zp-*YjLc;}kUaluPAj;c&6gFdfqAv8$pSPj#Gt*5IDLV2ItD{_5VQwXELRxS>>W$jD zGj6n&2jC4vjNLIHXbE;8`*^sdU)zm9>R-}aXhi?upgTv=zfqJ zh;Xp3OujEl$z7~at;7^i$<2?*gynbVBl!AzbO9vnIC|)ly$-;`ca zphT3JC0%6$xaH^7vuAgF95lGpHcUxC!ZS8l=H`8#z>)|KnRaWy`(nA#mD^kJP2F@q zC-*}ux7lGcDcqr`s|15L>X3c0Tee3;(Sb#*z$m}=h#Tw9P4<~6tZs-C@UHK-f+Fl9 z{|&VaE+O>(l}~Os8+_efSMIcax>z^BkBf6$bHka}d$OKwAQF;45 zE^^M=YV20mfap>@jJ))3T?_vI8WR>@Sa@IA*W*&C`uA5PwvL4Re%K{ zkyRK|CWBql)%o+unz@B?3zzlpTy#)xTY^G{S;#{K;g1W{2bXC&;`si^OEZpI)3}O& z{sk=cq{9nOys^?9qd|_zm=6tp@7?JC?p0;o+MV24hqnMD;q$a}YQ5%Gt#NM2$yL1q zJXuFQN4i?1FPGb)pX!qPBj!heuY2=q%STiTBB>>dG^{~?>IkSeXzX(s}{$bZiC6kfBfb_#(+I=Pp@)~43 zt2v`xJ)%E*jds=+TzkHvNXHvCq<8B9e5hgFD`?YyLQc45_>g5DKa(0gmly2psR* z?JE<;1vNexjuK1?U=C?SEa91Q_hv})z+@Hsd~YFHc4*%OJ`dNk*ZbbU1wyT(d_MN_0DC6fRV>+EwTB1yZ{3^w#+Cm(5dd9uCIa~}rM zg%s-KOf$_Vab|wxd+(qi2~ojJ5u0KAjD;Re31Xz8e3IAwKi%a$jeWCXXj z|Bp6MT=FN@Dpo_#B&mG812&q-9+kvb3XZ&w*W5d@V(ih9$eUBRDzzwFokECBqEU&G z&=)u{-sOFE-(f%gW(b=cZRJ=Y;=8q~_-kM*T=;EVn&diXo)RFAjUL0xn?8@D)XUgt zzCu>EDH^P`c1VRdzG!uWM^&6?*XSn`bmH$pfg1lfQ}L?aeEj>b?$zDxZOB(sMvgh$ zBAlW=L|iWU>1$ES=<7>veN$pIPAx!?wF)dMrguB9JAGZoLodU}Rjc3%&Pia^^IiHJ zDJPnP-$rbt&3Pj2U~Ty&R>9gjEqA~f9)P5BpXz(tWJw7?_V#MJ~2BFa#vW>)%|e zcadnuF0f(;I>czEe>qUhz6`rC@IVo1w0d(Y35` zVjxYTQU|^p3J{;On|G_&3IY{xwVrUBmy0)iug;#h)dEX!Hekoh&{yt8i71rcd(wdis>E5_KReWBSOSQUN_7+5nz4>5S7C=Ap z8pM`FOS?$0UM1m%m!mIFM$h|voWC5;$?2>&Xx|Z)G_R|`9znOc`C#MNQQL17T)x3< zS%1|o{vTeysf8w+I%fNkf*s6SAQns*u9q()K!^{h2bxzq7@ORkx^X(jdX;+ioc{5q zDaVRFWiz2kc1DEvr#+aclv!bifs)}T<%+8aA{BpNHUr&KF1wpAE3FxGN|vZnoXhUK zLKK_EKbN;9YLh14S12L4@#vsU=C%2wLFY9Q^O(4=B%(&y5T+~UHZLk%?HsM;aTvP3 z2y>|AUY=bIbk3bbH*C^b3}2UZY@#^aUwe4@IH z8(QL%5({L6b#cpxQTXK5|^$mWpA ziE}J7hQ|YAAB;Oow`*DDvHjDnt=h#phmCf7=#%WDVb$-n`iYU8%f%&bt9lE@M`s$l z?*jW|5w{L$j|$CY&r17|?aNj;c6a8JPG_N|!YjsbO*r=@bNu zl~Hj@pi8j(;o--g)D=QGC2tzM8(P=j8UL`Q!^1WQgPE%OCD-NM9v!lK))^grmmuOY zPi7t~CJpPvq;tLdk{#vy2kjLUS&p54@=+7-p?6pH733HVeUf#-*8lQl2L;KIVW>>qu>*A~9k*KV`G-n{59 z$*|$!nf7-;(R}QSbT?=N?*>Dg0hK@+0qw5~a`W+Qd9>LN`Off%L4fJd)6KLuBI={I z_m4Dfq?qv-JZbKKkJ_WY7^wcaqJa*g{FjlAS)`VD*`U>=ALj{r5aTT$)5BMmwWV>oa^>{)) z!Ml1ROmC6vgY2f4w5f$rfQe2mVA%kaf~e)1dqGhma$<{t%h|Iy>rMNk@8wIgco3X=d$eixLD-kuIu z-;Wmg4wIP>9p8t$+iM_4EZ7-xIWi$6PI2A*>l+M@Y9(sxtp>d+&D{~lnRJPffjtEE z6iHfqeDlr2Wu_I@gGNL{b0D5TUd9JSMJ1cV7XZDvK@+t1rK`8f|HTaZBjZOK8 znB80!+Pkj~Ft`$JC5AOU7R5rhf(qoyR3Jb5MogGQhzSTOL7m5^?QFTY?{sUouxp1E zp0~v|tevx23AIUc`Lkp7woS<@RBT2G_3QAEEeD2(HiX^4;KsYFi9@`6e&}RY1~+=u zv}XMRbZI}8t0G+QA->Dnyiz7kko;bqdl|vl{zYPyD30Y#57iwN_u`is z+{*)dmTaO4JpJhSMO#)1Q5J8Cf-zwU(!jjn=?^QL=NnzmPIKI&AEpgADkWvS|G$IAjD0_Y#sOZdytm>A z#i)kSIT!2nb1sJ0AvC`x3kZZ@=($?cjr=4V$4u5~YE^p}E#P>588T<0~jKt~&or9DLq2C`=6Tl=b^xU$= zH>ZTHV+;pXt@Rnp*O;8f#-xk6+qJNp1X`Dk*&TDL&9cDd-P?oOSg9Pox>(H75Lz%v zD>Dj)LK|%iI!;wO{>W{9DUU>L__fCPK2V`AfgiY8q~d^KSM0oYUQ3}H&s|)-<(g`? zen3L0-+6u!lU;BE`nIouS#VR+Yd{5Om=HbSs;CWzYzyVwKg%4SHT6%pLWJqSEtUk; z1};BvRMh~aEQMl{b%DjU>uk+xkopi8@4&JP=54rm>2j)P?KD=%aMqyAunU(SOma}o zX`c%z)HzW{Kl#t#R&8lf8wfOB4MM6vnlj+?LvB((aKQ5Yun;w*ZiV)uOu9{_L_bBhfZZ+%;~;ys)#`ns-8 z>HQkR2dBDXR0Zk5P+66v@OV0zU0Q*!K}um!-cS=s$xF(vioqcq3tVKt`N^| zIO_WxSjeMV*206xH6cZBAE*moLa_>%)_Cjq_-xkfcT)?4F_agY3MC4NP*`D%bb$@V zvdCz|`Nl>T=l0M&LUEc&4>7;Uo>JDgfzTL@N5UOQ{sh;)o)L-2jFS7~K2mrRb;yu7 zU)dv`t*(tzYsrAdV4OpI|AFF$WA_P56QW6E(aAwun>RW!Y*%VyK1x|==zlZb#J7FL7P^8zTh1Uns#!qju@HpSAfN5;DIP^XB81d&&fi-&;2QM7`}4F|IxC zSs+jFggapD7^i@t6r!fO8x6nVp71VMt9DE#ReS#Kr)(&>EO)3nrN30WAnx|-*9j0R z!<0y@R~g2_W&wAWvoa};rP9}Jv`pXM@|rQDKsD#Uu8Orj8OG3q zwD~}vhv_eKBb6`_I5cCgmYpOfep=L?TMe@>z~Oas6Sy2hlpeju!hKtv-$!IrM$F)^ zgnYI1`--w~drVe*=^F$}6jY8$fZ61PT!fGj8DjmOl>6geIg|533T%CzDPajofjV$) zamZAo-oopxru1!g@gtov;d3Q&(uLPCr=NKLg^|*;G7g$HREV|^Rme=pts&kKN8>`& zols=j&t2EgF@9BW9kKPq#<6Hh+1r|a!DnyGj~hfeN})_*tyfQ)51*N?(h|hyEBV9b zQSA+>1KQtKbDl*PCPxQoi&$FMjuXQd7nDHRqH@`M+nxEvn7z$Hvg^ zXfY}&nh)RWPUN_M4n$8DCo8p{JPAvB*SzU-^79HJ$5a`aJMwAI_zt9~Ugx)O+xDgT z%Kr50y|&H5zP$mVIkcS>(4hvcSgOfbacT{O=C|7*1ofnfqq~ECH7Bl6@OB&p3l8i7 z1Fl>LTCCu5QG>%AvKC9_oUT7&neq%Z2ikRXeMUbE8#9H}Y9LuFG}VkjnfLaxQ~Rt& zccQ2ug~%e;;%qbM`ZdNb4M9dRHsm`ot>oi0k{uUMbjbC==Uj!R*XMRI1M&rcFhl>tGd<11oxFR>|LmYs8h2<(XnUc28>y{qyT;ge!LB&C|_Nl`J;^C?O#-d_-tpLv6q6}7YuJdSDYM4bsRclRBR z-~Y}aH9^Z(;-OW4e#e10VVZWUql}2#^XxWWB0!O&ePWZIX99%n15<$qu1770*3i9p z$&cG1j|R=LqE%g9X#ik=h!_$6tXIX6tuPb(Lv6mfgNJ+qrYEgk{p!K~@!CtL*cQXf z6Xj@RLu3=u)qq=@2e&472d3D3pZn{P52q=<5&sDyx+Hb$K9)=D(0m>58)!kA zeakNQ21|YM#i?PuxNTy*U#Ng&^xEl^g82b%g5Cz@+(G_LFur%nqR*~lBGTCwIyy)N zc)xg(p`iSDWfY4EIU^4tv35iY0(*I#7#qaMQPdCT`HT)yvQc&{ZULa%XcWVf18j+h z2Lrm~IN-itJrhLtD6Qq^cVcv)zd>5f-Fx?=Y5V5*lS^zE0#ZN(;d-I!GM6X;Hc6Lt z?L~ZHwAN^Sul$eWN{F)2p%;8|eOBB@rm-n5*nG~aK|sE~tHmw(9jcjA<2JLs49QcQ zUA_=Wgw(lera>E&+btuz3E3`+?JoyLR(RJ}9(5Bv(pnpAVXj&$<6M9+cjSn#tx>pI zXmCMF0aPJ3J9}d0qhQNIu>y$bXcLx9V3ge>|EQBRUOn(tRxh|ZQ?YY(oob&TmfCjm zS`SqmgI{j3QL{U?oL1kZY8C{DnPI#fVa~|E5o^4 zCBvtN%>$QmtgoO$f)0ZEm&$@PG@wm2y_5EX&(CQ#H(?6f3!D^g;!S6HDL|gVPe~$g zw-lbGa2fnXpo3T6ZA?8fu}_AnuB0-o`a^wXweq`Vp7e8Op$_&PX*_>bNdpK=DwGvEf6C9DODvX6;coq zVaWd?)WHj;`a+uMq!CWbhGOJm#mL1}q0z1h0~2?R?Kw6hfb%Pyj9<2L)p9(aMFfQW zL7N1iK}{Ru%E%r66rVZ0>RQr{yP*4n#ABjPG!(#%6aOU>b*cR7ofg(In^yVpfBA=pivm+ohcf7`7Y`#nH`IX)QOWDGBOTEh*zVH7^`>6lZ56@iW=r;P(_MwhTN-y^j?TJ zPNqy|z`Gw(f*|7a^iD3pV!&tGA;}dJ`a37@`1C}pB*b%APdYfoo9K=A7G0B_s?9XX zmdyFcI*FQ2=|<+Fr~thKu1bkAtO^c@-j9U|W6WurR6`C$*FYE7DuN-srj)RzH?4#? z6Z)iPwy1z{An;|e&x|h^;vhG09;}u2`%wL+g766@=_a_XhxItRIsM=Jd2oMy^(v9T zfq8|cv*$Ql*PI7`xjAv)*Q|Q*t;F7@=PRj*R43y`n9u#Vd|@HYf*n;mpUrYw+Fh?2 z*QOpQ3&I%ajJ^APa_`9fBR}nY*>thKU+tU<8c!u5(U!Kl9+xhO{foUe1LOfBM-fPR z(0Vhvc>La7fXK;)Wy6q8nk&ev1ZD@AHWGAF8&ccSp;1l{I{UsqlCI>u@nl=)38X+=9R;n zvy)DFE}JZXSf^0@QxmzDs!*RB*t z-j9m{m4317SxmczL=aKBqVV6@>+t^A-db$0$u`};oZWQfX>;_n_W*Q8!`%{QzxhOg zz)DFsmRLL}DET~QWay`@gUFqQi6esl)N({5FAYUbjJ;h?hrZ|M9t1plxYwwFhBHJd?@fKC$VaNv(-JSq7V8 z3$s9M7U9W?rt0VD@g`hg@VCPjlBdJP4RGG~lu=5DrXHUwga;}VVaN+eyl5;71vKI$ zGswQUu>SBbN_LDQyYfTIjRoSvYpSK2aNnFq4&Nf765=v&0t71Ml%$yCk-SU`n+Z}QSx=$PlzFj(- zf|@9#wbOCpFzla;Ky*5 zlt0&US=_}#I|_kr%CrZDxNP>&`nceEjH&WfHc*^A#I9fK-0ZjFd2`64Xk(6!tj`Io z4|FRD%Am*8>OqrRtGF4A|EH4r?AA=KYul8C2RY;Lh{?PvqT(Uz&W9lxSx%~t`DrK0^0=xU+qD3b+5b`_G zsPtIW0#7#SsaG<%L3I6}?t17pF5#j^BB)N)M)=(sFwi%ZH4eD++aT_q7{c0*U)?nt zSvYB&i`iSK(T_wqo#l*y5iwC>-LNEu@GB(6r8cpPiZlvk|B>wwB(-j9$JJ) z>gK-_2r_@+mL9h>^Ph?`A(Tlfs4r*tD@1Das<4(-=?35h%{7S; zLU2Y@cL|Tf!cf`k*8Iz!yFYdv)xI3nWyLD@3#x{jq?8Jo!>NC_X0YhQ=uAh=e{YaS z)iJhbD!|Rf#G04EZlVe@UPIIgwrtA|T0-?j6qk^40tr^N>loRm>C~y}F3fs2)kFas zZm51b-z!6fAy`&vqFeaUH5lU%{2%_|o2Q{k)jU6%nRY1C#CmJ$cONE&(<6S^8^*RE zNVzuvKo<$>BE;#aWlBxv6IAn1(C`~h_wvc?!`+>VTOa%#)20`CgnKgeM6)hzE*u}6wygQIJ10e`|fDFT2ycS|T zUp1tWtjC(d(c@7{h2gyjlY#I_7{pamB8?sJ+f!z4QP|?}e;KV8Ni2r;^4wKb)g)w` zRZ|P88&*s*`VQIzG?$CuF-#KfTqMSu4q%wNK&HuJj^EJl3Tw6z#8@BdipWZ_&=g{f zP(_Jn`U9t@h|sAF4l2i}nwn^1MgxhpTK}ivKB644-Dd}(*|d8qf> zBgv?xE_k7Ar)NMu@kM!%`CpzGlM1YVFqwpZu|#j+xDu_`x0GCNPyUoJzEK>aN_T%C zAZIg#{f3TB9^xTf8E6?-)b)m-jPSl?!o>R)Fy^~rn;~O~jbY`+1!yI+UjLF+jIz5D zlhypCb|UEnFXnDgVP}Sz`V(jR;+IV~?O9h{7b_{osGl zJjQUo;2?VCv0G;HgN%VGAxp|B-tHh)%4*8={Ktu^?7do#`^480sm>1z*hBD3*l(18 zcjCq0aHkz9_u?+hF02Pp)e;#gzdjPXXXfkQB>r`gA9KJfHU`~W^T2RX+A;rv zE^}QUdzt0k2XDtATj0O(%k0ixV7#P!A&vhBKd<~lBJ6YSuL76pWc^P~+N#MX@YyOq zxqbabNtnj=l~Jz5fU@0+1AAi$d~ouz@F2n_hgv332yvpc$@<6NhkhOv!m1GD|GEof zYGEKMrB_ZHU5fmd!^04lRo>@z{ahAU_|s1rMx`)V`r&QW9p482xV znk?bS6R9K_3Z_KJlxzs*x!6_$#YI(W5{O!#F)3&I$|-dVP9a7pD{xV!SNeQ@nZM=! z)tGNNu?Yidq56_q7!;gLeFd0D*`(RwPR%Apsl=_%h3Tk5BKyh|z`jg&1y=>3;A#EY zTB;;7IyoqK{ysWPntQ3&X?e_xW>{$wtA&?W{a<;35~~TL66X&C5zO*|{eE;j>Nk2`-fJ`9l^g-@Bt-pkwTY~=u*qx8JRU|9GK?&-k-A7h)$Y@`36ZmASK<*AG7NKC1A(&#N} zJYM173ekm0B|B8~Fjr&t?XMj7G~BB*(4+sA)lRzCHrlbhzvlG6WVOo6$$S|DcVjy^ z37oU8A4h3@ka{A)N2`f%Q=o2gf%rik~Kl3B0* zs;I<-?yu7low>-%ar6%WM}I;Lg;}h*{J4cXU0K++GM_`4$Q_%&u@M(bhT8ZUW%da3 zM!6E)nSuh1Ec}K=0N_CYGD`xh1oT(XiYaVu(ry3tFF&h3rnYmspNUSsO-|1sG8_O+ zW^SHhI}9%0f%+456#?>&SPx}dH(2OYN_z*H+=UYBJ9~74m|C=cG)80=yrZCZh)WoZ z83Iueq z0h?!?%Cu)fH~6?$?-0JqPZ6PHOMH`E-GZdLpPgo z&C#R^eci!-rwx*TiNGB~m<@~jf4bun^Z6ygw)_iX{F^%_nhFfF7SkYUI4~@~|BtZ0 zj*EKh+Q;!hY{f#55U~jXK^g>9L<9+ylnxPT=?*<6f*?{-Lr6DB$DqidQj$XtN_W># z-?c|Qzw>;a=k@yib8{b$hk4I?@4eQwuIpNBE3Pn(+53}eKW@D$G)`9LE&$!X!@j?R zOn9p9=OdW5U<=J$q-I~(Lnbq7AD~))4kxYoe-8-&O0C*v+X76xBzHlb#YH*9k*|!zrodQN3`~0)Ll=wvXLxx2 z>;IN)G0=DZN->!GE8fiM4}AQcVfnhek{FjFp-zIVrAe=@!W-e$Md|Bly(uF$EF0jZ#-EcvpXq1&)xJ1x6! z!_}e41pKk(J`9j;8f&@#djhO&S2K}(pP>Rrpd8%Fy3|CZZDJ;0_57-8zAHbNH8T8D z=nl9Qm)tGRe&Bc`FLsp)eZ3yGyCrA{8)8d-d*%G(F>+J?>2&=)B!dnveMu^6UFUTt zUmoT}yw(AI7NEwY!L`Q$AJ9c$t8>yHIm8v9asvv6&F1;0g&)ZONTg9BqDO^Waox4Z zq7rYApnwCL*~wb(KOB)~8=^cvU-oF*5esH5)UxH9Jr?FN*;`Zvkg=9}^ZCIE_AU-` zEBUWeaFJPk=WfhY|Np+jwEZxQGQ>FExd$)s&@WU1|9hp+>WHav2pUgUvcuT11+I-* zZh+K50#l`_wtjB}|IorBmE6~#Z(JE|z#X_60fW*w)1xYn9XfReZYR|i6+p9U$# zUSRgAVNk%{w7DgLS$L@C&h-+}2BWr1R)4-cBU z+hRbWL)oPdMHbhF12oH>Io3>qgOth;1IV2HZ^|aynH60drn@ynluxFY4glx%I2yyc z0XHvObdJY?-vzaW@Cx*=%)i#1B*4vdZqNbILHmblXpj7~efMRlXGI#nE*GOfwMu-cg-#~nrL94`^=9LgO$ zBJJiPEyMClChc_>-!5r> z%IqSLykSJp$yC2JPNPDIQj$FqK1Sgrx-!dC#Od8NPe=zLN}W|){Q-SVT+MQ5zm5IK zGmDE<-}3TttN9r^_GT)U`3=?l1s_M{R3^PLw`z!``ZccGyV*S_*~>OxESKV48K?)i zYGCY7_jyZIp8)E9WtDz0M^16|gn=&|d-ohT@g<1D=fMU)^UW}7uyE6Dd(0=uaC1Hr zo_VL6dq^r=Ad#_5Z!5xL*7W67B}wF`L^GjwukJ0YHSdGT`BPfh?4S!dfnRaNe*%gg zMZsI7m)PYtjawcu)9rv!7!l+P3M+0L9YF8yPC2cnjcxlC9@@abV8&|RWASDtL)WV6 zyj7YL>qKlOK4e3;XsTq1cz5DDbkE|BR?s>Z-NQdQF2mklrt5ckVFy%4*k2E-9;%uL z*6W%tc+*UAHbxY_595C9cN??r*U%|y_e(jRC;y~xZ0!~M^<82&BP@%x9%|Fg_7B&|-v16NsCEt(Jt1ScDIW>#bUmr_YYvrEso2E^gcqoO< z=Jc~@3h>$S=MASxB!AoEGIER2t-c!X-C1m_Gp$Q1RW^Ck^WIkuEH-8~xeK}_%g$99 z#iI6e@7U|GAqHd7`Uu}pR>dwdyngkj<(;CrYG)anjfiBB z5R1Y=t6GBM)}SWP4XK9|lkn%Gnzq9YJ@eGbCx6%isio$l=*vTq0&#*fPMs`8DAp1G9uj zsiY=$BMLh8f#gv469pe;JnxXwFvQ5Y18*Zt<#)~~| zywX}_$*Mg**7V9-QQ2|o343Zo%1>~v?3#dY%YnsZuK1f;jhk=4+Y^qA5pgcQOO8*G zqr7h#+1T{+zx!eDLcGXqmwxev^^|48lP94re%s;w85z~8nXK-~L~rF1ZTomyhm6iLL~0{Sgxs~J&<8nU+uB>4yQ>iyq7IL%#m4MUTHS*jYS}} zq$}=BHM`wnO3Ct~*y@*|Q|q#)4#|fe`apBZmkVvpp(oGr^iJ_vhkq>k#xCm8 zw37R1_{FY|gBK3Y&-Kc!8eI88kufeqqgF|Ii{3+t4U+}?Y;A9adu%XUxzKEW zED=$5_!#ThuVHHQB+705r@kh~TH$)3^}Kdve1dq>n7QeAy|YEB>q*b_ zrD?9apE6EoS`TNHUTXgYgzegu2pj>;*%pNT%tOj3j_HihpM z7F^fuEqsi9S3&2vL84+}g{ffj2eJ5_1DL_7(u9q3M)>yHP}Zu3H(;9EpBu>>Di~(z zZ2pe8qY%E%LaVgkB5h%}snzT$F@I0b>4jldJ-{Z>9@JC zsW8bYKCsx$tCHugu)A}v{X87&K7#w%(NsUnrVCq}VFWhGqXRxvY%;Ta=jrs1zp?Io z7-vyu~a{PwlNZmqZ3U{;V*p{>QV1S8-KBkhDf2fzm7fEb4imqG91W zdn`p*(^E}%IxmB3V>~lo)8Vr~*|E&^H8Y!@{G$5rYdfClR0+{BCP>RE**q!opIrII ztHa25xDdXgMs8F0z4G`)?-a8Jsu^$0jqrb%ncsEA7K(ZH$O04o?AJd=!P%FK-TY({ zwgo}luX%Fx0($c+XBZLO7tz#>t7(lH-WrtZH{;nL*=VKJ!*z1&E{bWrHDG}yrLZ0M*ll$$uxa}g7Z@*Ha9Q^{nQoxW?ouC$znuV(`dX2-* z0D?4f*`=Ek6dJR=oAFd;M$#7$s1V+D`#v2t)3;Y8hAlbg_NllpMOafh>fd=KVAWl* za|%O!kKcu#kJAg_2Zp=Qap}cgtMge z4cC*aC0b0RxES?hBW9FjS`MqDg zSt#mNx6RjCJGdZbpKBcDGG`Yb7OttH>8OzLCe_7m{+~)6I~oa5RbInb9|_UrkU%!J zQwpa(y?f4&EIjS6pTIamFP;k6BCbo!m+&__4fZ{6I}=`>6r!`4-ca4G5kXDOlq@aa z-e~j5gz#d`M?2JWy<1}G-fh_uK`99A&i2`iwL_Vk=&;XadJglSfWM#K;E}o9usS{$ zh~rZ1(pi5-7|JW?-ModZ*4ShFd@PyMZ-0{0t~hDgyQTjJta|=GU{z>WkIe8xJs)dF zhcOA_s#VR(+(gy-+KFfdkGt`ywA$r@-33oPtE*X4>!%%a9Kd&;OY;YPHuiKS&xSK`!o<0==Q%)#90#va_8kElHhk{y1`+fpO*kUYgM|KF>B^NPB;g! zP*Hp{YcNd3u|PU=W7DGFm8&P>SQH@lH;(&A?#wRUpDeL!JszB+L!`EX^3C?>A%o_` z7mV}1KBd(*C)70@T2>+;@P0F^<@3yQv6A=nJDUukjIeRyt8S`IWpS+b9283==34nO z?}Nc6+P!EX7eEGX2IpK;*m?WaSF_*6S!VB$JY%g8;Q?EUy|!Un=<{3hE~A6Qu~vd( zQb|`o&Be`;EQ0zb>2kk;6dambQa9-ZJszv{{CIuxra(d&3{2x4ORA%b2m( zfo4=gEu>-Xsj%dtqpoM5!Ex4eY`t>>Pd~>V=_#~^i?Xd5p%O1&c*Y+*TRF&Lw{r8@ z*0Jr}222w7Oq`D-opra~Fhzt%oU`Ro*nO?f4fdt}+O4aV0Vbn}E>uNfdx@h2d(XLC zwe{j5R8rwKO6_J@`(%vq^qm3%Es;8TVw2(NTa-p>^RB5Ba7 zcV^A3Y_dvsB({pp0LuH}<>fBG2dZ4JU5gf^gUXJimv!)I8|qpG5m zd28b+4uHI#-Opx#ie7gzdRp}-W-j`!SJCdLO5|0BMj$^xf0+;uUhBT572CJJz~OyK zTdR>)MN)D)AvJTFTZg5Verz#fQ{*F+vSG7P{|ewiNf#{aW`Ve?@LBwxI^GHqDGT0A z63#dFH7jj!dHquc(D1u%!QIRa1PVCK8gqS zsBU%!!L@$-p!nc0EE1-N4T7;tPrdc-yu$Go+wpqs8HU_(M{y;tfQ2=Q^$ww_u|m@m zj@qpsc8@Qgaa?V>iqoZYpCe@COdKdnEDW6bLBV?3EoCq{x~VQU;`yzRAe=+|c{x2{ zdO8cRq#rmgVz9oRBCU#dg8&ahRQ5k^6c_xi`97}fdF<22n;u)$j{DZxe~e@otoZ*j zZuoe)^#L~&J7de9E4j00Yp?tS_9;HZQ?ze*@u=O3?U7-nb<(*n4O8>Ol;>XT z(#mQD;HT)ZDR@D^@G?&_WP@=nfYsZ0*)n2)3gss3ytSDz+W=C4Bv^)iT`R>EfA4l+q~Zorj$P*3$Eo?wKzn zhdw)}2x%Lc59H4OW8gk{0p<|{E<^Vl?xz&LiYj;L*}cVS7^e>hn*|PbzWACL)cfob z8ZneFzPoW@pcecQr|XfmEIBtcq}t!VlSHw8J5|E7@zU1gLWjw)1}B4UEd6~6Mz?5F zNE5>Sr{z9PZMY1?Tm)DAI@E%#k_XdTMW@5}t#nT*phB&!$!GPumXIIyu-Bbmxl0FV zXhO!GNG&&?2rvKm#~3>nN=mXI-X_F|{j3nE;QDcp+WVb6cd(XgnJ!pv^AF&w z4kj3XkMC*aN#goh{-w@4Z*cJx_o}JfZP?GX&rE_7M=Hc`(I%$G%;wbV@@>`I#>Cvo zoOaTMLiMU7GYmCN%V>VwbS5~_OIA>ETb+971IHA)ZHD@m19l1mZv+Jk*agmipB@kO z(5=_SCZ^R&70-1TBj!zwq7u%vs+`?`*AygLwvg1yoR!3hhtGl^a2ilw z_Uq%|C>O8sjpa>Gzz?%egWy}If2~*UELG+g&~8KE)IR;-vPVl|^azj!{dp%k7Nq1O zFG(4X*PARy{SqHH2asC^9PTL4NAZpmfUL1*){ql{j=8@oHjmewHXY9;zO%*1pE59v zJxa|qYEL{8j4a9r4-X+^}!;%;kq(}VRe;%4vWDsHcD=Hl(q!|9hIIt>Tgrkf$o ztZbCV41v7zvz!LsJoGQC=IFS|1s;o&Sy=|ap>un;Vy&{Yl$kkSbJDpe7$*Uv1q`f% zm34jn!6kZ$35h$iGFH0^KiN$)9FVmCrBE4v9=p$sH$l36;=jC=cd8bvecWeHl z`NOc?J<<;ahle-Rbx>OvkVAWcO<&-daK-M{A5QmZ5>u2x|Clyp zcG2TxiQ2*HH*7n(9^Fp2Z_gf8Qke`CPvP>|6PCv%oX2Xt1Tc$*5XidIXy~0`DwtIv z!|Cf%Il|2DXe+5J$JQ|l{s8BvyS01NW=`tYxp~Qg5+zSk%`WfV*c6qTklUe@KDP;( z#2+1`ee4n*vYhE^ZsPgFXK!!TomjJF6p`p%g&qWVVa#j{mbb(8So}e zUB2+0tAEw}>zPSeVkHbms_9G_*r4N$T1quu#s7XyIo92RG|F#i66mv(&_GOMW_ibj@;j*V@O^ z#wNHb4dCYx6r)PoJ!Sd&RanfxQ=$IhS8KPv2L>KW^zVZOM12n>K1`<}h{Xj~&lfFC zNZj}FO{k*neh=uLyeNt8;WNl8cE;RA-6fMbnz zjrbj4YV>7&UszPK{CMZM0Zmstbaj*CJ7?hQVxj^jjui>A;_TaWE zvA+HLnx`RD?HDWFAE&Emxb59gn}7;-a?6?rvGvume;V^m)UB~(|u?+Vs`*nyI79%lGd!OW<_Mae0Pj;(E`vQ{|@<_?uHqu z^!wR>Q1KzSegh80q|#B63ZEVjbC1R z>^9xdf%w)BImN^XZ0Fp~D>VHJU+cbQ`Oq+P{Jxa|vYQNG8oK!Jev!v{Aq*7`1q+nv z-fmCar}Am)^k8NUueb*sP7Q$VnHeg8%Li=izmD$Do8a3({4NI{m0vaqm0>ZfvUMlb_!O$9wpb8GB2vOZUdX)=7kwsRpIK8A~ruXpvC*QwwLy2LfL!AF zM%53mGp~WP#uIHuBss^o67qmud~-q~UTWWvi9V^! z+dD#k)!k6UNIGCBL0wQq>usdTWtGIq@5YoGufzGQV=oQkR4c62iM=Q; z+=f6DU%=r*H09&U*Xtt_&YX9F0LqzoaQ^+Vyt8a)&t4B8xnVq0f$rfH^aRXP+-AR# zK|4|V3OmW)2Kel)Ax-e>lXaXHVFLDhcZe3A`2%Ft^q-&ogJXRoj~yeUE$bbDlZmN-D<`R9e7ll@I`*y?J!#29JZ zdi3CWLUr|EwXc*|z`{a7`GYRA*I2P;vort|P@9P2EXF@pVGt z_TK|3v{`_aRJ*F+mlH=*CbbVLg^!!%Djr)?5n&O1ww7-p2*B>kycSe!du*r5x~<^7 z^us%V2fc};fLRh4TCdx(po*%ss3Uj>IyTB#sW;In$s0V}-x;3#)dW1!4bz!EQlt1M z5xMrmJ4@U0fenHJA9!RW<}5-%tn|z#hqTFfNsD-lO~&^5p!h#Q9##S0-$e4!(n7LU z*IoO5!&?W=?+qSRHCWCYF|ohy)~M@P2KS7f!dymSU>4CGOEk=L8efXrTH_siyG9pR zS_Ynq@1sn$%`H2jx0 z7FqmoHPEdmoE)v#6bngn_Xp_Ga) ziA_HS^jRWZDnR3?sDCOB3tqUP>$!gF9LvrJ%hnjl3&K1K#Lk@e1`&52q}BPR?d9C5 z5y~xQ5LQ0l(!U{ao4sA7WvwJvBA8RVd)iR$+0G1oA(;m!r`kw$SD+$>KvT91nzd;m zv0&HSwAxUt9o7#D4OGdXo%%BfJNo6M(z|DxptX7PL?}2F6&Y2mE+2i`Gj+X+g%DT6F%IFVAK#3GTL0}?L=(=8R@4h!KoJXO$~Zzzvi#w6a?2x#IekXs`U(`u{&u-~ETBsN zkPg@kL`EvzK>5?UY-AtC`XZ%Z?g6qYU} zMYnqCSiMr!D7&HK2v8RmNZZP!;5h3P&^E}aHuRZ~hBhdvD6eiR5VJpfhe>io_hnh% zyrQ?6Hw^xH3ygo%*3Vp|Rr)4IEsU)82w!$fnQeKe$#mK612Jz$GaK9E2hlqe93^F& zBE-j~6JUg~HdE)~PjN_^jKLA;^s`{w)9jbiTj0T#gpD~wHDaFQYdT32%P!SBZAF0d z;_7#QAm>NdV%Gj>i#?Ot(P5frOEkw)K7*hRj9R9W13o1*iC$LWC2!P;BHRZ#nLXBg z>*Rdh?hEja+uP|f)~vx}{`A%BE6V52)q;4y6Xw3vrTG!a5jxRFGJ?mr+&D#m$S;If^2o0T?5r)Y>p?g9;(N+RX@o1MLc9*wgLDbn@}}9cc&56oS$IkV=(l>?UqWFaeS-8v zLnAXOf2x#SL368g$Dx%6RW~4mYE|86=(A0_V-5%0W9x+kqv!DAurdQ#ECagVqR=x4 z$gevRufh=@wJIJz0Ke%2wf*&4aI|?i)8P+&c(b9b~oNdRpPW8w7K3?CJSjkZ@?S$u^oCi?~zJ{zSCj z?l~%985f>6Bg|NMGV842V^DAouDFB?4lS2B*|VJJ4VCDtchdKC_lq$9Ud8lXx8%E! zM{7?qsE%+eS$9ly^htEG(OWLxT#?j(R0*d7xvVa5b{9r^^&ibFX4_R3o0{kvp@ z?J^}c{gL*jks}mo5OEma<17y)fKN75Cso()sAGumshOUepB$%0rZ>mB%@Jy(e9+4c z<_C$DO(N9_f7NyAu@d6C>6N22`U9`AOsVf_SvcXOj^KS6k)kK{RIxRnYSd*e8`p|b zN@r!Ag z?N^mLAm%M0k_MU$VFgSI9=M_Hk`&dbUeCPH!*QZW8ej0pI zJH%k9!oOJDvXD%+qV#d~HPa&gG8WKm(;PKk-R7FsmE$aa6|iZ>P$kGbwQ&BAp$9fE5Vnra z>N3GDAw?Ckn9Gb+o2jL2lYfKYk>M|Fws(pN<6Mv_Aa5v7=)w?FKSyMC0pm~Ei2^>` z7&S|N{Th%Z3*%c(xyxtY@08h5@|qMu+cJNm*W;Q=9cN;qdZelC z+Ok{&VL82E%iwa^d4A!aZ+g4HWJlWQ{r2tBmgq0L=M0L90DF*7Q`Hb?03?$R<`U0E8R;vS|E%C8-&z?R_n(B5ooaafj&Cy+E0|=@ajk2-vnOp?Yj4eK!mS^?% zY?}l-A@DRR_uShuhaMJqml_vamkyVKW^7uyk?InjyvpvuCaTd`l7AQUU#uGvKtgcN zlKS;O#Gffh4nAJb!y5#^Bc=ZqGLcq>3}L4I;h!W$(O z=t6(@7p4A-Ouy`g0O7JxPDHuEHitOBLw6@{$c(WLpZ_pO?tAy_nJo!L>4G>r4p%?^ zv8ystAEga?IFF~Yd$AMKcYs^4*aR3IpTy#o{>Hk4E;Q79TBc*1PT5C>_A|{K|D?R& znTn+Kl7i_Q8()O2@zgh6_75mWXx@u7Z;FEm4Q5UEp^LB*1*N)(|CLfMstGyZ%=5<1 zw#*ocNW!QXl$Q(EtFyh+`x1Fua#p17!jwMCLQfT}Ynb;tm%VasMS^+L#BVghx9t0F zzaJ#T;HKYCUtV1CFU^GCr4c1=KW--Y9u^epTn_B?xY#J$I)jBl>;qQeQwl11y;TAK zFRgg$(nn)-hqYe2^KSsU>pgf~f&zbd60|DruRB%yUwqT;Yj)rnkb4Kd4T(fcfRihj zDOy6r?z*65H~H4k8&6@$M^p4|3(i^;T0J{{K%9jC0Z(C}Tz4#@{s#qif^vT_>=K68 z@4~}1yOiDYJ-pN)>J%`H+s45wBhA`w35^&WA03)rBRY@GQbgcBQc z|KQs}?jKpU|GYD6=lq7175$I?NT|T)7X)`Mz<;%eO^oX2)N?4}1mEE1dll>*U0t?H zPa7wz9*phiw9v_zRFwK7*gG&^)v_6h1*)9t+{~#dUGK%@x-(s&NyoG zIrx@WHj3f3Z1k;p3Px`Whb>5IZ1bEcSvd_^$8)V>CoWTh4q|%CkngHaKO{Xclj%-l zAuWyk^j)Q7XGgY0Iz^61NP^{Sb(y3M@%uoJIpS7T3~NXCw@Qf3GxeKszXHQBLdG+r zspa5x#BW1O=)s>v2X!cvNfU(S(e&xbg_kChOAOoO|1aF4_tIx=eZE1WKolw;%zgz# zp_eU2@dSev-rg)2%~7h*^OSiHoFX5-ob)whXSe<#j92!3$42Rt6dr8S%wqMH>Zwnk z07YU7>wusJt0pa0{~X>#`vg{>0>gbf&d72Bj1?qzx%&fRVLW2jfYSiW4)c#a!T+_V z`$*CLVe_GOfLN?LnenDq9k7zrjo!G$<@8HYLaYCzZ&jO36s0$O9gRo>G3rBDrwed` zL3X)GEayz#KyhKJux^ffeFRkT$#p#}7r)L_8OUX1CpiEuFu>snYY^ww%9g-k6%-g; zV_##Y@De+jI}!(?CYM&>2M7)95bQ-gToovdT&Ok%n|dZ2&2XMwbZcXA;icsdkZCCP z7jZ(b$=>#s(k57*?CxJ4Y zzcq{<6yPI796yql>23sf?!pZ!NR1G^eia6^RP0{r^_*^8eeH{HC%RkYf{ulV53f&) zD3c$$4wL-&P{xkRl#|AMSj@I)Pb%M#-{!IO+HWoE&g-sJP&+pXPpzXy zwk*kw)t$gEL5g9;Q+Qzn^so7=-scp+he6P}r-bIvW40gv-|`q{=9KNv%g>&MiY^|b z4f@(2^Wr21vpEAU_Na{)K0HG=Fo)F1G4zw0NaJPz7%Vek<{y0E)S(^1@Z*esW>qkt zR$19l3-}B#55!wLcx{gSIQ-jX0hd0q0x8s_QaNf9fRsd-LsZC!+4dfSVpunMgo=Et zF}iIB<8?S@yD16O4$Mpdy12YgH{Y(b;Mc0ZI7 z_fWkgxXr5qah>4o^UVgqpr(Jd8#>uw*>3)B{osby4>ya4urx?f*WmhmlaR>uzW<348Z+1$UOWsEvG&>Em?Jg`lI71)$@Su>5B`n^#S1HU*S^in~4 zqG7l*cgvrL_Q;VmfX`?E4)^Z&`-YQ$*#&{PpQ--aSqP&W$n7kLxI-CtlmkATV}QPx zu1H!h%n@pS$!~kCQ&JW!FX#T|efI8&`aRd1?xX^m9ZPo#p6L2hMU}gs&5;9X|6S1P zpjiP%$1#b-G74IfZ+y;Ym2kMLMiriS&ZN@)0!;fkEQs;Bj@+Rf3NDtG7D|g9Bnlna zK#jgdclv;!sTjvW&hA7H@h<$GKK%t~7DD+5L%03G0Q@-f6m~5f6?Jn8uftrPlkEK~ z@V=l9LzkptD!~);x2BAM&5xd;@4%OaH?3eCH%!Y)dWwe(zD{F%216%y?a4pjL2pJ( zEdh+TXdh!RlwUHyb>Ve58dntz?F;DLJ$jMw^qDDOLqoLJKsrW$=%ueR5O=awP4Et; z;_F*uYuPtv8DODi)S$~s&kkBFWd4`=m<~fWzTUuq5x-o33+VFTKU;@3C~G$*hK_fv zL2zWf>V_eQ7Ga`Nxi;X%K@&(-pygZ%;Y{Ewly{w~R}mSaBtvcqp}iYMs#GWPi&O(lxtv#_0;RuiK)AtU3h1}q?Y%l~4msgyZG z7W>v76Q4g2t8J%OBiuaj(6?&O4+J25jLL^@n5hkO!wERdCCE~FNmYH}EqTalg<@?z z?6Ym{((%%0wRu=!wQJfzR6*Ab_hle=&Byhp42 zc^LZIfzjl~gQ-KsW8wpuQ;BB&y%N!2V}_!F-Vg=acJBXX68HRG6u{^%{Tm(rv%LF( zsgC2J!05>tT5iV}DnjeQIhHTR$6rhzC9gd!48|55hVU&gE`yocM)X~kr}ty@j)JT6-!=bpI=OxRu6aHLs;PRj z->dbhdp8E3@qe!Q4hLJv5!?MicX)%n4AX<#w_yyM1G8>()`eR6sI<1ELJtIbt&gPL z>{n}Zh0XysD_$in<5u+EqpkT!4QYfam518GArJ4C6la6|IT@7-2vCi9uecs^HoC{+M@AHb7vV zoZjeeBVp#yNp`cbmhR36zn?su{9-@Xk}V49s7nvCv-Yp;7y>dov{<1>GapFh`2@`n zCeQ&v#}7FaQaURnI%bx-4rIX*5PuHHH)rTN|6UoAZEM(pmv=@}ey)b?7oBxAj9G?~)#`HO$ zdKz|_;RFOuWM}$~1D-@i!UqT-dku8B;M*bpLmV|F{~jKo25<2WF(Ovd)TE3I=T@rJ z1Qm66BmrKpCN!W<7a*8{LD7c|$}J-snhrn--~Qi|y>fvZ&COL|p{*~2l3x?dfyS&} z@%r0&0%>;7Yi_trMeRRF{O>v5?!U=Etz}ATAs@=4@RBeWU|z^vU<90-AZAhWozs}V z9|AZq5!Tnx9GC+Lwiq3V{cY-Y)#l_3>Vg)o0&PLE#RiV!-=w_#q^|EC(7xnQta@1q z`|1Z4&J8(|1C#>w9o)3A+9!X17h5>M?x3{r&_Zm0iWwcH-M43R4f%P`f2X^^!T!gE z1H%2^xSp;ZdFI7Ge$b@Z>&Ab+@c);JyeQDSn~{#Hx_mPLyo>18cnpS2*i{IiDzm6P z0DvGmPun6gr9#CxK0~l4`vLyng{PGeZOai|_|MvVkbgJfZ-1w#XZD@NVMcNW)T@m{ zq+rEP#E(ii%7K8{e8`==$A?rng*EoT{Mg z77I*SkMYFP@&xjVicLc~_rv!P1t%nFm57Xhu1CK#0LuO8HNtqE9R*9Kh-Y>q`8-me zpSi=Ih8(M&x?3IjTRqdZ5?m5f{KsaNi_YBZ3Thy^H~juWXNAi$h;*3!aIM!5h*mD+id&O305_DuVh##R&lDf*9X!WxJoo@mxC#hlJo%^1 zcp%eZa4^*|J$odq15gK)P|(GS!32D8-z6^aM*7bdg+%Ui5*k*(<Gzttrg2ZF2D8$lJbBn>pl^~<((_0WCBx&23NM?1NI(IE?lG#s zpgIJZ(5dQxhnw8^9GNM=xTB^@4Uf-skZ|{xf$1qSU6f$V0UGa-;q`_+XMkjHb?KUZ zXZy=|e)%wk8Ki$WYH{Ep)5!)#QUyW^G|-Tn2K8!V(2m!m(=N5^5IO-z7r+{t5ygEC zN}s%LABVJGJ|M#&Fs!#<6kv|^Wy7DBOO)W{>!k)?BsO!-$hd o6~993T!vxf#qyN1?_K;5;j)LhRMx!4t{*6#lgf_DKYA628+^ zJA`-N2k@xku^wnG_7IK*sZ&kh`%gM!a$C3I-r*j|!TaNEk8#mghj89|=x7QHSU>h5 z?=_XYL*h;hU8P(&<%e+jJ?(wlFoo1&i36c-ZN2)a6j*@%+oY6)-aoti_VvA(8Nc5< z6pzwny>2OA1sVz1r@)g6FNW@hYGjOI+?h!Qs+nYiu7P2gpx%IQiCUiO z%rgT5UcA39-2!&p&YldQW_cmUK$bt$R(!tpB00-`JW1;aK#(hS&WPx7|$N>4a@ehBYL+cjUVH}OrKTk)Ce zty26&9n&0+Ho%63OsU+NRDZJzI=bB8J7xRB(MIh$$gBinCmBT0DYg3|3yEBJB*CX2 z&giJebFW3~RqHY!I`c|M4#AwTq1LuQOIWYWenOH^M2Q;x@H3X$C>meds7OJwG3d zxoDBK&a?r844lNajJq}q_BtF(27lbFFc>UR-`56hH0s$%Ty{jQ#x>Q5ZEUKIk$6SG zy515T)>ECXgGTs|8+8R`CvZ;s#Yo*-t36zBIses@;|mwQ8${XWZ$1Gh91M%XU=U`m zd0+v@@NMN!x;`2TjK7WTC7fmr4S@tNfS}IneEOLZB zjk<+hOl!_~lnDm-R0(a4F^ku%OFvAe!R0{G9u;_eikAdoKhG%)l_v0`Y2)J&`?Xo z0e0zox_y^sRDkU-<#T`Dh$^hEuYz=Cm45eY%dzYJtsa_G7d@ zH~|f0g?t=PiEl?;KWbNNd$@nN7LIe zP=Uv0>LexmUS!PBW}cc~%ieTaCE%1C3#uXP$IF8aEtzN#Q4$0HUt+c60;P-9_cy8| zTdt!ac!CXT2U*$J#1!-o@4=k>5n1x+n~?bLbBsBowRB{Dv=EXlWho*6r?#^Uz>od! z!2jWp=yj*I)mjDIjFPtLI9mTu@O*&`q+z*j4VtN4=3doFE)_eJYDWLoeGR5j|Lf#p zZlO`qYO&*Q{x)g-;WO*FPG~p-uXeI7Q!HfZ8@cak6Fo7MgAK<~@Y5xr7oQt342E`f zS|6~KH+_$Y2Pf2v0sg!a5xeMtENBK;eghE^iMzHhiDFwT#g5>aq5m!at@QG1nKps- z5~YFQZXSnPpI1%Kb}D?K07h&~V?JR7=CQz{sElufWov6T{YwG*{mAA7f)+H8lm-c-MwCv^=#~pOQGNUAKS9dn z*uBlA$5d#b1{&(1$fCzc8YK-~0e0M)YYxN`0(7rJ z{KH8iXcymhrwy=Ok*|%XooLCRf zgO`;$7e~{%`l*~E@_sWG=2Q4Adw_1jYOSD!U7l$b+#FFUT&Q;y-RQjzw}eK6$0&@R zu_uv@@&~g=2H^4_C@z3-D8ox$dqQ*MxVfW?S0i{~7vVo~ufG6``P)Gk0b$&^t`+>j z;~>r;R|!XE2?)8M<2?5W4@35NV~H)n%<=Bx6M@joA9L!Inb53e=HyMS5=@Ff>)4&O%igTMVU*$r!G)w8}RW0Yr| z{Q&*~YV<-a*~sw+S`<2PolBp$?m{Dxf9XYJb)SZ=2Z^@C{*IN|1E?tjbR-==cQB~J zPG4}%Ow7L=3-+0#{dyH589I5QJ1`iMic#Y!hOF1k^eG^FE22J--qlK&Xuvkv8`xW? zCx#miAAWzpfd%L$pi)=ENc!BA0^A6sD^B5k&AY9Ho4{fRoZ{~Mc@{75_yd4ZCdYoWIR~>hH zI$M;#;#M7S+{d!bJ?AaJT&5AVv$=BYOG6igSp*`+;GpPaOO)Z1z?sl7*SjCCHYU1h zs$~|hMR=G#GdSM;YQ_Y{I6%PqQ`+id^mkW!hQQ2 zIp!!a=9l34K}{&d_e4g5c9AbCRxtuNwNuUQX0@V6EX+%hw2@CG&NmOmle!n$7Lr$& zkq?mr%L^_u@N-W)G5W@gwBtoO3!IxF+(-Ad0c z$NJxC&O@%kD>5tuEM7r_W4&nzt5ms}y!p&j>pO>NP{1tyX;<{KQ>T~+`E z@yW*`kw;0jg&z5EBtZIpP)@1Y5?PWIcuM0895^ql-v#136Qw#viWJlFJj*(plIF_Y z$G~H3Z)UUR9)YVm1Lz*KWTJM*Lwgpvec~5#c-_T~VOz?|D-`zo+6A>5zZm4lUza{m z^;X1?lmAC#AHeTinxcDe<&ZKc@&t5sRi!O07sQ@e#t`ic;m*cbkq8_altO) zoU`fhqg;{XT$~dBv5Rsg{ZwX>t^P3*kVIY#iql0ha4ID2#mXYSo~{QqrUL$jQE1>7 zQx4_**r0`MS74BNWTjsVPTexyna707ao|q{JO}RS#%Xu(QzB0sw2zNN`(Mqq-~`2R z*Xb@0VF7JL4)V`KG=x5av&kPPM*K6(AeZ}$n}X$w^l2H_q4_YET~HGm8W-PT%YH4k zBaIm1mPd~j0Pej`D6AfS$%KT$$}dp1bubkJ&;h&;8sd#C;M3+vLkiGXTp$L_knS>-Od-lCv?_1|{&guMqe|#Q3Id$Lf_iH@I^}L?fQ&7G^@Xq=} z>7B#5Uk212-ALi7@AazZzs+eI|Ba^W#W}p2qmVzMbK|S$-2H|N*1nFp6yR0twQ#h< zcbDGx&ulSquv$%AjWeECMogA{pg)(?GBV2+*&=q9etZT^_sf-rb1!DIy&^`E@b>n4 zs(gWd%YmaoU}T)`PnbU(vzdJ=MQb#&>3k`=APD!ZC+K+VY5q+jjcPB?HNOaMT+c%6 zdG%@Ht|0vLb;}>bA`!8Io*%u2Ib2&t-q%=vP!lFJNbNmaP!ccF32IkiBeZldEh>8^ zx*!Sg&b(!&F7KJ6SSevA3Ohx@+3={?EAZ3Z6)f{2@;!L41t5mRY4qx;{;?0^}akIpVnaT~t8n>%0+9Jpl=Q%j?QVXWYPEjH@mEe5|sH z-|;n4di_6_&)8KKvgHoqls$V(lqSC2le8Oo#T}F_!PeZYOf_VU*GDG(kn7-XazvHC zWdz)bf}M{pX3zSl2%Vktj~>0_$Z#tV2dK(+woj_u+s3ycj7ggi-S!vE5Y+Y)N~vpT zzwAHFV1(|)>td_nc#vQRKI9^wNELRipKfF8^2m=}Q=tG++zDCk2E zC+W2lDF8q?|8-k}5qpF6hQkZ`#;sR>G{a|_t_WJn75tFbM~;bhF{1gsL54C-Ag#fS zh?A9-MAIc<#E5#c@D{K6MQpqB0U| zYtAj+fQxKUd3~Kj`{d3TBAalRl(#Sa3E)aUAZ*ifkfCE9-?=g0L+0?}EH1zy@T^(g zjv$GmJqHBQqe0Pc5uXn@Kc9_n2IsB7k+7CI5UtpuCG zy5{YG{B8D-GNQLfTdQvFn*H|eMz6v(Zh0a=Xnw2#a|lezq488G#!O0<1mk1i39a_s zurM=_ERhG1%-r99{%0Yb+MX;}yQV&4qcbZWd`i0*Aop8+bQ{{(?KU=%&Z9h6s_de7 zI*BtXJXxD_Zu{u=_!X&j%kPirocIC>p*uy7y4>4h(l+fU+AbQ~!HJI`eMVS9=@D^b zqzYqMuRg~haRtTFZm9rT(B8jm2~9*V1oY6ni_fc|d%FV`CLG5Q$A$1!5hX8a`xtG3 zR?m@#X}toIji)45v=9_pxBw}M)}39K&Q~gJ*En%hQGZ5FX~o^#^O}neSRJg34e~5+ z>Dw#z72L=?<4i*VP&$v^S)7O)do#e@DuTq2=$kSPA~ulV2i_S@VeBDI5u5ZBMK9Dw z;`_6DT8ZEUtT}mvO?*P4o={+@imW+9s-q8&Lpd<~Z06zn=9%@8RCUyCk-+-1t~wiz ztAayWt??Qr`%|nqZ?9X}{@lulZs#CdBi8Fi;7*>69iLBgA}@t9SoH|HAsP5CL-TT8 z!k1T$0^(1IP^GW9*+c&2CX1IEY11aqC5-l+dtWUwJQ|+36(cyl(Anulsgt#UAn49j z=~{HhOE!}9FScAr$r(*9YiDPqol8FJzFO*gSF?b;fecFo4%uzSm%!El@5NhS{0T+B* zHWV(1=3x|Ke(3Y*)uq?TjJ3|5eZB|D34EI2!BUPa5Tn2r;n2sa^xnHP37sMjX z>6vhj)Z%(UR5YayOLq@X>OWrNHqc1aG#7kHk_Aoj%Wod5+WYJh`}3F!YH$vNOAaNI z@}2{3Sz`0l3H>)+-N*7fzJfr)A&J%r1WeCk3Ts7Y1IHMA67@s);dZyPJX?)E?IxZ1 z8{u@D%Fuf8_V~(%Q^k%~-bJMk1pt`m(1&QQmE-~%??=*L7jS0EWW>TgpB6J~o-|}5 z?EK$6xxcc4fC>e!pDONMjQ6jefarpXRaJ(%3fTF;G)q^d4cFh@P3fr=SOOX@!zm|K z9wasM#~rW41>>s_0qG0C-H^fnO*n+Azv){ZUI2)u7P>W7qt^a_quRj=fF?^sx{yJK{vOlN*K_+uT4B*cUMeEPfK3wm>%6i@KK?0U{S37B%NZx*0OAb8XN~EImpIv;e zM0$ItrqI2GfJbKTpRl-{_~;1Ok6jKuq?rt?-PE$3L9sax_{JK{oxO$La-4&#LhN>K zLnzon$d)4g$lpWDLCdn`%T;W$@<}f)C+1+#}WIuN#_up&_M-Q zWEQk`2J7d_R%UV zkGliy-=QcgDSBUK#Fn8A>@z$T*$@mna{DO|{PFFLcZ+e$9mKU6MS?9cof;R+??<=u z=hOw&4HzY$sz8Xa$}a-4daiBqg8Fs2qgAJIUn{hX5I~J*9-eYK)cyGZM_qkj;Fy>A z)G2p#9{<#D%6)}Sr5}9-QluLupem}_kRx-xwYDYc2|Je-JvLeYpio>)3;sN;w>QjR z(%?W(%j7-Hd8f3kHpE1=r@=X)hNgDp)|Rn18x-v89kj|FTYBo_+Z$DT%H^_orWzO5 zWVNqRsjTAC*SYZdU~-`DOd4Ib(V;!3vc9%Wlm}EXK^=Rg1+#!R^&wM<-iF z-VUGU9nR(&epv&#?$c?_0{r^GT~Js|K^xeQQN@eZso1f2{m?{f%Q0vW)Te>m#>Z%< z8FvRvGA8(vQwR@FU1+q-#F4k{tMiUg^-u=t_3)8~dDbOM)7Bb>dPOwHogyMdu1CA*#1t5Bb!KYZ%5I|dnA zj{9K8LVK0e_~mj4Peekbltb!-^Uk28h1~=5bvJGw9m#WEwk>r1hML>@5LzBCw98#d3bwQviz&=S?8g12Pt@bultqb5$Jik0o};m{ zVZth&hSE_~t*t*r9{r&X-p4dmwwl8#LrTRmy_UeUOi%x2or_7~5PZlH1GHjJbA^Om z(cw(E;%^bU78e4@{zBU3&^K+O`2e|^!<1VYX%jQhQv||F+{#zznwjkDkw&xiRL8v@ zG)XuGT_Kq6V43WQQ2w@5eo_Voe*Jf&QAJ|fT1{x`f)J^Za+;#~%*gG#1(Q#+CXD!AhXfbC=pBtiw-{jY!5|5-E9Uq!uJX)!kxi7-}G*c<$s1 za$py20OimiGWBUaDUdPcRvR(d%P3;34X@I=l53>l66}{xN2h}hakx|hkdn?_u>Bz> zJNMPefCne^^&dl+_9FhwRdv!2_mFhy5XWq(SRdQ;zsK_10(Jo&OJC1_y_=tu+_G*# z!i_4}ro(mTg6WxA_*UvxuIMj|g~~j-v5DWOd*IoRsTj-b7v$H^hWVkGUUU)+i2YGW zogJHRcL_0R#yrOR%QLu;7TXGpN{USMBN^JZgaw_u9-M4Yk5t=E@u4A`Vl!_%G!$ za7T!ID}ziFXQJi3Y3V!D?l#*!4hYDB`}a-Aahc~2DH*QrGSLD=2C#0_JI;srZWx|Z zB>A*KGDTbmSCd=)&V2(;eui$j991;cSr(Ontw1+^JBUqNb?lQ0S01+x`r_Ex?E ztgw(-%$#@Dq>UuM)=2q8nD|HjBy#_%OY}`V~K4_Ep>>Ev* zU+e%?H^F;mtlzkCAN0B0<3Mf=kBo)@Fla7>QiW-q42oec@$N+w9kPmuvyq2(^~*?5 zhPEGAc`vv9w0ZR^pbiP@lf(6n@cTBmMd+b``()4ifNc=(KJ5+}@9C%?3>E64aM&|e z%Yt)m`9IScu*g^GaCuxXRhk8}$q>bF3$mv^JQSgYO449~8ub3A`8M>7Quqb?1D^1) zXd0(pL#qHJH?VOjq8V>4F%Gy$BGanf)NAa6WO{MF5A^maPCL1YH=sUB-49HbeP&^P zO+-v$tr{MWIu?41L8(EHc|pxJW$&@bEQ&H`JBKncw-`FFi+FLH(ECpbuYd0TM(CQk z+%O=u$kg;rFk|3-+j`O=M9V8^<_MiX+`gO)>41_7={(70dloGnioGbP@%R34Zd(D> zr;&G-@^u(eKsa*UzcX$mqn@$mWo>4wPu!>5K((1^^^dNXSDwo#ndt*-VgM$ka2vFa z^gnQqp+O~m7jrJFE%elj7rae4`DBSWe*v*b95y?4>_AJsJ{3b4oX2nk>@Wx8r93Dw zWzlNt=AAsJ*g=G?z=H+tnj;iY09hfLCPs23xLp{3D59P&%l`ZKBViVaQuo4F4D+DN<`ih%X1r!I zPEue@0n5)^0?#;$6x{!H5NLJK*AY1U+A@O(lj;*w#Ic%T0c0o(AkT(g2eJAfWB0U% zK!WFy$6zAwge>WQ7J#M+J-Jv&PZA^W!tPfipql|6C|ZEdx(9Ak3Bz;_^7@V6w5X$_1(al$e#ebD5NscVt=ioSyovT4E&lo1e~!EnM>M-=}W zOF?_Mvb$6f@?BdBFneVQoC`TOY)asMZe+M8`bwus$3UtV8_qBQO)0;a0XD{Z@i5j) z32s;PAE*WkZRBI;?kzf8o;arwgjV`L&-8KEPdo4u>r_Apqc&`wuU`77N4p?*s60 z17lwnQr>cn8oVJ?Dz=a=DCFCO$xE-pVd|RbrBj9lFHD>)oeq)Dat!C8a15w=lXENl zU#5V`^uz6${wsbrabLQmn+T`TgBVLF!&pKK$_D4JYbDS^>Y@COvuO=&mtMon#Dm)| zCkxM|A+~aTj%&^vhj5TcDd1-@@foIyrP~fWwp2!>Y9{+h5 zVEZ;nekaq~(e|$Pw`e6-&}lLwpuMHZibi{T{J_afDiBz3m**YuVRG_6GE+9^9mr9L z36JppnzGPFWzqxhF>a$n_9lyh|42U1t7z9&-Jv-`d?H-{PtS*Hzs8*75p5LX{UoRpFN~ z@Fm2y&qB6e#0DaAkR2qf^ITKLo|rNA70sAhda&{|;N+jsC+5= z_#&0Y&Ac*Bc%2qpjPs^>vjNR3sD;7&9#ioc9k>FA5T%)bPPIk6U<*$6HC&R)E=2;L zJRVG2vy*WyUhE?ba*j;W`iE0m`sV`QAf>bKR~(wtvP7~zkX!8`7j6%*Naq@3xxB1N7YE$4;E(EG{mICuzKVCM*rNgpCGxbJ}st!F#uE zFbjcL@If|OjR*BSU)p1(kloSdMZwuqmx)|RprWu~PcC6_MVn8u%nx+x$4d#aJj6;; z{W`SzG`wh3NwG))B>KX;>-XS{%U>b#D=UazTX!NGospIZ-F;K!7bCo3gD=`emVB>{ z+Ul9n5GV;AEo?Vk#EW`}8aXKluhNf9M**a*H0vZ|T$k|!%yZo=15H}qJ7JyJo2}P6p?rd(Qie- z6CU2z$fE5OtMK|-0!TmGuktg0v2+w!U?KH0q$OLWdxnvLQFzC30?9H46o4ImaZyQE zccRox_y6*4y2sRUuEUKx&sX+lXu!o?Xno`aJubQhbaEiw+&v;m6cxLDXSlOczPx}D z&C7?O4VRjsCA6tJU9=d6i0KQM@l>Z*IjbF5Qw^JRSttn9=oWROwZY9VE?{vhKD<%X zXhFlH<}1oVlgBN;z6sP}c~T2QBp@YMcHQIXG<{XKV$N^By@wb+pTPGi&U#2*dsm)6 zu>X3B)FBo+;6fFf#7~|vyLK#iD@$EO{Tq1{(ci-MG6PFO7r{>(`GnLTa6qid`~VQy z8oJV2jMGnWL#VsSvFp*RxDB!{Cm^>6GF1{Zv+7%I7p@7Mj1B(` zo3Fq3t3MXz1G_V;Q(5}Qs8r00nitebFloL_nPUN%<08)o6#(Am1Gag{ICG-49FmOI zZBDssqDDMzO7@$}7(kTSG%$m>)s{Zl3>PLBdSwNApxud(elmuabdEPQLh!JIh?9escSikc`4g~@A&S2VSl0LI zR&|+fjpN62W{Sc@oJVz*$Wtq8@`#x2V;(D?c3~!Ww{B3}>lvtuc8udDR5&Ai3hPBU zpPja)q=W`mW1_u&HmWbrD*6lI=8(2>;aCTgEMR|Dero>l5t-c0O6Apj6g|Q z#bdaw7YM}x#egVu>}8a{WNpXmx8mIP6pG`K%3PAP$N)C?`&r=D4A)&5wLv8uBev>N zFTP=jM(TYNGJVgO(v#dY-zv8Yh8?@szJ*4vvks(qS81KijNGu&rN;2q zLW_*AwN|8k%pFLqAZevWAS}Ed5xZPv(qQ`6ZVdBV(oIw+(GA4HtC%`)81&8VCBicp zp~(F+Rw(=n&!SSNu14j}bbh8ra;Ax(ywKIR;fZ&`k8MUJ|9%Vky75&{x zLtnXER#-T{FPU$J=*PC*Bkvg%{^wmvc}Xr!-X`!}Z!azh<>nEReNL~07)rD78As?|oIWB36jP!HvoYJd2~2KCLNw8X+~PWa!Ha}Xv&@`n zWIuG0g46gSIuXP@srjE-B2%hRn>jcCq*j{^o{r3WBh4(p#{&+JlIdvhvyBrIX;t_) zUHmdnG4dc^?}DtzcgDJ8Y$Nl%b>~fw#H-ag+O~8WnZmf0s_w0eXwEq;W#?QKyN4}_ zKv0)oW@p|7A}+Ck2emA)#WrCI5R?WVg7QjOVx5+_BdU-y&X$;HF7wIXB3<2OkwM72 zEiHsDiBUH_((8zgYAHwtRjBB7k&=nRT?9~&vV7}lo0ah_M@NZ>DGKb@!0}R-G*PAfa*%Z|qS^FpQUwco zQbq{s20b13U_?oIq*@;oB;(fW0wwMqRab8Kf(Q^+CLiN!)&bezF>Q9|Md+sKKyL&xI`rvJ}d}P9G*lStg;7Z?g z$-$CrgpN=$Dmk~U)6(|f3S~yuE$(Ty9+C}x`Q^yp!>Iz2xcx^s6~X4J2#0o{*mHUN zZ_;9zxp+MwIIB}^a+J)2n;)_lDkM=E=E2|zO{6zMCwZ*jW$E&mWsLDlM9a97dbJ6!1QKU;j9=WgC}rtA8F zIo$VFzS7}u*ZCpqqD5#E166e{9a?Yf#x!+06WHRxYJ)g`C-hM3+9lDP1;ya3d}$Ef zJmzu{y|Yv0%f?MKiox{4g>W>zY$ds?)h5F+W)!mf4qAT@FVg7O)Gh&bQ)RP_qu_FM z>XFF`N&wt5?SDv!7xF6x_Gx795Avji=MrNy@vYIP^l#%4fp%s z{FptosO(5|napW}H^XC%Nbd92{L6nSpkjm=>MwURFrG?gSwQJMv&z$q7-IOLnjQ@m zut~8LN^s$%u|$J?3~|fXl!qY=mG2z0NM#sYytr}Gd{zd$W1?S(;zKS6F}(mLJw*P3 zXXRt@;zVC?;7xhcTNIy18@v^6N1xZ~fy|(XMar_40X0ydQJ7iR@NGHK`A!`C&@;&1 zf#fL?K5)n%)RB2weGsup;ApoYZ328XB_~*!N%t;|#*pT9C0gb{ zVO1*a%$LieW*4^KYU?`pLoFPcSlV>qh{SFp~{WAfA2I-NhR6era=j~%9+}67%L3s+`>;qxre58^$1NHQ!R+5&p{7g z^b1s>{M0h?r%OWBcL`EipUaf@i0-tcmIK)t9Sy7?Gpz| z6nMMBV%L}zsQ8P~hA*-nWvw{gy`-sgP=CDWV`1*aM+}GhBAIfi7kO7Z@p;U#IF4sagty_F`qE1me>}L&cw345 z?c2Y%T#zXX=V;eBUj5LDY2y5^Xlp+yvTS#Z`|U=i%V`D|NFsP`J{Fe46s;$>b-d8` z7Seb}!WJ&S^Jy749%A)52RLuM7uEa3qEcPaQT9XS%U~+>z_n*ekik;L_GIQ=Rn+Us z$_O*k?PWPX9D!oDW>#j3WgStdcV!{E;Y1m>R0&zZ(esA_8!@FK#&H>FS4}RCH`W@# zc8@u_ z)}0~o7gHhTggcRup*nF=lBr^4bw+fPw-a3xNU}x&L^cW7L7PwkYUH^>DhdtoF=|L^ zORn7yo3pq^?D9oo#IJM=pB;7^cbuz3v_FHC4G1F`OpO7G);vLijihHN8tM0u^+FPm zFH?QO`+n4&Q2s$^4!&!TLth^#7H5yXEU~ZPGSBKfpGEY^L%$F{tI=@C7jy&?Y3J_# z8t~t6N2vfDF*5!MgikCU=b>t9I$HGS zxos={Ox;}Y7|-K;!xBgKzS&@Y_d)jj==;BX>K;ELhvIQ(7O&8^=1U$arUWC31h!@#28#c+pNn z>On6!JK8z|LF4{9EU6mmDg&luR`jL@fdO?iIbpj=4-Znjv910IlA_ih-shv4vVVDs z{~gq$#h=Zx;gF)%u#)$yp@fvlt+`Wvz`J+1i37n&^@ z>>&|&=R6*qu=+`pN%YJHfJif2?2ewyY_%e}lmj%=TV6jdDTyj*uXp}!fyD-D%|5C! z638PA98pn4wDJDyg+HM>{Vfts80{4dv!~rcXd*GC28Dcz!;V=Pkm%8}XZ)c1{dWWc z{4Q?3kM!+)ZJNWf?|9UDI`Klj+gAH?R}uXVr-cb@{>~Faj3^-wx2$H9 zU-oy1hgbMt5KqO$U3(KZZz%8HDl)^*|M!>BOeUj@%BVcx#M$rmf)QzX$eMh(-e_a) zG9laI?f|YQGNE?^lTwG{uFbK8B|{o2z_l2Dy;kR3O+-SXZS-RI7I68&L%)>qqae@b zKLAnMEJ$rAoj1)Q4QVH*=|E=`gG{STH0Q znTExFQh0dKF>B;Q0>)(xe&x+VI#%SRO4$dWAna8GG6@Psi6ZhQFu}tWTnBj5$Gj5%ylPe2+!F2S;a2`a8~t2-jB~*xzXIf zbL~5IizN};v;uL@<=>)SEssPM0(alKG$w^=P#@ zLV6iVpFLa(auZGV?rnmKbSHGV`7f3okMX*v@aLr?Uw=ssX>&V6g#x%(*a;h=P zv(d2c%f*Rt73ahk5wAk0VU#zVe2^)PFxDuW#B@=S2O*2?%dA@_hjB-=E`wuiX8-#_ z`DhSTpOG>r!)cWr*S~b>#RW`*9R;PQz#p3wv-k|r;l}?XqrEm94txg}#v znNW?g!&~>|emVO0Z0xPq-?K4jK=>~fS6;k6_6qBik(p7a?StRZ*94a4Wb{HZj>bU& z>`Lz(A#2VoPBE*`$xf|1BvyD{)Zx&7 z0k`SiB7Hw~)*4JzvkUi-g`djOdgMqvA*Kh#*|vP>0@>1stCgf(7`V-;5AIs)dNFr1 z3Y?Bd`SdLVF12=ZJxs26KOGxF8nXXPZzP;>bm|X0S>`PHtlhlPuSaEj0y>n-9`ow- z`w<>ltI@X3ZcT|VX*}}L6w$+fE@uL%>%4w_5@j6(G^$@Q?-ZrA>j$ZxF2r+3Ff!9v z01#w->p^etv}Y-DIgog18r=nP2mN zO6bi1#?JA*bG0ya3h8sC_)#V(*ak?4;Ew;R)3d~a(KJ@7j2@-)KVZ0PGc9!kUy~tA zNM+o)3Q6FBK}}9_D=(8iR+5_`usnR9WJ}=u>AljCow1A*e%u%QS`}y2MMsh7=bT|e zY6H>kI6OMDgEj_qMirF|Enf+jh~822+Vo!$=LMy4@F18NBWlQpuwc)e}HaTxKXQ zy@l8*xi_~#7@OdG@BtDJIpSvMXnq?G3I3Ep&K$9)aM0ym$y0x}?mT$-0UwF823sl)*fM{l6yc6DRhT7q{gDzIyfMbXn?8t?N>FEc3#?EB$ z5m_<{IxT=8EQx$|`eR9iZT@CBLVw`4`%G#jmo3yg75*Qo3J1Ma{RUN`27`UISK$^6 z^ohivYm*f@7o0Kb_Dk-gc;}d9m>01vWh3l6nc=zgn-CeVLDF}Sl!2}dVb)t}#Zw~f zo`j|}+c#T70a=kTH>9a4D_;sw5R@j2rbnwJ%M80X_OzE7f9o6PcB#T4#vZjO6A|08{=mYv80bsRdjyP+j8Lu~fpeN7lQ z0(O_fvK6Z_2MH?MS=FY9k3OVZF=rbB(pi3AC}L`2kErN{vzbG3TN%x`9Yo$_(pZMk za8Ut@d(ahW)oJA2%+|0+4GQTwVu^aEo`FUr@M`qaSS6axDK?}o-DcT^r{T-{J8Ta- zjUM#>y`aPP$$s$VlNyulPb*G*li?1SZB?S#zVv-b^lKb<#IBo5vmi-?aA2By9X?`w zyyJny!Lp>;pBOyqN{g_tvH(HMB0gQ}PI|6n4$n^7TqD6F?+TOsrODZFD)J0y4rDqk zsi%W40zJ;Dc;gt6WDK%_F9OzKy8|yw(yraKUIASRX zPE}32Wyn#ZwJnAmEkwpyF5PG*A|emiy;GwBI7PLB*A0m|9Byj-BuYAq2_VBxQz?5oTV6XsP>M<>3iky5#4EJFL-FA!!U^HWg&nmWwKM1Oa7_<N`oN69d^_di5y=fyqZdv)t+J09IYCRb43K24o!UzoM&R|;9|9;|AsYXXQFCfk@Hlhi+WFC?aHC)0kSGP29L z1UMz>EMRuevn7hip@F*iC3|sKRrof?#~bv1G$WGLUwfwn?-7l>y5nfNmjb z=905mHhC((cy)kI#3eJPTC`0Yq&OhtfP+boJ^C*iddM0QJjw{I!ZItx(fHgn*>>DJ zN5(7Zq6nReC?`a^Ar$?izI!cJlTB^<9<=pu$evh-GCT8Wn*N`dQMj_PAq=c9Z9)qD zyFAjz1%GjlF~Z^u8h}t+$c@D;)P#ELq5Q#!rPCy?Ts9*2+cVX#rFGucAs;l4uf<&A zubLm0^_;Q9Ndz|5w>&6FCZ%ch5`MHhLq13l3y8S~`)>dK$keaxk!|GR&~4!0QcL(u6NP_Jo0gF7yDyfYh*j0#Z6 zS?2{=c&I{8FC*{kJqMASNr&+T`XY0!Y#a@i?&^YIT2Tn}3A!Rb1o{{OgQxmpzO z?PLIG*GpPZY$+nu8T3DS#37g?=Q!^%p)C4Lvz2tOg5B^ABnSOSZ~&*-YPxe7ufibS z2_NuZ97+wmmd+1G0gRX!+ohCK7i5IKH5|r9S&xgpO5%dkGsq0c|B=-sHX-wV_o~`3 zFk5ELO!~X))M=S@iE@}6$gvn4HWdbqIae!>0Gj&Bz&{N1FM-qWWx;&i3Vf^2hRcd* z_Io^PMOZkr`gN$+85q0~FF>OkDYTH~H5t3lWgwtHNRi-SaZo6ZE~75g;vR?oPtuf4 z&ect#;|Vp(e^IDX`TruN0=Gg)PRKf#4fd0?$?%1YopdVkK$Dvf=qv>O1jC5Bvh>5o z=Mn_;kxr4N-sMS3@de4SnCwr3m+Nm(RC(_Q{ZlIfmW8m8Q9kQLD2dWstu{c$U_utC z9qe4HQvHCuLux0!gs_aJY0ETT+7Tj#Nei0J7C8P!Uv`~Gc0}&BkD$6P8rK;V%G{gZ zD=qTBRtV!DjA0b*$%2;MCB61ZBA_^c>6Xclxo+gQa2LPVql)(&lK* zPv@MomgUoGt~VR|*4eC(^1Zf2N-7~?OUC)5cWQOGgKGUvf)$gKtCS7-HU-t*x%UlI z3?IDQzru^VP;rSn>kO6|Ot)wLzO3OU3+o)~UB3%H3K^Yjz8F{EWZrq{>EV>I(UIb* zY|SR;Fppc=O+D?CQf9~1dZ)Ks9(SCPJTxd;B^_6n{1GpN!M-fseSg0*VmXN z1T~LIxs9fLZkta{_w5!NuMU&EndOU^KHln!ok^oPX`I%$-lcn~C23H>Z5G==rglu% zf=B!(6iw@H8%lyyqNaP~)*?Q>S4ojciMHV}E^n=O)BLQIwYZjkHoWf%|4XOI?}EYs zEo7aLmaRa zb=sNgdU|>xYY^oy|Mop0=|yW|qzX^it?4DWn`8VWJ}@5-@Ar@Iii%8pm8`0wWh}`< zd$XKcC+}ykSX&JY)vHQ%&YTuzqCXTBX^!JQz_ya>$(zI_cw_KV7Zm>P-6fD5Ik<3M zSW;FodBQDI(H>Rn)@`)cdfagp&&q1C^)r6x*1mZ0BGLBnHG?hP_XuY-kkb<4UUKYAUb5HFV zuOK~dd}uB+$wt5Fw;^ymnzR_lIEz}fVS+C!c zZXcl|Zax^1(H;`5-6O(1hwd>_!;xPQeRr)vu({HEf0f_nw%Ba?=G{{l7L+-8vCJ~X zQDH;b)-#6tA7L+qgR6#wuv6+)$;-^!3|CEjUCD8@Dfb~Z!&vuY^4P3dhxX3U)z>8$tOvjf5H!^O?I?85YqEw~;g zh9a`!FlEA~^(wd@xsi9h+114`{Y>S2)C2xM$YM-<(wp0M9d10o%|WdSp9kBBkI~@|w%PI4C^_sib9jimW7tXf-v6Wx8-E!7qXw5HP%v1M> z1%J0l;h+fjBBp29)@4E|>QfWUY^gHRyiYM&ZtbQ&qz-n3btau2)DLM{b#>c{gU6B+ z)N)3@Ts=&VK~$GkkwUvIEA4rHQ_?#euXQm8x021G?Kt&%a(uYw{sO!R{bNOa)uST5 z&imeuw!Xb=v*dF9{d&K+OqNUeB%R+s$6;t;k;2~HY#cnCm9MwVPo5u6yHr&erx%@BF;)D5 z;pVBY-ZJSfg3Wpt&8>On&2x9B-i*9tsU1IFSL+);; zeu@u_h*5Rwjh*TB7~k(aR=n_hbVq#0MjCrA_pqOH|GP!pk&>%od_4;KuED;L-R9Gd zIqtnT(srA6^SQHiKHJL|Euv*Mj!UYY-=U9=g@3pIN;mR5*|CwEV{=ajad zJKN=78PdW$vaCDKQmeE3-n;6=#PeSrY-6;YADs|tKUdx~V}{&lcHr3Sx|JgYU$l16hn{`66U9U>HhIx$?`5~08n?|3 zY;v;Ry=P_`->Wj8yttPGDRi3Z&7i1)8-nRAJpEx8EFK<+$^IU&VnN#sX4VrhPenHD z{Z?$`2)y97!H@0n@$Xj`yRO(D{%m{r^jTXAca+dF*jRn7cFd^f`kjnT%AM8G@4l*7 zo|6c~TlMBii-k1ap9H6WyTP4O>_MY*%%zYO`^HX5T5H?*w3f{xd$9IjH_kP!uwZb= z5d(xHWcjg>MqE|=qV`d$@_YEU2kb48d-&ZUYdPonh$o%tLMO}u%ptBJsD&SklF#+>);fC*$uqhvqY(#T9k%fYSH$-HiQu>QkTN@*}&0yeO-4i79t@ zWPJ9uG+Wzio6FTdGUY~Io8vWAEt{pUuIYRCq2Z5*Z>)E-Jo{5+g9+E+l~-?gR3!_J zYUJ3=zaw{%7ryIVp@*-v!}Y*e)zpBkt#a=-WXzcu@Lj1Pr0aRZgE%<4MQ`t%q(sdh zLgQ|K$VNQd=RbSb2kZNmn~&a6xtX)S&r&304YOZxUEUMP^C8cN3|}yJ-v0ee(et}9 zBMo|WT9s2@B6M`+=F{mflZY{Co6El&KZG~@<(x8;wXvFbxyz~HUp4A#AG}{(-pBUP zzfN*O45fO-t_H2g*uCRLV{82_>AdVbJRwo{tp+yexa{W)dz~EP7_02*Xi{%|ii4e< zJ>;J;jNH}E&XtK;7Vv5LmlD|82OEzYRL;pM*=8B{Zmib#L1lJ)*VY!=4l{#emxsJf ztCz|AxO+lq{PeYmYR6;MZ%;`I%?QYG46779AJW1^U+7XTj{|vvba#Hd6?=dMFJkrL zm-{To+M+l2w7%V`3Y%f^{TlcE9>!!1%*)7Sx}jia=SRM#v0+_uOj-+%G9Jc0%S49f z>*n&K917^vu+P#~;fb5lvbj-)|$Re8&-c>?RN z&50QJ%3n6hy;DikR=dP>t8bq6u~V1fq6LiwyC5Q$W(23YeQRaXksZ#-jF{sbBQ7X? znJ>@3rethK&U=#?OL=%sOY+g}Z{K_H z_z$P4O?6|HoziF1D^5zTr*60psPQ5>QUgr6PG?Pfj{eBKX#g?M(^szaIht(Dc;|R9 z>3q9sr_RWPa#3;VR_?{Q+sUBd6uI|^S`aWuFyhzHq60TfR=zlPj7OAvp&Tn*=joQu_pD$L5+Ym8iYl<3 z=eDIr%@DA``b>zmu85E*UM;$jZ}U!sus%sXLyCig3-JZKFBKH*Ox%8dSy-@fiw8H) zu6xyvIAq)M?RFEEcUOH4k@@1 zg*b)YWc&m^?|`;131Db6b|zI%{Lodra^;E<7NS@KOX9C7g#e9hr(MmZ55y~`g%tZF zrPiG3iXD}ht!wEYzR8VSiFbcp^5+i=)<4^RG|&1BZEjIh+4>BvEH=rn%l%E<7Tz`E zf66(VRYX&~edC%+hwAa{i#tS9EM->$dRliDp@~7%*k^Uvpwo8pG(Sz)!lAqD_YgJG z=zA`?5*YPO8oWn-VS!qeXLXhF$rt)$&!3Y$&zTcj=ItA|WdR!~wW?v(nvXDjb&%)0 zc9$6UBG*Hreh(QT?5f8xE=o9AK1uL{d-lk~um0(}S6)TawlO|gOk{`shkIBO;g4Yr zb)Vyngk&wlwRsi}_v@84NmoIMv%HUXS@>`FN4GTg61hM9FU&rLV@2;6E>|dIW}88l z>5N&Y5+X1I)1L0%(*jEG1+wKnzLg-Ng-h#`lvk)2w{O1l?8V1;!t`LDX6mKv`tqBv zG8PbToHx<^Aknws4qd6$V%xx{E#m^lw)Ogw_H6+jS6Tm&V<23_t=M_9x^vLc^w`ZH zQ^po9VjQ$T7@?Mdx|GW!^%S?ghxW3>UM0SxxzEn})3GcAL0I{F9h=B2zrr9AJZbUr z8x_6u2HhF2f2=-3^P7vf8lhx-r0E}xk+HGD<%{%`+AGbpvKsZ(>oPKSH#bj=(sq6* zddAvn7T>t8^F+D9d@l=ZDV3j`_6=svkrj%mHYOWYUfXDZheWuB)PiyYu~!)(xP5}@ z!3SNt{H_;0frV0gNtSOL-WKtwfN`s%EtZ6r_OdX4;~LQq@dc|z_f5@SocLFUb)(|t z<-LW*or1kq>`?1onw8aLt5Csu|Va`S1Tl?c!n8CE4 zB8K}Wd`?$x@;~kZCu1Sae@!*iF8|{$)E~O1!MRiq?@5abMZPutlw^A)x6SSXDN}eQPHZd*z0*B55(P zye%_wzm3#A!hA0DcF=T_Q!{?Uov`=5=kpjjLUAoJ6h;CS`wB5wf?mqiMg%x@K2A8U zEH`_5h2eZos_Wlc{xl5hmW<_O)xDJ_y*<2tng4WJ{1l5e$|ZC2mVe|(DhF1B<>s4# zCHeXCnpGI#=3AII=~Tntpn66#B06jfF?^*H{OuBSeJp(Y z8%?!nxI2Ekb0N81rT0ADXl>_&s!MvhRh%&6t;ku2ATdkt#fKmU1xur zwRqo@?Eo|V-WjtlrT@8wcPDs?Cg!iK$;i0e%uExAa=)(B9imWN3zN8v9RNh;Os;3hO%=frns!^dBc)gxbbf-d|5V>&fjV&rRle zxqrniFV?_Ibe60=Rm94W8-hqrBDYM;L44byJmJ!4B>?K`CEbiy4gb4l*B!}U)mp9mUnP^>I&A-n2r z#-A7k3IuGC?xK!gPQMgm*&cpdpZLGizvHT#pr1!u@`Azn^E9e#mYAv{bRr+W+u^GN zd)V`s&*!gc_}WHE>qCQDDd$7K+^+5etA5w_cT)v;d1Frlu-n8Q~9&SFEEsI0r3ZFqz zole`od&jY_g2wPrMGA+Jg3GZX)DbwEcXvY2c_^iPB~8uf|8o^ipF;e3PTWAFs#H{~ zs+SdR^N;bqc~KS;g2uC*T~08-n;p^c#6tvBh$G+w0ke2n7;a!@3grekGUmk4bgz)5 z!;SCobbJ$kI^`J;Z9)&n>i4Cbj~-t-QLF*uGd4CGZ^^cH{rc^a+M4F-H@rNKoL*ki z`p6v@vn>xP!nqF(Wy1+;mVLAkcsCV#K#-)X%l}9wvC~yt9Fy6}|Hw*KGYrP1xpU{1 z4}HSvz=;t}u_1Db-QS$FnePRnx*gZ|ob@{ZlLK53s}EdISaAB- zXPbw1*a=h5vJ3l;?FX1Pe_nW+$M|S$3IL3veFu{xRf4wj57*SpD`TYv^`^AM<^*0R z2t^tr(RP2OaPWweiMv6UooN={lXx-IMV* zH0{|;E@s9rMqb2;mg|JwJ#xY%(V**Pn1lUxa{Jz!UP(@31UM{@vZr& zbi+muai}>;g+ViJA`kedb=SS6ixc$899?Eov>B|X|A=jbVLCbUX43>NdmmX;)Nt2G zU8>qzoe|o9E8LPuz*g>&NlWB5#D84_-bNudY)aIjp&uc!ugvAR`xC$MCXPDR9Q_&R zETaFu=evd;6OUL4_KY`XIGZmb@_yPmg+=tg5AK2$w*ss+N6*h@p6SwLE#CYg9(T!jciT=O>1> z5J>TGY%n4kH^JBnhhSx(Vm?XSd+t*yBq&!!O{1#%Dx!#F#kwFJ`r2Ou7T#Qh ziI%4DAawd3s$wytfB^Xm^%^I8c^B%@*=&-VFLmYl{H!-zrF}ME9g(v?nkIaFLhvZ# z>TC^kOaD7?_wIZHUfVUbu;8zxwe7o7+jK7CfoEecqKtNZSxjX3t!l?6oYaj7n1*Ck z&Z=8w6js>9Rg#4}b@D|Xg~QC4UNerLcqf9Jjy&eQXNs71D($U49~doVP9aEev>|I& z(?~5NI11GPAZGkeR+E%=H6Ax&o823nLON3Ty5}r8(sp+nc3x!rJm@fZW7yeH^N*$! z>=%d4_csy^mR{;rWMdy){#)Fx5o2BY?K0~oDsoReRje`iI^mp`M@g?cjh){`iq(o* zNjW*jN*+KJ>65;`fN^!YqcxV1L}E&|Wrrq*J|3wXBf!z3MRZ`HfW#j7wV#tnGCi1$ zzq9kzu^Sb@lL(rESdP?YqH@NYcd$QUTJ;hQu6XUw$wtYqic60kTZ`>Bu{(9F-CszP z@worE8-+-mXQ%;+snY-t9z~`Za#WElifcPn8j*X*2pgE%KYZU@SKE=^4^3qjsaD#m zhX^*D2k-An9t>P}WxL76V8Gk|3hgg*xZJ2f(qMRNIWaail8H!+bux#g{Lj(ZKJtv8 zvi)e>Anh~wvy!sd^+Q)|VJ{OcWTfsM?uyoY+TWuafQ@+hd4L|nx1W@@yFnG|SbnF% zEjEi2rLDCYp)Zpg-#3oFPE=!m%5>v)b!TPAm^|PyV(v0Fm0KTUA$o5aQi||-o6D8S zu0Q_99)`W_0PKP?0u2QXqbtQmS-8455MwE|kP^Y*+U$uAb?1L#NZa!M?6K65eh!blA`HI<3|Gdw9;ZC^*Z%t`#i8 zi&$9rtcjgzP1O@PHKg>gc=5Mz76EpEkmOIYldZGQWpphIaGvUD_{O&U6NoF=laxsK zf4UKs3YQ-{0y_!A3rs=jZ^=Q&$L8h4$%!xt#Ck?|CYN2qVZD2=pa*ksXsjy^=JW^l zI2BrxYC!IR_x_d3Go?+WCZSdHEUP8FRx+ATNX-ArCb^r~v9Fo6QOr3ps*`CqzT16U z<`<3k`aZW+qo6`Z?aNAz_wN=P3K(n7J?HHoUxj(^XW`_K-`*o%|<+9tJrn}C4e|w$A(v_jX2k&LfWO;&cC2%rze~y&3!sMS*whK1$ zy{Zg}+**E1ujX2i-( z^@Q1&rX20NLr-Vm@WN;2x^F|<&Pfac4t?FM`+PI4ADG)QDyOwQJZG`Ys{QG}B7yF8 zdImLY=1~J6I}soN-Vp!h7x`sUyfo~YhkLrH@V2MX#^Uf^#XV-*f|3)SzO``^z3kb1 z>&WEiu7v_r=!#!rxo@tWO#c+ym6a`fVQ}4*DWyj4#j5q|zW_W}IFIShC88%lVcY9${yT>^_+D{4g}4izrs1Hu-vh9-kr z@fXwvkn=6st%jSUWZ*qGW4NrVF8J=?e)zTAU8wcEIQP8=Nx#nc`z=V;*1nb&i%w~Z z@KaSTHH@bH@btmuX>fi_kwu{X8tPRCS5I=``=$3jnO8V~I1y$Dd+{l$kp>~i!} z#JWLK@6#^|uMt!qA2LA@!SqeOM=)zxFW9u9_1xEd!yzMNDCD~N!A5*=oh#X8DA$4_ zq4fA{gAhzsqS0iiU{3BWIi0g}q_#)QG@@NPeq={9GAg5ji-Jg(a$Neki)(9NvPd7- zJ`K1{;gk6-4ZsTrQkMDwU~NWO=Ft<;vqn#hcv#mVzr>bshGxoapcnH`&Y>gl@9wC@|dGlh;E^y$4hb7%p#w9&X# zGk!OH0d#M-7W*2ROhh}F4~|zjm=AUIy*VbRQ_!S!(?>x4apL^WrQF=X4;nT7dr^{~ zA?$^Uio@7z?%E@n>doJLW?k6q^f6^6chbbTEU?22TNA>*aEer)^z#==d9B}PTkY{c zv-YlcX_iclMi!s;K_7v4gIrn3F-AS9;UUGQxO+>u5^l>LmSaO%^I6vcUp}vjF9c8s z;#^4V9%Ub*Z{qD(mA+90`iEPQb^%%STQXl@CrA?r&clEfY~h}{;~`4EKcPcQ<{8&_p;=v$qM!oHbi9+J)H7C9W zFtczV_r7%`MZTy0T~ziCt&YqP*!S|W+Sw)BzVQ&SPL7ezU8?PU^-L(`#F3VK_J+O$ zZwY#Cve2d-T(_&w>YomO;m6`(^dI)1kH#Y>XN=6dbx~*AZ@>*ak=hJ$iC4b0+kZUf zusPFQdTs)MFNd@7Vg|vR$bnkH!ux-0U3Var{rf(xm$#+fiYD3vr9?JODN>QW8pukr zx28&H8D&*g_NJ0iLYYO#JS2qdy?@u|9H+PM@B4lJ$=jQAp6B_D`@XOHx~>oK78K(K z^tNR~Wh~d?nw_tv%9OOevvY=`|NTyKHs=kLzn@zJ>$5+T$v;<=4q{v!K$-+#W}Qam zX6ec_JN?pGvkS3Igr;>Ks-S@d1pcCq!d#Yy-CR@b`A(OpJ42%6P;XK~2V94DfUEMu zj8uBD4sEL(kGzj%DiQ*-!BG2WN^Wj0?X$4lrSC*;mL~a<=cxy(gUV}{1vFLyxXKtR z<(tpaYBiT~wR5QFCE1L)4Ke1`sd{M`#o5^#gCdZu_33SJ&N0NJC2*8HTSf4p{a0!E zJT_)?Y+_SWMONH~hRc=>sM|b2`oPOG#Tt%YBpL&l41kfY0_=>VbsMV=vq)YR)S$iu z6Nkqz_U*~you)pl2#aW1C>ZwZ{hjsw!2_=-6vzufmZC_WH1#2z4qUwQE|H9e{2XU} zHAY%Z5CW?b#0XdKyry{L+&ivrp{tlAPi`-yZt_Nw5lM2}f(d0A(nRM$6EG|`BN*+7 zGE++7b^JLA{(u2BfVA|0jr^n+yA;Z`W56I)tA%+;#GIL?J`d1dLpcIvBD|?j0wqE+ z$GZ_n%IgSRmj~;k+&0AQmfoRlKs4mrQf5znQPTPhA_GRy$9-u4VgkaAj<J{+aIo*<`3aZ6c#%2%eR9CFF2mRMTP zKG#;9t-0U-+9B<6g7_x{HlJjzw@QTJP*5G1G4_w^S1_|aAfd)-4|9;L4urqPmf9Jj zx17DWdp6}-S!d&^Npy>qEB2H=Og1U8M4muCnWCF>R+#n!CCl6zWtXmB@}0jiD$KU% zNmNxVR;nbNCQ8ex4$l(>x@)CT7eCQmOWALXD3iiU;{^#DGr^;nTyuqKi;K*lp462N za{3Plx$7gE>)Y@$bleUbfR?5?b&G}cHTlGH@Eq0`ZjSo?xB~dh^9(=s1sa76AH{o~ zR_2r9tP=bVlyL>s71z}1Xe_1W18OMl({m`CA4b-QH<&PqQJA;TX2hH`zh?wu3Hcxo z#-jO**OOBALIcoY|335wC}vwkMP)?p3ea$8O4j#mBin2N~?&WN2j7k3iOM(dy%Z(Re0%<=0t}i5< zMQrqkqLk@xd{zN04|kqaLvk~dA-vs2O)DVX6f`E806;JwM%|W_%vqUGQCbm>F=h-_ zs9V}A+NW`9kE)q>nY`FRnVZIda%kWE&I42PfXdmaEg1g-GA;4Yn9;P~l374YXRzQn z8AT$ADfA-I*(N#2nvXV*f>Hc&K1P?ovuZ-45*P>yARr#J&v0J#dt5-yNcCs!ydreQ zCzCcMt;mRIo1Pn?P>yczcm$RRurY#DkXnrP+gXokxd(op6*hP*3`50Zs1tG+o#2pJ zoO|PrE}}T++{6>~#7=ZFx0r0*P5(~GckL@^KP@?l6_v8!D+XvA#tGY8+%xnHFm~@S zb|d6b{?~h(`0l=w_WR7+&~K`)nBY0af%?;^ zg>;M!_s3vLlQ?{YcFPT|CVC=d+|JOZpcB&&5YWM6tpx8RSKQ^_PyG&TsW~nYJcc{H zJd_PIM%sN!^7O& zU*zuNl=W2use5(-xE`#lr1l6+O;%33wq0ai!onI3tfE`qP`^XrtYZq`9oalAuIcw! z4_)rrJy$>PWve{&a3H6(V8yCcOL?i;=Q2)Yzeh+329@ZF+OKC5@46Rvbi|568gQr0 zhkhF@j2Cl{)DE~?ujJ@?uT@v+=w4mgB4sD#UF=vpac`6JMD^X zolt$q)w5(gkiN9fV~N3braA$o&CF6@7R1AX0J&13!?So*0N!1p3)V5_NY+B+5v9ni z3##Lv5&(s<;q@$;S)5rup~rdNoR81GzRA;*^;2U*#XO2~Z|c!#urSy?@~~%84m<@3 z0{f*GKR(gfY07d*q_|B5Kyq?9yC|2_*ZaN89&R1VZi5@{-urrCCViRxH1n_lV!CV| zHqT&WiwdNFPjIOQDmvW8JTzjj1qoM-skce`Y$*Lnoj?CDd7-Z_eYMS6Su)nK`LOZ! zk9_OqFbn_hq2>HRB1)YpRx7VP=Ld%-!(DA_wCp~$jy!o|X4|jVJ+^jws$)9G`~ruF z)$;9n@)g17*PO2Kb-#Hyl-KDIniwCA;}iR?9xNXl<0>g=R0f@e_K3`;j7?~sRnV{( zEnx_$ncsXx7-+Hmy?gf}h7|+-3cL+<6oNNw+^FjigZdezslxmAGk5Q)KW{GZ5<8uO zzjO2OFueRT5^j-8}>wTfUp!9MJ)P5qN!U1jF+DlQ%sKd&55ljA{$ zOUq%@W^ zjy){mdD9)K(~rn|v~V~xa>zZSH`&`LHkMs|nT|>DyV=t_=n?ZLc+qm5%0((6VOt7N z_%p`p?BX)|-XnQY|IODIBCH}pM@y%+T77J6u$(trD(Bsf8srsZIFx54&lm_g;7U z{H0w!RN_csB9rUaCH+Z~w@k^ArRi^*OoJ!Sd^dFUTNeRw^-zOO zj-UdJe9Z!DhExc+IB%F%veACxUg`Ac`0?ZO!R^uQJUY0&K>w?RQ)^|}CBG*z$BUv< zTJI%1QJ&0YnR;hA#?R^!&Kiy!f`pH(7r>ZO>(=QpBFgQvt5I(|({V zSxpPOzJoj0(O0;h^$~7XCKjd0-uKPhwY;AuXr>)=b-TcAzd=gKH^IEmJbhZ7L)0e^*l&i zKH8()PjNIu?1;Do_dV1C25-M@NVO{rn+X_zLh#?`0qr*Z~vo*jtu>#+!a zDrE}&kWT}_5K$exIB@T1&wYb)CX>#3+Yj&zZ>QjCL9;?}t~p^R4ZGFr?+f<1_;ECr zIwf~^!rNajW%-YA(7qt5TNGHCY-KK1Xw@B{WHR+jqo6;1%#+R2w|^vio-y84xn+p_ z3XU_Q#tk;w5z=2CJXkvRy}x<&s#WDx9&3`*l9cT%y|#(()O{x*!p-fH{T$TX$)RW6 z!Trd2NxTTnc`u9bcaiWzRPRS3{X}lan~XhmJJ-hY*3~bb_kPd*%hzuBZN$jS{B9cq zgANkXIWnda-6kI@q(ojgu}A9gk)N@e;}Yze3lKu&YdlYo72IRfS(Gs~Z(3&QHxS&m zZ{%0u>CAAVvPTL<4t%}v+OL3?FW~*G&t()X$`em_CjWHLabNT>Y5LPTB$9`(W+kO1 zjhw|8gYlZ5e{|5s*sR0TWm;6wyk^K=ZGNA9=Fi=ea8cmQp9X z17!>;RG^{m!s0eoiErM%hgk4)+fEMI74-sTYJduERAu@064T(Z$5_~74h~~$`7OLJQ4MT(9>3B}E-|#TGby4nxjZ|gdRkkp zKUIDeZJ@L4&n83jh08|?`02XHc+KSxz)<>d;|$XF6oFPfHTG215rK(tT_3J>8eUpT zr!Qn$Z`;0m|EUM^a&k_hWwuh`1w5T&k=;{nn$z{vi0|5-f4N*5=e9g}@f+c+h^W(4 zF%;LknT>OhY%k=JM zEmIsf+0^m#O^X>fLYB0Eh_`6mbhw7RuIihw&+9^RQU<%$q+@yZu zijW)q8g*-Fp>TI!vsa!{)McIlDHl#J@Dg`=mnB#>oJ{_=C~{u%kM2wXto)Xd;YWj8 zseS_a`|X^c<*{#H^31adJzp69K(5oPU-J9Ya(PD$d;4EW#nYJAWNFurH)I9-+lWVg z5881iP%v7;aZo43_^7~Sy!NaWEheXdguUk3GNhzf{*$IUp(ir?i>N)boJ1;SnS~oE z-H~gO9lTQp=#X1${Mnt>GA*;$AsTsxC)5I{mt0O{EyqfnluB~d%o0Q~B#ESh z&xo>1X*eQ=rR64_AgfkE!|4-VGPx=hhjL?l)v9^cScrG_e0i#$ndfy)bh6xV+J#zo zg%(^*GRN1Has18dgT&@Z(SZi~V)p|>iINpYfFwqtc<6SVfe)EH2v{R)<>Zb(xRBoA zR^DOd&1%?o+G{yy&!#O=r;kJoe!g>_pRk;WaD-epn7aaKiGZ`s7xVDLR@|1l_k;0B z_p{#_|6Q1lY%K8naF}vk$pP)YxOkUws)Osleb9qt23#A75_iI`I?BvcqJm zMyqXlqQEKEJ78C;y)iNXjqWa$&n1)=Ms|(Wx`-6BJ|W?^SeCb#pnzF_q3v|uW7)CN ztwsEpM~?<#-Gt<;`~r=~YUUVudM*D}G0~|Fm!WcLI@cjdtY zE^d#?fXQ#I%2JhQ7Rkl)6{)L1Y1G`?6jp42w5*^y9I6HXH0;O{Q*9!&#W|HXZ7+`p zOg0ZEk*WHOYSeW9uIcm_8yvPK?AxoUj@!R_)}v^r5Dm@o;Q_db5^trr^SpijxliOW zbw?9)$_&0st7H}rr2h2{GKd{uE2^NJ`s!A%HdEv~FUhLYrawdL1W(`4zIV%gVwfKJ zOGJ)+(8o$63K_C=kSy7b*HO8xGd@D#CH|nv?CtmwY{^4Tl@gg+&pYbGR%hBN>9rTP z)letbAX?`aot)#;fa5hNI_x_>!yi6zM$u zQC`lScZ-E{36DnmXWVX$;l0vMHHW8$rz>LR!Qbz^^rEcFf{C{XoZ{(6A_I}swSE7i zOk~}Cq?$Gy&O$`Yf?CFlA2=hPI&|Z?r8c4Vv2DzbJX$F-D5r9S#Pq<;v-D;iU+eVw z_5hNt{iF_f{j#gKa4%NpLQvIEk|son5zK%Rn%gsL?jEN_dq;)VIJo0JPvRN9VpFS6 zF)=HER{!|HQzI1-MKeVNmYHHMYXcpm$HL%4&0?N+j_EJy>ggYgh1S;I&8ofQ;riux ziML!wvdXIPBYQ4w|LEHv>bFZ=E2b}OxbUap)Gv?B2uzrbfwN{Gq~o7)Q6PC!oWp?{ zTrG~dx`npiXELesw;!z#X{TH*q%PK(vJ{{fkk$&dhv`3=y+;r1Jb#7jTF6s8361{{ z_W@)lVMf;5ooqcaCQ8k6$@iBzO%6FdP1wE-=@iwm)3;1m5BZrbx=K4tiwrVj!F&B2 zsbzDjY7L*8!!+|+J0r_a4``5(0#=S>s(E8%a-9j2PlG?qJ*={~tA-!9g^3hF z81&I0(enFjO-Le3F4pkP!`;+O6JdHD%IwG#dEzBuQw&LIO7YIpD5JDES0%u*ueI2=>FPqS}40}E-?Z{NOF zQw()s-=w&mc-y0vHBKlB%FFD^w(23Mu!Je9HTPK6GX?55-7H}7Xg11!h;C!klQx-@ z+)ZH}Yb@S$z18WwsW)U$)SgF>BhCiUOa?tn()Cmdj>=X*QbFbvDK9Q z#;ytfa9LqpE7|@m#Cr?sPya7=CtBZJ+ES^cNVPLEN-7oDR8pSjx}*VIkd|GJJgJBK ze~@OSQ?m`!PBH|e!R5Me2A^nn0L4LtiMa4N6|kjWzBRIJMeiqzU@5xpz!8?LC2}d` zwNUe_oPJp=TN_B6lH!1MBo*i;-{J|IKM<0S0Qk{1PhOgqh$*_Om4?5s?#& zbGO$i9_3q1nR_2uQU1~7dX+YmnLx4_<-#BL_LYBcn|scQ1&nlXl0v!q8`7oCi%ime zA_+CY$vAmw8-Ijc0Awn(=j7fKb(>zVCJJD8seaS%l??b~^Oh)rfoY%BQn_PH1G4R3 zw4Gi+KH}pmzY}OIBta1UCvK9p;l0K=+DGylo+(dG;dfdlJ&U4CRtsesFs88L`MThI zz?RHL00x=~1djcupd;O61}s(H#6S@eOKp1qz zy9`OZ&i?cL;oBLq5`w6sJxEgw0H^Xy+)a2&SFZuVF%Q8Jk0DfH$7Gas@Vwf8(Z>dwrT2Ix%A2s%u-y~cvGAYe z>POSOnT+!>^8=)EAd1lG^78>>fx*yDa#2kUF&yG#fY1j_7LBxmT!Gm1EDFtdC6urXe)*=xW2DxXVuZ4FCDYuchB-Y~GgCBe$u=`T5yG?W>WHzzNL zKp{SJ&>;jDmx$yk{g}S4AQvD51KEedJ)4%{&*w43JbOtLwle?$v*|GXjmvzZ27eX{ zISlm*rR6qIL}0CeT#0C&((;&|CHbD~^lR!(L)wykZ90oOS9eg{o@0OMT4KSt#cW9_Bj z%gpn{bZJtc#Z0NJ6z$!>*9g!xvja6eAU&vm*OX{)>Ks+fPV~OW#fgNOo>eNZW?yy2 zxXz|P^@u^j_oj2?845RoNj9_nI7@vu8OZ`FzzYPPb+>m}I6AUk$xt|cwMnjF%$H5C z9^ph0T->9+%x`GiQc9L+KOnzlahvtOfsA6A(Ny(oRk~@eg^$hyO7Sv!DDi4oi{P{!Zm6&f19n$FLK4 zC1bkc4H2>hx#kKoAdpS?9-45FtW=llHGfl<93xDze={2&R(vL{-U%gmy0SJD5 zmJ0O6aGx=lfqVXYRq#Gb3eHL)#9lIBGOj2%v8|DQ67zmS1Wrr)kYG7z>meL#$-wGo zue9={#fYqfQ(K(Id{@q=DA)dv9czTYyyq&tlBX<`meYuZny{d{@qE~ie>2^=+h!iq zc~kiR7>2+8*vE>n;Y&*P@3YUMp16!oSxMt%Sa_JB7<_YU%S?hN=}hObQz#$*dmNn4 zqCtg+JF7B-IFVn9+gsi;u0Mdjfa=mJZSwXX- z>;8a=^46ZEEhx?pqrN?DCVRF~IQPE(?-+dl1G$aDkgymfnh>BTUjB~;^K9mseC9@6 zab(>a+w?rpV65&yv_qEV+-HB(C1fjNW(J#|QusK#F|KZND-i!b=04@X%!am}p#A@E z50Z3-6EJP(g6a^-h%-_o=PIO)r?3g=L_=MU*TG0+X3;gE=K0tg`vFD@SDIHP{C>Mjvq-`B@#DI{rm|5p zzCC*)defFYa_7-vjX^43My+^Dz&})djb;;Q&$Km4a72rT47tZh57PU2Hu?Q?LLYlA ze@RS7V6qZP*Q!H#gV_L3TDPeE>E=CGZhGxf6KJj+8e|z;>v~5sJmfgraNz4U^@53! zuJUsg>?6Z*t=AnPRZNOQK zzKuqpK^8`qF{rGhRS(>R^9I^eDLY&82-6YF61 zW%{*maq_+L!Owinb}RISMH{3bY$9&xXy^r!#XR5^YyWO$ch>&)ojr%X>DWU>77^hr)1A8J}xTrZN9PxF^ShON3D;?V|dZRgyVFA z(0cbX0<_P(asS=(8g%9X+(JD@VLq3ZViGUv7(*loS?@1G8Ymi&xSMeEomIdhI-7R9 zIJrZNaYxr*_0wXSB=c*>pvpe$;@pL0Uyw5uQ+EnsK9hfTK+@1IthtvI&Ox&%*VOk< zZ7FCnR*HG*PG12F=ku+j-3f);kITQ3+aqjCEdRPiar~I?=lR3RE9wbM%HqK${vZg` zzOzc@1@^j{*y&z5>XU))@a-MRkx9y6f_?pLZCYvrRMFGxY1CFLlgd7wf>2KrpgR60 zZ-d=q3=J-&zvyz(x9393AyP>q6#pi$vZUp-neXad>WXF^D;9a&BU-d4ZEGGUZa;p< zP?2lPx4`@K$H?VP$j)c z$(OHKbrz|b_EIwsx0}tGC*jVbh=zi}#q&tD4g+J8xBsyfFtBB7GXgCerS~p7fUf#L-`N60dZR zYkCL&u#mnx&@a-W7dvk=Y12vrt2#sd{Lj@=r>Jn}-vZ*XjQs^ZGlcTAaO#{<>n-uC=*EHHd@6^onNY9^o){vv01cvMbn z>snFu=;@Je&B2<&D?phgMn}`UD&_0;*_Jy5Dd|aEQcyh7?$>=w5H-K!RDqUS#_)B+ z6eb#C^2_asS8M#(n8ZY%>sZ?K7 zmDhAG2>YKw zEUB-^;{0)RaVCEjrH0l0=*QTF05SU6w$)13hG*Ly-L;|*L3Z+5Bm}t;;}KsdBdXz& zyuC8rEU(X6ec^(?mRLXCDNyoidoaLA$HB&{+qL#RYAZ5HpUfalm9^95E=-|y+AQqHUiEtBWm!AJ7Nd9@MC`27iVeqz28dAm7ZAD$9^8{3c#n; z^)CjISX07{nl93*>-So~ak%T=Q&aYrXhv{MPW?>MJPxFRcD4K?@`L`9_6fgmk$ugE z05(pHJ|$1x=uzJ%#15S%FhMYprO}~R-Wd$o@mR@Yi4gR{WJRnLk3@97hyp3SeBWM! zXQ1xWM5P=-i9kUErC%{kSaK&W@4rL~*>~beYZcEo75) zf%nal(P6wQUg`$L&-brORa!Up`T2JQQ8|*|PJPTjdPZ}o>EVUZ@wc1SO-$}4Gz;1B zbTo;*Hq{tD+E^Yo0L)0PTBW5mb0V2~%jW}sBsN9iU5i9G6xSw*n&3Y3Ej_nwu?<11 zjyv`aS%;6(QcFfudG}n)+(>@~ymm9g2GfkUm~5>-M0Vs__4 zW2}aM#%4}iiEB|S_dN^#aAP~QwPb1}(pHpDtYlCcEkgZtDeQ~{S`>e~6aCzvVezI`A>3ew@#y&0Btw06_58 z9SCx0U~lDb61^Xo_@(t5$lXUm(eN+}tP#;ay8raKYx#lo=SQmp>YeIxlF*oIHq01h zn?8adH*vP97~OT;Pz7$R-zBX_Gy*7T;CZP3s{I0+%2Zm#$W3v#Ha9uSmy35g`%tbEWd_ zJF6EJGp!h2`$z%(9jT zjep&0pgn+djz=0D{k?IYZVq_jZMv8!Nxk4u@*`uidS94*Q3-W)Fp1Td&@l2$Cj0$% z%J?q8Qnh!l;;|l2@{?8Zs1+=`lqgc7eq1Hfa(_LOYnf-UA;RPN^DGWm=yrtBtGPGoozfNWCO0ulcg61>WEm0o0f~mO%kTT20Z9%VMZcrqmLH^9Fh;6_l=5}kUW>y= zqI@b|lh&SD@z(VvH;=l0>L`j&)a)OY2&llMMV7ETnXhn{bAZ^6*dcjK+;mf!D%id@!N2 z60vK|UO3_fy|OJDQ{5`YZ*xnJ{*qnkf+cJ%OVZZOhYS~F15WQU7)(7w)Pl+nZ|6(I zh@kONl~|vjo@2#Mn5or=eub7G4J?5n5;-6ZM9bF}*^p2y)#MttPUs z53;{BMD@Gq?qYwtgPRUNio>Y;mpU=Yo>jRh)Ua2P}bKzXoS&SubA&3{+( z_35Ujrm>Fh>AGvWiPdeP=tPQ5{@7V#=w6ZHE@abxx*A&0X3-L1>(exJUXmF?gB0U@{kPu?gxNWzg-6EEi|5V9&PSa{%{-0m6a1Pm zOfHc9)ISrt`u&9ZK5K`Ak2)*W4FY_pQ(q{Jb@37uXzZ_X4Ksu))QxrXChskI3a3*z zE~!=YnX&I9rP+o)vjlFkjKNmYz(ul6D*1y=IrBbjOCvEucDh(2GKFR3Ct=Fxo zurFu|>nKhz7r z6CM+1MVz0}pyRBe)w*Vm?j`Zyv9O6!TPBPOnj~!djq!`6AH)9Ikc5hZd})chaH3~` z*EyRBWD?ax>c|Es9rlQ zen#$+h~=32$rD%&s*(k28HCeqVCo5}`3&hh^@eFxYB^1JZ&^o}edH(X$0R{p(Rigz zy(~n!TMGk1vKzEG4VCnJuQ^>(uEnY(qT5yJki?TWGcVaNkCo`@N_q|J#1P|SXe|8= zH>&t~wG1vH`pRI1oN+i9w3t0cv!_mS^L8vsOu^pQ6-b*MtYsf~K8OIl#VlYKSUQNE z4wg%7FZ+99MGSd*VvB#;E`G^=WD0vGS={Az9BV&oP31|PvR5yp-4VQSjjZzGYo5#i zXNv=F&{p@7bHvaI2DzSRGF-y|N|oWA;PDMePtSa!%r#D2P3e&*9OR>g9=3r`RO_iD$funid=5`6fw0Nj%d%S4ZjAwE0lJ`i=jGVk#ZeXRWW*9voiAV0ypm(U) z2l8SwM4N_-k#=!7j$(Pb=9Ex-u0l#gkP~vvV!+=A?!#4NJa9)c>12oMm=r1?w1wJh zpvgafQRhekBE_VO({KcmGJDlnr$H{WRKZ{G>`H$4Q72rT4)QH-@vH1e_GeB1?aSo6 zWCh&}w}9x%--xYU>Bhn=>(nQu6`+_c-MCCB_Z9_o;tH+wAt#xRNs@k)b@(%Ni`5yX zBRbKNAk{aILs)#&Ks3{~T1&fOPU(AFX}ws^L($M8Lda8I+LHFF0di{C=YG!L#>$qZ zWMniDwdU+;`3kuXWo(B#cGw>{lV@NaJoYtM6<>TzRwZ`bX0`G!7Ug@WdRo&eC@$p2 z?Gp2cvtmlm>6ql7F1uM^(EYO%3yh-zd42Mhvz|i|$;Xkj7`(k~lN(s1m+386gIzX{ zugpCBwX7T4&dAq;`N84UpbS)}SR`ICV<(VF%MY=ua)vWzgVFyw;T%u~Z@M|zec+Qj z6YT7WU*F)i%#Cub)#s57A|g$BwXa!?6SN4AQzDA2a6X6YmR;LUSKqp@4#4Ec+bdF9 zecJ{LWHM5ju2WDiM)tQ`Bqv*)$RBI#*pph*`KaksOaaM$Ozb;16HEla4=6=DpXg-RoBOUqVVYJt)_$TtT$JRTA51&dL0#7B3U>ZTAnh4s{Z#7@&6PcY}6s zKXvSRRH@3$17ntyMM8m2wy8AilTJUFeW-!@g+4v|bDPy;X!f(PP=Hbs?b?Rt+E~yR z%&&gK`@ME#Qh8AO3dgWY|bcp1wTEZqoPWp6*)i8!*B$<|~HA@u?~8 ziL*AnGgYEDy$jb6vb*kY<3Na@567SUhq8ndTTaRx=32Ni8Nbp-$B{FOG^fZ_IF|vS z3zA;p4rC?4iz6Hq z^#nzRrOYf!wYsX0y}>j8%oNfo)Wj2KeEO{9_DK>ctd&+=;ZA125o-lU{m)9mOv z1#=A2;J8HuE#xZvYvzt6X$-5WQN!Zerm*m3pfB8KSP^HJCqvXvx>^g*GG+!3qY-tG z!0*a`7-K0RD3gEN;0^yvOUhNaKqM|=BMpR=q=i8)UbUIR8M+Mg5k|P-6ez-D3+?EP zv9FI8x0b$*9=YcQ#8Qb|4W}3nyam=!l!h`y2bxnl*S|h%GHOo({TJ_` z+m9?*Ee!C zoMN=g?ID}7j82n+UY0Z~xG7GuK6Vp(O+eE4_{Cf9j0(+6Y+sGhJS1Apuy+Ydj+|XLv%9_oIE{zh0`@zpsSxQYT0w`CmJEpMz6R zd4@;yOF`{f8Der&@jp$mC@svt?_#D$LP1l0L-N0KwuSC-f`b5Hz%g?WfU^)DDSn+~ z1H$HyCx4OIWyTm^?HI8NCiakw2)`xbI$FV(c?7+kr`;>Lj^|ih+ldC|MvoCn-z4Q;WH}D z2pCgNmCxE#P)up|D}%9}Ui?EwZ*-nmgP z($;cT60wWz4)~|wumOEj)_}~B5TpuE{<#rz--d;2`95_bt&$_AW&)dH&sYn3D9z&Z z;kDN`g}3^fZ?!Q%a#gwvm^cOf4BtdSP`%lq&#D*JIF>|LE&2HhLxX+o_}HxrG)Gvd zar5c6yFOQe=)mi_+fh;Be!(>&bJ-3DpT1Yvf#?RUl)@$1KJ7a5zA=pcCxej&@BE#cM1OLZrq}k@drok(DW<`cxZJhJ21TmUwuMmd8>$43)%!SQcT)SWhH;ZpUgnNqx5D$w4<*X zXj%jHE}piHPtx}`<1RR#nexxapCIe;I2=r%c{IUS7XIR-kE8vZW*uCS=7%#M(8D6` zf0TP%|8Hh}(;kuRfpoDWorP=ixAV8ec#wCpg_%2Y@{75%uH7zZQ_!>w)wFr8PJE^B zy1EUGj}41Q`BlUazPhC!LF zOf%bJ1fmO9@y{&thdYyK)f)*zy%f$L&=qJ8TM5oS$lw#=qzW9>aAPHU_IktJ@zLYs z&v|DAi&@{7{(Gj$H9`1Gj|zG4mH)#s*=#sH zyfLr|`~Z1ktDm`ovjMKF|386cwuT86&Xp;8t35Of`u$G(P>W1l_BOO)SpVMVz1%E#T+6b#A*Z~X@G>JmiU?B^^r#) zRBgPXS}~LUt=Iei`JLoR#;*3S)hA_r9^MO_yHujqWr5fPTMBj>?GU~snd_G16E11j z&iE*VWtx2$VgQBm@Ee_&K`D%f#BxWZJ)4t+aFv33c}q0@j`0eFvl~nz41$l>9TCcO?wQAkDa&FdblJ zM!}{6HhUDj4Fib@aem3C(+EH`4LGxD?IJn_z|8%yLDod1uwu~0@<(j3%f%mnX}AUH zdXjn)r!+DmZdLG5SuqX2%GFP0wax@ftYcV3asEt4Tg$mEe_qd z2E-c!;n}nQKUP4QORyFAwg95i2_!Q&zr;qJE9j!_%~$}VGE&U*)jPC>p|<;2pqqC? z5~O5|l4l2Mi=i7ivt;@0wQE?rxaKC@y`E=8KdoiX z5@)1MkvQqPU|b!B!sJkm_wSYeB4T;I5m|bBo0OUF9;a~muU@(GG{AA(%#J;K4s7qJ z+#w*Kia+$B(79K<54H8!*k51KZFXZ5*WYs-pv4L<_530mbu`KBg8RijrmojXKlehZ zx&^03d`0^pnTS3s(m*_yse3J*ck%Lk+5g&Muc^{^SVbMr?vfg0qOiT)#^*0x=VJ%l z(jxrznM=dpvJD@VM{<+_BL@Z<`cdWA{8D&o2GskucdR)HH-Sx%se6HG$X+57*gIHu zy?=)D57=xs`UwVu=X)Ld2~07^#ogeWu8SWNwR=u{{Fh@j_uoDXl$d=TGaaX@ByJON zCI|r>(Pn68qb5vQ-VlX?`)fO{l-bX)?t{ihnMjP>=St0~kxN`%Eq9PFQg;)^HCb)S@nXqy-tjJ)zu3G6Nd1VV(b!zv?| zAwdNxtE3(hEl*Kkx=7o~MMBTRNh*^2;VgEUBXDtSU$awnC%3q!TI`}vhAPVUgv>yG zK&SjAfYiS6H*K1}M+5g3PfSWo750s_y&F7%ThX+(%oJ#Cm8tR}Y_sWJ32KrkHO$PZ zf7g_o&c-65921)c!sHMGD$?I#jF96tE^{@%srM%wdka*N6=Fhxamncn`Z8ScAqMpq zPez{X8TcsVrOMtdq(t>F&QT#}y$O60kR z2t}rQBDGt#N_3+`0`D*Mik@R$ro>{m_&VL6%MxahK|1GF)8kuL$3I!PXrv5>U%eLG z{}6`3Dt^c$*6#R^tSEa!-=n}x5gk7Fz3GOJ&>bFY9p)N<0ftY-U>TuOI1Ohyy};k6 z#EJkYcN(nAe9~wCkf!lj!4x*7N#Vk4_%`W(FY7WqI14nALgXy-Oa+zS&xJ+!LERmT z+4*P+h4ptM$?=B>irw(C9VI;g!lL0Ddy4xt5AE{Pq0J0D=w8w6TR!Cf0Y*Rn#6;M& zPP`K|8=6Qo*6DJ+gakJ`Iq*w$0BVn!^St6-qL039mq-CApZPKi&Yn zJ4%D(TUK4ZfI^JXLABJO8>5pm&5)PQjh7;MAuA6Q2cqnj8kE_xSg!EC3_Y)X6?!M# z!G=kA+c&q0#-N^j>TI4E>!j++x^bh@Mc{@H#h-Ld5F$^X1t1ypD7Dk9J29mri$;Z1 z-wbHARsg<>Os#ixs-R}GOicw2fWi_j-8#{$Hf`h^Yd)@6_qr)XeaSw;4^HHWw5JBV zo{o#(_FsGCZV%*jtHD=6Rm!?SF{+`v;jj@>Mf^k66O*x`r-OEhWd?>%-w)Q`cQNNs zti83^7t4?`OFcgTyC8Hx$VCL{ zn@v^168e~|W@H?%*CD)G?Z%#(t>o3tLowK~?@~lZ^&-e6&!9Sm*Hq|wSP2r7vS9v5 zpw`C1i(8(6+>fkxFn>XFq97NmFKHgI`8re+ot4^raTOyzL31?#!jCKy??8T~HPKed zvGdm$QBIHDI#O`+-1UR0TV>Bpj_z_YPU9E}lmyV=V!lX`uVL*tXlF%W$0SDVdHPz*7$WY;G|gonWxmvMVVpv+@^$4hv6ocQP7 zCEotC$!TWvlU-WwL;H#^FRwQ}GY5Z^R*e4KI*+%Tn0r2cMQD@BWAWBc%~|iz&7-68 zcTfYf$vKL^so{p-=EF>Zz|l=Vy$h*be(NvlQYeJ(bP&dxTSQ|rGG-;Gkh7tK#4C?Lw)gz|iy{_kjlqZ{*%8Mp0=fFMEV)pn zOW|{DC+(a%%rem2A|Q2$3w3x7aa$0l%Axn5pfK_w+es^f+dgATem#Y#Z_{DT6gx-R zOwFZ}&zH_8g}*nSJlxQTf1JHLm7b~5qQwRb_5o(4Wc1~@BCWm%=jFkpQm{?=WHYW?ZTNEXpPRu67L z-wq00)ig`D`jK}MqO@+drekmae6_Jpfz1ydoGQ5WoGqYkI&M{K*(xmryAu8?gtI}q zbJ6ZL)h&C@-7K+D%E>cLU-kkU;cc30|L~*Po_`p&cg>sa<4Je#3dfNJI4JPxPA-Y9 z$4>>0Y9ODa`0T13oJWwLUNCxNo=ZbAB`Y75fTtGD{=J+3xsO29LntE0ja(aPkdCYH#oRRnVot{T`+uA>s9nv#|oxLwh~*F zC*%o_sbpR%eJ}XQ_Ql}S8)wkcE$$;svY{%AuvD0ac(Bcip(6w$!OK_tNMJs@^c7r} zq;Q_)%2{zA)zj;P0DX{dB03k}g!X=99ObAJ9q~qSJU_+P{WfPZ8fu+8cq-f*Nlr2cB3gNSV2b&jnieu=d_YGW5%7EpYm(fET; z;PFeYndcEo%jk}oWPvP=x=i4wfa|H{IePhNM@qJZCe3*W*zLcJRbI`+csqb)V^*kmYp8o_= zdzqp80~39yG3FYDH4Fv&k%g|Jv=Tfq9-xMjXSH8*MQ$-xn(e~C|z@;(PTtYQ$U1sZUR6g43I<$MRKO%)PDV%$^Rj6f5(hvaR#%&;46X zW2_TJyJq~wttqTm0TF?_uWh6C3t48!mROx{1lny`muX6pkcJtIkFS2kR-D6RZcY&( z#=JuVWlRf78K)+G*)0Y2QRMHV3UB=|$}jc0_GH(~(N85*hu|E2mP4vC70EUHdcrT1 zV*F}Ym`CpYj#R()vVCA2(wMOYWPa1#+zm{#9{}sFd=s|s!VhRmVE1~!Zul|p7a?ZG z0cE%HQLrG%*v{ua9rN5kpi-E4HpOO)?*mcC^59>nmvkM9k6LbdCyE|C!Hif{nBxB9 zSQbGt3EcY9(+0}492?9g@I_c7#4?lg1SoMIO>d%pb#^AQS1SoY!DwAS2w!2UesM5D z5c-<3FSBIcbqpG4_oP&d#NvD%SL$E>&?}}-YR;1ta-~_ftBD{f)n5aqOvDNPqR?MZ)k!d*fhtNKF7v zxo)dwpwxn!A0anMT5rR%;K6QVgal(l!prA#$j|39NsEnh850)8Fa7?MNX#`FuVp~%rCP6VXfR715 zB$2KD1}m1!ejq!#RBapC(X$A6y4A#qx48qJOzn64@>D+3)f}KMq1t%~S0us$;Yc{X zx0(A0RGUEP;58rGIV^QzWG&IWb7YPslnP9AxZ7YJj2WXXT(hYRmQX3{TVZ%apbTQw zH6-@hth9r)!jY&14jZF`uUHlzR$6O&7|WH1t>A3$AKgsvoWJ(BfBv!>!2Os*5j^VP z26qaYkTTO*h4<-jZeIc)P62+2Q(#gaO>Y*f-R>Id*C3BmU^*S55wbhEY9LF!_JL;i z1Xqb5SOXZ#>T~GfshFo=vtkhwn)xy$Jf2894dsl+)y7W?2VyTuo#8>xc1cTkY9C2jH)P{CRbsF^xM$o$8|g#5e^GkME6a zc}|eYeS7D-27*Uod_jZJLN7dke2=QT7Jd=LPaGsE0PCz*F-HiLD4tp}{r)R7lQa>o zdf`cAT(pi1>g1Hn1+xPyP-q_1&F zzY>LUy-ntUScmb<4zjb3)TKQP9vM-FbJS#)3AYE?afu5Ek#fkq(M?FO9R%wRIlApg zy>czR&I`LvL#EPd^k`>hea~Fy{F)61RN-V19z&4+4FYl<04d;PH!sIl*n7Lvjz!JV zTPJZQmP@13h<5m-EjUS`nR8qVX{=%8*q@%K47y(#j6VI_L+W65_OY5c$6I<*oP_F= z+kN8Yux_AS^sr(kp{amMtm3!o9bu%E#BaT_ud3_Nd7bxs&sm&??E;&S&E+4h;cR;p z>d)n`VcQ>VWii}!gF0-E)xqC<1Nqxv*0MclX1_T8tD~09<2dDUW$MAAL_XXPt39q#e(WG zxvrBk=8xNM`;b{N*}l)ksDpSs!#9XaK(~2e@qEse9}Yg`57~(($n3=lfoBI{4u&LsLOHoIlLM>f8Y?^_?hw z+W|jJReCtZ;L$+URulhgI8fT(?4@P(I;5`8L448?j48w8`vc^R+fKV6w^qc47#J%A zuRh{b+B5#{`yQ|f;HSr+Xxv^!94qQr7FSo0WKIg9-JRmJ+|L!gecFjV=9|xdLOV|yw*A7^;x=p*;KZ>(wNzv(koTX)edCH>`0aaU#Q;e%ajUR^Y^%l_Z);@QAQHJv( z0fC0`dG>RtaYmyXT&WyiHSZNGc^OqnJ*37yk!i3Gjun5n@aIpzlGYy$y8@M2PXX6Zm+$W@N0>68W<42M&dl*`wpY#@_MnHQQ^K6nA#0 zE&o2BX11r{(|3ii^3wX7&mWvR%Pk=?M^n?j`}ReP6)U=Ox~F>dTzjRPmPhOqkPP(Q zE^>GSt}6%C(>IbI!p;d$59`|`pPT3{ak`Zwc&OvXPNQYw(^W1jnl|fr^8Wp1;iHb; zfq@TQehbe6!C1&+tdStUX=@lY{kWiG=!@03#i}#+_`0jY*sljxRCbMz>O1s)TUxLo zVdAaE(POuh$8Dwmj<;^P6Lhz?Thz*a^R9KTUnbmLouhnJT+PxpX`yVT+RdBcl>1B8 z+!V7>;m%#J%A~Mk1-sB&7T*;oQrE0rby!U=_1E4{Cr+uEfBR>>qulXgg>#l({i>%~ z|M_Q=pZCBZ%Zrgmd(vD@o$VIQahI_9+BH6F_{4}!93?mVVDhgHvFYecCGMAB3>-Se zPu*6h{)+m$)nMn}=hyGQYE<0xL1xRcVkNDAa<5&Vt?Yd4kl}l337h_>JMWbb%J(N^ zY~4cMlInb=p4+%ShJP*JxBb$gvR2*on*>i<-aV9b%lxcwj}_DKj@;wXMn|_)K3vJ> zcK?~=*>=06)$E52U99;mEp@9C z=6^c6v+3*Jd@jSAyWNhsxePbwpLlS);{LIa1E(6*w~j`lIA)ReOpJKh5Vb zF|qIH&`N1baa7gvZ9Oc)m)fM2F)r(C!cxnXyIpwO&ii*h-)y96PUxjQU2^-b9rq=( zxU3kVRj)H5TnBk~M6oPiSG!R}Ea64_x$EDnPn>Gh6p1}@D9Qe{NZE>3`JAwPoiM3G zxGsHc3W1bI+VUUrUxz5XmwY5m?~J&9E_%cJt1ew0uWZwPZD)?qP(5C2yKftic;9;E zb2KMs#T6b5@WqP;DJ%Cqk@4~8UvJ=g!t(A9ZG1L&u4VA%jR&4{z5U12V2)+$h5aGb z&J~uucaFX3IceW5qIK}}|1tK}0aflx*VqCIf|R6!N+U>1DG~-DAsy0< z@Ls?7<@w2+*7HJPciZkc8Ol&>eJA&OtFl#tyo5L2-gvexA*iM*9Ghy+C-CvQ&0f;D zmX>AfeL1?aMyT=ixpeg@sAX{`dHTgG3Vb@c`wv$8v`AXLy!ETsNJ?~-?BpD)81LA9P^9RM zed+00#3Yn?!@{VK^NEttgjaUnm_%&QbqR}aU#=g~4LnMt&CjM^mBM`ZFov1$7&dWe z1f6tT7${>E!bJjv9tX3md)qeA@hZhu&+FjIx;pHeu7y}D7h1~Bx^8^q&ebjwPsN=j!$oQ zDtj?Qqr`eLaZz4#%)XGb=zTe~%)felVnub4(=tmqOuSKxR=MIL54 zLrDw&QrW@ytY+E8r8VMMCiB#(h%*!?LJ49^33|;_xfMAT-=tved+jYtc%%;Q z*7=`{cg`_Y(527ylW~o#=re$m*E3lL#@0ra` zq*v3}!yzP@6WZ-czOcLTYH?5)a?GMBYza#%i#8bQ;OOhOHOV703ULj#R!Xv%J!cLzf)5@)uv5b;`{}l}R_{4_ zGkX3R&xUei*^Bj&<6T|Pk)%xh+$tRT{l=Bt4USuNsb1MbZ{b^|rI3Ueef5FiVZIrZ zXZFjSTf-N5?L=Z@W4mHP&jkQ9i+`V9_Zgpk5!bDLy0Kl0xJvx#!4L{^b|Fc{ zdzT|@K5xF{B%>yI{MG5Rqr~~(^0-UiU!oZa`FVH^`Z&++7vj@eE!cRPO0$xH7hN4_ zDv?*w^L$j1l`5<@|9q7sYpl~(S#yaz+LI@P6Y>)^De4+!pwg;3_!%bIemQJDopIa% zH(R0E%j#teJ&_=;MIbIz1-r6V-p-Qv*7#z!HuP1E zBRM;I1_FmexVo8NK2z(psYISG_DzHFtLPMxD!6AgGV&oY^z6^_J?d5-MdY+H&@&pO zKTb^esN&#k$S9V6DJN_^pQaz`Sr|NVOJp&(=o3~|P0#E(=9`wHLtm>-o`yO=R`vbm z>EVUy^&p?I<)1$#+#Q~C^6^~u3t!{tW+sLJ38A`o4I3#pjjR|V^G`BL~@6{8pDBdXDNS^xT@%8pif6AU?%j_)?Y4mZxyXx!11JPNzqxa5F9ty>|--+ zGlvu?F>jqxb@?#5fS^1fM6+`JC8XX;Oj|bE{4LP!4cM=v(I{C&oaA!DBVVvwzO0je!7I* zjrWRr<>HkC%{5FtS4!ZI2ws-h3-PBAwS3|evQQMJF}W>4kx(#c2#<0(Y9S%7RD+_` zwCzSe*(JCPvr?36Ib5D6{HH8wu7X>%tr^8{eZlD2BAPI z9NdY(06c?@?lVXzB`GNvsS1diMT@tik0MVGC+csof1-L;3>r3fa7GLmuusZ%hA^YftT+>Y><+Q|{B#QJiMc#8m`zHQu+-nX|q(>NO0W0_=o% zhCrMp`Ol!~>-+AH=Nq@>NUA6!Be|^K zz&k^pvzRku9{ao`9efF-^L3+AJoFkb^8;(%tMGA#THFlqm%4H1EoT*6%(2Gj&!t;> z&WWzF3vr)>+W?m~1}<1~N(p_yg|AZA$O~=)CK($`-%v7i5?5< zlt9ySl~0hOLq4=H;jbNGhElU|;eRe&1uv68--_WVyyg(0%TWzXf=bK*04hQvV(En> zHI7>B8c!`>Mxv4frEx%7@@_hWs~kMIA!~z$&?hb)VHPBFc$fS~(7=ywZf=7w5}}IiOF$;>kNseW(!DmK+cYdZ0r>L-V&MHVN}yoMNFS??0=1#J_Swb zV12!*9TCAhDSU9XX2GOF$nSj2lgrJTjK>^W5$6+DzX587Aks_AFQfXOqDN5KwpM)@ z1NER-sIX2hm;`FCg5pJM>iCxAkP>cfcn=b{Z~bcs9oY||4)}6<^5;@gUfC$OM~gpy zzJd=`d@Agg*DpLTSLAcf%)&(yyfNQ6*Y{3JVg@yvc5~@LG?X--o`g^&AzFU+|Nhfl zj}3}_O794AyE!gC)GXCEcp93-+V!Raovciiskl#%k-pE{x&is5n;K;EfXGQrG zk-PRZfq@}Qo%~`z*sHrai4jqYD=T9cdFeICzpb?639Ka;67K3*J+n&*3v3wEK=Ni^KY%a|p|43LMkXIBY44?#pHST7*cDsUIB9?)=%`z{`pOVBIyI1twRo9Ci^Iq$iA!XXlDx2 zj$?iV^C6Ho_2#zjz7M_V;}fk8xf87sQnhDntA`;Y!}k$EY}Hkq4)!tSg%Bb&AibLp#YYGT zX-GOx=Gtca=#lFnyuJU$Jxlh-Xuifkn`?C7_DVFrjoW|sb_ zI0cQ?TO8QHo}t`-t+FI>EUE#>g9(bOIA0cuOh1I&xA~9hiPG7RAMD0b!!<(kHmtnx zrYbvxQXBp(?DXgn_MD=n(w~d+-==^`F{#}lD9~9#HqKQ?;-F4Z%T7h$ zvjS3E=HOEtF<#bw^~#q@8mQgayv%E)aOPa7l4cNKx=Ml6yIRBF0e*Zxz&~1;Euflf zk~ymYQ;ade1==FT+wN+qFS7}yBr1;TRu3JS640baoywPF+(&!wf3&02{!aYX^#50A zbi)R|+u0ixAP+b>+qqS!=Rb5{&6tC{gMeBs9UzKmwUjWWVC+qsuYO96WJ-;@7ejF; zQZL~e^!Z3h1d-94c>?@UFMy_@?--I8OPF(WDwI({~9xL^;&)vO=DSX>c?sQL;MMlBy$REBmR1-JqC^; zDV(k6!I-%r7o3t`=Iw`hKhAKFFJ25?g=`TL)#7C(E^hgXQ$?J^oI`4YzhibfY9w}QH#qx8;b$X8n8{4>!@ zN?a-mcUgr*yp;RJph19hQ{tL(PH0>QdH1d5o*?TJCxekYX?hdbpv@0+f6fAt3%(7< zqG9I+g+?zrkO5+@5?fD-3ID7oGqVK?Pm#yWo6p)qH!8AADR#p}f1#les=8b9#?rYD zA4j$M)U@Y6Twx;fn;#546lVSmIg)<*$CycQ%K1o*!I66nk z*cp$HrQTxKHRr^BXsD3BjwTJd4<)n2zGQag#uH#E7mP-#h$sUo)u(EaqH9<56=Qn% z*pcG~h=vs*gZvr%VyxP~ex&Sk+7y&$NUMJ4au*Q=`ly86dXIAVB3OaUqLXH&$SSB* zi-3nbAVq#jqn5tMueXuKAWw~iVYn*30o)@nIAF_9ahuhWUm&hrY{ordRuxbx zQEyrVys9j$kiS_G_RPNTa=qs$cT`xUSac{2&>m^0!`e)4yrOLsl;k8RAb#13tV!oi zBiGROgA8)A1SWrS2#hJ5P|KB5e}sy6zO4Bd1_RTXNr2l#A$KCeytWT|B`Dfi1BZFT zBZq6?we_>e7Cv4Ke7xa98>p^57K2<|zn+9I;m4Os3lct(0kcP-Z{8-t0afh*fZ^gv z=u>wdiw>(bL~v*>$BC8@BIL7Z@cu3!(5xfHm&Jy4(Lro>Bv5^rea#l2_`JA;M3Ax7 zb#B0lgceb4x3G#GT2*39YaflJ|2rD{Evm^)qWI38+JpW*<$Hp#5wBz2Vbwk3U`p*dJ;5+gv8{_8_ z?ep)|q$XnW->Qie=tl@RU@!U?B-X=WO`Xlk9cUpWk;jp&%rdKrp*1lzB(aL=chJES ztByl^X*%ROX&eiAki~vlrzJ|i4wrb=w19vu|E86xlQL2ba2{isOY^@l7kI@(KYk#x zu7(MZIqyobIu>be9=i{E!R)Ju*r$O&RYT)qpLJUTG$JCxd0PbAK3o%}0{dcm0cYaX zkC!kppXLv0Hf^i9C6D|&Es?%K0TssDZ#Y#HRd7RVA;Ti!V|SUY<{a`5-e&uiB|I*s z5{k?DBTxY8I`~q;qEiqg^U~U(}YpPU*79r-lZRqNQk-5aXfPSS>mm6*xTLtXr$ycT$S- zeSA8|Es|(nYY1M5KtN0fV`C(88Eek{A?UuKpo1hruTdR7ZEB2V#Db6i5j5%cMk-{p z>Fg_^%O6wkqu$xVl|x9lg~a*5|F4)C6UfcI7@b;tKEUhn5qhp5&@8?gUS^IB%2g+S zK3Q`dxgqI-9%8z)-(*f#VPobF7Tvgwn4d+0T~Go*7dp^%pNbnkOtjy_{MYvKkS~;J z04=65lvw!}qg?R66`z(XN%*~qzop7D@czE=24>4 z#}CONZH4qXNZ?S^iVChv%tT_d@q!}&{g#eys|ixI{bV9m$2CPJzomwBuGi?i1^(h*;*gF`o0Gwu^grKK+(QQnp0JH>p%82N(mgC zK#h}b*5RDU!!mP=9H9br;;(Q?=SE*J@fxM#1=Q6CJWuf++~k`PMYag-S8>ik@Z>xH ze~p%dd(o0W`XA9UrkwVWCyGZde#$u&O3MCwM(RzT@&8A?X`u(+(*33)5D=yfMz4(1 z7=F}}Mig>MXw3eXkcsi}IiBT?2f)5H6nh1K4V{QYLMObz@ba+h>IEz*@?B~{@_f#{ z^hEVXM(hDft?*xu0rR^DpcdN!vZ4`zKT3F*Q2z~8nt#O2PUk<$O?1$H=ul9dF8Z+8 z?ET^t`GtVH%IzotUL)jX&hq~Qo)tk!+(T!eG57=pAS^$mb#Z+eol(z&FiOpA&M@Rb zI|wsQAj(fm&RuQGJAFPXpkN?+m0z0GT(3M72|GqE+N1Ib)=_Rt0} zotq@9%HeJuN?Jv@lNr>6NKk^+#4Sn0pB{pOC~^KB5;P|;P>Hqow7MviZ0 zbCdhkCWPwWc>f>OM&ckA=YL*5v)4ESa*kwdN7O#@2_I4VfwBcP7bHJKnFHA*$e2-H zH4EXPt()|kDVUFmRB&L(CE#!GXn6*blAh(?gvK0Ff8f9Yy|llDEW&`lD66^mb={^L%)` zNYr&Yad9mm5(;;LNW4Pcr6MSUe=wVW51r*dL+2hgbo!`Naj{vH1Ch)~=OI>zLi!2j zo0O2i{3aJneI6?(d_}8XFkvYM)=zD0>O_)G(~nYfFs6hy8C(dVzrF1mj%fmdBeq5_VHT7E4~D?ztCDs&Uz7^{ka^`|%^S`F4|xK(YRCzK$yLwPQ27tmL&WZ%Uv(Jtv}U(Nc|_EIm5$q$%#-0b?Y08-sK389*|?T@Hrxg{BC&Kf~sYo>u4d>^`B&b z;AKQC3aLLV|5bl}^Yp*@PK%$Kh0GldorZng`3vEU7gwS!USMYr^0#uN{i`4_+i7zB z{Yb$%`h*`@4(4{u@-!D9EorC<*UBfN#(pVFphM2+wSRv)Yz^&s%Jd{sXBRfzzzJ8g2G?j{n@5~9NfdF`){~?kFZDKw_>{{4PFvs zq9D$WjKA+#vtIeD_8dd>f;CP=cV?IuC{;+T3d#cw<(&~k4+nLpXS({p{EC= zfb`2mTK*Y4^!vd>kF@B%eT~BBMOr8ykd|i6uwo$^nl?g@K0O7c#S@VxKrW|1bRdzQ zE6%Xh>tethl8%#-5C;ASbszeHUVr7&_kYEYB*}jZ22k>j8~h;{Q2lHCqz3Gr?z`ml z+$gFRAJcw?6lDR|v$_<2;5Vp0NJ>&M#F8GlzZ55j%l~X=DAC)=L(2x_gIumB{t794 zLyKRW_3BNd#Ygk`pbm!M1L4tMK0x07po|n`UOmflQ7RVFLOdV|FY{Yp-QE*OLwZNq zq7XFXxrdn$NoZb!WUfHi>35Uf#;m9)ww~eS{(N*k6 z`X6J*R3svXcNjb^dI6vgMcJBDLWM(h8EaF3n2p5!n8WrjSD?JIh^(o?W`l$`{d&b7 z>>$qRNcTq0Pox>aHY^{_=Zc60bCcPxK%Pu6f)=`@g>)gJJ>mY{qWQB22ya;vx{#pK z18Sq-mqFSP@0uDEp?G!Fg5(~phUm*-cLnt#x1`YG03ihA5=nb=Vq%;i>)I@1tkG{!fjIC>_^=>S21!3F*e0 ziEEXy!d`#yoapA5C0woI9{K>#nC)liO4Ex9a`!X=iBxRw59Mn94^2Sp$iBQx^gB$7 zL7eJ{LIft721H5!(BRyPBN`lSe?IWy`~dBkuvj*P^cRxv>rH9z(ulFS7>mDI_B*fa z-#fY+Kmd^NNK{O*!Cd{!$p`(!)>ReA*?VVb{(lCAhAwm&iKt1=288{Ok#QP$&A&v( zF(RV$BNj? zA>I9ihhO$(rtH5`FoGlmDfZv;wUn?1uZ~X}r-+J$+;l$Z|CYa76tOB<3wwZ-{LQC- zxBCAk71+1S3=Xtxu4}UWq|hjV5OnxHVa!~`!ZKqB%|p4j)A}L-x4fpIq289}CT2G0 zA`A7F1_AZ zPiP(iWWb76nCuWAhmiYD@R$5 z4+cui_=0&Y<@+s)z>%lI$dQAKg)3!ofJXF16H>5i(_*e@U^@iJLED{jw+Hc7BCjz* z<|4Y)7OXRSx7oP@dVr!F90~zq%U>0ooNVF)O?mfJT09^LWJu2sJU7=8*Fs`0rZ;b2 z%qJ8j+tuD0)|DGwj&q~?!G?E;Q-+hw{ojpand5U->p}EIe zFq4-uX>qBRn~A0GOX!GC#{r<^!Sb<{4WAF_MgLXjMjHB{wr&sj`PAU$z^@X6oa90fkkhay3L4- z*&*1o&K@i3Y?-}RX^p^JPj69AFu!0cp4fJs*>-ndUh09Cv$jPAmbI=S;?^*kAo|90m=GLFknhifxZ9tHmL zVd^9!!ls6XG6GG8*9Z<={i~9KKX=Ov%7Y-#Xv7x_azn;r-(i?|tB@Ms)If{~ zr7inXV@WPI0<5`q29p{SfMX)M!bgd*d+U5jF@+idp{K~bz^&Mw^8GUOWd9J-1#h(G zla>CQmJesEdUR)r(a7mXdtAk01@o1Rk7c>Xy5^0|RxCv6Pcve5kYHjV8^x&@_Ffe}(zB0vTPBRN-@f#z@x{vfZ{%e`xPDM} z7hy0EKjM4hpGFc^T~8&c;-TaP(hEYB+kW-g(Jsc0-FWp#sY0r28nELk654w0b3H5bHGDs{= zbE~@Ft3#0-*zgz$G_18-d+VV zjX`V`eLe-caVNw4=tyG}+(vn$tUKH7uRDCqI|#P{h$a`!Yp1&TG}G?YiV!wM>O+1-x|=M&bS>awE4Iq?7kCqT16gx;PKuarh`N6 zP6>_quU>)OWB-RIAz)2c`M_IjICn$)r^fxytFgEFg(xT~6x_%22Tm!MVo7o3*xgXoI`9aq+z3rw-|^b;YsVn#b?2*3F&wR^mg5WOzSq|&#Dt@M}$ zC6fbvTk8)r_1SRF251}kMt`dqvcjq8Dmviyt1!Ipg+n>mW-zny2gyc@k`ce4M=&{7 zI1;Q_sXrl#S-zsjv4Prv9{aOS}D{*V9op9};>-MB$pb{+v=L zp<#RRt1bLJ%ux&MtY!-Pz8I>IG~j9pfun4ZG`NQ6MV{yWuNj+7dseFP0v87*@B-IG zTi^Cx?c$!N5C(5sl8NX}`)0|WTU=WczjJmsFOsi5K#=3r)`gq*Hh4xJZsFLEy?8f7(B)-!v9R~U@Ld7e^p*jAk;RU z6=!Dc`_9Hk`CYciV@o)MNp{>dO!GgZ`Wo-*RMfVbcrD~kW`}Q4z8r;qq}LI_in1W% zWB3k;#wY>gqgB`SFq9qm+9@6Q3hb?;vpr+-5{>s=ZV@|}m}F5@Oa2z`u<}|{XCv!N zZs&DE4`TQ5futs)#$aJ#WgmZEaV;5=f(z);K>opkqJ;ZqPKJie0X5X=zR4vZI1~JfRm`{UeE{ zz-5X~GGglFt~cP7z=P?Ap`;&!DH#`qSI)Uk;Y%Xc@pQg(IP)>5uINTzmzW=gTw`}S z3qQ-@=~HMUArgO{CnS;N^!vnQ^|8X*PcC^(9WQr5q;0!v`GK!p35Zz& zh0;ul_14W1{5YY2ytZ_IJgryYj~Fp2cb3ksju^w?HMgNrRkC`sak~Nwj^C4X! z2^SXPa@dI=A>lLr=(@biMRC#7{`uAv>be)>_dfXtfpq0RGeOpQ4tT(Gy|yRBkMT1m z8LefWf*m7jU>%>}SEbC971c8nSFS|JoWY-7Fo zAmQOx%g?DrPt*W!L#NWg@7+D?Mz%hmeEGO%0H(l$!+z!3>)7->v+QYo?L$aTLMy3{ z!>ldx+I`b#Qm}(ZG=Wyk(Ff)qGJDL#tPdxxqVWf}q|x+|8=(f_EGh>c!y*u*7!G%Z z(2q^bz3h0{t-(cLvv4@^Q(YjY5`WiFiHlf}-+@Qd1H42;03X3j-TOcTtbZA(r@~*W zC6*R!$vgisf3+RiN2B%eK5>wk?W{sn=F)~V!6BT>-<87q-W4hzdirSj2uc;JViftSGH7#S}U_sYVn5svP@uQZWpI|}*-`K0@?Cqc_sRbOEoWxGWMCP3U z8#|`YWG&HSk*_hfBpI{72=+gy{v>2$=fD!?z+!j^Gz5?gP9ewx6t+4!TlAF@XV19I zU>atWj$2TFXH;xNW$^Cu1lbHNOed+oxPJKUwpL))IU0OBl{gN|(2T!!JPtzNSAG_Gox{ODTX@Fuu%5VERT=k3PXK+Jm zJOQ!UX0}ETPG2vvDqmjfYyuj*g~PXXnNXv;m=pfCS~oxyk->{RaG(Mzy>(fE{Yawz z%38OFU2lm8=CqhUaY*a;eQgXe|H3Z^{TVR`g@lKw-&)3=q2MLyt^U|VhC&>i?@EQZ z{Fbl=o;mhqYcKy}c=Wn&NyYh4f2p#$?YB@}ebHycw%Ud2J`I3hb9Y1c{;=1=uhE31 zT+((v(n24|f)eI--xH`~Wa0J9);1YSi}E9#Ul0`_+f~R9lQ7v?Eqe(9f1R8w_u8zQ zDQdCUy?B8Br3HA?3ocga-K0sk9?m7{)=4^AuK|&8V7*QLbSm+ z1u|;z;6eY@QQjXXhb6|MAv*%95g!}3fw`BbNsNU)MU?yMOD?D{0DsO4q3^D5Hn|&g znk|#hP=^1C=vNqzdMz?pow+>|`GXT`0J+Y#7}=Gk=UZUlbW7*SC=(9 z5`E$vcDvoAf`@MhCEw|nNx6b+J}+RP7vqntg>WC zt?k4_E4Hbg?B;rB(CH-U#3ePh6Uba6p!0YEbdT{RXe+IhIGz)-IA(5siPa(49il3m zmy19H*XtA#1@2Cl6kmqkQF2koL?0L$Z*c#LmN>^*L3io%qA-L@h7xNaCI(2#_j0xoHAy3qhEhQ|M!H$3{#Cl_)&L2;&p zK}hm7G0702he!G>PfY{3UBnX=a&f8ykwKb#?d|*aSo5qJ+YE5ho5MvM!fo-v+;Gx` zgIhPabbyaerSvv;40wemHQ|Zv#$l2vmTX)6?2ESg#TLvy^`tp9RHZiHN2SpUDSAr# z;T^zPh{c3TK6?g%3gbxGi!SL7iSVsS8i$@kTJH+=SSay=!WQ1I_C)LU4V$x6r_)Y@ z7;>Bduc9%a&8yrS98iaFJ~=xZB~93MQTVDv1sC!^J@_kLVJi!#`lOl=b7c=M{=6Z4 zz_{IMZP%^h(c1$KrcQ11t!%1ax@a~AkFYd^)`%=gFm;B0VyDetmF2|48vtGg+XWtY zGCCqwz2f8UTUfb-K&{uO0JL4ImS`>2rjx@?QqJEJU@&E58V9{;DH4BFXWnzrqV+EZQmjm7HVnn`rT0UsvT04%EkHmlghR%ev_ zOdW@Vgv3j*=ab4GZwFf#BI`+N91;}`VlPU~C_W!8O-l1={n75uBW3pi4z2rUr^$Na zHuI>PMBOZFt4qr9F*_yCG->Ww?wmBLdXoRT|1By_w7>xR-P6W1#QKD z-vkMvT@=8Iyu}s`rCPbiNMTdyRHSOzqv^McK%Afs4(iL9Tqh)(thcaO$e{qH&RjYN zr_1K&h;!CRlOA4)IB=l*uB*(YE58=lNkVWcnw#PKXuatMSo>ywjLrpS(t1!sh?3B- zRIpy!^>A97GVr%{+5M37$X#PlGFYeyMt@;c8bvM9scyB|U&3a$va}|pD4wP?CY{Ro zP*a$Pa!K3?BtB3Q5Ecz{0^lFz;&VfpZigDfsU2=oQeQ{>q0^FdYUAop@qGuLM)7?beS0Cp2u2xK_xp~HBNOJhC)Jq z!QUb9=#%(gV@7CRM{J!s$y6n_jT!ss$`>0Ok-br1Z*R<_ZBcp5Lv*T{8uA?91Q)-F zl#2=f&dT5gzJ7P!?$AOJYFk<9A4WEdO?^;?;QtYIf3TPTZP!p^WZ5RQ@YYJUlSXc}o|kB!5GTrU=KP+FPr@tYI~tR5|LjI4il zgbsN@tRkAeudMuqn0)=NJ{XQ~HV>1(ozqdN-o|_e=d$BFmH|eNi+GpHF-@qhmHGgi zVVS%Q%UGDyg;82IxoKDl^A(WE$f|}c|c}8ukTlSx$XHTHF4BsUkpU53^2RxzBX+!$usoWws@G(DM2Vo6spxD#D z{=VN0oLzjf{jj898L4Zw;tV)&{g71wSguZ|TMHqs^SGeH0zoBwuIV_HSeIVw>S+78*I>1jG)MI13(lFZzq!E-@eI3FQAgi%Sor5vUGSr+S{M$%!}VT} z@NSW8r-n^@fqRR~t5>J|i@KOn#rQMi;$EjXO`A&IWe56&HOImX-;+Jz-nza=D(^3` zCA=9_C@zQWj7ojbJj|DVxY%19`(R>B=B9pW^Qov9qs5U&8nH#W%1?rMZ}q=kADhc@ zE?;T0LN%Br8aPsaBD&8kDk_Rv!tvPUt$Q+tz*vo|DJdmS-+5AC*q57*LKkO>_)SJN*Fx zczauQ0S|wv8V2La`ig9NegG)O_d2<2zuSF*ZnU!XG95FE=h~BA5ZXt9Srw1*f`D5= zF6!8oKw*?}@gAXQdE40ev*QDqLZ6)3>|9~p17_-yJFBZUbp$_7KtO=H@J{6+m&KXF zT_)GDQ@L-VxO*SQtTkGfEacKkSY=yI z!jR(~Hsynl;U?#_e&8h`ruOkvTN*Edwzn`1j5*g3B*=Czr@1Xm21x`4g5Ynnc0Xyj z9x7qn?rMi0T~b*t)$ZE2UG1Dn!vWu5zaj;jY;dx65Q)FbYYBo=#S%D>*;-C^PA;AQKXeD`4F zgW_qgM-TC+%EjsMLVmsMzQCs3=T-J$Eoa1yHro5;R&K{OSRVmBShj+L%hpOdF6v02 z2Dp~Bdka7B7!70KHM9UblNS|XwNxHYT9s^xKNv-808$^KEcYMzid>@MYDSthtqGtE z(5C2eD=!>)G-w{beT)5WJ0?r(PU9UXeL0=Re85>T-qS>GlL z@X1*1bsQwG65YnU(|**z3j1S#UUbZFrGG_#dTx|{cMW8b&ilJ9B1FQSUVc$i%KJ6f8 zT1mj+-Ek02haWCndR4x=Ne>%x{H;%uklb6|vfXu?_yyTm!|hkvkNyYw-Q(4<9&Nfa$P6d>NsI@+cJm%NiP*RNT(T^Ius>v$ska8;ER_@)~4(| zH-O{ksqIeBQDtciCEyd@-QGAb-%vT8`Dk~GX>+5^%bVkqY#K@<_9e7(hVA1fl2-F0 zXMQEAcblh74EFU57g!iU^A<6ksdj*Y8=0qoUy2o^H;~`?+x}rzn#s|wRE!CqYX_qD z%E}k#D`OANdQgnVh<`Auy4R(}sWm^`Yvfg@&h7=Ulwwd51Plx!q_ngw=mvElE|RzG zUP|&$Z3#8TKoMXRWvLQ%Bfwi5WAEeR60J%&YYc1+E>ulZZ+>!(F%*1+^7xAa=D{!T zFz3fxPPeTY0tECb$hFL(SX48k+ z$8&t<+%OI%49dcJMb*J~MFsYlt2>{xVXU~<>-mQnvN&{@TszWhl9)t!#-ZunJ$U*J z1ZIx#n|=x^!$Y&J*MWo#$H30R_a^R(2!=M8>LSEc?h6xX)nIgfL6W7m z8X+F|uB4n70cQG>51?elmeLTk9294CLKkX9Dhq!1kgJqPnUJ4>Syb zUa>77f=1$O#9r9xUX!>rB!!~yPTB&hW#64>U|-RtZ^NDCLV8Q?bbXn+EB2O|alfW% z&|2$bCYOXW;qJDz>5!4tAonIhNb$Aht$2JwZA`Av?)D%Ot72aa9Ot`!anG_q+uC&Q zRNPB2Rr^0Xf~;LYO3%UVDx^B zvQK8UtsXZrU^w;sS1DZbJFqb1Eb}?3u41IwHLIkdkVwJWYy#u>9XC8VWU@`i;0Xm( zwf(3aq-9EkYWErpg!A%pHlP z+{se79}c8vedO){OW+z%JPF-AS&pCe1D&|py!d8qHl;o=@&Rr_LekI6eYOt`3gRk!19!dH!cG z^lEGe1O)^f;97SV;Sv7m6xnP*7=0Ka`#^M{|7ow6ad*#vkzVVY3KtobPyjT>AC&`d z0eYcV+rp0zyV#YzcdHe9y&>`vhyz&YFhHCAStS)n1v23pF3mzi0vq930DOcmtyIRuH5{|}g)w?5IRiffN zWC=-u|89DJSj15eF|zUu#tuIz(0{F_Uy5W--leWcU6zjb0WAls%Cu*Dt$*s|i zQ!HsQ;t~(UECO-C=@t?j`L*gWL?$6q?svnhan<@N=^f;-B(JFb;ZeBcS$9yBEx0VO zUKBI2up5;(aYgsdAspBab@V==o}uB1K-DO{H*d6-9|qLZr1+r)3dZ~SZR#?LyMDo^ zMLm3hDS9uDYUm4L(K28Gr1Y990~TXJ@L6A1oBZJQ+~L+ZzCB!kYQUesP$y@nE7Rlk zP7n-8-PW9VdBgy?zUSzfDQ3U*jco>RknnWW*zzKuKbP_x-C7@K9*~|AnVK#xHoPV$ z=kOf;MrDT8QfFcw`Y?$FpK+xsea-&MDl;j%|6UoOl~zpDAd77uNn2&lc`_&@ap6F` zX{ae@sVwHaLhcfQ?QPNxwNYqhImQZEbA48gdj>_LPUD{9wC6g=i=<>@0#xtkd&it% z-HbxfB|>mrvFq>o+z4UQ8Z0k^=IKrBT7kQGe_5g5@s7js5b9tU5lWb0ny3L_Rqyo- zBy2y)o!eOIe*g9CYg0*Bo{VkvTaEVBa~x_mRryc%lD9+T>XXTu2E}ESD>UE2>CdHi zsCHElVB(&igzI>1&uEa}BdoVa^O;06i)V|CVUOgn(}OWG^p=xTayT2A#>rdDA{JhE zV0Rd*v*dlceQmXpv)Dtus1EIG`%`I%PlR26Z;ghe#?PKz8Dt$wNKQU$3?l%Vgu6z3 zgJ`fvf&)FFIJ{UeckuyWs^o_1W>&XzI?fJD#%PeQTt`#oyW1o9n9q?7ZhM<};%kDa zhMY$TFqGw005?v_sFcRsQ(1M4xtXGu5zoGIBb^O?5BTp! zUf|~=-eNnKH1#UOABC;&f9zgqqZGC!FCoU zWl?C}Kf)WdZ(2ddR{wAYx$W&j3yCuQG~jo$vZWmd-brLeEEQ^LJDM#jGrrC1Be0R9 zU%n=IrN9+Mnzf!tdq1^*2*TWHd(wo6{PYnE81X|yk5T9mBlH*Di!7=1YNC+>RVh_> z$OX~~#Lug-cNa=H883Vt@ihphKQGX19t61KI$XU@k2NYZIvzO;*aW9KTTnV29u(l4zxoM&AIX-&sBAP)uJ4? z04s!R72X+hMCF%yu4268vuJ9Vr*_4JA1mSGZXHRX=y zOIkj7hrXf(Z9-u%fF`kiyD2M|Q_m@BaGH94NEU-xLD=cVd7RRpUlwv*)w9-+AylfCC-OfEcvp zkd=avxYb)$kz56L|I1`ojAYaLjzvR51p5Gfy2EI~VDGXHw8Zf5m$xl*Tv2;`{n#3` z>4Ic~uQ!6f`V=x#apV<m6B7^>RZw27)<&knZH zDVg-HeRCwWnywm3IWa_)z=L}WIhkP;bOs6!UgR^7A}yJ_c5zYMtQiKS>pQxhqkEcc z9%nUz;9&P`8OsKHb=*$L*AJzcJUWFvU#;>brL8M(5AM>!63qDYGW>~;7G9EkgnR|y z&pkEJ2m{@KLDAz4!}u1amp*SZq#hBA;`hd{E^)SIA`64Vg--85yBu!INZl)b9Ky;1 z1ABB3tGs9FNovjh#2fGUTX9>*fv>{;r7=^bL$+=IgTH!k1dL`QY0k6^exQocrxfNbRrphd#DHf;RAit>E}_+r@CLM30xu|0a1dJWHdPd92#bMSLSA9V)K=I_)Ir0lO`1%9<~ zVJqg9_*279kgv&-e%5b+;Fky#MGmyOy{4+(a`d9Inb+b(1KGKA*HBB}bHn_O2!baF zzdErX%XZ8TW*eog+*-w#gSX(jIIylOWR4Mm4)hx9(KDsD;UDO8G+~PonA~Ci7fjR8 z+tKUj#4`20W(??`q`3+c_j^!4WN)U0>cwA9*s*W`h3tH+L3<96N;)wXq=f{AI@|1w z1DAenrOpP(;HUBWGmGA}hS38bpFZh72KcWfs?^D~OaW}ozGe>ZpY5+iR&r$I<}M}m@6l8Nyh*N>JhqvRfn&%pq5 z65I)F8AMbxW?UWc6$4IoQ;N_@@;XTh!Y*Qpx#nWBj%xY|*83fdLiPym4QQFyeS0z= zbH(`|!#0TNoM^vR-u{;%g~0IROC$EW8~!O}ET7k0XNEuLQEE&y!9EGwHCK2o_w6Teca)hOX|H@Upx?{X*x* zfB>r1t8NhIF~$iA1W#-f5r}~i`>nHIQ9jF7b>MO7tW2up416HKbY5f?knvHyeY!H) z)XcRvRhH@aW$ppf@=+gH4g0w>9PIHap_B&9$aqkO$hR>h3~7)GzdreVE@pC2ONwc^70^T(k>p1M9aIh~z)RyuVR4JXGE>zb>a0Y9oM zqS#G6^ECU1raJIf;avT*UDxudN?jO%@BWwNWWj8$LIF_dDR|eA(VQFC01FW7qqUD& zOlPoJJY#@Xzw@2H1&^9J$w|~AWOyR@+oKi`E>-Tj9(5d-;8l$1O%-J((Q{@X6@v8{ z*_1%90Rt(26<$(GSY}@x^k5Fl95-O#VmNsc1N=0ce?Y(y)k*3gIDwVXPz0)R2 zd+({8wAb&vo+t0?`+nc|$Lr;LJw5k*o!2>!<2a$y0b125sw;pOf^}g_08r%v%$o*7omSpH{HZ>W^>zC_D zj`;Lk2OYyKo8>b7h)J9_DlU$WhXM$mNqsoC?Huj9ZTl7 zWL?f8rqr_Ku*gNILkBg(?|x*!hdOiu?$K0sM{>h896Ben_6z`UC#S63R4Rjt@P&)W zlv3wjIdpGCy>Qi?b-VM}X>KZ0nidtPchwyBxVz`3mgLZh9m0DVnY~YEG!S^nDj^$y zwhbVsb%!}Gv`NbKIh95p&MYB>60)Fn0q5Xh7(K?3kS$TQU+tJxOBQu1>;%~xhsgCwvZECR?ZTo*U|FJ0)!16qK^rlh|} z{LnQ|QPH$AWA0&m+Q>>!VGuJ0$tyO%jsMADUZuqsOJM0R#0Q1F}=YBkel2nZWrNd-np7(LIk80YYJs~==IhoWy|)0J@4 zKd@v+3k~qNF+hhEA~OahBu|_#*EBAPJB2(D$hr}GBWB$LpX;I{S|^9&2vdHvu7dy% zXocc_qKc}^Yf#w-3q?OL;12Yxy)r-%z*Yqe3dn^X9FADa)`|!dchE^i>VyBV8X_U= z1`tr5_i1fW{hX&Mf-S&g1La#7tB?NfM4l{G2l#HxRMDUt1nfKHDLkn_v?}I5bqv9$ z0x9#49G{9D`hl+=;lL5B??B9%T{o2y^p|;gjKW{kr5KAqxoc^M<&e&iR%2xY8$f~? zT+gvTl5x=1%YWemY1~&(E3YCCKL*cM3_Y>yaWZ(?0_TxPvi{6&P!t2r0w4n=U~ykL zi|{!k7jc~|Y4U%WN#K>O{=0<*L?aSFtfl3>KtfTt$4gkYd~IIzN3sUswIG?iOTEIyBcz9lx3-p3SpYvD}cU-G_v9eW7YWyg+4`OBu7#@G<$j2+FK7y z_CK%#t+&CAh2&q7o~UT@X?SNJGdVC&VS&;&{AZsSNdoCXF*<(87I?Et`5;;}xC;8J zyYKXP{XF)azJa;J zv3KPU2ndi&ResTMiD@q=1jf|Eyf`N$xZph!Rhp zHzKyy9)Tb8myc;=!hKI(eQWofxv{vFyyZHRE=L^%^a*( z5=4S+cmh11>vIPw?poj{i7X$CCr;iiXI9^BsA%*=lPZV-`ObycG0 zuQ%Lq4zbx4fw$pTItEYup|CvQU7|ptfqMXPV5u@2V3!_G}bB^3vVD;yP-G2P#R}Z%Za`lc9EnhA3On))3x| z=oEnd(Sx1?tNJgsemcvOP=W(GouC@)rz^#r;gA8gi}L`xviWH*JSWt&pP&fhgFQ8p zr$WRa;8_(P>~ITWEsGq8j$*eyb)VG6(L}P+%%WTkq9dZ<2|7c-H>(8Q%H9J)B2P2$Y!uA(oN-Y_>zgh|eN-u-Ax&`h<63`NXqmtUa z=e1R^vVaj3GeJCN_xw)mzQ<2FKgyB3$h&tiXC!C-qqHvu6SBsxK9=375>}xr_0V5g z1_@6bgjL>sMu{Um)09^gq_K8XEB38E47c*;B;hkGtpm;e|0o#Wc=cykg>=0M6^H-C z3Xn2V>MFzr$A?IP;=gFkbPPerPCS|qAnh&geyp1cpo5?NM!p3SP$(ea9Y4x`7Tp$> zk4+UPfq4P{60rY|nlQT0qB;L-djzQgntXu774q`;m|Ruwh>CuNJSZSK5_XvQ`oyMY zcB368gsEr#l7DDvQ^$S_-h8N5vTQxa1^*GEf-}@t1>JlM$~zqIDfCzR>Sr0xz=-M8 zDP>MlV&X@6A@9%6IU%U)MS8xI-~v8Hw|yvh(EbP@X>N@VpmamnZI6Dp6^|Vgi&9jL z&8Smant;DwC=}l8)N+QRxlSQXTTlE0HiBU=z`;UtLI0*1QpSLOL*zrBxoBG;Xs3I-6dtDI46kgB2Xu6=~pEwXhKhlH|7S@1Q}b#M~BkasoS1#o4|d zH04C$n}N6@(9z(pBQ6i)#ag0jB#Dxoh|R-C9{!iQ8}>eP;R0A{Mzv9D`2u#U!qA0- z5T0rO$bExC8Emt`n*US}B<@&^Z@_P2Q(3&PkZF?}fgJeoEqJ*`Y(n%$VX+!$l87D0 z$=88G0+5Utt2^egZc1qP=fSGS|4NFz{C)2UoR=^l0!76}rF_R?)hkiak$VEY)Mbc( zngB+Rm6zg&4Ix-JA)j=Ql34l+J4NDMWjCzu!pJ%39mBE@Q@)pxp@>|9H9yZb!8BXc zAx(|(OM}TFR)GTUTU~-|(#yaoIv*R`eyop5)do!h?1~h2nQ*LkWi^(VlpkD$j0z<= zXh(`72`u!1ZCRSrBE2L%r^ZWl!0ksO;pos?nyA9O?O_8a1ZSk&vxG=vSd0dz zzlUuL5T)DxpO7bfHBn675cVsI!>rjWGUhrGLX|kjiQ@+>e2$hrT3Dw$bE687+XOLq zM1B1lA!X2{SL4iK5T=Cc{))1UFJN&erf5}&`x6^J4?bTJF&ZYE!eQ-cSWF_lEq{Zk zsFeg0%>_sMRyErA8mU{JVk*YOu{ol^pNSpXa)XI6sWzJQL2nm^qIm9_T7zAPRjn}N z@X~4i*RM(t88cm0Bpm>mqXE6)=nL?6B_{Jb*{J+`s92%g5CiYUD6HTPJw{S?=v4o* z`UJ!%JQ|vzC_O#q4~>njnflYfZ@!?W0Ap1E0{xim8FbxkB?Ev8+PmIbsKo|w#EP04 zz58n6@QU9AD#QYSpiu?Qy8sOJj-KHRiw!gM=b<^WQt0N_(b(z^IvnuymEb5qhGzM` z1I>sK_mqf5OueP^eA^}L<^d=+9|i)$`zYS8yRy0t>quP2dg(VEs@}q~>R~eRk-Tt+ zV|%lp(S5ZuvNq#?+|mg%TvD4QPEXI^F$`15PjY8ZUM8oXcMo=ZxWtWP5EH@#12N08 zJFJ1@VU*E%k(*D}!io|Tk@UVApk)ZNpxFCT_tNbF1OwLE*Hr}VcG#O2H*0wodh;jT zOi~1Gt&D0!E&?kA;hoGgqb9fzg<3xS&ci#mTXZ$ANx`fKUEu&?P>mn7pGeRELp`#! zZpT18&V)gG*>*u|tCI^ggk;*hiF446%BzO4NYU~-8%g>aj8T~DlV|N<#$#5zSc2`a zBg8nh(4oDvBovYMZwJfai&d6y{(v`c=CV~+UJbo_ZPN5RhL$J2lCQX-K()xO}OWrN#N+MQfMMDjmwx`-&3ARlm48_aX{bDi*~ zBz@?55B?Y;b)s?F&RzqO@uO?zKdhktw$BtFTAa{pK{nBI8|Q)DYaT1g71 zi%w1j!0d9UIc)`dSp^-s0JP+E7~~1D3bFXWv~^);dx|Bw>dK1K{JEyU5I5s^EviPN zML@0I>rhliR15U!7q*;$IsIu7xoJz#(}WfmgU=}_9#TuO8-&?Vcb&^L z5H~i*-HLUZO%#2uGY=M8#7z^!$C477c(&<7_p2V*3Qme*XN0#Kq5%$Ht?2-kDxN-2 zS%I+fCTZq~Fif)nJOVPI;6oNCM47AH6yR&MqGdW~SZ|RR-nBbmS3R?l76m(B01*!W zkBZoxF8)FVSIxZ`tWeqCE6L#a2nEaJZ1*DP2V$DwpjLtQePu-H7G*I)ZU#i{#lbO; z@FYhjr&R$>{=0u5jxbc^3#RCf)TeHk50rNRYx{MpRtzwE$&V3n+vVic)Kn#U_^aZG zt^_AfKFwKXeFM@2_Qzq4+z9L_5G+upJpl-ekBAuE;0|x%M#A^O7^t;*H0!9|n+>$R z$_45oNtDKG_>r(zFW^)0Rj6<7hTvq!0QqKt~eLlKIBO5Vr8BU9VchBlUNKX)z>n z?BTR*&^*pByo|wsAj=6C){zqK=g*mJ{ZT)U1c=|D3Af`U4>}MM-peSLJ&d{QnpUlK zoE4Bis#EIkQef3BwEfCE{?21OX?7?0bUc@4%l5uOxC*%&m#`q<@AnV}{zhj|o_RP- zOdu2tfZB+fbwAh(5Om6Fy4Sx*qs^Y<`NKc9XKpOPUmmLPmmcyNcpCZmvohWRUO>B- zkN<>$4TKyz27c|q3|bf*{Q8Plen+!T^V03Zi~InbhIW0){088HVEFEj#=zb+EW!%f z%}*);RxY$tt7&gPe-|}2Erl!Mw3$l}6Apajy$hvG)J|J}-0JI39_8zRwR2vijZ$2_ zjUaX~slmbxtG2qk3XPd+^WA3e!t4XS&MM5m3@r+`4|#ctGCbeA!Yx28@KJ9tLl}Mx z5a!C9=61v~I%?EnYnGeWSshIPk^)Dm3GW0DNn2vMsl`2>Kzk+YP0)m1v(2{<2xlWD{m(r1;g&fW2ER`W}{aEjE-3z#f;ORHC;82Q4R-II=zg;gP7jxabbZ=VN@>BSzOWe_J3 zYuLI5XNWw{@nfKTz8A8YS8ocOPktdLBk};sFBP~OApTXt2`V|$ufSeQu&1iAIKQ;0 z^U_CK5&C!J&@9QPX&_O5bDtG9v2xTEzk=Tc0z44{!-FwMs0KlH5r&+1#D-9CS;1eJ zH6k4zi`8)YQhH{x-WQSZL>l38Eh;7M?Ly#b&(cA58a$Z}3d}8Z@=7x9!|IO2OeCc| z^h(F2shr+eouvG|e7sibb1L9Q{%MzkeH_4z*OboQVt96}heAZlISaC^i{Hq`fbKib z2)Y{PM*8G1H)#ZdW2~Is(C|gKF_)rZ7?Bm~FLt`amkSZ+KmevjvC^&?e z)^$2QOAZ_)EImXWbl^6BmP!(jhaxesj5N*04);43Y74NnF;1jeWgI2#mRc$cSZK>x zSXfHxw~}V=eyq_Dbal*s{Z?#IHC2&W@bzrk`?$?6u5|;}D=PWSfN`pp!9|9(XoW-L zryZwT!S!d+`(K|;%G^jW@3D&$N4=pesq>X$G13&?6)2yQ z7czQ{l0?Y=!0Lu_akMOrL6UN6-&|uUb0tD&#YP-DXvX`S>+UNsCiKBSm*;E0vVl2| zhyvJxcwB}IA^L?5!fu;4GsaL&;2^mIu@iUqoS5!rQ8{}7oSp^6I3zp%(nbx;+|JAZ(_0bjBePV8h4k3u35Nd!L%SzS!F?tiM2$ak|F#e^u;%c!B!9Xd%LT8SSPk z+4|1*4}E%L45|Q~jA;TfOm5_P+1DY-WKQ$5mY<#m}V`)5sCT{f^KWijkaz#?*qJ9TBrmGF8W?oaM1hC zHM$(rx5`GZUyFOQ3&ZGC^(8fX`X!ZZ z;u?*yTOhB!0ZGE@g4=daLV^Y$Okx1Qf>wWj`uXs6{1_t?mx#xPSYa^hE$;>=G=QaJ zZo>TKgz)j^wwi;pB&olT_ml(JyX2w85p%P&-_7cqZ5PJObJCK&msUes0fFf1f^AN{ z_MU;;VqAdeKtUvIXyqbeH7+~Rc5ZTX^)>pYyNn(wl?Wm+8gS!A`)Tx8a;!`i0UKky*kWop2>tz7qJRZZKqXFzX{J>i}{Rdvi ze*yjm_jfZYi}eISQ3icLf?WbxnC$nd3Cq8{X!#i#uU?7DK5O$mN;rTEOv4wg0%jo` z#CBhmxIwfL5Fs(|zWvI23d)tB*{HLS^cqOD!>GPV<1L4jNqDEV5TP-Hi&9gKE&-)X zd<+0`BBY2K4@E~jB&rT5QAJ9TDWt(YI}wrKVRL))i2r+uuPT`Bax)=q#op;Ow6Yh$+Em38)k=o{Wx8~R2;jlfS2 zZWh!-`&T#40>%`4s8Pww*MQi}z_j*s?Qz!>$A51F)ppSI_z`K#uJKb)`g#6PnDQY?*{ z>q0b{FKqHjd;ens6qpXNInU9Oxe9}~!g4WPfi;uI#hJ1ybgx}SR>0u}RbmPv&+kqp zU?Iouq}qs)PNgW|8N7N$2qH32IaaMgwXOsz)op#*V{3qPCG~4pHNB@o3@wCx5}sXJ ziY-vZzR%MKi3y~30LTQLL+na65Ie9^^i|>}IXHiT;wJ3IfWF&Wm)l3zVKG==!JCrG{s0nprqUeUMt8$tOKED0Sqjck%rG<{o=1h+s`18 z0`Q{2fLIX>!S*rw^H_v_+JxW(e{91Kl(Mm#Nj24^P+?c^!5oQxHL7iHCPKReI$#wf zD5b(2t}P$%?&S+e+z)F^`IB8-4Q57K5$2l!d_&ahw}AEp!XI)Xz!p$*SYq(%t(hjF zC1cqf+JNJF0&IP-B(h>5;jJn&#Qh)kAlt!5#0RJiRTta6+YV)RrsNTtfIyajV5c|K zc_Uo`Irpy!^@lvV!QH5v1JniY3{c=`7vKT7)F;~qrdd=xw+G_g+(@btziO;@A4~=d z*<6UzdLH$#K>Ek(+jKYc#^HLSx#?CegE9+>vBU6P z;7?^BidguW?~KpM#gmjzZ6U5P%GROX2UI~Oe(Me+Iiw+iCIa5=)o?}< zUNod?!_6Gy!OjujI)NzA2%+Jk0%Yn4l8-=Kg3xCIEF~*bb*P&kQUKZLpPo@)U7F~i z9yGXx8dv~8dmih40GJNIqvinw846?1#f;=pMF<_NGklhHwL+|{?} zM(v`47+}%>JPlH1C^bJ;nPitxk_|2H1lat~q=I`W4ecV0<;pp0lKhcB3-&bv!lvVO zaJEnKNnY^>?^lTvr5hq@9=jB>^K*DZZTbF++vhXX!87pyWrNfQtNTz15M~4nu7LcH zDIalrpae2r=-35;?#;`+q!kj9HL)r0luw9w{+qemQIWA<2Elg|^Tf``GVVPOkwOys z^|&F1PEErrjzG^BUB}qiT<_ocCD74Gp4Wcl%6Z-+kN0c?)$j32-;TGrRg026kgB0W z*{l`C0lP&2Fy1RHH>oxEw5C;R6yg{q~ll z5WzG#KEeM7fJxBKQM}>?B;xxQ)n5GYI5I9?cZQD|Avqy`sv2J#7(Q{LPwkgQ)RpD; z^DRHJxz$6oFWtUaqz+wJ$oOIQEQ0pWnIdK7`nl#u?@b*9vo#Czo`#i8h zATZb`#2`>e>z#*(R^~u1CRH1V=cJ3?&_q>p9p&mw+LM>tI0<<*YG=q7eK-8>4xAo& z+Cq@MwJ-(Y2Sn8?S!N^8F9u8VqTFYNHlP4{x;EzD`hE^$h8@}9>U)(H@I`r@-Tshk zJm==*&r@fkD%~L_4uIEQz5rdFls-0L21)x5UPy=5V;HdM)W5=dqz&#!9)t`BPB zPh7Q269TS-=9fE~2>UA%mKeP2qGiQW?y1y6uMeVawVn|& zuoN1WOObNZ!}|CaY&5*z?mRr_0qzfpjUiEy);reVE~$Fba|RP!)T%&%rzS_@wBu@KR`5ThBmZ7OG zG6JN)iZfMgASprGZlJKb-A_v4+clU@$omXXmay6q0UiN_bQ(7%y!ncp3<$>4uz03Y z)Z|$(nQ&S_yx>|VNL(p#CS>LPdJFQ?+2l?i!^tqHnJFN9&-2r-TaXUpMdqIz)<&HN zV1}TOusCK8I#Lg;)e-GW-J7O4YMlP7S>I;D$63l^oP?GsNcEwV76h?GfTI1*Sh5nN z!o+CsOLSue=G<6gYosK5y~2yP@*FbVVmxmG;|sK;pFR_MD(Fy{!fM*N5hA(>unE{b zcoe(YTD0B}2wbo*5vIT*%k|8&WJJ^k%`PCTfN<8&C6|2li@J_gRGx$M`6aYz=Re-* z2qN}}jP26*(4YwA4vzGU6)c9_O>pP=t~uW-F@%Z|VYYg+SaOXNc(Dbj&?sIY0~pRG4^9 zq2J!5%0D%$8p$omw>!~{49{eLO>g@8y}?%$!af2t1JW0II#^ka$!x8-vn&)$IC;c_ zh_Sl?iIrv;$B|*pnDD174xp@JrqK9?xd=b)VN!PFx8KCL61DO2d^nom)4hOg@}JH6 zH!`d8{)ygz>#`JqWaa9&U$ccA&C7Sz!MC9HxR)i!Qs+#u2QT(OYYfg-^lMHf$*87u z)QUO}q&4t&05Sa;aL}1k+jS1N=`e7^2E{p{Ep!v4Wb^u(9#a+km&qinz|^D2aw|HYxt^J91YVH|ABp}2cwb0}AVR3i zK3mb^1Wb?*Vyenr^=uFc3{62xwgVZ+iVs0`DXC@z#y@Cp^ZopE>-b@9T36?j5GVqG z9q3O?!s_^~3}exF$I9q=YxEushdIb986fJguF#}$fs^}Zdn$V4N(Wt!!d!svRV~(s z51D2IC}N;~^(l7Gq#f+_l%0NymdA*W90jzoR|f3Z!Qupn06~Huht})zm;x{tK{y<%K=}assk!{q%8I9ke3eXaADPw zIzrv}uPwSlR>clY19sHPLZh^z-*f3hFbEQn@yrB?)5 zuF%iRvfJL7-g3Fyx)QmwBYY*+eE%D7#-RhXI>e#}4#@a~Xk0A3Y0|`L(A1_jy=-a6 zof!)1h#dUi_4uNvoyWo>%@QM>_1t=G3%k9I!?QX$4Z^IGj=XqJPJBRY-i!G&UgcA~ z(Sd89hv)FggoYf7@ef_=8#kZL4V4<+aH!u9;0-C;D2gVdR-2QY3iWGDmCTl)TpE&O zn~JC1tebEWFbcYU)0@;WGJUi7R{C5iXU3enL7hRV(N&2_$^%9vvojY}Bi@h1-?U9U z3~SvBE8kzkU@+k(4}z0}a!#mn5tF=8m~NHx5A3Z{+=Jl=Oc&8P+K9n?K26Ve>wrV@ zJ-PM)5+l>8yM2>^#y>C^pMoagx6g>~R6j7a4bqB@o0cNPJTiD)BA_TSDgB`^w}CmQ zbnLLQL}Gx^z=!sX`ZWKV&HSa?&zEO%_2w(bqgS)koPX-14?BH0VJO{tO-JXQVa0kj zpZSER6i@D*pGIbG6OS8p1Go9e!Pz0uls$m>m>`Q!Sm=+=$GWO2$MHHVoy#lO13 znVj-O@r75fUNuv;$WNp2Us+b8>AmMTvu?x18712)@w{44-P-z1REJCgPai9-RZ&0J z+hDifR#l=B1{>G$Z@gNjP2u8puu&^|H!+bP=eE7B5XHSsy*lgNPrI@)-^O3~yCbgK zSJ^iu?0KJ~UND7pL|eJXiorFJO;xjk$Ry|9?gg%*gYYgN`zM>DR|+M&k@ld{{d1na#uG2W*7Y908OtksbLS6Y zF1dVVUZ(JGun{+`;PcwVU}CBU56cRx#FTr=jrdYyF)>Zcf_16*V;0H5m)kic6GgMj<1PX!C zU~YS=^T|cspS=+f`>7O1s5z90|SDLeYhg@n*v=o!mf&Z znT#N*9*!hqOM(la9Bv_Q%%qf_;&V?~&1q&bqR?>${yoryIph5Jix955m`yI;>W*5{ z*h1d%_R?R!Rk`#Y>b#pAJVZOsw;d{x!)(x4egx2@gD0c=pU*jONxpX25cz7qV*5*8 zuN%GaHg|GGEZdZgZM z>lRfmvj@e?>Mq)ewnAHZ{S9B>Hso#(dFK=<*bRF}yY9sds!wSHw;4 zSNq6>vfF~#uzZYSCe=D^gKP6#>Ath$l<5}?Eo5V(w(1oU2UH{0sta@BeRuxH(-0C- z$M;NM)b@Fyiy`QOLt+E@x8Oe*UR@+c{ncft7jW)y?Ei*#8bNLQ?lv7U|eU>cJVn4BWnv@ zgYZ=57j3Ymk`?@h3l}UtzbV-+DC%i!&oNZHsg!k>KiakDQ_{J+-@`_K*hTM91_~PY z7QG4#_~7O|IG3flrBvEL$*$*zKK1wabiv_8r=U`L2{wV96!b^VC1z*c4sCCnm2%Cl z42G*aiK}lGx!t*EQ1^PtpKUYpD-COYa;i|wBPV`1%a}9MW8cGhW~BDQZ*q^}<4?8k ztfi$)#3p>*n*xU=vzn+Bf z^v~uO!<2Crf^Ssc8Ry43M=zcc>?_V$v^Smf&9zHH&T-6Id=gJuAht8J^MjzOO4=UP zLZNqQlC;bggL}m!8tzv{@Y*Kogiggv7m%DHjB#LZ^opu(2u@cmWeuL1oY){{*IUji z*)DGBZB8_5PcxYB;`$!VV|GipA#clxl#r19rm=aa_JsRew7rkHEFoyIjilGe>ql zWB)Aip3~=WKET?Xj0jvo*~5f*#xtyK`h9%_9obaP-X(h!+e&dlicV^<2&Z9 zedy!PJjWfW2kv^_L)SB}Tkt)Y>0!*GBeYM zojz>ORef0alN0e4xpxhErVFeeeeTP*cEDfKprE>Ic$uI$YUV$e}zvhWlU|+y}Qjv&p~Bx$hCdSn_;`2q&iOHZk1HCq?(GA3z8xa9T$J z*BoE;8( zcPdstfCqu+`3P4iBm-xt1vo=BM{nXsQc2BGWf%T=#iDiPMKuw=o!w zs}5Ca!wdQKHz27qvU9{>_Om4KHP3ard+0=aH<;l!@}6$E_n70n@Q-Wd?V_1*;ONz! zR>(-~vX4M7_r2wn@+=#x81`z7y_mzUcUWd7e-<12m|Rpzo}V`_l-rLvtq-Zw1s?vo zzc1ajhT#S5E5UG%z=!WAQfE$eNi9E!+0Tu#$V-2YjQt=H=IT5J1x4F-*xBj_kPKXI zmc{0dZ*w(O(?;85l38ppn4Rpb$Wdfr7@Psnm(Yj(^%{vp;fi5O0*N7hlEagK{#iEY znBVJO9g_Z-6Hd$x;!)Rwyf*SFT>1rH`_Wr2PEO-|>i=*Kr-zMV6~Sa31ZdEc-Nrx; zf>|Zqy=dg_M=+;XRsR1CEAt5m4T^<8u5Il0oiN95Z<*>Zu5-`+b~~pz{ynsLBJ9U9 znAvc{Cu%%nOz$uf5Nbb_bbQJZd89|DYCc@*!R2+fiH9)P693%I6}}0Xittd#?)RAK>vvf4pTNBkGTaL>*8Z$i_d*waKiu8Zzu@x>QUI&_A~-) zlqJ_s={V8eM|a!60b_0Eqv&9hCH4>h=ejYM}BxI*53D5!^sfq8X; zDlNF%#ProgmJ6>c2zAk^JdFUK0$f_h@^yk0u>OWN<$Bb@_-K&Fg$AX_iz=F8^FSS6S1w6_2_0S>cS* zUj^^mH@SSuz6{|ut#`>YrYg3&)>;xr%`XKX&COHUS77U+FFWq(7nSVEYc;(s`{?am zwT$-5IjuE8VCRR*Q7qe$hV08Jg&ZA6yQx}>PmnvXsV$x}| zK*LFjA}#PDUzLkxk1S-^CLY;eTzvmfYn!X>w~X)MN8gBR5}IgGHzj0 zbEzXw{*cz3lS&B|e%P^B(8V#gsC;TQxODz$Z_=4EY*yCYb4*c5_T2Nc-;h%%bKA|$ zKD@WjWnyFSj?&<1-!sf&J?P==m*Sxuw z%4Yu4crOiK0l&L_H>1xnx14zl=Ljt`IcB7Y_R#C;1htJW6n5IPTH7Wijr#N&oGybz zI=M#6${1QD&`u=4L}h-zM2Bm?`C zzr&)3{)=~&2X5U_OqdQ-Bq|`ZA73-{=VoF)d*2}G%PqBn2VW$~n7gzJTKhh@I}XoS z)>-jHkvnBxW;HEo3S#KqIR2Q$nIsncAnF}pR%&BCRxX@nQ3RHWS_WL8uQ!zG^&YI(TP`}kU0_#B0T zjjlQvS-$yX{30mvqTKYRL-iv3f9vCJE@IFsQH4 zMx|BrUXb6!TbUe#dZPOys{#wu3aN1rYnSq=yF2-gYKUWFBxd$t>3JxcziHsI-T1JA zgX!e(GFwf1<|hC+rB|>%1e)q(SR9$bB-l>>!P7Lq1F330e-JtA+w*(y;r_uH{ z*S7IBBY$qcwe`jJksmLKo>Vi7)?o{P9_`uBMg`9C>e1FKlSA>+(^7{XdE~7va+;}S zor7R2W+^V-5qUD<#=qy)>HKDbr>$6H{`r7Oo1G5B=$!b3{XT2d@-}oJX5UPFwPV z+?-KQ=h>^1WF3+8x{um8sMo(Vs4Y!poNd3@@3L)nG2HS*J9V@2-1INQS@wnTg0F_B zAGfZd7O=~rQ3IosSc2~y)04))?LE-YyIg&_=@rl`M+Bn6`RoQ`rE()OjO`B)*w1dR z*g&3?TA#1(O~&$;_mGfILj1&yt(!J0%MMkL@)$Y%I{7{7d;b{>2R=4eG|zos1b#GR zkjn3pNw12&k1y0a^vRoBw@SnH#G@D0;*}ag@ObH)d^lov8^bm$za6UJ+b$NK(u3|P zH@36tOBv;~F{UXF0WK&!GM=2GNS7FnF-)m@X7-BEQ% z&000LDQQ$a+1(0xI=#Wc*?!8vXJ*~JRDi~{reXv#g!NA5bm<9R=%a3Rv$<;fs7_2b z!NL7hSo}8gwLiIq&28&h`+`w>`bMvhRblkz^nL0yep~;3!Ec<9X|iZ5d*eYn&MpbQ zd4&YU(rfiic{gV>eE1qK`R!sP;otpT*S}KoIlq&ssDNI}Qw($yiavd)MkV}LTpWM> zx#K9bF$rGc^8Lvk4_0)6%?rc2;vj$s&koCv_6YN}qUOC$W?j&b$oI&pd(c<+@z5BZ zyFZy;l412NW0)b_nR^(*_1Z-yr`#eD_3d(^I5*p^l@a~qP5FoM$-_MdwEf^gsgulF zpv&j9)u#RIBa>+Oln-<|HU-0O^3~fIu9UcQ4q!O$*_lP7_Xk#Rzvzs5eeK~wx$^xe z3;%!7pmXouq=ia@QW`Qc9ye>saY)$Ed9kYnLpPU+XTud-|1IzwMMLlp)GT|t+R>=Z zV`~39E_(ZUL%TFI8o>{q(niCS8;Ufq8an{@H#v+CBTD!S_Kg@I53f&?K%ym;LQ+ph8LYtlTqKOrFLelN%_TI^bya$H)<^-T|j zF6pP|l5cVZ6yZ@@T$dxjntn5#P6q#Kf^XV3^iK3<6Px;0WP3i0ZhjZL*_-~h$y9jw z!C3i)>)I4gQP^WN>HtUGn4);XWerc-hkGN0rf~6R%>@-aO>hz1q-pU4CeO_A)#SuR zC93HaFdWh_4}_`oJtgQRW)T-Qf8NicWFHM@=u5Pfe4>TvtVw7@K!BZv?zeOBvbVbG zLrdxPvd&1n^5RLOwQ?Z>zc(JekrTV)WH<}WW6S)5zkA6_=WNnAX7p-^FO{qes%bCY zh8P0>eY?9dHB}0R$OWU{KLq3k}i&kjbg<&okU^Dl!hZZ)+pwUJL@Alry z+^^(lY-T4N-Ft!V25R&#Hn=Fe?d0~$LYRZO*CdC(@rq;5SnN{u_zDyo>kTZaGu0wl z47P86qRW+qF@=^nBnnfYh`7dXG?YtBHt8`QO>I&Q>40{`>L$#mB*HCBS8gaKwP(1t zKY}zMbHhaZ5oJ1oFWqeDAcVi)-U|J(rw$#yMiSWSSo-U%g8($U4-nleI@5ny{=TZ7 zo#SEIOkG|G7ou#%tHSq}&0iqP$c~iu;~w_$^z)OI7yr<{IM?%4o5JVPWzo0B%3mgI znpC-ddNEd7}Fh4%i#$Ety#>Hg(HJ{sd>`}ZnKhh7rdBvUMw!REfP9&e#n!247m4tfd7@xdI z7s>i$5)SPdxwpeAiKsLzrJ1r8XL4D)%%c41hDF9Am(%h^?n{_GmBS0QTmwPpmX=l9 zIw2`Ek2UBSho!vw=JR3On}a8h`nY>?TowuU{*|3x(1uMzBLYv|x}~CF7j{^d&}!^G z+`6whfj0`c2;R>9B2g`|6f1_vYd@4_F;F(6Lb@>IJLP+Q|GA%FDD6G@QmOTd?0h~0 zwq%7u+(L7~rLGlo=`+j6-#_{S;j=C}`jo*JFaK~eJFofIHCA$N&go5apZ7t~&c4j7 zx^ljf$#7OhOoDe!fbj5XC@C!Q)tZ0&cnOo6sfTjm^>%(cb1TYo^2m#|rG8%+zE8lk zTY(NYc>a^a%h~QfZI25ea*vs{Q2-g*ZTMbWs1?nGX~NdPY3lVeFt+_Z-()=MX zR4Ftt0Tl~!2M63M27eTVDDRj=krBne(echsGEXfKnr-XzKY>PsqgD;76HT=J$?q;O z)IRKwj_Hc4@pV69>yoF=AHB}$QJH7N+Wu(Lk9)FXL9TixVmSZz&D@1mm>dQ*oa+3c zUG+u+(ooJ$jaz{V)7d^IEUX{-1y{fH>ry3h$gU-4X47#5mK&We`yNFtv_5A5t^`yf zpsSF=K|PKp2Re-z=O6^!IC+PcJnmiI)l$bTYVDLyAH=(JOi>3dj~VA7?hO*jp?6jx z%CY(jhF5Ww9>day{Ue3VzC{;0tpih|i2LVWHo3u$g!0WoA@z;U#g&Q3z1wWbB3b|R zcSGJmH)xo9_~ASH3COxMz&Yn=#JnB^1y3c`dLe3GeD zJLV!S)0MXr(m9Mg{LQnRny(>F+H5n)`$A{g^nRnp{rX+b z!~U~TB~FXtdvXm74RVb&Sg>t^QnEwd{)T=&-9B;!@i%@Z)#X&>0DY)Z8d~o8!F4#ltp;iO8Jb!92{-Uy0 z1LcdCgUJLYFkCp=_@rlk{$t--vuRyk^Qj~T;l&Fk-30-#%}+TlxE}*^njlWZ6+$E zQf>+L)t18J`n?x4;g<`>=g(ZjNELRi1UW2`P5@S`O2GFT! zD=b`|a87iPY~y$9N@a7NB|Q4-%uf)mNDasH73N2?==d49plcV2ubSf;6A2X2QSd^a zzQ@H4g8;6b7{Z$a^myuMms#*2pReaNo}=YaI1KDP@AnzdhKsM zS9sD==OsJ7-kt=6CJE${6y}W0fMv5N< z*HCQS1;fO3Ei2AtNBsPek2ewWHObG;cr!JmV10P?;DU8o6&rj*J4j3ee`NV7{|N@;I?xQr7H~L1`EJkt1Gt|5 z2N_=I1rWrF(}#uT^}0i3)`CoSZ>k<&5qH;(rn5Tu4kV5^-0=jq!pPY3+Z&4Sj4i@*F`(_ra#3G_D3^%{n~U?&a?ELGrL4?$0{UK6}6 z>@v5rdDSWTgr7q(e`wfqJ~lO3$niAC7O_Q|&Zg7z0d@g_6t7#0Zkb@;Cf!P+9481w z+S0!_uEQX;b zuhe@7|Ar1~ahfuGt>3Wll<6ikFeZF+>y|e0cKny+si|DPt#7ufjZfJf*LPbw)0;QA zWmg3Crprpz+!PAd_K}=)wj~aA6H{i}3w=e6hp+rq%efjFnkYc|!yXh6b{l=yieE5LxMBShk5R=!T%wx%BZTqtre??L1 zS;-dH__xrw_xVzhS6T(e*xS?blS&5P_3fBP6VtcyM6{G{Z{Fw1&2frkgM=zju;@NC zA)KP#U&+)#~O8bOx|2o@J;X&)I^_Nl+hBPk4K3SuE=|qH@ z&iMFLe6f(Q^I3dQtf=70x^iJ5%7s=lx__tr5acC`d}vB0+?^Au>w9|TbKkz80}TGg z{g>DKc`z~B3$Y%Pg+1v}LUTLZD(iX5D|!ijol=j>NJvQ&Dm_+yLN=pdY;XS{ARyqD z%1cPL@+ACd_+#qRGVWY9l2478pPP1bER}_4^X26>Z%zG9*)Ca-ZL_GqWwBLbWacO+ z_39NVOG{YKbfa)$V#0*GAgzO7+Sc@1;}mIo*`vNy7tkwiK2#k+dfyucog}uS< zN;&+#BH5s3l;TI@Ga^G1qxME^hOD9OW9j2`tI*b*TD`D19|SIu<%TCrHC91};K9yP zpW5-`_)h;v*n0=$y!Zd(SA=kogOsQ!k+ud(WhY8hO41-odr8r>NyA7>%8a&XZ|e#z zsWh}mdu#9CxP*ub@|J>CFtHhPQB-y5)^NJX=vEjD7=ei zN=k|^<%g0rze%p2nSheU)KbbS^>D&^b){>7Hx|09?D3d;YU2B940!e8ehv<{TwC!~ zk@Nh*a)Owl*riX_Xsvj5a98E}O}qlDE+pjjHRw&Bg=p}3uBdA*JEtn>e}p6JPQ}yi7K1Ik$k|!5y1b zJS48=)EE7lS%2;t8oiQDdVgK1F%e!RAB7iQ({}Le*u=}*Z~T?(lUqht#_Z^{BvoN! z8Q(y^V<_}l85mu~Q19bostNK*hO!E(G-f_3Zr162TgQ&7=Pg+v)ZOBMs=rCeu_x<- z2IMzS5YPpG6`(~lXnke1h)ZX0RvWl+A|!J&a{SpPiPa%Z4w(~AK4e~E zS&zbAes0m)eJqw-g(?R%G&v3T89$a@5<~b&(m`^vNAAw~vdoZorzr~;GP&Iix&E8P&dC1>C5-hnzu6k&?Cq_7BGrZTl%`t5%_E8;j##R{l-V_}= z*nMF^RcXSK^EwvevkkXLoHCQ0KQH{%13*%!8aNFjHH}R5^4=<3VG^@@ETj{)noVcA z;JQdD^YW-mpMLx|N5qjaWa(T@jgRjmPY1P0s=C*pl%=NhJ7}X{^qq@h8v}uTCp=01 zmFUk zD)9y_+KLD>F@kUkyNRrN?znlw!HEfZ!@L#(ghisq-9lSSs6!nwB*rt}0Au%XYWc%+ zW~vF7FDFaSWIe>F4V_HBAm(RuRonx0{7i-hu^!>({GYoBMbBzD4Tq z!7K=gn68(zSK8{idBF?xY2nB1ZvMTL?^#ElHZrdye+V8{zW>wok+DUgU+U`aw{q?q z=?_9o*Q^PBP#URu@=KI#AVo~8c?z%Tsr!$|d&!(ZnJC1%|~ zK+@A67|rtv<8AEV;lNd|oK{)Jp2A9dG`^))%???Zt(;)4bgSXiuwV=m;v2Gdcd^Ut z9vPd|o!c+(ia^nQ_};xI&&3ku^Y|2fpt(sr{L=IZn~w5@!4p3!UpLrta3&LCWShRr zE^Q7Iy)wPbm_=+={`b*cODXUl2Fe)PBvO%nv?`yE@X!tg#A4EVs<%-kWoFE>*ow^V zAM;&Ib*>c}1uw+PaILG5JRAMRbzm%DsQQT3Dr*Alj&vonaraaxCKbhV@qs8<;`jnX z)rxq%xt-U^G@6NL)iLkIY+-XRM>hdnzmw=37(C{g;|Ay?`)6fxR;ZaF?c4p$ju?2WL;C_gKKR1$C2^TDp;h$6XHU zcbqWK)g$SB*T}?;U9jdxY*o8*n);3#)+sYjb&T9V2LaG6*j;`wp{=aJRu?SA3cs)R zG$m67r>&Fy6MBYG@sD>0y3T*$o&jvMzgVkAj6Ox6tmXg9pt09+^Vx-k zl1=pa%If*aW@z^#Qz7XVx#2)nmEmzFS~BNUQreZkfp5Qy;`u~Gv;phhdJJ6ysIFZf z@)MnGI&bzCc}~{{DC@PsoX-S(pJh373Q~w){>HXmP5e88)kO-g*{-FbnOlE!rrPsE z@!j&dO3x0?G6kt6xQ=6C#KT9Ad|A0=8}c^}ey>6YwRWYvpV9K6A3cuoX%Tda>M~** zwtLpx$gW3xckaj=fXFe;6UDLS^-<>P3>VW)#p16dj`8Q}+_OmGh(mM-{Q^V=78}O5 zICcL!&_LQFM&HXo1JRrlf<&?9AE=}9rTMmh1nZw#u>>(-Iyum8ZL#id+@UFlFZ*&~ba6ykaXROIJErUcD;oJ_p_{V?}45m=e-hN%dD_f`{CA^Gb0I zEf28AVl)iwk9G?uZ{+2TPkCSEYnSK0NXp|Xvo_M@kXJTx-0f*I4 z_OJLR7*~Facep0~(kBMVNJrATBmB!J)-pP+{Z+;aJd5(uh&}j2>@owl;51}l7SxZv z`!6a_kjd*;sPf8RY_UMQt>!13NER(7s*fp`eW&TG)uR)CfgzU`X$qbg&tr{|A&$gS z#dFfruE&0xjvQ!;)y1TpHJkL5E;Z{gpHJA1qlq}o^j2%S)@nSHc{byj)=(*_?nKe+ zQC~tmKe*!A9Vct+^Ty2XbsAuHI6B8jsDaJ^nkfGVtvKh|moC%B#6g(WgGWH8IkB=IT%pAeXOJ@i^LR1(m!{~?>FoSJ zn7Rw`HU!k}t^`A5qej_%EGpTNgT6WSaps3r!S5RR`2%71bdQvCcG+bGTJpU&-=0@_ zegQ`7C$VDDW?rKgNX{Tw3Jz$zv|pSZ&q&;1oD@4k4CB?4b~jT5p8n=L(5fU*mHa~= zTULDWLST+vvF+h_05oHzf+Y8`2Fl3>uS(!}TV`;65BIx#niyHJ8(%GIJdXS#X?&Zd z|7MnEbl&kHn)_@e?ZiWSZ~YCT{YO*eJ@6`~UBsp#5I`LH(k8NISj}xTs#7wUMZH1; z0kSKu84=nRn4l70fJpmYItelZSd#qV4a(rxj(Wy{`D?pzD499??f%Eob|7O}|FglX z7J;sD>RHUIf9M()x#^tp&w)phqqS`{+jJJbTFw;KvsA-;?7CUKT;l~&6@TPcn zA7DJx^R$S|ATJy7>FZ=CVtZf7e%;MP8+@G}ur|7oVxOLVhFpN*gZogbv{Bnycc*I% z64%_&RCMdvPr)Hj-*xzfxTgJm&CXY^@X~41bpA~x4dBz?*|U1_vS|U#9wkQJf9zwm zf9+$!5WUFFE>Ek|`>3Cxw(ojaKi&&9Y*@&&itzF@%jmz^fD(%tZ)GXWey&l~>5 z3Un6$+s#s2EQ4YbZH>Vp%?_hzidB5L2V z`~NET2x+l>q@18}pZ~=R$81aeEg$MO;io%g@7=`r{EWYpM_U}8FO)fRk;cf{voqZd z)O6goStgwswE-Iiv*)R0F_58ZV5MvHLpx6-AV-oo^ZFZ0@t6Ed3^I6#IbknLurO9; z10&@41D+6C6NC~&0rWuT3{csH=T433mDO?sQE?4$kZJG_>+XfK-NU!utjq)9Xg_|p z`00N(t@6h|s?RCBXV&Efw19n?(v}@zCM%K{vX%TY`tSV6l+7R`Kj$pv$8PPu{!c{a zALEpSEJ_qNJp26-@#Mg_VpJavoUB1apt_!WY|JKrKE_-{V#Z4LYTU)F1Gd$7{o z$(?Kx3}UNp)q_8jIB^kYGh5dL#(ME=il7WIR0g(FQUc6@IUaO6uR6wsW5T zPE$um_5H#ir_Tlu`W7p=F0W50EHdw%gwJOUW;T(cZoaGvNOJ0Zxq@P zu4})bbtv2j^%(7g&b{aqwlX>iVA#SAjG-9IVv2ihur2uJ!pbAZEk7Q8$l-1v@h{=g z6Qof7SM@33y4)HZq$ivzGb93HS0$kT0W=v<$8q#&w7f@eke{UtFfa5x=4-adoSg+TJd(;|Jd0V8DvAA8~e{74!X~_ zn+n84`gHysjtWIZ!8~g5qI}7328~?lVJ@YO-+h4E?$J&pk>Mf>-e+1x)ZZEiID(X{ zMfczvFoCzeD$^9z|C-Hu2r=gw1V4=DS&w1>bt#7-s9mX!EsMyz?5W76=C8E#j5E)A za!8U5>*Vme&Y(8=e~`v+|1kU8j1(AQMUFd783l_gGKo;q4*;)tWk-?q>eNRo=E~Go zobbHkw^)Gc0lr=N_U&kH$Hu?7X=2rQ_rApxC*}SgRGxSm_p9KGpDRtU(YYd%CQnMK zt>>xm$RbJYt-6>cR_~KM$rJII0k{!zuQ`q383Oe-{`5dYK+2s6PeN=B~oK z5uUYZd4$;~tSWhPygoUGDS^T(MKoJ-Z?AFORsOE*vK)y6mLyoesRT0X9cqf-&rBw~ zL1v}d=>I{qpF%uYuTtFDY+bvlD&q=X*|gG6pPmntEf=RlOL2#wE*=5QIKro0_Mh;? znVt&N7kTi`ewXd9_vyGSmem?rdut|5785H{X&C)eX+udlb3pP{ z7s>!j{SMm!&=%2e3zMAe&X|avqv($` z$?Bc-^5FuJgwPDa=oX{@kHC$uhUN7V$Fe=Y8#_-IAsgdrCO&X38@?3%L%iKKgUThW z&z@}q*ChM#6suC4j?(8`BQ*i|rAILevKF($JUG+T?xx}{(-%|a^vVLQo0XQ*k<54c z8b0i~dR>ZsTB}qnfVO2zPeMj+*%wh}Ka_;6ogET%r2cHnKV$iIe|aP{<{N+Hc2@VP zfIwgK5*s}r9tDUYLKQ`i{#OxQ_O|rcDQmO2J_{brUFeQDxiNnH7257UwmLP0h&YEH%zVWRF$T?-OsF>re#RG}?E(g4k+3$n15~u!KyOmTSGGQ4N&T$Y zBS}-fQaQ57HZ5Q0I#^6tOlT9wK4G?CJCu!~NYK(nC&-3dCt!1Ky;}af(9sC92Cj6V zEsPFQLBre=I4onbLz~7Zt6BeI97kW0aJJZEfOlew`Fur5#x+7)OE@#@D|Z#&l?y%! zG-f|Cw&xQlV*fvbJL|)kz6H(ubuVBN;unQviSwmEz_J6{7Jr=P?7cPzLQX`rZi%jL zS?MkW!qLC>zR@xYVC7-of0KlG0iw#m*C%I{`QOqx0%@Y_ET8haQX2AI-4=l8!q@s2YS+N;=7B=oJ``wYFP87$+c%h-9v`E<5$aEt_POfS(>2^!i_7Nc7*C z{E;I;@8{4?-v#RdB`~BdU*?0GPQt|ps=Ogd=k&?{u50S>?ow75|I->j_kFoa7kOVi zvF*aTslxm<2gif_osD6^z4vrwPCYKdGn8k`ZG5K7-MN82byg`g1}!WYPkKjrbf%aW zbufbd-v>i1gOnCRB!y3%4P!ATclWv;N1+YTs07!!ocYOI$dWd+On(1sKVBMB!8hxZ zqZjUa#zjIeqfkmf3!aKWE}0>qAg3bOyLL-C8vq{2zkZEK_Sm(sJx)9h;@moTu z+rv8+U(FrP20X5R8f0JkeKZX*0!`@q>07LK|Iz6JmEP^>n7n7NhbK|g1 zvtenMMyTTtYt>&lgRt-!Rdm2BoX=<9cH9;xeAs}KVY6pRU|8P~DZSJJgcL!<52~p3 z9V(C7cJr{cl72`OQOtRwSM8M2|C;P<(X-6};DP}FceH8DOF(f4yv6xvZB%jSxh6Dd zhv^GYCSw_Y0J6*0W@46sXTWw4UfU*}b8O!S@sV|>_}n6R+PX`s=16qbg(Mmf`eEEZ zEuzu0|2kS8#V#s(Gu*dG{i@ASoG*nK#CGp|?1U! zw#e^)oF_DNp$bQiZThh#HNmBCY3zjF6>mvXNZ2}knUr6nOz~b}^;s}${6a%m;X-UO z^D$ufTe2B2&Y*8;pX`UhADXmeoj){vdsiO5k%pQP%rbUEbQ}$Yslma-BlaU!Pw8gm zU+~Bb#oC75!TGSfUU=xwq%)6uElD~&4gZ~VSRu1kZ7=V%Q};gAR@quRQ9loi?t=g* z&3R{>zvyJWpEUU6)!Z%QUjg#!Pg$aP3*-br3E_90C0J%$?b52o@!AxZ(u6&# zDt;2{Ut?^}vrD>RtS4g+QBn2AK_mvLjne063tPbct3}dPZ!UyFYrWV7D$2~+nu9ZK z`{=2tkDOWrs*{(pF8H3=3;@u&@C1Wc2GQ5@7dXIRmc=ih@3&!EK>EKL^Upo%`Sj4$ zw@98J6JlcR`&C4qn?{gHMwv&+jP7-{s{yWCUu?2?rr4SP^DJjSUb3`-gVXl04(IE{ zJLZ6hnQz<-3r-!WkBA%6ZQ93M_F2=dkjCr-=wEL55J%_*yc?di`@>28b1J~jd=y$~ zN^e)FEK#%H`L}FRY`TVK z=^;Asr@-^s-_3@yzkHuY%K3$0{I09HVFeBJ>1IlWv5<+5XGl%hbhb5aeZKv!%6s$O zP1JyaipnKiFoK~5jd`AY#tf`>{8gX3u~70EwxbF_utDbvq4Iy>tP9Vg0)QGR+c$73 zIZ`t_z!d2238+TwQb@voPu^`+1b_9qV~}`JWAyZI)6v2bA)0a^vI5{%&GwOBy$}r~ z%2+(rb{tU2MyP}kHZwDOXG3!Eo^ZPfPMvMVsLm>fg7R9;9tzStpN2otMLXsd=XJ?U zV8>irQ~(dj$R)~gduH&>s;jfaUQQiYBEN*<9pO9K3=)n()1uwct6;9hqN+6;VI7ni0*SnQk(Y84p+R51&XXoc782*nOVGf38f zg;3W1|I?#7^6??*m+F{003v=~**%L^A5NeExEMeucHOX4`R|5j_^i$5zZSp z8xXiKi{Tl8dAEMZk$)1qxtBeE)0P|BsT&VK^FGphC8@bhHuTeNAD2D&9+Ka&iagJH zz6#;bFksHdSA;8s1Bi$%?~GX1;S6u8jm@01>azapWTl$+j8{+8@w*zqEnwA_7UrIg`o|$!7*z&)o6N2)VnIx=}Ht!?yWJ#5gGb~{I7i8sXJ)kAiM=YMx-dg9>}&JDm?}d+R4y!RYKi& zGqG3T9Y8vF@t8QxH4@Yx&*93CmnfL`K^_N3IGT|NnY!^5Lcj+sUIH3p#XbJ>o^KY? zn_l73@K8BxQt)TMX)<3RqYI3nr&@7VzJJ_?h}FXzzCe%lyB05PsDNQWl^qxY{J+79 zi=n$_E(!p?K)FCHW@5fJ@N%i=*J3{Mg};519Go#7hu;0aD@^hbG=n45S;=P0YGF3MUh0wPQU&KhJZ34mxE)|D~V}atRL>SXzYfH_}h*}NXMSJ^*tw(-Wl`%fs*0H z(LVfF?MVGfT0JABCc>&VDVWveHx%kllm5S9FRN&{nO{aCctzIp%e^*4xgHyi|0_qW_l7_+EKU(Ma^*tn*(f-{jMMNsZ zM6%V^b}X>G_2yL$2W{J2f}5i1fY?InA8Q>Z=4bzV`_im3r7sLut|eYnm>IJ?GeGEf zw?JK*E4&Eer&pV92DY;esL>Kz>5kSQfeMP04Y54Kkm4K7;&j$Z6pU0#}MD z;qfzHsSs zrbiKzLD-|+7DjQE%}u8lw8GQ@y+5l#Wue|DloZr-889G%m_qWbSQGt(lxu!i`)#@f z9;YbK7JcxKo+>gnr9Qk=p=}_mfBT5b5~JxXa{p0IwQVd5w_BQb>GD72l!4I$Y&e2~ zO}QpqLrt{{%#)79A#=Vz@v2?21X{44e`Pyb+vA?U(OJUAKscD%c*rzb|3=~6X=49- z>s61=Uq%qwsjXM;4ea^^>{s#=PCkE8$-=_Z_upHONIOah9Tzw;&5{tCJvHnewPdzJ z)gQ|+cET1|x@#6f6b}zt*e^FnZUd<4^#Hx3r~n?hS1!2P^7s;e0`04su*#`!IeM$L z)rW#&(Txpph`nQoOWPk8#BDwih}nC`gc`j+S3m2Uefvw|1396BVf!8nn!gZ0BE@0s`*txqIS46SzP@9cxyG=6^O!?QBJ z(z`Y3_y-ntDta4bnpC59MOBW&Oyf+RHLJfMW{;scHG4kr{O@+>CCk_t;hQ=5<|?V% zs6NOfqFUhCv?%Fu6n zzKKrL6ydPhRz%=@_{N{7t^(3SHs>YbXbC)4CGh;skZT*ZV=z9zy_w9x!?X#8eLQaK zdUE(&&Fi)ph*>zYzv@007><{cIr@DtA*uDgW!3%g)2j$4H>q&aDt2;Z-$pbQZq~QO zPnXI)Lv>6#e)17xV6)7=aGpR2*QUEo<}5h*k!jmS^i3d?AKG_m-;}wD2}O{v%;>r= z&c?k#e+pci_62}DLLbrtzrob;gjVG-`q2vC=@+jeCHCoebtQQEPbCPIp+8QkL8B7{ z;NwHJZYLa;ZEg5@NAUDhciJsR4K=d_3c~)E*RUw~FtgBE$g!=1WmdC>`rQWl#oEc9 z>EjiNvmob=s}2VfzUsmS>~OaiRh|-H$|~k2R^H?1509Bhjyde?vo{GD%E0^mDJSsJ z#$C-%^&!8{*jBUq=3%?L4sl(G5>KA;Bhx~=*1a8hCQ(5nDyb$~jI`U&dcWsT+;sVA z66Ftgz5HMpNVtih^L)L>KlTUecne#*b)fADWP1HJ4n88fBRin)^YT#uN&by}PP~Yy zLtf9-O)p_7kkcu;rmk-QxYZ2i03f^d2@qbRzbjI;AjCxKL&a>?fRB4NP#$b29t@D} zL}-NSkac`^=YZbo83-&;`;IzS4P5aM5SwocVQJ~1#hgX}9#+c3Dr~!&Sb7(fKH&C* zjRo&}s{YbQrPIL!L1gbO=-dNeR6(A8a){pT8{Dgx?lAZ>z1s%^Fo5QjnlFHVeWQlr zTWi+khWcPq*!R(D^1fn{Kl3}cSf-&alyNAUB61YPM?Sy1b~wpz+Ju44n8wQnV~kU*(=; zxz%283%KU0@-*mhAGD@nV^v2YnK%63fnlXLBi22}UPiAMsOJ`0^}_uDK$*h@!MKwa z{jcsW@#(B@5sz>&GjTw3VSAwT8F7$EGphv*Z;>!%2sa*>R45R&R_1t3ZEo-#Z9Vn- zPjoJ6W{u?ErQ5ko*o8{7xlgIN5)$nlmn1jDekf{GnXyMF#JMb`Rz6<_g4q*cqGb>s zbVr_SgA{edx=>N(Z2LtJaesakCWZ|$1##wm6*~6&EuiRJKHBcRUg>jY>vp}SeEr*x z`iCR$WUMOgitX7wZX=Zt+#^%?e(-&t-108Dj87F$M(DToYHx#%N^BNZ?`5kl9b|UM z(ZW`yirybhMy%RN(ux<#V%lI?LMN6gWp0yePCy%#yjHc~{cfno#Ac(l)~I#dgZ|%H z-VzbhZNmCmb<1m23i;JF16yedxjNZ-EyZaJ=Vc>xV5WKtG0a8h%rnR zvYgVY+H5pcWTs3-%oeFmNmGfCvu$Z{o99@Jg6)b=(AQr@Tpyinr z5=CPwWpcb{hz1-+ql3c}6F$7yKooPs1$$+Vi_f0e2XohW(prb~t!+237 z#}hQ|WWTQ|>`iddlhXRM*~6H!=B%fwFLhDMf#O?B}1CmtXJ$gaM2mRfZegYLAl(!RigWt@Pry^)(l(VBC_#PKjZ6nHP zk8yyPEpD4f^NTKf!ij^v%Nw$%Sh_zn8^pVS^RAjQ;n`mo&4>c8`e2sy%OTUB96gA1 zWZq`^$V9IA+e?n7(}>R*V~;}Hx+Vv74hQ>g?5ubD`BH1VpjM;15pp#+7+=@#s_xin zQXlyK%xV)D0_(Pq1Z+z(S+94q5-^%%{LF<{c+KOVJvRytYXDvK(Os$aYPXl5H-9^0 z_G!1-w9ZeM0zTHsKeZ?hmX1Vs8)S3-J!CM?QcA~ML~3pnvIeqtSFAWeZ}UQ?8t38> zdu@9E+y_Y9?d>VIyEyy9{mICeig$%jY$u=q?Z_&^(z3S`fL1WX>E0>Qxh?~8t=`@T>q^%#L}`@ zVX{r|>_m!{s)@W?1t~VJ6kN6r7pK(Jw`e*UKa;mtyL~O+bE1qG} zNalqOGPJPSJ5NgN%`(fj{p_~O^ykH=GYne;rKvLhs!%%KF88tX%ELvoJpiSOR@upRE4cK+hNF2OAUWOUq66sc4>I%JWD? zlvo+_=lD*EFZ%FaGZbj=$kJ|o&! z{m|)v8$Tp>!s+cq>vsC`sWw44U34o7g8qrlb+I`lj^f<8T8O;r0MUD+^VNp`iZtRzJz)*Phy?W(Q?N~~( z32}trs#V)`{ZTn(lp4BDb~(&bZa%wv*<=tj=|}1cw$4yreXPK|AvPs}bU;8*{j-V-9W_^w}qR8txIfT5e4THHhgA z-V3+G$DWt9!>uQ8zh9~_nLMDy{h8Cs-$^beEzO}Xl(#XjsO+?SI6MmKPu45JbvYn^ z0~G2+w5sRWGpg71?nAM#tJDu)NIqvJnJ<@Xx>O)C-u!g(n|&s+{PO4^h8YdRbAFb2 zN@tLQ>B|)-c{PZm>Kkq0MP5Jpg6K-C4@i$Rp@TgPX^p>Yx2T@Vre7W^Rb{V#kR^lR z378xi$AG#K4b*kE8e@l`03IkpL!8^yq3ar z3CqOy25XajUc`r*J@vD@3gsk<{e3JWl&<=ejt91k^khvEN0eQ$fhfp&+QuM-0#ZA9 zL?GAC6y$pO$rM%6X~%uYtd$Z=9X}O;O2|vE_|#w@$CVvJrefrZK-ad1*{^|$M-q}H4eaZ<1vTvghRdO>O$0VJ;)CB3%Syrk@g_E5@yMLL^R%s7py4w7F}8qvc&u zz8)6acRjvva(_LYbG$Xa;R6YCRC@Y|TC@v(-}ExUnr0Vyf1iAXq_hkU#amGREGt}7 zZ5qHa>{e{_0P>@QiP6XNW=6o>kN9TjjIDhp>-Ozo&k%I396nZG54YR5qh6@hm*p2V z-kA!Wo}oEr&`7$lHSbgdxR%*hGM`Yhz#PNgSH6j9hupyK@XWD3=`6PS&8)BNkPXgLw{7+w;0+x3`RtcM+F@&V4wSDEDrLo@;db!S@eLrx%LMa_SpTf z)s+hADt6OcCupW+>@%7n<5p93qZiWL!i$+PumxG{qSeL1|B(R~_l+vJ)-6eEhSXml`JWPSyAP99(y>zi1_?{_(ZI8!U+x4o-S$I^b_9 z)d-Iw-0PgtTX?SS^O|aUy{69L=;R7{b`2!{J2!)(U|<@12xd|**8BBx@KWz0K-2l- z0`e+|zcOgB@h88zVCeG{v_3$7@bCuUI@Ww3c`k;Va|*ZZ)*ZLeO;~FUmv2yT zW@MgwZF;Nw-uLf~I&}Ho_oqu&Sj3y}K64CyYZp{dW9a2LFS)MtVza+!Z&SVmm;Mdb zuE9;v?ZaoQ$A4ZTmK^bX+%s%-|eU!Ik=%l@s${@u7)J%FpA2r zGXL2zuH9`vZP&PX!#e+Ldr`;PhJ+1XJ+IFv(ChI#==JqWuB#*w8@N)d8V%xGqR#Oq zLdo>_1@8~tUf*mA%_;5?7J98;^y3ohQr z524pSu+ubB89L(QE61lKe}&eUHj}8zBT#0$Ok7hPwfB;%*?-~3lj~kNvASue7u2R) zGgEM?>0E4ZyW-^=Mt+URIMyU`DjYU7kQ!j}L+ulmh0b$`>lFF)QqUBJC*$0G|%fPUCzpA#5OHU zvTF5zu`^%<{mg5Fv%y2o17Z^CFlVYy{6sdsa}45P9|~=vg3NK5K;cZfh++F0G!XLj zE1E`{H5@);bRjzKoU+~3e0vZVVp=m14w=ndE)y!^~V+j>Hj2*wdikBA_Si>Q1 z7H~Duz$rA0LPS|DsvGt8(%i+eZXvFdog$&Pqn-vG+^#;Az94msvpYk~3&*Pu)`x5W zZlN_@jpPtg2Y)TQoaPV0M?!y2gS2vIZU5FgB!ORu=wQ0yEW zuf#5310K@mt!?cij%rZ zSv+^vYziJi#%-WNa$%JxD{xFkNFL1omTE|U{aluKFs5Qtl=Z}GbcF?>(r#cT00;5S zIyLP`rQ-sXd5iRpM;aoT_(ML zcTys(N}c({i}A`xxXAFn8^$(>lYDn_>_S;j=_WzXrirKA6NehQv_9JFi06~qk#t#& z!I(0HO>}Zk^n^%VXs^-fuorSu@h)53j2)4OV45Roju$U*i|Wc8av@vF!F4a2 zsG8@|kNt3vC@S|=hZCGD+G)yx;$v;>WAQaKzSSc4cf;gaKoV6UMR>bv@(K2LSTmU; z4$6~Vxe7(7e>u66o+MjWc}Y2>0%Id?8+sE(g{5bDZo!3vm_eZJto(Vr!+FFewuwvb z^y}ENp_<9zb7)&Q$6Z_=+f0(yy{CusOYDkq<6^euN|pVu^Z*pHTMUq!B+?ohEoAwj zTlXyUY3TCGU|YUW+_)fM*MqOzZcbdhB&!e~8*<6dNJDJxeR1QC@;BdgUrgQt2Pb|eQkn~nol)lW$Z85@?}T!*Sio4LM0!b`p8RfhAE)v zJ21@YTYd}f5(i)%n(*{vT6P?+h`~~ZBg}^CIHEEkx>l#36FU_1`(-rI7Nb%RgSi=+ z@o-!H1o6ohR>u7~mgb(RIuMCA2d;5wUZdb-p9l9B|31=l!{~2`)sa}@9*YJ`Jbwjm z?MnujuuZ3CQI*;e-=~ezy_$~@jR{|k1iLMt42HHe2fgpV#Rus>@%IYtZo_4Yj#4WA zc)Bl%^DSKN?`|;>uuX|+&3?eESEzeC?Q?8dbC$*Z*N$a}h~Z90>+)Db2g}SB_^9gf z?od=5&fsV&7*i=6B{vwksD`?Aeto2ZJ3hY`)AM(TPBu5)vef0?qeh;wDH?Dc+80vu zLe73|cGIe`9V@WGox{Afll|N4s5KhYV19``xYA1knT1whk{tQ29wy>dUQL^ z@huJefXk-UyiO)nMB8-)K45xkeHL91CVv!)b#tD#7jxjBDJK@c;ss*Z>V0oNyuras zahcWs(+`gVo0Y-n`mRW(g zc!)eieXkqPWct_{G+ZHgL3_|pZ9C(syeCuZs|}yY!Gdv*l;|^S_Y-ryr6F#os|Kw} zmkuM1SUcC@z`~L7@`c2K0rmntF&?bq{TLeOQVjPl*|ikIeHx9mn_b?$2jZm`>h>EL zavIKDpj@(4p=+|Ml^0TYxUM$(y2=&0Hbv5Ry+jX$ZBSpfiQjf!0*;ER@^d94^`em? zm5mdrcE$70-+l9Ke~aHL!zmz=5Y5Ra&!B1^1CmsK4P8<7XkM{#J#J;el40cNQJws) z2qvW5^m6~u;r0LpA7zouV$=j38HT1dFy@%Zd0E?+#wzHhQV_pb9Uf#<^HtC0>$Uh> zg}_RAO@Klc8F14bX)FsNrq*Py!m})%;9{SzPpYF>Te3o`tSu!h%nK)WymZ59Vo@>L zKSIoT(E~o=SmXX{7`W>u43_Jm4RR(IBjBfv)F^D;uv$f&SZhRQ4Lz*&;%C6lql0Ow z3SN?Ak#m+#>;o1c%-st#THex?{C~(nk`H3X0Tj-bYRJP4MP$@N&`sA3lg?;EAEI+b z!+mg9#Bi6HitZa-D+@;cU|$gW7p8jT{2b77JEDb4D-K~*H!WY>ILjkBdDvA|V!+-T za1wF$TA~x0Re7k294WY*-E8yT7LYjba-Gm2S);Uk0JUU$z;ap!<7!;t6$u|~ps}}3 zMP3yG&bIpQbZ_^KrEd}cNi@QnV8g0Wf2|mX%WGRSne8~JjA_2AN6jU2+c{ic_R@t+41*Mk>{J6Qqzw_e@xLwfta4T zhB&E6xTF9}7|>3hHHyTuAIcG6?div)?B{*_c!+DU&V6u?EdSzW5uP|Air-TV6GNrX zFcz1ORe~*naiV^iA+HxdYT(Bgq!ddSuG+ge=e}64max|5RdU%5hmoI4Lp~+W+R!$> zVDN*K^+5m_eATHnyBWiv;r`LF-LB~P+R&pEnO(SP_6Req!)Y>#mPudR+WPIQd8U6fTN<-LK?yd^@_cx%4{t{x~k4xc%U5YOGPMxod5wT85Rus8-{Dwira=3=#S{1cH>CCLI%0-BC|HoVVC|GiJNxejpDL{>JZRVt_aUK zrUi(g{UA;?8Q#64@x?oi&gR-#2+HA_lYHF0!GhJ*2 z3^&2ScY;;vt`aT_(mN?7Ewe`N=XfI(})>-k4Kv8palTdj%EX# z7}oWe02dkhSPl-u7<(g10@6ZsicT-i{$|}=)UldgzMF)J8~EI0)rr&(IdWRCvMOf?){G0tVcIk z)ufv^{7wB%o}u)KFQJsf!+3*#$R~I>qB^;ljv_R|YI8WFqTwKW7`Fn?}l_JoU5kJGI)JUZ%`bB|mBl~hTt zU2(XE9kJ!v9}tHT*%@)<0K+8Kv!ljgKjjV1pvRnFOo`vHqeWGsJ19w?^pdDj#*Ux` zTDQVQ&QwS$b+w23+cNq~#6-euB_Eb;Q>82|6g?}k>=aJ~%NI@{ggIw0hYVS! z-cuNfFuC8RQ?J%qb_vbM14m0Z>HO)zf$;DIB4Z#wam$Twi;k0jpw-d%+~&ddfpl3)o4V2j_uw5vYfibUj3Qz&%q*xM#v zAyDBpQW9Qfn!)F&%L{gAZlE*K&ili&WBC?qY-_C6FFxQuKkgAwLs(p2rT9&OE!3qw z$%=GAwa7>Xa?Ke{*#o8|_|lMdf&fqKZq;sk#lhGs66xUA?%Pe>D~Bibgqva*)LVv8 zuwNn9^Hdo^1B?T+Rt<+Cy65r&+)j)kGjN?ovqeF%^AHNjC+XKocSIBO`vgo$5#3Gd zB6mkKJnP|~3ryVCQUC;5VGUY9i&5eHq^dcUP;kMl`}ZDkSG`TW4)>*zO81HHS(J_i zOncl-zDMrvVDRfGjwt_IpbSXZFi;{%&L9Ynr;bs$Vf&*CijtIX4)uII*%Yk;|4ctQ zmDUo&4Wh{}CtpVLCq|=GB7~Ved>tu zP}Y{e0@Bp$pm9=eOj8$u(n5h}uBCA0uVFOS2rNk%ct`X@aNEG$2*2_T^ICarMpC;K zwZ>J3-12CdKyHvRmu7UE9EMW}CRrc@f>}_0&&;%Pp(!J_I4n{TWd%Uh(e5^rnG}8+`3cjuAhPGKN&rLVV?FWCq0t;6sGwzYlb7M6|0_^2`j%;|Q|opy z`G1@T=w2a7-&+8kE_kto6izb~1KYMP_W$)7{`&pneub!)L4VDmvFQHiYxoNcVj4fL zSS+=b;l*u(US?$ZX*{Q(qu_84;eKiy}{*>jM%>;4YGJNx({3pFxT!7e1X#gY#*%R0uFW7&#i06MVrKO6oWM+4|$ zrYAQy*gNa$bv7#`g|zIq>S_@0X%NVTHJ6#gi2F@O^O-R*s&MArE{_uGQdNE!MdRxS z=uI5ST^`eGX>jOQQR0$)Qcu|zVh9Z=Y+}pf>Ypaatic7D<~mgG#Z%H_!YH>^HlKVW zkQ-3Syx-t008s{dH`)DUQmWI=4L2cZ;Hs>=I07%A0eL=}oBH%fOeB4#ZS5Ph!LdE8s*VU;xF)%#Y zVc!ln-+Mh-#iMq@hN)ZxG+(k%6+f{I{!FE|jZsydTVoF)tM~md45hA%#A+HyT$v)9 zxVMl1LK2BNRCD>)kEX7{6;{;sdT=i$w+pf!g9Sx%!~^TYBzPDc`}2tY5j@Oy{d&QT zFi!0up4s!-G|8=zJn!>DR>WZXN++WzhGz|pb(?ods3aGuGV3glhdo`3@>={fl;707 zP>kyz+WDWO8kKehEnt5FR#4c@0qgF_=ILq zbvS-gaH(G~Jl;F@F+ybd)dTyfA9#^9GW?!-&er>8a>WmNn?y04Y;TT3^NAB}?`D*0 zl>fqVt?Us8^Ssdd?~Q^MXI{YTIHq^0i1#Kd(~s+gwO{N8w6MlkANNC~inv61+buoS zlEqnTt7+tw`6a^^u(peTY%{nT388YLfaNjR#t?BwuDTz&zB{x_1&+u4xboc2oPb7f zy~J1-IM(ODNmna)EUTY7RU$Wfn00LV{41aRMb)X}kQ3Us7A_GaQW`x+$ji03Pn96E z4v|5?vSq3*^_?L_X4h`kw&bLRGb>6ZgnKDK*WB zV8-Cc`SA%KaSUbSd+VboTa`Q5SWw25Vx;4!|2nyBjf39YJ!CvzG}sB=b8AON6yt)T zA0aL)0yx0PWA_P##i0YED#+e7RrIiK3xe}gPSCo-xlso?1c7oY zN46CCW^F)oJ<=koCy_bO0U&eeWKqSvfsR;?e7m=}s5-J&OQaPBxK(YAeZFo>0!;jb zwMqWTX4CvyzFw_M*G^K5gIp>DW+Wl6h5F>;l5TRzeNW{N!n0|+NA6zF)rxT(mv>78 zd_|ZNL*_O9mbyOGXC`Fj;vOl-oQqVSm^hR%&`~Bzz7yt6I?{~+Az5hoFgS=9%lH`A zHGAGB_AA)+aJIPD)n}QFf@$q#CgK-^!hkR52d?fTpYwq@zknYr&Se+FYM4wJ;-+YL zKP&H&(Pdv-Iw<;_cWvmK8*zI)0C;jmr*j^HAYr6(C#`G*cY%x*p7v!B&MvuWFgaGZ zdfm2B5TH_R-qe?+q=~q+zjR#Cy;|gSW{+c61IX+%ao3W_#GuhlSvc~WJD9o``Am*$ z;g*8kNil24eNPw;bk|3HKDS+wKJJnU9G)V>b7Wx~k*jCu7WA8saR*o#xwqa)A6K%C zPfqkN$bfe|j`nk9L*?n}z|Dx&vngsW+Ove)0cwzN*w#s|U>Yf1I zc&)M$+XOD-<;gy6>5e8<{GkgK#^0II>nD^wGPTIHR2YQd7A8U?6-rL4*4VIMWqjWC zqw6GTJkRq2CBAkpritPO>`T7ZU>ut+;?G?sJ`pmA)#(`_ckj3k6;5_0DA~zW0qP`- zxkav4WQsDL%H|8dasJiok9k)MP_M$nV98BDDtH2pqB_2M`vWKfmXAOpD>%MAk!&c3c+j!Dp4jz=Lo_ zJ4n*J9KkIiPeZ^fhQX>PX7qhP?~82>^LN}cHI?FWd)&S-&(%qILXOdb;Oo-SMceGZOJE1+fTN1 zq^%#mO??S>j!RQ*=C=0`(U6&0Te4Ype#uGYO6)oAL2+^$N2GWIA}q+7LAPGVgdY!L zw>a2+I?Y1o`kLyEhEgkW^YbA|L8b{@E5Cuu=jfbGv^p*wYyI?|p-W2AJEwn+4qNQz zsyZ;}Fo9wJc%zqs@|l;{j@K-&U*P|)u?QE(I%m}^p2w4XH~*=F;RM{4f64@$f!U*d z=F436`m?iW6O--LPSz!ZxM^Z(9X$TxuLz~#j70_=F6@eS-$46 zQZb*3L_c4#M1SBuXL_$)m|fMTd&ix_RBk7(QBpd##=CxRu*NF`UW2VXyf(Kzw=U%q z*=C^oT(f=E>DFWK`(B9tr&p#E0B zFp>4rKY}eb$0kJfwtHU@uvGc>aix{|U}wcqm6DNRQGJf$6*lx@YkqItw&wJ%93|yo ztHCGJR?e8lBqf=&%&fP*sba+fnpl2H@PVKX+t$m43eAqjW%e$gigTwiCySohlzX|~ z)p+B)O&dl>`r7RZzq%-GJtE@YGxTJymT#P7=}*_`)0uLOxC~^@WEH0yT59fUWhrFf z{$Y!LKWF2bw;VoM>Q09vfBFfoI`1>bKp{4%>;=b~)te3oD9!ctf2l0+;o8WntV#3N znreRX!B+>1%q_HHT5WaG*uwa=eVOZkPg4h6hPF3x}ONUHLOp7EJ1zqw0ue#lk4i0Zq);(~NqS@e@m%X&!&y_tJoN~?sE+urC8*}OE@+? zC|qbnAWaURCZWRTvde(=zAyYBE&W4MGAW`&H*XsA^Xlv^+2v7L6&d@uMcqz*>gt@e zS#ix~{XL)0-@hFEfQ@IP>kC(9E?HhVsb>(Z?zlJ6UjKD5TccQwu>p^P_H_E^S#lYv zC0{I~g!KZ<9gxHEG2oTIM}0AanKm1Z!cQKIi97vlB-ygMHA!}rQS!NS?(1rz{*sw! z4mF=0L>s6{37cK$ZxEO3+te)!qQo9N{md0sGPS1ZAw>Zj9K3sm_QsCsO`m@5e!ZSm zZ02ayWT{>4sFu_{s}-$%*)Aq#Sz!e+ZS=!NXR&8+$9Xf~nu0QO|KdZkw(XCrduX}U z$K@+Md0mnv@^|RyXc%k;n z=zXt*err`4@3EO}++rIpCFrF`RV}HwC2wAdyL9$J{pIw!cjwMsra1$)8DnSP-LIFc z(d#(dvqXtI#xjFvC2CR`u`t=8iA5F!SmAmAW^(k6<8N_RMfFoe?5ilQi?AStCtcMM&M4AMxaJV~iRy5Yai4ETJ% z=lZ|3T>4l#bMLw5?EUTU`}V%^dET;@N^TX{Kwnll_eq{s(sRF$*=%%vdEe+?yjV#( z8X1N7;oKie6$#`wk=Md2%30TN z@SZ79d)Hyi@M_@^ft5okd^)zttk0mkqa@KPRcgO6G$6?a3uWc0aB?ovmo)3cLvq%c zICc=fV`9Xe!jb)=2e`zcQS`-*30YMez9=FScQZTdStD?9g z3{NS>Ub&8EJNdT{Y+Jc~;7@zF!O13p;vKe!^8&Okkn<*EFzYg@( zs4vfTTTf^8yGQ~h#N@dKg=y;;gK<8)a~Y-K8-iYQlVb?psBQjFnjTGyTFN) zRj{?mn?K3Fh(!JaMx!gfOx;K^w%MkTWaalgT`;ZGYSG|I>PY21uRNjzD1m3@Bj_t%q>)#vWN%Jwi1K4{4Fj; zm9)h!1&5{cbhp3WBYdlOw{PooGYr4a)#Kn{R_a_FlzjS>b7>v!bbZ%`w>)vltGm{3 zyBHp@!FMn`Cmv@@DZ&1$0fEsm~bUrX(%xtk}H5!z4G)axE_q{wG}el z++5N}mL%sIrItD}@`Z-`1}jPCxasD%?L#7y&G3A!2iqLrwYMKRiOx#4jvW+C=D0?} za>@18$}mT3N^Rd^`eVpp?BR&*B4@d%LU~H<)THz+eY}5Ldf=VcujhvnN%d7FjHw0{ zE^aQ3P5Fzwc4ICKxaDSb+q=*5r+cfP8dR_-c}Jcs7(3x@OwfIb7dJK>X{xVj*N-z~ zWAQAf)wa9}Z=ILRHxC=jtnP1mjXOEw{*0FGsC{s-{b1>dN06DucMLZyST8 zyzeZ2p#cx`TtXk3_Mc)i=xQt)v$ipxTH3`M_I}^~{oLO@Jp51Wfp?!L+Mf=oW|Vmg zd3Z-i3&%5@PY35Z3|C4gkeg4>B}W!0tWt2^C~X_fcVaK?E;ZutNo z?$v+8EqU6-!}yL#mu=4R^_bA*W&5SBrh>YiS2pO`wyW6nQTJM0--r=E%Leif`cLHX ziL>N5>i4}dQ8BuOBQjYodBzdqHpa`fdNjj;SXy=?oOTPm3H8adXG4$7W8Da_UVKf> zchOU0lD3Ta+6*tUX*+CE=SpT`F`FqbAAk+&E(!$l>z^_WN{#J9FNY!U6dq}z)w}up zt!^Acj8KAd#{7sosANnEwv?-JUDGNZ(ZAG0xy89p4rhoHeXUY;3f{O?_$ck)48( zyREI!*C)}wq%hi!UnxyY7|e~ku%t)Qvbo&G=N5^r>Cf6tF6gu{fD$A&)D%~bvKu9^ zU@-K_Yuk!__3H~&#X%hNn_aYj=w45Q2SVzmWS}vu7UnX{GHu#0Bu9@AQf)F%NG>Kl z2U_*l_5aTYSjFhG<4IH3EJ`+DG!#;}YTxWRU#-9|W2cf~G}Q(dgZ;R+Ztc6*zWWIJ z?)KRDr2x7BWjC~EOnAeF;Vui4yp22V36Lh{WCx8y|4)6*2uQ>j9DINDJ7wClRK)zV&U-zwsnTfK#v<9sVM7 zw)Qx~wkt-whbYg>Pw@|Hy!Y32xvj1-_QP$jde&(Ku`wF1f7rQ18NdjK!GNGJG@PA4 z?At+pSbzE;*g-*fgJg(O8dU$cS;LhxSh#NFd|FEea$Sb^xMNu@}(!m>^E_!&!e|@09n(8c~wG`m( z^Us<;v9&J{xUTO?0McL$&QO2)?iZfoHsp!|?%KZIx^`%3Zmk{Kz1#j9RUG&JE9ySE zsT%mcc!!rOA#Wkpj0_dlVTyiTR_?FMV*3qJ_5flR*x5*E3 zq{qox#~>(4!~ARQf9)pL|6Ti|8`rNJv0r$V%+uB@LmFp8`&Hv5S{*VrL#st~V1e*A0p8_a>lJJ4Ss&i%i6ZCk+qTll6CE|IH0V2WfTG5BLJoqw03CU)oZDhpokjALR0dM|08_}@j@mDnc- znW=DJ_ryXkuM{p0IA2!Y2l;4^BzXhY)TVD5m@y{pjsd(WkDJ(}tsBw8K#kET?AlPL z?BGxkpnPT^&$KezDyxHZ0pt8}(%AdKQMeHCgQ-TH`F3rjJ@7w;H>Rv?%zK(e<@h#4 zyn}$>HbZ}4mVtE*41&Y7ES z51buG78i105ocLiT59c&j@Y}!Zyh|YUN!a4PyS}<3YTBe$dab@s>f)Upd8<1zv4rV zqjS71D6nYM{urAc>_3yI8K)f{sgI|n&RM5%R&(spKx9Dys`1{V52QH=Wp1HNPZB@e zH3qBF4|7&VbJQYexA+~UgBI)B*!}u>%{rF*sIJU4y7|E96as=Z4*UL)*FOA3PaWU& z$@cfWws;5jez{H{{FvG5)+O}uFV$Dv(R+XI-P3QlrEayl`Yhy=s8PFPqY+Fh;TwsM zOX1({xXq%dziG6yJrP$jv~O$D$04l)@EzV+>;)4YStc%JQB(R#mp`;|TfFV1oROXJ9L zw4Ge_Ts-xt&~RzF@}_ETyHH%4Zg?cCVd>=)C#vMd)5Lad5&Ti8MZI-n&&bq3W4yAy zQQimG_de84qfrTh@laOH*F6*SKPBK2ubwL9RvAycG!D^XGJ2$KHlGT^Gd{O8ekhs~ z9Xs#93!MnMh~I<@_cm0C>@3IR$00X<#$W(2upvpRE@sRX+Ze}G|&$|ojnOw!k2yJrnG z2EjFx&@2076*OX_^TwXp`GiQ@_3q%`xA&vcbBxB8kj})A{GS2I=~kMahhfwXR8Qi^ z84fB1WIqonDBRcZwI%+_b&nw_Nvq(O(a zF;xEBv(C1bwQHLso=M|py=*nmARHAI+ok{roir6vU^SOtJytr>`@+Q~eL>1mG!YV; z+QO1Du@yLr-hM3v>fonvtU?MVos)gMgJa^gcQ-Ta^?K6O7TcMt_Mo$Wu8W#~IDenU z&8>vl!hYeU=XdAYHOeknkuz-iqSLLGF^^*yeVZBUnSh&6F@5@)ZWG@Jnl$2+j zB9eBjW6oORHvqPksRok+XvCA%IDT^bc_6luj!+6F7tz0UI^#JX7Jxp6O@rM9FK{b< zI(M#H{HT0FK4S{4?wy;SWf=xqun%;kJP+LK*FD^zyx^f=FI-@irCQ?Nq*pm8kkgH* ztV@S1$mmC?tiK={_F893emYhiC_#;yO@0x_j(wWC_t3xbK%errJ`Gtn>s-K`PoMI8 z)r$iLhUaK#c~wIxv!yB0y^3_nA=lRU$t8eF<+36ff;~P(oaN-LO#5_G7I3F@c$*n{ zmOWcx(ftkx&8s191{kqn_ot#dQN>%f?=Ts0f37(`XRJS?e7JnR^^>igZJQ49siPk1 zK*qOhI9l>F27fCybtt>z4s067Be~LGr7rxCnlYZFZD^>O-4bK$FGWM1JQ08-x@85AfFGBKK6jB!Z(h#nS{k;AW0 zo)JC1Co|q|iw7N^!&kajnWvRuuj5W|zr}+tACo#{ecRAj+q+i!Ad5g?MuykdAW{7Y zk#Y!XY3IyeA3J6>f~7fq7#eVi6BgGCnVK5yC3aT!iAwwiby#BSRpZEq5yqr6Hl}PD zc7HDVW1)KKOwMufZ43;m@DKOx(I0yxWtrSDh82fEH)3B7FA9LjzH`=m%9`7n`NcXId1PUljcIEi^l0#+i>VXA1+pp^&)Y zql*`FI)$|2Dgm?P2vAOh9)Jc`jtY76k%3SpsB~a|&I(d>6Ng3`D)W^sJKugTu54)M zH6;zEtEcsW`}J6(cwk!(A}~qc*-U!Q5C9ezIA%A0eJ4iRDUJ8_vC0z z;*~1OL@oRV z83S*KGFFt!F5~e&WqHlw_J~=BjrH8r_Hxq@!+VXX)q=u5XkzbIi<|4x^``pOr;QxV zIOI;I8Z^pIvwe~6pq;qdwVFj08W?dgz-|NmUf<%ZgA;_BBE(9oEvfS@71hSczP2sS zwkwlkq2_0mf5o zUkLoioJ%1vI>{pL(mJNrhb;}TG<{Z}WLDF`Qd6ecg{xPjW5-QX$ax!_Q5eVBjVLd8 zT`mrG@*$A-E=GL)wN1PxSrw;`k>cZ|j@)k}Jx{ugiW#}Yj%8Mmnt=Tb_-So3wdAnu z@ye@|DZ1}!Q2xZQaP)FT*3s%2NNI*R|LRb@_g3b$ympl<=9tTI82B{VX-F&!sh@o_E&p;%u z`?9Y3j^K(@hys{S7&)xZ1CF|G4$NfR|sAa=gVpGuwQ)4 zkwKux12HmRmAdjFfZDuB2wh6G-Ke@7b?4^I>#h43^CN*dhM&VOod3SEkYzMICGj_x z%Rr&DEMCzx@eEnwsJl7XwGUJsEd&Kmgmemj;#&VPpen<;on;~nGTeN>Lz{NX-wuVQ zGHB6{Td&BB>4e+kUVjI%4TbzyJ9gWi>zojtb=l?m_tM@YPt|NS05OGgNJ4GyJW$p! z(@>+eEYOsfe}f~^^oj09ECXuTwz=<=48T=>$z~@);us8NJ@V!k77&S#V~5jpw$2wG zN+FYHy4Je`;R@nrF>z8*oj@AUj1tw)FdA$9zP$KzWy$ucS1D;0O7jhj8)M%sAPs>lik$LQHX=dROmDR7dAb8!$f{uY^D@~_-$7|2Pdx|!5 zQ!kEMj;|x_AGFbr8ZGuyS^uc5b;+kh!89wd3?4iL7q1&PYu7`OL5JTGtKT!xiB1lP zvlo2m0C70RyC32Yrx%*I`A(U~_sjRtXNL+k!bx|5QZ=T*F9_do;jlJm>va(H^ zM{d`3k07Q8MQcHes6mEu?;srR{glh~bQTVy1ox1*^CT-%P=jDAdnZp8J?er$);$8M zre52XxBDhabOOr_N35sZIxaP(EW~eUy`3Vbskw_?Lis&>J+o+x&5S|KE+HgiD)j|7 zAKd9Ba%4{pDPuP+K2=q+m!mob-F2Omfq2@Q4%ArF9+Ml$7S6<;#Lvw9$XIY{W#^E^ zE#ax2j+s*t&MVrA1FR02GJ0YMVjV)&-6YtpzlS$D9awj7{zlmJ?U%dvL7XD)EY3C? zcp=$Pplcg4^+_yo2yyeDJ_#+hH$uuxw3{<6tGFR8qxAuf0ElYW&`!RT!LKmoUY6l= zv$kV~&ofOoO! zKE-T&;JZ(0nd;}EXiXJF@TYgFqCxZqu*40~^QkC84u-lUWA2v(Hs6b9uQ=ZbwPHhy zc?X2M`?{V1qLAqzF9OPA(bv4`8EiA{5VQEbHmAfwXxWb^tEt1#LebCjgNLuMR;~j` z0+uTdpv9`N^FcJfW#27kJv~s+5b(*nOd%i!(o2(N$Z}g~8%Oy~U#`Id%cFCu#cJJi zNXI4h<#>&Ovb8hY5F$j$E&FCGFOZS|r0IkFe~J1l>4vD%{S5p5-8$}wXi}8WxBkM8 zQOGcoU+V+e70nw{^4mz9&EKyd_c!K7ESKqF19tX7qBDI+U& z>-va-KjtUaopBgf70F0Y1Y8Nu5pt?o^ZDqOFj%K#vSHakI=MeoJHB2ODcW8NcP_aD zZ9{XCVKhFj)_tVS&rCpc|AC$ulIz3$6>F(=6Lr{M`)Q-ca8 zH!R2v6fs~ig*U&w5N`;KJHIT@yySCTeYV2F9!cee2gvM1;Q`<;>c2J^#zCe0{`ppW z*-aXi&?8r&8n@DFdL*jxYePJ7BEGEZMk+ru74Y1vJa1W5525G>DCVqO$0eq8s|Mi8 zRrLxYU<(!Hugv-EAa=vTv5k7co`7K@!doQ7bGoZ;Rx?hG5A7!gSvh_sGwpG5djRs{ zP6Nz8a7MhgxP0UiH}Lq{+H9}O2M!hcKiYlbbG0;tz+G1EFue_{2%~p|n-?;&|(mmsF;r-CyU^WLk!%F{dX=%vs@X1n;H36siA*K-6 z24aAX-Cp72>z?$$Yd1zd?zA46G2YZbsU$AV@t!M$B0{5!vmAsS40Qr*0lT{f47jh z6o&x7Oi2EP_uKRs8%(}?a@Jk9%$qwU&E>9@clR9D2W~I=U5h}p-aoZqfK!=}IO7tpNaKh*$2j z_TXD7(JG+@I}j-nL-YC%G=a_=3QZ83J#BUysYefa6^ryQmQ2F1I1ATVAuu21no7wYa9TomMRs-63Q1UQ+OAfYG>#4*5j6Su2;OJJmDZ1z3rsQrFyu9qu;A%lU9_ z(Z0K9gD9GKysAlKkApZRll~wGD9q*1fa-pESi$%T2GtE4b%HyWo4$qO0H|0`-bz0O zAqVA|4^W>u+ZK2QWu1#U+7BKMfD0Xh)z_E$rnW!K%WIwgTZCO)lum|={ZI{L`P#G- z-6>y)ZuI$_!Y7#%*J7oe^o{Vy)|5UDmNc{or;M*f=u0-zJ4g=mTgi~aC0IV-6ky4% z@6>%N(l7b4v)u?gi(~iiy;N|)@wF*b0Hy~4-m7-#3v}vRURB8C60Q93t4xvp9BdaH ziUTu9j4gg3)Y%U_XwL-XFJBHEn+0FmIGsf!EzQ;+TUM62pi+)eJ(K^wU}<4A=I*U< zbVqp-Ut@AZ7i4pJXL)Ay`8s2O@OuZ8)hk7b$}6x5V_ZgUxSS(n8dLhz)U}Y?ODCY zXDgq5WN9RnZ_8r~!u80_)eypKIg<|dH@2z_cJ}FQE zAq1fxoCOxKuRk1?98t@tdDF+L)UU-a@!yt!MF$ zEvn@V=Z$6`C*&oL`YhTPfXu`Wui<#~Xx%lX;bEuf_;C#aUrr)D-or=uURD@2v+u>f z)74fIGhDkb13jfICUy>gfL{Sa69jS3Tz!;tIXBa`H^xUda$*88yVQ?QAO|@1=xU)= z76L3Dcv%b2-5%si6__G{ULU?Z}o50)4pBQm#p9UFaaP$Jjd*)>I3khf+bR|W~ntnrJ+1g z{zq@qX|`%~-{47_6b@cgx^1ciU>VpKCzEe2@%`?j!ZQWLp@C--f6(e3nS8l!!oxL7 zVzu&p23D2cUM&{LvmKnAF2%)_+=o|Gi z?Rcn$Uz|TJJEo)k#dO$qG5M00vhYImb#nxK7CeC{XdUIH8&?4%9&2lFEwklMQkGa< z#ZLT8a0WR1@@GH0C)>AbrUJjNe~)K$QAx!wQQ>9vI_zA1a9jqZC^~R^{r%4N6Hk4F zxu8C^?!jr3EBivK!W?CI1g<&YTqQe`fFa^O>9g(}X2zI8qJcCk5kEMbE%b@&?t5^4 z#5NBS@J8IEd+XcrshZ<*P`4oTY`wfinD!2r`(&qAy$HU)+hY@Vwe7UDhr535Z73=8 z=Nf{1_^;VL?2jzzV)?ueEJJsMVnro(XZ7As`h*1<>KaJG05M!t4@I{5h#n}Ig5J(D7~Cgv?);*V19AiCBgo$ZNt4nl?V9sH zNmK6wBu$Q&E|NBTgAbxO2zw+4eR_MYWxFLqov89x51P0q8us_3H~K7`DA-FMdkFdM|HFSkEGa8$v^ zJ7GNjU1z!N(ix#!(p6M-&9)y+?gW9kK*3gHd-SA?awBXIFs)$SdH^oMzOnpyicE;n zSW?7I6_AeaT$j&CPJd@b0L2BIHyU&g{*w^zBknHjp=|pv%Yyh_PR_6Mjpqx_YE4vt zPp~c{f6iMo#1d-jn2$aQ z1ge=rKh+M?dtNp{`3sYsyIOvFdIm-TE20BU9M?%N4D9-&3+@2Durqw|yObTQk8oU| z`rJe{dLnm5F1(2DZH%`E4o(|+tUo}$MJSA*W>5z;3@R^Pt4Q{5t=@`hgm%ny!OV{e zIo~H@r?v7ZC2eJ8w%r1c;oor1XbyrHK3|;Z5J{i^x9Fy^2c}d0w_FYOi(qIHI^~Az z!g(e_M`5%ZHN~*C!+23Mq}OptK0nDxq)IO$ES3L3bY`TlDu}cg{P9D| zw0sj#Dqc+N)m1cd$K72(VFWrr)YB(|{WTM0KKiK%m(uV*D3ln5pL528k!N!CDe8R7 z23QiBvw5ShNTZe$K=gj&Z#o4gC!Za@=WNo&04fvC8M^dU28AVIpFu9|7|!ATVQ;Db z4jkap*RT6RB$n;S(e?Jbpbmc*xt~qGK6;n1Vq3lm5;v3RP34pukW0WB{j35P4g`93 zcEV|(#rmrJ!=k=Czf)lOsV9}nnhw;0Lzjs^c>D`@e$1=bxVDnF##GT@~-BN`YO zigX+vMA8m&Um?x;;Nt{7S%*din=WaOdy;I7w(Dsa87C(P4;I)LDWG=GMk!`K2{|Yz zmKIOEY(j2Eog|1Wss;1$w$7pw5L)cMbG7x}uSnr%hT$XMCv*DEl46Jo3#r0(H&{e> zppcUOQxQ}L*)dv7jWay<3Z1Yngkj8s1uUs9`ofS z(Br`mlRR6vPYMi+ao*W45n;dii@5rGa0L)s^PR5Kk{uwK6xSw>^e@D|*9h2ME=L$L z;9q=kbZ3bA)F?N?5FpR6wq;f!N#4=_%kPvs&7+>rb5N*oH-ZzZXJX+x)W3e&NUUsU z(EW!xkDj{wmP5_xd}F!+2T+WKPe7jX$?0kTIVWqA@{KG{`Z-gKL4JdyqfOt#O56x= z#gv8;M_MMZWU${9J=?fTuHj360g%uNH^}a+%0vbbP%D`JYmUZXFTbPHzrx+MzR;}5 zowe@Dk^9OEyAE^z!&E$Ci3aVXME};#`8u=wspVS&l?c4bWoNC+&z?>rOXA27aIWYt z=z7D@NP~%{c4x>#PqR562^9A?!1_+TR4>qs16RpC`~aNkCr?2bvS0Qr#>>mbBDjT# z$oNBHkKGQKm&a${fQGEZT49fj+Ta19kq08g2gu-qeW!Vtq3?kvf>Otm*>sKgzrW8` ztLMfc$d+vgr6t$^P6-;iH11E70|?eJ#<{C1L!~C+pgOHt9H1S@lkVfLT-T#>n#Xm; z!y}bEY~v~@3oc^S1S8TMIe)RdVCC-neAaQZM%Vv$R$>4*0T^<80K)qnp_if(zZt`@ zH^KCJ?q3uu25ST$aGI2Owh;^vtQ6@Dxu6DH>&Vg@5cn+&50vzCOk|Pz1Qggj$#g{? zo{OG_V<&?nKV(jVwy8_9#h6JB6i^iEqK-1sgN6&t0CXN;Dv4K@)`0qs3vr(+a@0ZJ z#!v0Z1Q&w>sD0Y#D3RI74@4BC*2-$fg}?7t)Phr)xLQ(!$F49_&mcb(QZZ4fg=o$u zgncSw9m9?X1tX`sax26&)cLZs=$qKSQc6?=_RYN^m~Jw1z;X*4)gKhxalqxW`8mmtB{e8wZP>KQ2 zGIwpMdriD-(ImS3GYrRp>fDfmJrY@;sCa(4ruRm>%4xBcO7zvkpdD4Rk7CS;-#s ze;6?6RBBZyzj;d*)-&h0q|FX#Po$g2@ZLEngNL)XLt3caFc$)1zcL3^%o&iupfVT- z>H3m~JP;`e4IN;=DMyr=kUR#;Mg7;W;UFi<&X62Dw)OS0Nkb_5>#r{8;nT|<1&t1! z5+H2!<1z^9sAbK`9Xg+A$LB{?nh5Tas(JJIOe_$#;Gew7wt9D0d7PQgJ zOw26;7a8T9)dC;yxKP_{dkO4wZUn&ovjRrjw-LgwMg^V1rbFmrIl;>r^);N-k!ne= zv=(_~I-&FN#S7OO%iyKR&~7EV#S0M5HU)LN^Y6R_+Y3n+u5RaGSplE4M>|X|%I)M` z<-hL%-r}K>M?GsWzo&3R`oh^QKV6IcoUsUdPzc|y!2J%z2{oq&4{?9+) zeRFq4qAlHopG`9el#z`dq#da;u>Y|7zXXg^YVpsv&KIl`7ecRUJ>0Gl*8$0QMa}7V zBa#N|#BUaV3=y@&Xlwh)htI50W$QG?GtFZ=O)LY!8Qtu!v0X2(IRZ#jHv;=xF`9z` z>yic{M1Fofj$?>Un(^lR&-~fSp(U#6i+q~t}5l2ZZ??>Mo~mn zjNfqc9c^ns!uZaQVMHbHN5k?a^LM$LWW{$h2YULZvh^&%<;LhfkMIZF9=!0aU{`E3 zw9nE9Oo6STAw^R=O$}6f)G7I#gmNpz;^0sJ1s(KxZO+ljsz zez1o_{~}rjnq<(^%8R#vNWGt)8WJLvG^`#(RCr)L_pzJwkRhYqEMFG-Jlhm*iAqAR z^ek$hHaEQtTt(uN(M%Kxx(DM`!7OmyZiR7iq1r4p@yw4|d_kMwAYP!Y=1~1b*qnl0 zI)OHMKWm4Gfi%kR0xn+R_CR;^E=e9~$s%!x@(rlia1Z>-(8`6Ob1bc4H?r< z0^c9bv^`6k;PN>8owo4JURW70rIUU0nwJugMGn$7xpV+6$9s=(HR*X>Eg0D50Z0k_ zP^fg_D{m_yUOf$y?-lwvK&hY?Yh>*^F7W`gJ4GQ8D0B_mZ^yZYMis6|< zvZ4>(0W+ot1vG><7E#m(oTFxY;zuBA;u{+S`X9A*SBxtOkA30k?PqU_&sXLHogTFk zXRE^67A#H~ZxIxUVh*uM{0eFbXau;h${lC&5_CZT&*3dtsb1mKk|g(UKipM7T~DP| z=8}AH@7@G>cCNw*QOiTRC)-n?mcm5P2*o4cA0*-yFb;jOx8qMQ!T4lXp z)y(N!14ds|gTs+gsqGoKyHVLy)HSidZm9zRDV%p7&^koWYGJwEQ1=a#MhNt2;{OL6 zJ!$1VR_{)oDGW>85Ew;2dGgJf(Zw_ z`_U*M;WuwyUYNKWYk=GXP6?dM%t|1@qweF!!3D;46Q?a#zS&Q;CPCw|2^gS|1xG`P%W`U2^uB@N*0(u3=E@GQ5n*=wHZy;X4p|i^gSpF&KkK>5zKyvjYsW9r)%3yK3e5P1lf^Jsd(!O*e`qZ zBPiHjNsM{j?+!KvOUvxJsBFvX%?vN0PuJkC=Z6Za3BMRw)7?b5Unr57h#+F&b_!pS zZg+HZ4LGLhKKeu71hToHLKWsk>;dg=2Vn?CE3!iaDufSL0(2mm`0A3&i)IQwoLC4F zGaS*+y59m7lLl0aZ6;$d>)f{3*-zY~G#nT!quGj=-waw*jy-eIyH1Tdp034?r{Qd} zP!sP!wDC$qk|mI=em`1#2(hDr^?7JUUwFhbwyVXYB7P95)j0CT6SAG6`mHw?e zB+u>srGs?NMQPpc4IDz2SFefCnFa*dV1+JoKXcY+h0~1@dSQn{^gkOw60oBkzF1ye z+8C&?xdtN+dZjcpKN9=if=8scU+Xk#l;P9_i%#oD)*|3J{;?;)ROJ5#)Fee9l^X)F zx1N0Kk8VVG^6EW|mn0ca_66nsfAw4IX+R`rgpzTe4jYpKd5PWN^F3;5lePSDOSdNs zyWqT6;s+kTqoZ8dJy_D&R2UoJ31Ct4;m)w707)?3-nudoimPNl2+j_Uo+WW!YT9Rh z*u8tdx29x$p5+<>)eHzkX=GKvkrV&l)Jm`zR4ia-&hzeDch0vwdJ4ZCg|QiSkSRgj zNKRj1_kfB6Rpp4tF`R16TlM{5u+I_vdxYR0GmNl1A_~2#Kt~(lD*Gp5`+9OvtUCgY z8TDXtQaIg~>0Z1g;?-^bt4~@L*Go6$X7xD-#thc*zh)FxG#0QgkWg;%7jtq4IjW)1 zajHKLqF78x8@-wB*Y{eYjHFPU$ zQ-PXV;1Yxpf(gm#qkrE4Ulw}G6yE5DTR*t+krRasC>O8zw2gT$ey#xQHNmI1p-8a{tmOj z@Mh}G9aS+d5y{cTXZuUDxrp7OF2mD8uMz2Mz`;otJPEVb2qciMkzGuef%`$k5tv6( zxa!ce#4(c{7?spP|1dvWxkW2YKZt+@7Ea|6QSzLIe>XfkiZaU-8AWyMz-YWr4lA3i z`5hhl!{CDFG}j|k^_T|w3v90e?sjrlHNu0ixuo1ZB9TL6li+d~$<13y8k-#-rs^JJ z9JLk&D6muiFNjBx8-l}#eWA^MKQ8fiYbm&{TO4mH*qKcPEVw-Jv=a?E0Bs2Qm8Ea3 z7=zsYA}1qR|I0(L`nmCXGz3e5g_RgMlM}l~gacaadNXf>zS1oz0;^D%r;vhHi`!1uEbs z?xXdAQPO~oR?Xcu(+`nDdF&9>FV?dkB`%f32S}jXY=|8d^917u0z#rvps|3(auat8 zl)O`6j0Lbz_p%rjV(HJ@5@8lce(pq_)AI69r?)-4i&d|hnn4@xeV8}XejZXdq_fT3 zEw1PK`-SrCT-C0*%8*$rg@3VDpRIh60Y?m?ANxt3MD~g%O6(H`J>6vLJ2J=3br&9C z4q5AEX|%6|eWH^|_5+IR1G&4_I_4;f`iCK^5nI8iv;I*b(j;&vAs;|XGr0vG$XMiz zg7Sq-dPDiFJ>yP$g!Sy%s7JkOP`~MemjfBBYtJu_@7TVB>y{{pFh&CQk+eF<%F4H_ z%h;*YQe&xG5^!6B1FE_=$!0%EC&6fH*L|h{+r#b5H+nL|clZ24(QYGfigZPp9*8fK z294$8;nm>xZ>>$#m7KF_HvI$fz@3A7K4{KAJbkt|#qS3gv)95N*mc0`_#EgNgFp07 z7BcnyMt@b7S|WjJ^Zz0|#8@kyHb2yeR{)GK7UYnrgZO! zpLyg%opU(`uvrGHh4L#El>I!<4dQJf=Sx*wABWK}lfDkivr(q@NaJMPL6ihw0Qi8r z5s*R*OkALpbDoq&ZIQKT=biPk_x{gVCdYwt=oD&mqIV?#U4^`>pe0g)rwT|B5REEs z2gfLt2Q*X}z|NR~$pMMb5{w%OYOR|Ojf5sZj85n;Jchbzpdm$*uc`hnnAl<8la;yW z+hp=%?*0U

=ZxpXiDGvYnRp9!TzAqs^%u7uRdftv7ZE{n}~-vJspu5hY%} zq(TCkoLcHyC74r4n=D5Kf)wKWCGonr(?&Sr(a_S8<3cRDz7VXMX-9omtJc8(5{CgW{%g zlV9*^s^!qC3i0p9kK-XZ&mLje#rTM^_S`y>pkOAYysYH&w56zq{#@cLwThSfx^4=8 zcRja}8rkbfbnvx~HX*`)DtUfkVRJX@_Pv_5jYmTG1pjOcwACuKWE|D0%4{FJQUBzv z-oRlG*K(JSkzFZsPOjmIxKw_~oN(Qy3NE9pMzdYw#(htRO<>6J;0 zr%Isa(z%j(iPqB!Td9=~&-_Zpot<+NmD!HR^eWC5yfr>VwKbS^v!aMOQ<`hnQcK>z zxq#uyw#{C_dLtRN&yAFMLq@Cxq0qLOMYsi_Xf;?Rp!%MxtO^%XUe#(l#$^6_LOUHv zyXVAhJiT}Bax4zAb1g7)V_(8X5VnekZAgLb5u*Z&AY5L6I)^Wj*E?rSSh-S*>T`R){`QR-B@}TQOCLUkDXvqGH73BW^aHT6VF!EXJvoBuStqYRR$C&;PzxFzl?SukUH_P;S9HrHK-T zHC7<(Y(;f7-Ht7DzkmNGdRjfD_XZd+oqjJb?>4YLo?c!V?RLPUboBI&2DHO|u73`c zc{A$IhoV3N#Vt|z_l|Hw)}f%!?Fa4Kf4($#ytbXd^)~Ae9J(i2_uktc%&qSETH3Yl z#^&{6o4y^Dz`nV3S+!evpb@$y;&)|RDGqwG9@w3fW~VIteqr#|m@{uzoEKYG5FuPT z`eU{wpJVr_Qy#a+`Ywb}T#5)2GD>0FI}~nS)4H|dMq+BYz{Os}+I6H$Porf|EyxpF zU>BFx7CBs_Jm~8eur-3u5zd*SvqLu%hFTCWlDWept}pQEdbHb(S&H3G?nL3%Unt+jU zwCyOGdiEEPzJifSGhN|Q1hKHAJy`uwe`4cJAo;)RYx8!(@txQAZ1A=(Uc6{2NCA7X z8qihXAn^I~=kpgXe0wd=por|zKhiKd3+7Y!2^so#Ha0f<{a*s_0Uqpy<33)h=Yj-E zNTRvh+6Q(EUF|ZYPVw2E0_>f3O4fznzdhdvL%k_3&IEKnnfYJGah0LpsnyL#57{!i z1+QPNb)E6)Sic(WY(|CgnEM>YOCRZZs9Q0M{VF~#PvzhLls<-f%SZR+#hytK^H}^0 za@4N(3mwz1;l`@tpkROhq@6}8MAHok9L2Eqd2mXRhR=YLWvqEy2#*fkEE)Ily|+|T zX#Z1>FrT*4z*;aS9 z^TYVOs^AGuF4DX#N0l^8rAyvdAZWR3JWtFu+-EbKU3G z0*8zjzD@99i(=XIw{qT}{t;K0iP3e$&@WOq+BG}Lc4Oa2*iD#Rj>MsdV>@JWC{$^f z(Oki`S}y)9LS7hG*A1oTO&e)qBCq99Y7!Twt5fzNauBo_gXEn|Q7G34O(kEY0QOo4 zxpAfjH@Gtr3Awc;+jKX6fZK!BjIb+GJa`Pn2`Gi;5vlx>sm+S_ERt#;fzg;7Zx4uy zGP4^XI$djDBb`YZ&Q2XyrFWFa6uZ6+XMB-GCdQ#e=QU2OQ6ER?isf65uL+YB41&M6 zA-SfWy8Vd&0acYqMCGp=Z?WBFkaWCr?Q6Gm_k`mF4H<*uyn+6(VpTi4%%>m=VZW|N zJ3j5-3!T>s)pd5?3vN863>swvvaDE9pgGZ*g5Wy_9m}W3LA%%G>iz8}Bx%g=XqQWNd!Vf16T?&PCH4nYQSF53Di`R$B`O@+aa zzG!BAts9hzzDMMp?zXIFGZPYe?Z{XMid-9B;-;!=FlArFO1e_usRZRN_>kdo@S8AD zNz!hJ8CbGjY=|}#pulBNxB~7c8P#pB)JxK;yhEh<{f!k43;-->PPZR zv{8SW$N}$Hc@e|J^DZg*))`w%O#O3_LA*ItvV)W!f1a~@XlOkIZt~(9(AVg^oA0am zE3LYC@uCH0qgGO)-2Z?iOGq7cz}9{O-=)#GzN(rxg|ZLTx`OJ(SKx3dG1_MMDd0#< z-5*Q+8Ei_MwqT;37ZU1$^zrw|n^alVNFXKx&p@yd3;)6&>8NeflIx63Y96651gRMC z>{;6l(|`BP{@9uULq3k*1N6~$pN%xARJSk+pUwXMGP!+vaL`m#eZk0MfD6$7aQMzo z;-WDx^Il(EZ13c9*DmvQRiO3O2S&U)F;m3#G-#$%C-m$~VdpuY-BgteGxOX0DQ!sE z#XROFod@CKLpOW>wB=S8r$0FzmTIYg0)xsG;{y$@r!~ZR&`u8Qx;if*M!ehi_KbuM z%TZnJBg5`m-JWhw0YMe*WIuKKviD-UBYj{(T?66Il%z5iOyTl%llDqhwVQ zEgITV(bN<|sWi2h5uv5M7fo&Lja0Pv9{=Mqp6B!Z{qNU@$H((@-}iN0@AvyW&*MDK zt#*`fA0P7?rDF>e3NyoeK5&Ow_2sJ;o{8XF_~-5;mJ@YvHi<7H!9d#7naD^Y?*#Q;Rv0X(98Ox5ypwR z#`Bw8h9_w@cwbG93>Sam+4R!o;@0iSro+2ilQi#)XO>+cC;72Sm?-uUXS;}8Wybg| z-O3g-?ZvU@G$_>u^Y^@#mDfH(Btxn0h{cjgPQ9OYq~BM_5&>2QpYX&;0MEe11R8%D z(rVaCG}ZVJK`4TW?)M5N4(fKys{CqJ$~o(Bk!y5R?NU@w*sHqtRZdRFtBv#(HNjvm zeM|zjouRub@3i1ozW`#YYO4;MVgrT-51WQrJq0nSd}W`PDC-p^0C<9e86Vq;=luJX zz8?48ZNjfiW!^knk8!?pltNHc++l8m{Ox>oEz*((C7a!X zx1P0F>s5k0rY=|W#<@}3}6 z^azDO(@1yoSEdu>Bwiv=CY6S*{(1JF^V&5^97Fu<}VfZ_N$=6{o3DeFx!F?UP==1gv_GL z&(*OW`FI{SVxDZcpZWFbq~$hKd&;s*DiwVPvtwsiOY5AzR8>caxf5-WWe~~(6?bT2 zk|t>s)@vg=wIPpvJS|Z-Ck}IwHjmkHj{-`4=C+hIAiC9T@6P62rQheWS)K0Yga z)XWPPa!hLnrEgJ_Pz$`R0I_c-$+=7jg@3~x->euJ*l-PPnBPsqmqmu5DSrhGA@G@+6M*GgEHRa2X!ftodw&TNy48X61lW?FLW-1oY6PL2PEVm5n#Pod{T)*5f;f za-g0ntv*4YVlRCu;BW-Ra#K|l;P3zkXR!dtF@`ig5M&uWbddH>=fX%lXt|L^WlzDnL1#a~awWP9;S>`oE*Ol0Qd*HUIGgnope)W>`u)oWctfs2 zAuwfH++lo!IH{4B5U3@RKr>yFcI|B$*Qk?huGL)(tz;Q4jM=Qqb2A2e+dO7T$BW9^ zX6ZJ(HaE7=V=KIi;VjBj^%kW8w5h%#ilw?V^3|o&sOP5Lly?FT3kfN3G2-p_oJDH$ zuJ%KGhsi(yj8Td&c;|oU{puH)Br?vC1ys$Sxu-J*%YF78a9qSwJmC1r_GUs$8fBDu z5oCH)GHz@R2^L!DBogNQl-ordULL=&p%B)6`=r0ffWi^Lqv?snf^-)nD?}5YOEZsw zBa zGyQ(m#{m7VrRVL`bWm7(0$%X+;ExDw?u*yR-3`1GxDoI>ygKQ!x>xU%n_xyw``gDI zPV?smZ^Bclh?b7-5#BIEtlT#=bWq8*Vtan4j~>N7A++9Sf3=-{KgT!W>bX#98a*p8 zJc9tW70Iyn(gT%_jBAp^%9`KmllZppVBC6-4(Bw}?QX~81QdVWaYWRn-}J&i%C~R| z`7Td+D0h5P>ET8jH$1z$uy!nf#Tx+uh}sDkRco%JuZia(vVXGz(hrsQw|G%nd*CqP zSe;UYR3%sW5+8~~>wG_F z9wAFRqFPUIZ5b#3OCcKZ8VVlQtL<LQx2)FgQUjem&@d!)5K+jn%f|ECt z&36xc4`&Q0fJnI>V;a&3S711d4JFr{Ws@38)R5gTDw?CnH)!K)+qjG5``jGC;c>vO0Kc7R*tWYejQ`5od!z!WVS}T1%yITGYRPAvZBzB~ zr(8<&=NAU$821ep_~duzY_uG^K3G3XM!+In4vu1^FAp2~d39Tkv`Vr_NHjB@HReHe z^!Qo)#zeEA@s3MoLs&MUcNqo;dlvT`52N_5V)uR4%i3s_KuO4i{?tx&c&aGw_kTJc z8u9%-O~i&YVj9=z5K4jXeSK%|U#i@k5~7DT1mI;-l&U!{njm2X1qBHh1ZD0Z(s&GF z72)V9x-k>61KR_vqURrZ6*>LE5O@eYwjZM~XSRy=;{GAyB4Zj()r>@bv6Y*>|8W*43YE`buw~LXSUTEm%VW%+L_`?15(!($1D;InNEh1lSJxm80;t*yWv^|0QroqI$XI zgw;tgWQ#*gfX+}}j!q?F7Ent6>9zyR+1uYVC`~HTvGnKWezVxQzvg4t?8I<6zAWT~&#jv>%jeP+~baIsd^3 zHmRVyn|T}Cc}Yl5e;=*H{lg&cH&j(PQI7+AW_^2mWoaI?)$3MH5bYCjplrXW`HP>8 z?rRBTe;*L6Wbkqy?vl3<0SXSN`P=925qvH&_XG;gg-e&##wl$k1qqt5BI|BfF z318ubd{d9i@8ojW87<4xH=Oy14o+e?C~P={n!h@I0fDn9?bOSA*BWEC>dub{5FU1XXD8ZX zg)BsRljS29j&gpI|MubG&9NbiqYM=59z1w-W@dF-bG7RFa<=uz`*K%pxJF)3xN~vH zi(aJKG!xNjE7>l<2|749vwOLYK214w!}+?#N)2U>_kt)%YcSwAZ*OqFZhwp{+cvsf zZ;yysyK6}FZLllSj7+aQzwjluV~v!TVWufQnsqk!_E4JBkN2m~)YkfZWG?r_P^v5e zFo_CjZ`Uq)k{)W?U%P(&II7Z~l39ubV5g8%#Pzg^K|y;ko8<=w%NzKsGyF_ z(LCQJWRz0BfSCPe5;8I_oBgb(7TaISDOW7a&+ioyqDR+?+g}}{o}D__QP>A#Do*Vd z=i5H(PYP!6MR=kYfayzNbp*Sg0EHtsvI=LS|{4mOt!Tn!=PS#+dNp8ep9mp3jriltC*6a z`b(VtWvpvS($dmNdNho}-$7gawaxLHf=C8>^3;yEwgA=JTDVoRU%xcddYY3s$B^c2 zne3}9stTQ$jNv(ag)42}BTB1--ccw}FsOne0eHZ(_z2wn? zUCxB&Pm|s5aaX#G8y8DF7}qB_#YdUc4kqePbGFWpO7raRK@~^j?<+@#iv?1fC0Iy0 zr4k2blmXSCnbrOjOeC9miK9g;hgSW$?d6Cj*O|l~tgEd^QHbE%cK9}uaisqxU}tEb zm=JAUA?}aF`qjM+ra~2@6o^h4+v&XQG)E`v$jxe=LSdd&gGVxSgLV+RO3b}2pSFLO zF+Ab98xx0$k0m^*ajeE?Sw&FCVuy-6fc+fD%9srs}JqYa=2XiK0nl#TIo!1xV8 z-oSxf+tBa;6RmVeeXJ`mhjlvXJJ^oIVDM`R{1D0yk(1S=ud8flxYUHfr~`b zs!KlyRA(vrzqvZ=fW}3?EBC4M^36{A(wBS&q@kPaOmu`$U7O6dJl>Yil`~bT+CgeHadQ#_lIi0;L3KNH*q09Dxf%C_RC<60wc-5hXE^ zkNyFJxS$HhTOK6ltp#qu$@=b;hky$YxG?_%OrJuWuG47IyY%Srd>`LAfu& zygrG9;ap>0Qx#?4P9dG6WSrnJMXU(gOoU#py< z;9`4qG-pt*(#G_i@nURyTXJ+@Ok>3WAYrRLU0WjCA&t{9+m%Q!k91waUs-3S^7;$) zzAroNvHXkgKXljgT3o;OOI1~SQK~sk4C;iNq!y>KeJ;)EnN@w(Lhp{EM{3-br7qz2 z^icOUQd0Bx(9m=t$}+OM938BPG}aQNHI<)u-hk_xN!Yp2k2|t?(gl9 zAxsbyRMa_`uG;(d9Oh*}ErRwgN}rr__qYjS?UlDr^AA1^2zZ1ohe0}W(!bmyRsXXO zF(g5W@hR8@6oLyOt>u=PPfT-RMD?QpCv@!=+g|b@Rg4p~_r&%lXuu#OZ9-R5isB{e zyYpd!v_$p_))Xo&LmiovKez=hs4X7tjaxs@Dz+b5hz3qyuS;7L3l|FcrFE)x-8 zRmkNl*x_UIQZl2LXMTLWbz*l`>JLld#7>y#nC5;F0U*S@)ciUWOfG`WOF~~hLDmX( z3oKabRaTh`8Wt?xG2lcHBVIv4Edy!be55b*oo&VVAGD;>vt&KnjF_0=P*C#h@bZ6C z$L5!Dm6A1S%{ua4OrHb{<_*1sg11eTTkZ8IBZx!) z&+A3Cel_=ZQzg~`L6mzG&i7g)P>{VRlAMO??Uij-OF){1wIfHmWsj;AX*Jy zIz1qh`l@xLsQopB{R!8Vw1#?FTr~ap(!Y{c|Ggjn)Y%0`EFT!G z5Qc;w>#KeWU?Lzo^otn`Alpx;)KhT3Vq(Nh9g=qtw!RNZxKJH66ZLcYA+OGf1{^LmJO*FbtY{bH z>S0=s{?Cl<-`n+~yMq1I+Q1Wl zV|%Gc2QE`e-JJrBT-Q9y8EE$ATHTz4%XwY z7@sk2yGFeLV>1q@_U*&cfXQGF+L>YcQwKR(*wacq9^bq8*7CYB21$q0nmg!7f|ftL zZI+>Vl{EdSvG)xX^q!?19mOz5iR56Muo;CwT%QsJ=G0xGr%t$wSECW^+-c->w6F3k zJR9%97xpD|`$^uA7kp(Ak9n`iKzAsH7it|LbUz6HHi&d?*#p_h8+8*|iJ~-kp1zYe z%fkmd9IE9bg&$(N0K_YpzNB=P`nvwG{_sDeDSw{>{yA^~F3DSeXep^=Fq4S2p=6~&p?{`t#>MMU z{}}1|4-}4n-gEMNm2B~q8>tDrS+}gv&}@kePUKXdISSGXbUpRb^ZU#yKebUq(9nfu zrx2x7Ql4ogBsa4YLKjDegpUWGKYeAzw`$rTzTKRw8w$QkS`4&x!=CxPzmeWIm+?mN z>e+1eSD{IDC1y9-VZXPYx1^aS*8Xwov}OFaOv4(>#7ZU{RkmLG3aw(y&cT@$B4#wd z4Y4Ow_E6d7%6@Ty`XDl|4_ZeEi{`ZhgeTpbABKTz&fX0e66n*~U=a@Z%a_FyBKLC6 z=2^n{@t)ErejeAbe^APQ`!GK#Xgd+L>m!s{2~BP2?p_ukNkR7+rgY!=akcRDAwC)$ zQYg{GHtw1F;`86Gi5u?2&ySG&vmV{nwz&j?nETrdS_2bxbZ#Z(yig?9z)ZxkkCEPM zgQZWM+6%HeiEz866@;TWGxXAMBAYF0NZ=;9k^R~I4V)l8oWw!X+7qYxxJU~N7LBqofZ1kITxHwENC3L{#;$Dfjz3Pxp|l0+UqLoBcO)46K0iC zumTJm?8r4ps9G+ABSKQJR9Ja+*TFW!>LwjVL9l4*5+a%J2Hf?9dGD=*xAwvb2})}J zSXn)Zf*`K1u68iSy{k1dTF49vl;9Rfd*K*756+xoR3?97yeOo|_h^Ri19VN{9L2WA zu$*Dr9uWnB4cB`fQE6W>UBbHQZL;cl2jd&x8*ee&%f=F&_3rFe%VfxLV{ky3Ft^q< z7~MWxZ!}1p1g+!PM>}R&4=41d>5>t0ej7^0AyNw0?9g#JxLMw>BXm>4bGJcb?#3+d z0oJS?e_d^XCu~#E_ty^iYb@|0{_E<>=<3(})qW>2LUljuA?A>BI@ zXJ!YiSJwTJolJgM^NrG4D9{YU{njeJe~Nb}5Zy@*{i@ z3(`7ZUnnS9kz0YA0k2Y86WKHBTC+=VHf8z0SU-^w_S?1a$MX@ zlkzq%eyCdHX_5qCfZF=`0Acxlo-MxGHij^@y1!<(^>&9gs2|OJ{!4f<;wvZwEGJQm`dr6K1F$FMniq{GP~bxVfUh3-^H9 zS*XNw4?AIiixec3FY@Uf*0ja}$<}qFxcJwykzIQocMBK?wFvB@6etZmY)^}G>&3e) z3K#K(GSe-AR!(3J*Hd4Q5ra3~V-v#LV;e1rP(Pi4Db9Wrh`$471LtqKG~Y8|uoHvF_y>5QN{H`sc(*QQOb; z>i;ER5Pkurn#ZNIm(RlzEnm@n8#$3axkAZx*AB)f*x-isj%bYJcYhtXAIU7$SvI9% z^E;E|G(B+n%oaDXkB_KM;)HAM3823oc9`<*P~FRu3%x^jsgU-*c4U|FeRo)w+(k-F zeLXTeY+bNeLw-J?B-t>0gyj$ZUG_1}*zugNb|**_&~OYAwsKS1`xXxYQu?qaE2*z~ zr3C7kK%vtAW;*H4?mM|NE{hWq`;t#Qx5DppC(l($99`pT_(FAbe>8L(-qvK~28D{; zV?;6qH=-`NQKu_VG?Yz}fM4sCQ3>P-kaRz%kxW@;_WC6qe~PhlhtRPf(=cv1m}#1| zsbZAyj7@KAVN5v5$}0air%~7mlSu^lLl^z_{7OR#D=H9hw!vDoPstHL53z2Qz6n79 z?-A9WNB^ZYUh$%hv%?P%_|3RE8AM{ zD4(*je|i*Dm($+mghWG72gg`HeC6g9p>| zs89ZL5^Z!B7$=*myWoqk%RSZVUK}N#>$P7)*tm0d@8X@Up;a#Fu5_qU={8Kt323`0 z@juy~g0ewg%|_D87cVg};-Ue!DP(A6H5JlO5>^qVG0ap>hsOh9GYIdEmRx(UirLbz z&ghFVjS(YufjYr)Kj5`t6Geq<(6)yrxj-pAqyIU1a)0wi5`xXvS@(MZ_JIMYC0<-9 zjhlXO`S{rXMNP6?^H*nAA!#Mm@DRe75cvkJ_$s8RiBJXm`m38XI5=8d5W$T#(yz9b zcb50WW8Uk`8Z|DML>5SBM#BG*uQ_kPlG38fUOtS*g2fA|{uuiUV>Z>Q9wE3O4rA&8 z`_XFfc!Aat*9F+41+FDmJqiOCmiV1XDm zRSgkB5PQ1Of3UVad#9Yk#es|OFG2Bsf2en^9W3pJQff$;3NlGI3Fz1uqh845~9BWTG!`E&A6Zy@Zh<>cbe&6b{L z^ZRJ;16^Tbd;8f5!__M%kyhzCA*#(Su1{H5mOOWgbuvgZ&pfkn_GGos{Bc3FAz#AiT}(0Ww@lIiPPdjPSmUj?y?6-X+W^JxXC_#A|fPGCeQ;Uq%8^&4iwC zUhn>J;_5Y9ry4&*D=7GHfvsF%Ks120eIoYJ83s*oW<*6dR`-=P+Dbj!x~kXyQb6$K ztj&YPOv}sUZ4UC>EGz}RH(<<`Y9EQsynm4p*RL{A>k728)U4Hp-N8XC)wxm_P zp6s-*5no>UQW>49-xRW$lJdvkyv>|VEdP!1;F^Wl8jrF0*x7!n6bi0}h6Z6O?KWcp z(XEW@{N9ok7mFNOX*J*lnX7mL&s*=rmiax$%hw3MmOWQIz<0tkX}vR^Yf?ya=#~%%D0#k?Uh3v%W5uZ|ZEy^|x|`hTGWX zZUvJWRfnx^Z#N3|j5;Aw<0)U{^NF)@V(Ye2k<_?W<5Ma>o(XhjGsmm$sCb|Mzx@!) zeGp7Ev+Xb%&a&Vj6K2#<588WWF5$(C4wIQZq`C3Z8yGhcv2TjWf>P3Fy<5d!Ke{Zp zDRnQ;*N9WC+-es8^{0Fa`!73cg=YtB#8OlFla^i9(%^OU{FcFu(K&jlYI@{R81J;$gaI;g^)%B?wwdJQzykf`w*fKDgT>Yu;OMK8 zU0>VwTBssGXKsvEn?(SCIBq=m(GeS+g1cneSFV$CUuUUV=YOJheAfc^2xe$?N^5Jj z9q&-sM2TUpH$H1k`HyQV4IlR`JbJcJp5VMO-+53_(Dfq6*Eig)x7Aocwf1t~H?HDx zPuH^3&;ZzbKPO;mnxow^X^Sj+o8?_U)ROLM72m9Y*M!%)H;!3~3m$+i> zpWem*KAMmIJ34V9T0WAleQ|bJQH)12`XfzqY)h&=jd{RfWzkaJ_xd9_^Y41Er>dbH z;I`b+TT#bo(z%e=(lmHdUH`7x)wgAhTElVaGh5ncP8gSR@i0oO$-g3-njY-l0k*6A zRf;uY^9_pH?Yc%|h8Otx`DJ+v)*BAHe7^JY*z+&DjvT`A`FsS-wPhC=dlWt zxXEog+PLl7bLX+o4ci0zo0=QTXBzuzqaajs*;$^U-=g#hV}DxCKA8scz$Ci#1gi8r zsMX&wUGNCj&c<5r{pQ7{NdDoU_0Mm1Z;?9n;1Gq|0~eXHYUR*+<3tr=lqCK}fStv4 zfvw-1GS`pvg$_?EZnh2 z^o%fwLu9^mELNJK57-k?C1? zYs@LltckX>X z_wcE7&kT|A)A!&F-hITW>#C}~YMc_@niW@8*0)A%Z+K+<8;;U@K)TsjgJKH)@>HmuiRB!OaURm=glr|%lO`$X_`_}HlciL zf~)jftz_}W3!U}K0iEpFYT_*#Q62hUC(fTzbw4WI-eN3(OVF8!+KI*&=&RC67L3kW zPfs5`M^-SQC&bE89#K_pEb#J;VYbnGU(jM_KEKkI5V7Cbbv0!8ShSHqC~?}?c@i45 zjf@D#%=f>`T8tJiPD916)rbF8tnJ7iYx1%S+VNi+~>6(QlaGEJzncfW1GTC|2L6U zP*%92xNlE-dy1`lehd!O@7oR@RP;eBk!g-uAeP^v^re@$8AaH3y1q%ZcIG$MK1w69Q_Rj(vh2{!ZQ4T!`3)5p#cRE$V zs%yY=qC0Qzi}OFN+T&ElHQ(uZFp5yqZ{F!9P+4&Wx@lJ0BPwl=_MP&adh=w6p+EW+ zy*5MR@vsY&&&clLU6KZO3siCpG<^Qd+Sbg?&tKKM#`TcWQXmInYf|hoHM2NZ8>>?f zn`*laE0?8rMGr4;BFBWUW)``EkW@QED|caND+R?hqF#zN0?zRmODZ!hY5l zN=s?K9aINk{p4&mrWL&F>RL5r)+Cyab%^U|$@%)SzoCw8J(FCF$ezupVi`rZq0axz zdO>xn^PEBsbMFUuhYzQh&A9A-GU7J4#gk0clT0R_qRfA&JDuk7;~w>h9-CoLNN#f{ z4hwoymnHX2jdpjNgflFrPDe*>d3H-<=JRp0frf+Q5mi-z#ahQ}g$u+SODLseBwh%s z$VoNq7?zkjh^|#bcjKQUaL3FLzmQNxR_EM7+t;YP2AlH-Cr+rMOul+o&Lt#7O;y0J zudgCW>uykxN^G~c*ONVyxoRra%GSeO&c_4wEBfeUUsDvvis9Zu;3T;;e|?LyTvBr2 zn*iG}E$|TbU9P~497kvBW*zAD(AlsauIPGn;DAW+jfl#16cC`n14_7W^K|VGOTmk( z4?P|xxXlYo3KejQi<7mU>$X(YkkXB+&&kwT)YIkqNBf(NYV8?&tS)WKt6?&$t2f(H z=;g)f*<-vcOBV|$*fXat4j%?zP&PDOaNYHTRX+!&pQH4maW->x z%_+Ef{#Oim!Oh9ax3!y}UuUpsHc@X|r}#?;;XAvTXupnDyj)Gavz%?p{1no>qr`pCQ1(J7`ZB3gdh2@` zEl?p3Pxq6Smd8f2Z;~*&3X5|4LyDOcJ5mR&sbxi<~ML7H+Z=gEI(SL)jwi$%;i}*3u`AbJs#S7uls(sO z`h)uIu3iN({@qlF@}*PsN3s6SV8X~vdjGJ=oXt=V)B2;*Qm0`qh6lCqy>iYnmZNopkwBBsL6Q3GsPa}$A3mw#U*-8N zTw~ql%!)hYB&C?f-Y*Mp71_n^NL;_=v%56SS|));8mUN`#sV8PBj?BGNqittl&y)K zm}q(ql{S9<)T^m~+}F``tqe^(al2JK75#)+#FnN&a|GMZDF2BNnP!=HDxPyl%^@E6`y@vwq={vU?D^*Ehebi|uret1CZXH5DeyA52M}AuHC7m8yC*?nsjx4ll zFND52AI!Cqnvyb?E!>>i*0#H@2?ufe$b_1jn(pi`LFFtqdA4KGvgaNmlFsmGt*gg|Qhcnm?M#}7$A(!$GllDm$l+d8SXAjJw_E#1Y@?^; z*5KjpsvEKY^KF~ipX48s9Kp>8i|I?e$ml-^Dfuxcw;rb394oQBe`ay&C;5{{%!i10 zU=;ap!5qsTohKf*V2@(ae_ac4w)_kug;S4@Z{*I#e z)QoAnIGYh1o<4!;)RH#}&d6G&4s?6t!3JGGain1VLouD+5M_stFW(L_;qZFiANlG|TE#Xt$!^Z1J+SsMkx-LyCst*|VAP zwpwL0APbFv-jxIgxm6E{CaHPJ&y$y ziPOK^E~%5;dc>l{vh@aPZTL2GS8u(y1s_@a`SX2W8VO(jR_#O<2`A^E4OB=j>t6OO zbuyT|fK^7W<=e6iLdZY!^3B&Ja94VV8wiB*;+=SdMGb3N^4FdL_HfY{azTG1ZM5+z zT*Ny$aS@6f%02p)3ww(7lfMoo$X_F_v>6f%J#`PPe-|rg^C;0h)xkSXjjJRkG`nz8 z43i4Z@yKqQu0ZnIelwrZqQ5F|`sAyLh84PqxekjL`JiM$uq(l46?OX131^ht3v<+2 z&C&AAtt%gtHH$wV?=C`_a2LFcL#`3RR$IlK>iwKniOCt_WyOnNDeG~lwUqH*lSh-| zXr|1#0fgSK2GBDx-CKGRs+$lFOywnIi_z=+B|aS7mA1V*kUPqIloAh8T3QHy=kPxF zw5a=<6i@m?PfmnZ>8rjD0sx`1SS_;ah!#sqddGdlO`mLsOdGEUb7tH-s}oy|waxZt z>s46LGBQ5tx|eH)bwH>ENh+9)Q-_J#Zf)dG&C3gaH(PN>BFe(U>b*H>Js zpoCyTQg7yqN;fz2j;%#J}}y{3OVsE5v9!B#)ZJ>{)5veMc`z-Iq2ORjzF=> zFE*NgVN7Voq)JJha^v0ebI$PM#M<;8Ya>(;;dh$M%;;|dFrw3W43XpVl9wbc7+7wo z{dvTq$+&e9oV@b4+$dCwt z=)RR-#$d|FpHWFMat^S#(rx>8Dh9##SM+@$-L>lbOW~+)dQjg<`EG}W|LeddgV}g> zxrE^JRECKvOX~c@qs^j5LNKshtG>fQb#3NpXF5C>22qth>wnBqI$dgRU_J6a-KI*{ z=oTjrYHDgGF2ySI&*~5fGj7W%zWxl*$W`852DlWtLy`-jKq?y=j)g5Oq{#R!;1WD9 zDeVQeGh>I><=q~0kn_&0bR_r#Oe=2D1hghKiW26rH*Jtm%| z3h8t&+wG<&=E)Tg)13=VXxC3J zS8{7r$We?H4Ufz(?Ah@;VJCvo%5uP!?aK1AQZiE~L#z7g#d`)Ph|EQlmX`kUp>ECM z{!&laDlhVfxOzVwAPICQ!%|qxK!^J*#BYOAOaR0m*ps zT?k-G0`ml_C1^FUACqDPMli5C_NCB9bY@&&d1ZOv17%dydAKXwxtEZ)w3Pq>m)R<{ z#^!F`2aKsIk<#rq=DGojNj6r?c(-ip(G$2O@)Z*Poth+q_P)?%L4(S|;!3{fkc;GC z(K##G!@$mWEV9-v%n#H@%V!V#%OpF_Z69EDU|XcU+;j_aYdsxvfWyC#u3npPr5I`K z1r-*9<%QefC2<&I+keH|VCsipU^5^O!SB6sMq8N=H2(KQ0DA+S`|}G5Q`Ye`BLc{)&2H zr1BYn$pSJn#BXgv|um~BtsOd-C0wR;fT0&WK>rE^r2A;K_ zp&}>_Q`Q*g+_a3krpVgJX!nbk%(RP0*fiFC2Li>V8Q_fhAh*JUi@7!vZ=>5%?93JF ztci0yCGxmQbdx8@H)X16R~bZZX-Uu-=nKcWQjw;q*gM2Uqnl`uYj0tqy=MmOzmW#D z`jS}%9WM(zRH@woXqhr|S>?8FM)=0VY6Fge@^_$D`Sl`Z7V)E)q4>v}z{yGBKIk z*iCn~t)0eyps{O5iSP|8zD0wOnCC=5BVIlU)4(u#^V$+2Y5vLcOr-lRK3&)L*>G{4 zM>c(5;o%`y%1G|eQI#qoYV+QB6@acdW8fcNDg#h zTuEPlxdO%(G6;G=BeZ13tF0xju1lflL15p$_zO)A!okN}uX(5SAZd*uRR$_RZ=_#H z?^N>lYVv1%NTiBBf2Od-AcD?W!m}5_+aqo?-wQd%1c%ni5JrQc7PF*AjiH}`VzydI z#oC2_@+Cs+QcsT-7dq=PrKcmIJ*TJ`HjIh9vgBVxadr?dtIbP;shdA{nQulaNRkW? z+mAE4qTxflN}N*1%?*C*ve0b!`n3=!=^*U09v_C3&FooMW*3D0_Ea}>_u_BcJ$g7T zJao1^5Zg>h#y~T3958~LWYD*7j5u({1@HE~dV7#{SE4Z*=S%q;BWX$w47Z6yB)x#f&UUwIH;wa^7J? zh&YHe9p?`~qY{*+wGbP{ybw18Iald6M%g|opT^gtPt@jjD} zknH$!Z#{DngqqAohE&10c4LVKHf6W{|M3`+YEVbYqGH_MU)rv-n&&vYFpu)>uhGy8 zZm5T{_0Sk`zA#G!z{k$O!>AW$T^Q4*F1&A4HUs5w7irlmX_!0+9LVs`ENwk^* zO=b}>k_xt$7Wfqvjl&j~40bp>jxGu&sl^e0Ex?Q^n7-=Jgph-$tB5N9Q)j*tb3pHR zlHst-t8DAN`@frdEuKD8E~hFXige1j;5?2rWM1zbR6RH3g$SChk#lrZ;~z8X>aa|V zd)j&svFCM7(Bt>DCrEdH^5lE;SWUmPs#ae)ck7ATsqoCnJ_4bZ_O98?llOyf@^AY4 zdm%$!_XURJm(ZAU1Hf%JJ&C{x^dfQej1q&g-OSL>QMNyAGIJ<-?77=$w4;!U6n5Wi zh>$)NUpl>ZH>m&!RtLo!!KC#)Efua_^0(+c$jiw2af2Ht(J6p)B5S;Xnf~kcyA1Zh zXA95cB!U~o_-Y(!scMI@TBYBg)9a-TvpMDQRe!JOTpf8t*dve+9|C5ft@N9w@4fKt#3`Ig+A4=ikPLbfNZ>OVM zLxN`GBmCJ(^AD&pUj`rF;%~1lG;E6p4P+R^L~V|Er^i=GNNC-?P4+hyhkqp!MPYCw z>~$CMZDQ@>(FB1RLrWmV1)C3OF{2J-eSd(BjSiJ^_Oj5=MXu^k=C&h#kl{-clXcJ0 zE_Qm$R6jVKel4b^c^)X}V*-K! z3L=ZyhYeA2tm*T!*s!qlOEt;sZ4a<{DpUT^e))5!Py)vAkaq&GOSNL}zGj_iBcodrJTXcdN9F1Z&zM&QE&u zSECr#2VPz0ip;sFlszLZ^jHDAsOX2@%=k6d9lkOWD@#mscSjVR zWp9fq5!Cb~{n?{T!V%Gc$h2Q(BDRmhZ6tqpQ7INl{ClZcW>bK#G3Yq<)jE*fkuC}e zCIMW|M>31Bwcnn;`jFk~skq1=0Q>u)k$nf=w8zBF>-l>jp-J!Ww!1;on%8LbP$|Z0 z(Sk=-_PXK`Po3INs6xV$k_12hv?SorO)TS4ob6=O9l9sKuni;fa5)!~_O$YuKMA0Y z!)(7qHWl4ZPvoE5;Mx_a08|E9YvbgNcQFJQ4x&Y|?KJRT7NV?adYLq_0cp*1wXTKf zQ8$xR>GQ8tZ-<2P#E5rmqjk*19$jVf)&1`8RuF5JI4?^-1xr=QH}2YXBvjYmrY1+$ zY|*a{;=X?&M{oHjduyIp#s|d+3e%Kk;Mm$%5@;?nUZ^Nj+25v(mbG9b|QcBMldEFK?@fI7|!bU(DPKg1S&U>&|`E#^&WQrHjdk* zKyGQUO@5v7v1%GXwT9>)R~D;GWSl|J#WoKKPPS^BK6ph zU~<+Md+PFyR@RA`=Du1+M5Og;Ub#HUvl9P-%1hq2o5V#;Z`hGp<@}6M#Vq{8qJ=Yw&GSS2QZc_)XQJE z@%DQCMRcPrh}H=p3ZijbI$S7(<#u`fR1}(6W&?xuM2-@xxf-u{cXg2B*2sXzT^Szg3 zJZsFsMj%K;*hO3;^{7|sfTp3Ji30uA$x-5N<7Fia$sjU7F0}_G3;hX)h8Z!cU5~xw z3+40ZV~JpFsa|NQn)^8QJ1+^ zarc;IRGr}#RKeJou^P#A(^gg-2U{OQI=%Ii(8ovgL_`EDX>rQ#?*kFNmfU)4{F`)O zKPLAXP1FjCjZ(i6Qe9=$qPk_?0)fAo+?HbHSHlj3iUe>E{CJF5>T|G!F?DiakDTRP z@(wKY{%Zbj04BBg_@AMvTwzy=bJ&3n%2&@`zotV)_z(77`@}`XdhgsR*+8qx?hINV zB6%agkD~yRLIrM)bF#?b+FB>B5}kexpo8mA-+vg@`#Z0xd-C&F4Ap=*e{ouN0ri0h z$}e13BHV$pYTt#bG(a6MsX4!A>5HBB)mkPef+lWt0J)0k2&O)7)t#P!x1NJlF{T#x;b- z#L{5?$|3!lh~d$S--X%4A^5ps&1X{^YOyD@V;+Lk?CXbtb zSfH9`@>fO=T_sIaIW-44KaiwHVR{vv+?znyc#-v*4!dwig=(5;^Gmyf!F&hSflYTX zz{Ef^C*b$9Ha&m!(-=kVY=nK$Ot~L>XGFf!D$fa#c#0iSXww}`OAgF?4AB$UMg=zc zhzCUZX46}V{gqXv@wpXgtgDH9(sR_=9bJQgT!Jpt{?~BZ-}S?iHt?Q&gl?VB!bpvi zb?YO2~XwZ6|jnHRC= z^H|PctP<1xBodq+KR7LwI?ZLj`;AE5fyJl?@wk^CIS`oM&!Ftc<*y-)23D;pwwr^WC*mL)mEd>T^|7;a{^DMtK3SIz2uIMHenv{eP#ozGL zr`r>{<XYz z?>EJ(?i1-EaP2#e>fuJJGX-q7ktr9a#awC;2#w=^V$SZDb_FLH*frUr`KX=IDJs-8 zx*%3+EYMSMZ<&L$ZRF|0(`Ug_S_q1njeQ$`3Xn&7q+f7y##XN1#}n;*H{6tSC5Ht?lmJuYm$&W;hqK6C)R`x*VXkMrbvJ#&y64bMMJ_w6)L`QuO90XN! z+U9~D@B@M-c2!Bf^lmtQ)NQs1S>xB31iPs_^#+w&+5rK*BRep1|D~{-QjGt);>zI3 z$ak~VMyc4L43mAY_wlhZ;1i;4x{`ag5uX99V4u$OL2ESvlUjzu8%Km+WIh>jK%xy3 z;lNj|=Uvm4o4+<3jFdjDJ%4W`gnv$8O1M5hO)cZr^OovQpFRvGHPV}gcot_!Ka{Pxhh*g0?)~3(#S-5U#3`!eR?27ml1Kx7^$$@rnrVO( z%eZ^PHLx^Wb)!!3j1VUmvay!#tZB#Aailj2#^**i*6wyCy7$Ptq3C^8$us2nNvF)) zNa=8$)P8SISbO!?gu3HSGx;~uGLR>X9DjgCx8^&RkBzwP%Q zDHsn?hTMs5bwlH`2YR3`%@mw{LM?!^Wh5H1P5DWwWG{iRs*jrthq2?)s&q3sux zRXCsYpK#HXoG*7T6?PQGPrt2pC6M@H_s~W!VCoOoiJf7iyph9x0Zqq@@M4fSNcgWs z(Td8Sm$UaO&PcR>*8QnC`$jP(TprDk%GpH}l2;fWvNm4)f$kL}m-N9z$HM2G?j`2| z>8ow>giyr!%YqIG<@HY&HksR(Y7Y*H?MW`TZ(n|C#x6BhqdOZd@OO1C{Psj^H(V?T zZ^9&tYUPu|O986+^EaxNE!PJwZo%4KiFRp^EotD<@!Nom)AA!EYw;_>p`MIqN6b7l z+<0#!)*fNn~5fB@a9mWzksj zF@}VA?6p5^gj|N4{!94VvCIbv@eO0Xc)4a-KOM=&7Lef~<&Ej=&66*mwpwP9QS*)U zS#$LMhbP18TJ3Q?a$bQ&Xa)^x_85tk6m+3vJ~7Y@Yv-c!UDbZo)Qmc$ zFL!xsrlG+RP$M8+PXaq6oZu9JbHRtHYYa3OnE8gIkL5E^<58X@|6I%Yc zvj673(_p@OBR>NEJXtV)Q@&8TTT!yaC z#^lcP{A^CDUh{kP3m=ors`u+2n|~NHCp@7iiZ28&p1Dc2&g$ENq*YSgl?ayAx4oUnifbNqYl^^DSe3_#yo2AC|(i z;m6gP>6$T!FKwS%PK=XowvpU?{=U2HhKlw#w?IZGmuq&2-l#FHvbmA~jRtG2X~g#V z7Bwp`1pME1F^;1#T`^CWU8x^q8ZMce?6qfysWUv2NhjhoH{Gms zE}lNn@%>DY{ha-D>zHQpWcn*P{~nl#%|;&#sZX~SB>sw+AvUT{VocC4x}BrFs-U;0 zM_XuJtbWmktjah({WAc`24;MxAI?9WP+ti7nhq7n%qlJZDJrgRnYZvbet#QunzOGZ z{C#O`MhKv?K?^uL3 zi1dT0=6=A1?m$H4!H{xW4HbEPow)P-P^tXHBr|gh@K$n|qenGEP$RXZ$@S2|gR9o8 zxeExDB75Kf4Hnj>VqY=y?Vmll29+{E1Y2q%Za1>y9>e;wj&~HpWX>3|a!8_E>4ib9iI1{$;w0`YIK?T#ROVIPC+=6pfMP;SfspXjv zn7w#-0gOzG@-@v|&nx(_V54dD8L7_l5|3~3>?11~WP=2}pof#* z1k6qy_`6}y-_V)^oHHnV#$Nuq+Gq5HqN1sJkfMEMb+v9cy5wz{7~UWJ_{XEZ*VxEt zzKk5PgVv-$EusSq@2+2vM_L4#nsk0135ohP*~d_lB)aFJ5fNS)#L?hd*;eM0Z7U#Y zXVZG02WwA`>xGnkQekh>dH(asCLF>dB5OS9mn_-5fB#yD_L|V9nWS}ZkI|&R73efu z+i!_s8dx4gRF%SfQJ7CO6%*bl6bWOkvTkl!k`ZK?4?MrTyh|O_}RVZi0Q?bp~y8=Jo5tHoRCw z(SrgRAKxJr6*f&Ls-+K^0+Pf?w@Cr7h=_<87{ExtOZ0|rOWEinOb8VI`QU+C93|~Y zsAF$cR4zoE#ie$HgoiJOpqxNSSVRON3ll?%^&Zj9#qr3lGj z-DhJDH{++l1NJr=!uMHtVs-MDGp~dDMdyvCFT3mI&zxBen+$R+k!%9&I&(H%wqMxhFT-ow$6JJeP{Fvl5mo^L66B+%{vNqh;ei ze~UB?>k5nBJOycQg$sF<4s0$%uKDw%3&YWrKQ z3DmHX+HZ-z0 z#xPGFfy!LEqo(m2k)1o2!};cXnguV;`~18Bs5xU8SO~Ijh_8}E*|cv~W@_$Qki-fV zkrcixG%%sDN+r6!Z!mln(vsHwQYQHc!-_gTZrM{cyaiy$t7?P+bK=+)Z?qRpDk8uH zyj@_C5H>taN}^xwJ+guS#!$}_2YVk9kh z`0(Z}TWGDDY(v)VKc%Rs%5X2X)+l}o+zT;pA6Ns!S5K|#8AePk)Svqim-jK~*XtQ4 z+-N_J@S%$Yi~IUjNF0Yd0}oWGVjG*}&d?k*`)n;g??E51*N_d)Z+g`2{QN|G3!X1; zGGG}Ry5VtxMfaIF<2@VvR$c;*@^3#ZVG4x^_BQFRWgonsuQPm%?GY|A>($Y4- z3^g0R7VxNMEiq>hK3(`7rqaYDr%+f^S-E|W$`p0H`N8B<_!Q!i@ICK*u@`&O!r;e2 z>~em#2TQfj7-TJ3!R8k98c)SSy)isI{Ia=YZEbBhwPRizaVOSsXiSXHlB+buE2t0` zs&{Yiwh%XJNKtzJvOpClH+N6a9_z7OCYdi&r?CCX#Wp-TVwHh7h6Uv>Y~4Mf<_K5d zbJG!RS8U-+c=6~Hx9Af$Z`_&Bkd`v!Z7xycXM@+A4-FR%R)vT)N(ERg3QX@#sILC= z8Ihw=-zsj!^5qBB)n64V*P2}R7=ITRr}R86Gm1DLnt3AXR@hERVZx4_Q|aWqXQE28 zGOeTgS<#&^F*rBA|Aj)pXV0p@>r=kwNHLM{9mwi7yxIdOX5OJWgIbw)ZO@4gU~#bu;~Nkolo<9sc}lk)jzih)sd(92zT9LQo$}lLf zurir73QRI5^%*u?7o@bjZg!nY7bnl}Q5LKwbR_oetLmS`6|Z3$jwmX`8&?~9gun=o zKqf`W;X4Lw?25-|PghLroOliMPqqj7JbIRkBP#2{)Xd%~_=|5#g0XvJfQ)6&x$g-1 z6xR9=&{#66Pxukq9eOw1B)vb$%4eY$k-su5Py#TRQM%zFicQDMD-Dj%@4KZ*{C9u% zaeupl8MwfN`b^?@ro(hAM8YP9a+&lZuAGALJdew4#Uwz^p_xA=g_`NeQZfG6WY@b( z?cTdfx%Xa2M|+H?ve>-JnO}2%e@XLzrB9rOrsjav0f_=_5YXrzeV#O{hJYl1{09!x zw{5Wwj-EJ?D^eoaR{F+(s6IPr6;GnhMopp9UzZ3yl6bNA(SQixekb|x8PcZe!RD!YIo(&EG%ttr)b-07PqNKz;)>rF-BvF9n`wi6pas6Mf)IO;+ z=#SL~-_+?}dDD10+EvyMZx)pgE7FG?WbCg+CvIk3Me2x?o49SiLCllp@WX%y@^ex2 z9l`auk47lvmVs~Pc$0lR6$Ojh@&Sk+<6s~$pGj)u@w$_W3JRYgu-IzmAfS*E28q%ehmw=q5mV|aO(RwKiv1;A;V?Pz&ZBG^bwrTDvEYIGj?^i!%55-T#@dJX z_vcr84fB>?zz#tox({;Pk~`~l;(}nfCNncc3&y};g1u3q`f!L0by?PAk?AT{){6Hx z`p4U_x24kyGRwSGUH_BJM80oXwtM#d`|U9<(gKv5w0SbN(=CbUx9*yS0Qj(Gx=zjB+Kf_MTvS zxCIkQr$Ow9?|$a({W>T}cGOOz@d5>b!-fRZX2tHNn)+(xT9l%{Lph|bB_IWY+&%a1 z-KB4TqkM@^NKmF92YQ3_*hT5>kS{Cl15VTrFf%q3AtpfNu#(L$JCqLl!=+S7;m30phHn;o> zE0$4s#gr&o7ibrq)esAVb}QZs7r1DyOxmF4xCeP+5n`j;joflXUK%-z_ovXAvZ5?D zt210V0-E!8uqu5bRv+^ORB?Z#8k!&bs;aJCT~{OFy+O{0lwv5`;EjeCs79&Z#TlH!czj}_&2zWjH&!1$Yl4BG)~JEY zBE0B2g?EG?*gkpkM4*rlrIDK2dJ^=edSmQw7Wp3oZ<~+r#7H$R8zCy+a1P(6QQ-^# ztv9i;KY0H<N)6mkn8N)0JnPFg-D>QJ>3-PGda^k>a3%vPz8CJv=U-|WL-qShl(Z+5F9o_oF z6%CUBhBo;!neWwV^VDX4kk9SwbL#lzOIDm)D?4)T z;zd8z^%~HqDTz6z{$BSMBZU;eI`z=$gJwgV;1plVRpg&<7j=EcF7nD`~5oV>S^!yPz98zw#BQ1DdhVt}u2 z{m+~g$XnR%#M%t&c*dWM)JRt`x}vAYOSemut_?2UEAWs={B<78n&Tu)?w97ss!`** z5km*PRZlG*kKs@vsS@K=Wa=OQv^3cd<`d|1N6fb^8ew0wg=|Jv&7rE$;Kb~C{HCQS zoRNDGurkG}e*@;2^wwV^TLL@4lWuOmY12a}K~l0V$1Gd1A{U~=I}F=Uxtvp04qG`} zjXG%2u|CTW^jmMjDJT9rKi=Ou*tQu2#-bGTeg!iFB!Uldqi->Nm|`=`jmwczD)?L7 znAJa@{rz2}=sFROZ-NT5DX%=FhXwhS}Kamit+Mp3!ErNZrl(?|7(YDgxK|#Tji1udCZ-f z;4HbSp2Rwv14+d(yAewS$ zrCEgaG{jaKjGpjqaGdVfN$0x^h`VRG$(Z$$g~}-)R+KVq8Tl9A}v= zpg3<74fkvSX4o=XtyO@+JKq-V`QP=o;w3zC)*q*BEmMxvFM4$s0eLe*JDOei5APWO z*5kyVpYE=ZvYW$7CEErbLGnQU@bH8TDt6%hE>_(`NfADtw|m-)hbkl^lXbSfR{B!M zMB^nVjqbo#C`sjwVlE^X_glZ;ldcMS076AoSoQyOSuLbs8p~D>(?5UlaS?pmTFH}2 zN>Azop9oZipjmuh>^=}QiK1MDK{f5eC`t^}20GLw+-r^4>Ic}iT2ym#9Da46BPzMc zz}nV>`okNwT%eYxg=Uqu5Juv~=u#LsCC?}FPwRO3pGv!`g)_y+zhE18&&j_t(rnXV?9iDrPive=st}L`c&IjU#}dR3gHSzsz-`#^)=U+y zuC5P^r-Q=r#*xUfCX9qWS^4F7p;64ccUw^fZQZtwH|c>&L_VTnUgrMp7ISm+-MGb& z-U@t&19Pb4@&m0ezU~I)_>6HvU!PHJ=9$3j3~6>GCs3{306dTX>d`vawB-e|B5zB( z097Los2?KLTs@vVC*$w>1TNlDe-s-hlVYl@(dB7Xl6mGh@Uh!f;}jZ$T_lw=$r1@> zA#z2U4yOMmjj=0~B8G;p*EV>z-M-|3T5%|kMNzv5d{i}2#kz>f?nGA$=U4ejXCVJQrcD2ASESdtbgB4NS7UuxJ1N2u?m= zpc_B7dVIZXP+kIitNX^PD}L10PUm8&N3PpZ28-g_%!tVphTtoAAq2DS5_9$8OSbf+wP`Y|H&BEaZ@z#G;8o*Wsmz|p`PU>J9P74R^!|4?}qcyOGRgte9w zthMDXUD}MKRk@Q75VfY`q+Z0V(8`4hLpamB>p`*WT}a3S2bY|nl$Mtk0r~{v zX@~Ra*W&(2qdm!uR=zbR0KXX&;^-ZZ_Fi#n^!<$?+x?OF zH4XjTx%AKmR&IwOt^1hkDZR>ifX2X??l?fqAKPx5NkR>)-tSRfrDd9-u56P}!;-r+ zQM7r{XKGKBHx+#ToVIK5<&^;D^_CU4?}?Y;`19M&g4$|vLhVC>k3OIL3{m2}dn>~p zf@O8zxO=&#!FvV&opj?;ll;v%mjpcVO_J>Mg@YWZv3yVI6l+z_o_)2$E4s`3*|T5q zPX9yre`1JSl$#Qa5)0V&`3$?goHi{q#MO&r#^C>7e()K`(Ip^A?(Mr^R`%TmCB#o8 z_wpAnvVZ#Yi8O$qeo15%NttS-*@;V6BB&AyS=Gy&ynKAQhnS@9ApdTSE8rwuBrx|? zLQ>Zzp5#GL(20%!JPjaVF{v%k#e`?Z(aTjYk?cbI9dIlo1$v?+@CVqmE*k+I{re=| z_t%d4ckk9;typA-K~6pM#InPyXtZl$Fa0gDU5HZu#(hSs`c+g_>ED*_7!v{R=kL!L zr4k|TB#eo@T1Rc|P4F9BwRWv5&Pg}Q(@&ock`l};dOZd8&SBsEd-p& z8=zktew8UvEDfRgBWh{)0`#qoR!WPAusBT?Y5_BJBZV_|$eRI}3XzV39G87>RPrh? z*HD9pYNq2zz{Y=J&mm>E2a3*^m3F}lw8n@(;&|TY&*1_cJXC&KSu`e)$qPXaF{C7( z>K7TwUujSBTFbzYg;jo(Byd)PO^w|Lw*TUajyDkP`M#$FCA!Ln(eA8(O@nMXALZYN3X7wkK(`zxolZ!kQhH^h*0Ing~bYeey?5pSDZZYE-QmD z6y*ve?tb}^+bbaeBY z;F9==RAuxL;VBx^Rt3!P2HjGr~GyYD&wsXixu3?i>D&L0Sg zj$Vl^8c$<{z_{_Wa$&pKUZMPHR6mlfT_kKuLM#h-(84I(oFg|a4a(rIdarVAjH2uuIc_gc6JJjm$;Vl1&DZbh0<(8L_IrxuhVuA_X zsMWOI;4sGXvj8g4vBx9jq(5up+n8U@znRYEbrG$@k+(_=I#u8;Z1qplT_uMcm~EH( z9i)rj7HJa~lncRKcG_IG#*SQ}Z$rYkSwNt;dlXFy*@#NJJ=y|#LhFL6dnHCUNSeJJ zLtf#EHNRVoKuOaV*REM}zaLfS#6L>Je1%`_jrNJ#l5w;I&SwvLNDK_wNed#gC$$KQ ztkRmA9TM-$yX*E8yesdP>sX7V4MJ;4VUp;-qewwB%`L5#_98oM(>8S$ys4^Z4I`a{-Bp0u(3hMu}Oupx*o%KwWyZ=wL;Y zy)fFV%rmPtZmi$TLYK+9c5U@QHs7^P)yVp9uKv<~0&%#_(Wbj>o0DpFC!4^<9aF!^ zj~*cSV3hQJsP{Rkf|m5}qbn`Ay!)f((o>IOI@Se)L8bU@^uxnq&36h@nE{#B7kauP ztUmlvR?F;j%IrAho0!-<@jJkx9m;GyNB@>IJ#-{|vp#%V-MicN_vOWl7f+KAP*0QrZ|#=vsGeA+Rf%pmd0w{NSNM(Yz!0ycfXT8;dw zT{a+#&O2kbb~YtWLq(;dborqLQnn0x6X>ax0|Ob zHBW-G1Gk;aF;aiNi(v3`mXYh>7Col#Ux5ffkFiy3EZYzG9%J*(Kaa8^`gU8dKSQvu zBv*#@Z)_BB+}AwUe`Iz!p;qn)lcLnz%paZlfHgcaGFl&#(E3c)gI`G;6=2Nd2kK2k zLye@l+AD#n)R%~ov{#GR_rPnrcK5aGBylM8zWr`IS>#ynhfBG1$(>msws3E=1sYjy zc1`xFRA{#y4^`5x?4JLrDGk8tzl5(BYf)q09?J^sYras{r&%_*|NriG;WOU90H~!I z!3OlE?J_tCaG4cRsi)!TEC#;7X(@eNI~xmy1V`4A2ag`DsgLH26x_MHRwgBMPp-ozIoQuINb^^ZUreh$yq5j=f%fYCkCpnw3*GN2w(oiTvFV z;M6!uBaA2bI|hLdmYyr(tNYaUe@X1+6$*)rJh{7V>86qf9ier=hk|Va_S7E(si$=? z1U(~#m)eqvKo#RSxVQw9%FD_C12xRv#2I6=BNG|?JR*Bt*3M3R`HB^BXkHUcEz>|h z8JMv^lJBcm;Q096K8I!u>1wF0dRltbB2ADKVv+p8vZV2M@`ca=wmcoD3=u#!Uv58! zo3!#=^w%$69``H0+Vi!W9dqH}vBb%kcbu$rH2(kULmcYILM|r6h$a2RGdfov3mkCq z-bb8LX=i8eL2BlCKa_wVt%SZga*pXJuH$0*Pj~LxWo-TO?_oAwHYnIVzTt0E@!xMx zwu~zte$t$(p7Bb&318_Dzxq0w4CT4=nDPKXRUAAm)mgKNSb0%3wC1Zg@2P)^C5At(5F{Y#sCA+qj(SqnA~S|B4y(h|NDe|RsYz-F!^`+ZJWz*hiLau_ zqCP;L%_gY%5UF`9+Whipf#QpW46FESQv<+C1#M4OLxTh8ZC-Bx-9(McUeMAgEj~f7 zfel>x3&xVpGRE6s0&()g2{pBs@-#A;1p33zN9zf?2=7v-2hy2YS?_@Q1RiKe7RN%e z+;Xl8J<9F%*b(cH1q}gWZ9z{NO@@tf&pohffbqlyK6&B{AEYRal4AwE6`W7>r$PM(yrvr7}z3e(~ey)OGa4At2QGiUdO#^AYO zhj&i$n48VFrv#m4%RFt6OY#m>ZG^jb9TI#n9g?rIhBT4HHjzR?JVb?af0~(#E2fSq zA|hfp4ihyQ#P-d&134L#IN=9`A%OCvKcdkKNoMF67vPA9!M~IzK$CRI&5r3QgQy;mGvwWcvGi=(LWk!v51}wl1)+p9GSY_S1f#kxr^% z^*zPTVu<6wi2+lv=4|q)xXsqr_fq^F6V=Dt`+f^sI=ZqiiaPdHn&;eJ!W~3MK`=ST z6{-4kp-^Ci5{ODvUj2r_p}Orygr0FbeATn02s89(V}QL8~GN<8fZsy_UNqe zHZ`Ln1R)0*rt)Hjg+2*6usP}3KPVqPx|Gm8flUGY2&*a=0NIaJEC#k%_e3A2Tv2A% zKvcwUz&>ZkIMQ(i-==(D;SE>*5#O>&K8&${{7e*MrLe~2D>A0?DYeHH!P8{=HRvN< z0hH5HgQSg^wcq{01HC{Q>t>dKBp2OHt#1xL5Z_hvcar+Qri&+@>#!P zpmbwzFUMX3)>pp1PXJ0-L|Kva9U?LCi0??hH+JycIaaX$9(#KSuiJ{`_zsBZZ4U%9 zudf$+hBpQI&Yex=*h46{G#fn0e#59b-nzeI2A)mI^`EzUMi)g~|-p#9i- z$&p*VuAk9OMK5|O1r7E)=Q-*?oy3L?m+3r}Ew8J)g>=wSA1+5iz=+BO&w_9i>^|Ne zC5c5+NuW&3)NENV4FwS2m=ppz8iIs22mV2W{b0s_HHb>~^UM&e73xDYN+Fwdy(OCl zw8|~uj$>09+kJ^Z6F4b6hKGyiLH?qM%JB31O&zP^IQoq{Y%py@RAGqSC9!1u%&3C`Pegx&p1n!Q80*bB>h;w<`Te#m?O|(YXeVog2Rfv)C*~13P3>cWbH^L(# zp7{9ijGaAu7EH|UK?BRswVG(TfFxx%gN%60Ix!mM@2>Fm=6!WfcZWzhv83qEHI{fa zM>Tr?>>qp=P2AC22iKxNglSH4`bpKSdh2aC2r0KD22$Rha=Kt+t{A0a=sTDm6H~1^ zE;p5|aG^dnJuZ23OJnmNjOO*=Fn&)hEkD05!U?gK2N-bVB zH*&RsYu}INN)%gY^x064u{wRI?j9ZU*dp#BMIH8Z>RYWhe-bHGo3~GAwLaRfDwZ+Z zLba0&lq{bAvb4<^s*@eRwlX zI*$n*#e2rNs-fRVKrU=6hu@TE(6agRWQ&rD3SE$8a2MJ7u2u_;_xwXb$H{@9q0#^Q z00u3v=~Z=}L6~eEQ#CI3q#S1=HEReLec_s=m*q&xh%wy95gFIoD)J6O}&l$PvxFQu4y< z@#CNkFLX;@lt0h*^S~iePu=uU@Y`cB>t6`#xz{+MS8($TL@Bz3bCyScNcL!I@Qhxy z_-C&@L`2ezVwL5)-KlBhJ?!r%WlY;DlB8q_E|4Ss3rcQj&Lk@JNRk;3G7-!ASZB`$ zy=gMa3G{GleAEZmGeRD9hg6XF6WR$R5K6D2oPAtl3a%)=;t_CH&t4_aB+zB7l*N$O zc4Wu3-_kg?NlIR{l0t#ZN%b4~h{vTSXNK z+F>eTf_Y1lENt1~`~>s%td#FEF5S zw2zRD=AROBJfF_zK0aFjP%ack^FN-9eHIlJ#Y-!zsNDPbQL^#|?)vok^E<}S7=A>m z0Myl@9$YdW4>*VN=vy zZuIo_l4E3aI)jus<&SuBLbwLv0&+O2#YW@l-1*Er`H;g)gqljI<&as;25 zv{+}`WM&UlB*w(Z)duALIA!Io|?Lddoq+AMYJl?8;P*VGSYl-%VNHqVl}w&|aJnu|~}XiB=Q8(x4iLKfD?OzvRe^sb@xK z@JVf=&6%u68~2AN>4J}GGl(6Ve=F$C8*XGtG*^rGfAzfn#uH$3&ry%Z&GYrdxod(u~a+ADnOy( z%=?pU(>H7U+{E2S(ziRcy*FWgj0)=0;8DDYzaKYlkRqO=1lq>NE;%jxL_wht)49;S zd5tP5_TIB*|DjiZH;7ukBt>!@d9hc&6BrXFg-)}Roywk!==3I;#-0C|){*qI>KfuJ z;41e{OdCY1Cw_ZtXlAK#M%;fTLWE9MW@b~jpOn3$TDLL4OM&JSA?0W`xKS}uV|9EZ zVLw@Jx7Q{BC9aLNB#yp=ypV~3vcpcQsfWO={+cvC&{(L=&fW7H1u+VhTU8G;k#PdR z&=p$AY{>^GR~{qXwJCzB`ultvXA^3TW}~-jbT*9UJXd(-`@aqy&6n=^ZG?09Y|{aBZK4Vm$7v5gtKkZWyYh7s&BUHDDMDhd)E!v#O#C<|oBPD~ zfn);cPt@NU%yB~cIVVE3vDbhzp8#$2JLhBQ@LNthqG5$8YY~Y=Mp84+;TfOkvB8f8ANS=FVL^ z>5ixc4f#*GF7y%luXqerm`+`Sz#r7FzLpT~1$mGw=3=QV|DiW&o2j{DicYFO?-ekJ z?WbJRK5S!?QZ3V5Q`0r(2invovo}gyIF;jY34Yqr#t8?>k;mwy9lH z$HFKaE;=dcJ@sxw3-m-@4@!-H(UhRyqEkU{WKkWwCOnZ4NCwa^eI5O z`y)n6#%VvST$q)7wljX89F1{$RDMOmWb_94nbwh2RSZ1pQB_w+mpafxF%c7KQL2K0 z&#pn#1Uwo6DU}+)$IYgx!5ZVgAR9U3uwJv<&GD+>IDiIA-zfzQGFLjrJ06l2X3KPk zlLkdSq5avjzLA|S^7cLNKUW7zWhVz_AzPu*6j)tZ+isONu8O~?VCttvA}5EGAtvJ~ zKVI#+5?bnpkzbpOju0g+=rtW2SCg*@+cLG8tlfo=Cw$O}$?+32|J*M&cI=FVVtfM% z@7G}MTQ``|4}AQifROom0RWN&>U427Qg@#ZP<)Hy`u@>iaWXH=nH>ZM255GfzXEWz zduULaHN89LB`HvTS=d&YK3IDr8l}RGAZ9~Abzb7F#|X-o3pDS>TS!OunS7TwKE(** z4;bDTQf@&a&<6G>yLEmYg=AE5glhuM42THIJ&($PUkIb%#|dr>f_oN%N$?mtth_AC zD4?_HKzwYSctw{I8E}Kn+IKxFHf(n_g=2cOqqFnD^raANYoe2Bp$G|xIG8t3Zsoc% zJ*ZawNZkjq>yQZ%Vru~YBBytletE|(O|1k_O`(uSL_4dPXt0TCsMF&G-_;TtrSoKL z%N{6W7Dxz|g=zIJ zNQ{15`7Sp3Cqx&md+ISmj(PsuFnTbdWqN1)!YhFRDlkOd0G<##czKz}w_}+7ZCk65 z-f{PWW+sR>7*X`8LLXI2w4CPeHKr8P({E!`34It>@(hM$69-B`~O( zBP_&XNGr~MoHLo}5kw$h^A>zQKm*!Zh=aM1Q{a^PrSVUvGiE_?6mr(k2d~m23+S`- zpAR~lJanlDOFv;Z!%;VToCt?7l}iXO$fHBm$-+JZDN+^ht*@H*>ZCTnR-ur-wDQ}t z-}^QdD9bF~)Fdb)!v)2KRECbHRmNCRHqg+AuBoVhHHPqQZF^xRbsK^^(_1OfQ6dr! z6$dsfZVyH3g)`&&@{~o6sWSLytDyP{1)LijhsvOu&dq%>PI7uwAKKtnW2=2=R(Tk| zNEKZ%xzqUSTwi}csQYk2fK63WKdS>_6lz3=;4y<1##jyn2D!g$!#_kQ-!Qp) zH}(_s^{mx5E{ba2Ulw`Hpx+M)VbB#yPK*;2uj_@RBiGfVR1_v!(vr#Aakyp^LjdeVfS!f7XC&+vibpN?D=NRX7 zPc=rZ{(Ubd3I2Y(>D@?sBaPXbhSdOr7yIHg&YyQf_$_Jwg;9hjHTyPXXpQornF65Y zGmd#03d{DB*L_DnBemIt$1<}D`L#fEu6}}T;;SpFge|n_#&do(GIFI2@HVs`^Hc0 z=x)x+iZt2=W#X|&c{E<2Bqax$^wNtEFDckksm7AT*MlRG8T{p++>A^Ev}uG-F(0zwPYU){VAnqi3&IhH|s3`fhl zJy##gH?Q|--b?WlX0*-KkeR`oQEJ9E5xqGfRO}}eq8^|t(oEjZV=h11|-Bpn5q5iFV$+ODM!V7a+hwk)cgEpZVs9B80LqhNsx@-mU~(R=@?^ zU0W6skkkG(gc6CS0V9QQ-+-X^SYp!si8IfxT}ADwjZkJhj43$eFA!w7gI54+pmSOviR%#8?jOAwqyf36;W-{coRfH-Az0 z<4w}l(IGC1wdd8K-mzWb=E`Fw9;nDt>$C5&JvcmeEAh;b0vwx&ij3sqft^9nMK9#^ zv-*A>2_q5XEacp23VqJX2$h}sNoJ9OXKZmAkrShH*UeF7p96H6V+Ff@Ct2Q{aDb2v z1mAe0lc6RA(d}@OwrG9a1V19WVzAq$9Gx=Za z#DIb4?Y#(MYY))D!HY%rJC4=IiK1u6S{$IxlDs~s0Tu`Gu7A95FH=u=KNIivRCnJx z`=HX4xHh?4ZeJ^iFJNd>0_<<%WuKna3bfjw7zjs?%R6CjK{RgV*9Z0Gh7Xu1QpMZ9QK4_f_*3wd{d;#ru=ThQGEdU)X2LMr33a3L4=fa81St&n@agv-SEt zdyrF_DWd5S$Il~QMYnmgwf$b6wPB$d93 z!mp9`;Z!>c@|{grAOb+tqo}~j^S>TI{KqG8Txi*x=lrqZqb}2?ojZ4a!Z^au&Z>Wc zN@=rV(l0*R6{+XP3F8ZbO*47N9z3wlMv?t|GT%qbtsi~@l_y!TDxN%vTMDg8q2bid zb7;F8Kt?r`jtP~G&l2fhot=ChE{^K6VMb1{%~I$D^sogEIW>cQuA#v%x8l4OYh(d< zgEtsYKb@5^1g-T_ANJ{=uN4c|xBviTTrN{1FxH>5Apr4=7y5&RpLatO7|9~9#30U> z{>%W_dd53kOJeU9E0bjm-n5iL+#8_r6BqcCn`H_)6Vay=P-g5R%0`gpj)mYUiA@F7 z!B$#OM)h7|-`A9QVz^U^$}cZ>+Sc_wg$0Vikr1fW6^?OCpS}Rm7d5)|*N)hCPk#vJ5-9wtG~<})mGt#v zYWG!>O#Ve_NQSeDzuJ^D`Rtn0(>#eD=%BYISC56DbJbupQO>L1`+ww2rRvWNVj34w zUGt|G;cj=M>585o+DvFLxtn`EeM%^G=M{8xxXFYf6f0;bz<<$Bxr5xl1+$+8;8UVg zCeTDrf#GUhbedtJIv64kzVBa1<__Js5#KrqSJpKL{x;3fhC2*mCvXoJBIy^zL_v=} zUiR&P0EYB{hc;|U#~7ro#&fhj0Z!t_U4oJX5HYjBV2AAh#6al`ZwK#Jw=vCyIELSJLNlh-`** zQh%{uYZzB_A{`HaF4AmJkupU=;l{uW?^Hwcq>WA&()APPjZw!)cL;)6%v~{KMeYM*Rc9_)L#x&HbQ@G(ii7&j zorLNniGSncXMk~wt_Oc^ba)9@n$`f=Fq%yn*-xuE!dyVEiW!blphdJ#(?$h4?L|7y z+@(La@+`M;uAZ1!_)7!HYq>-Uw8;?ib>W=`do~ckGVy3gM_M@r1PAijZxpS|G7YR5 zOX6~L?0ZoVxF)#o_^fuKdPa|HnW0LrYuP?_5&ee-8^xP+|0fPfNbhZCM5o~NbBOx# z3+1z*`KWbscnL%g2vq>ngTiIn%O%ij0pGjprcmnYe@*J7X!U)UJ}qWgFT5D7HHFT5 zn5j!mT@T(Rjx!=(5~T}XneKAe^{V4) z5tW4RU<0%)-{EvR51~W$c{%cbkv8TOwZFaDRORTMhChERt19ico(tJ@_7LuyRZYj`1D)X|0(uxgF>HMKeMx=5LKC_sn zrVDxm+RJ+c>Lo@BTY0>^>^L+0s$M*8N|K3q;PqNCG+a~M2xgsIKh{n%an@9@nybSA z@0++`$s0a`KBL|E3+Tyz>b}`6mf7Qa97LrVPUxU#8B)fnKjMms1+xN;7>{BAYkk>C zrQ`3Y5qy)G-TW$1=c{IKZ`HKEATlJTT%1Ml+!e7T&sCa$N-K6`r>-^1=wP zf^<}GVnxI|>YAn_ZfS!dxBvSgI+DJT4-My$13KM1=M31n^i@`~v-1~f>j>{{Nj~5V znLQGDBw<;sz*HY0X4&I21--bj$x&DpVce3qPF=1hYS+ry^72SQAYslxfUl^qCma%R z11&ts)%X>27x`9jpKAzchHP7$#>J1h#_v~WXf}6OKZCL}5mRpS-{*TODa*cE0Mcnb z?Z*l(WzEgn=bOVu;=;XiRv02KK3l@lCG8OCwJP`GnM#DMc6fIST}8} zcY@18hDm})%etEOV=~X^`goUYKIRhQIGx^{%3oNXEyUe(6Xnd|qPf}V(XTwHGO*_# zqo;7|R`JTO^J=kL*W%9Sac>5cqbcRHovH>4REzs}w*!Cen##ZBXL1nR5ICh<(p`pal`m(j?T;^L5osxtiqVtp- z+z1{eMzcb#64(B#^d~(=4q#S-LsM^=*_x$FkfF7?X5bzvId7^*i~1Up-}~`Bh}%p1 zfH<-w~`Y=slO7B=wpnpbQ#CS|pSPn5;kHNV|Y~)A#;dDg=3!!;Q++z?Y zPWnywsWQDj>Js!!lrBtE|Jl^or{wstKY18_keYqClD*4UmR>9A^`0-KFt~u$wq_-2 zgW$#$=V6!n3HZ{Q!ri__@eZcbuhpOL$rcl8e@)M2P;EKkzNaO0*MR_ooa=HH4&h;8 z2QWqwVB4{tC=bm>uOv?>F$c}Ryp&Tgw$s$BdOmA}P*uO=66p}uxzLEoY22waIIOS# zzNVzOc#O0Z7e2b^pw&#|4uAdP&np$R?oI; z&0_X;0fPM9S9|wk(ea8}_O*Q16dn5YM!U-k1l!~W&PP>MJJA^0!o7T^&Q1RCZveQ>N}DB-K{nHjJeiW6vkK<56rG4^A@Y((eZeQzs1}S=yHue-ir4moekb0*S-%}p)?<-c zJvC1dcf0q!Xit}!t8K1)cI+g(CjkqD!zCr8nPDw+j;EQ!We23MCnRsdRB#<6hO;3r zIy_n(LYz@YYWW13oC%q@E~CKM7B&b1y;7-_w_RG@6NOYP;ZUDC#0#l-5! zZKcMsYCPi$V6Z*ux0aJxeABlvGGpGncvpsJz*9Q8MJXnn+TCj`m;z30c;M>DRo5Ym zLuE^%}Vr};oM=MqCaheK8G16eJ9f1J9m==W=fUX`1<$401zhR+DSwZt&hn2bsZsI z2!{`EktDk8udCQ_sk2A$ea_A25<@ zDtMDGDVX{t5Vqr6li49iYVI z;gGazz12G!7Q2kh^1AO&c#K0?v#TM4l~q|=-urZFxWI5W`i$!|u>>D2IhyeJ7_D91 zn}{VWt4jqz3U`LidxO&)75wJeTNnuZa{HKbRgfQ{uncpMEgYKu75OWAlcA=f&hXiu zjaEqh`NHyj1Q-%RSbi{p4cYLJ_JhX(V!ANRc!^W6#saRtPFevMGS&Os-Q2`iLP%=n z*Lz=+Pap=}0ij!&?crJ23Srd1N%cRq!+hrPf^ zWESV}Ll_t3&tmTjaLNAYb^0n7=j7DsFoqBT(Vac7{mgyYGOqu>Ds{h?=T+K?- zu{TOeJnB&c+*?k=p|dYhFmvWpx8$`|#OE+%@NKm0h94@}34=Wrq6LF1RGC^R*O#lD zUEcL1>5{SWqr%^HPrY5;X4)`Skd5JQB3klI6SFZ}NEtAhZbw#B#WGw0DKtezC(^z? zu3Mj`a~(=4UV5kK)eAbHBOm+LhR2F^P|m>Ml~H6G{9jfqZc@o^r5!spaLyvFyW6%MdtDChI#y3D>kKnW)ZBXu;+*DtKlyK(C1#Vxu3tb`#1Gn!T+P`J>aSC|M&6J)Ye_e zrY$AP-n2+XvMY|t%F5mjn#wF?WQ1g|6xmMYMkOPhWRoI0dmQ6`y{r54{r%75{&e3T z9p}8?@7L?~d|uCWT`Q1kN4p-GmV2nk^fUTd(TB_-w#qnQ8`S9zj#L3VS@F5ULxHf0^F1bHE zs-*8*ZLpX*3o~(J$2U{wXqkVnXIK9>WWYd?@3Z#96QKkHfQzy;KCC3jzRNt23LSiF zOqu~f%U#GUUG)TcG%oYlXfe{(gI(VlxZG=hcT2-*5GzaCA?3Cf4DApa?6c2+TSwkC zD>^)UHOfFz7za_naVeXtiagS0N_&GMIaprv)Q5fbJUnE{dSP7?1#nb2YcCs7dmD6^ zMYgIOz$VI7`7|z)(7VZ=9W#>fX|$9x26yzO?HWB+i0@F)*a-@AdGjfM$jURdI3o@7 zj4CRoUmkJhm1br>Ht5U-(iDUP{b{M9jjL8^;Hy5l=frWuz=;W+VuB8qk6+=WweH@6Yp%V7Yl8{Qp z{47%@#>-O|3dNBDGV}bk*J`c{+puk_bXdNb5nBVwyP7%N#(cW_r)YCsP`5hq;4;jD zA%C^npcnv4k6HGKHdSn%U#)gEUD*B1nHYuKHr?S#rKT$2VZ-f}A!SFDb{n;wba z6e$`u>spudQkQlGzbNi9wg=>dAdtC<{jix)X68Z5fWs#pDJY?&^co9LwxW^ao7D_Z zf1joP{Kkztcl8@%0D9@Ime0ifb-|wJrwbLh{)n9ULY%o zp(+M2oSe;2;yzpbUg!HZzZK$gUQf}OfnrowE|ne?b{n%Li<$7%H(obp zpi5Csh<9R>1vZDX{YHDWT7||H<{DBp?YYlEagR&6*(YS|;{m1OEXOLd3yzW1>l%LD z{Uf@1z6L7KC-sAE=hMW=C{dR->PH{L2rA!9f9G4lviokZr`vU>2WL)vw`#oy>EALL zi!2>$T-5fGxs;iF`42YQsCD`lG_PPm{hg?l^F}lf>Bo?r8%act`rbWj_}h(FR-k!6V@xWm{D;>|J8K zv~=u4{wousmeU<#;4HW4@4$}(mfbnd9T*%8P3J7nCE0&e`)4+9{>OoW{^6Hik>TNN zMH6*rWS6WR8fRXCptmK@`@%0I)V=sb?ujxeLLXAff05% zq=HYkBM`1Y((7xsi|<^{>+JX2-_LY~GeOrN*#BWuW}C-G?4|s)%3mH&sjVjRT!xRRHjl2>`bEiHUaw02R|%RHmxrWAh|2msl7Q3D zuDyEMJxXsP*Q6_{bq>S#_@SWqm zyoN*^k}cmZai>9ujqi2*AmhKV0%3gPZ;{s^)X?R0=|4seYg_J(pOEz$zdZ`&^b4x; zD6W^Stxms`D`6xK!HBPH;#r`!*#nvdo0r}dw2usgvcwuy=eBxtEva+7|EU%!isK zgeD#sxt;$j#K)yq-|W=>b|O??fvAZyZigOC&zMtCTjBJ*vxni<^*rPdo6lT!JD<21 z@Cr+WNjZAjM&$hwI^Cmx8xAKR$*)^OQLa@}o!oSarIp-7JW)cD-LotHivW$wY%GBr znc8xc5%k)(R&<_;+9tMMKy56lCCFTLGM~Wj;i9+`n z116*w9dpg~PF>-H#Uqs0VWo)LXh%Pqg6?IJq#zXDGUi$y)R6aC)LHexopytg7?r(0iyk0-U| z)s|)QN=j?-VDlp@*$}LneVGmbo7qWKpdVL;j^#CZs6FL9Ejw}W+|;(=uXj8b$35ruWQe)-%Tp&Z zkaSn1l&&RIHeJY&>rU%q98M#Eqz0TT!XKXf7AX5H{|;DHLsX&bUm*bzJS?fViZdMPq^?|NBVZACze zI=w=T6(P?pV~gYMgfdkkLVQpu4_!EnJW`6e83cOF62DZjW5(RP!$lDyfE&N&eV(;L z3+{tg781by_7{j8Fs$wOscx~-ZFYJNNU5r>J}`QF#%tM%-#@d$j&Yla@lNbu zL=+^hc7fKnEDyQ@H7$qXwh~KZ{Q(I=d&??#(7F4%{?@n%+eRR@8<1ve^_Y_G^KQG5 zxNsO57QA)nH8&_V3oNK8YVU8$@nwwJ>|g*crk9j+LVJjQz=tE2ZF40S^8;`SkDB-}D1=2saZUC7`C*$7HF}NGQnEj{>n=IZTS;+vY!IwI2^^Ya04?Y0DD!Q-M6?Jz z9`_QvN&$BnDb=|DV{48ZV}`q@=9E*ifkJn{<1le@c7*->Zs?69I_50dF?&5mBY)b) znke&s(Pv2{Mr=b4DdK}I&K?r5;)KsRo5g1;+KYaOFbM(1%5f*S66Y((6D2~#XR=+$Ftwhd!Abzqkme1m9X_GlXk($Ye`y}7kSYxVQ>Q~ z)r4}!FNj-17VV^H1#UC|O36dl&7sV(O6a?NjK_h4MtbzmK2BnmrP&|0{t{Sv7q%ZM z3HBtJXmz^jfrcG;)Ygc``Qls&XPYSqxJ0Y!MHx>E5U-QzX z-2r#J6`h0qE4i)_YGUpSle|diHrW6q!u1xriZ?CR&ftn_;VV&Zn?WYf@_+z}&QQlV z{zo{2=pG?(Tzw5viZ-0l+){T52@Dk@nposD?ZPwK*S2N$*%J=|yrIBi_t6P+EVHp2 z@I$<91>MIV0fgM5^Tyov=HppsUFg3BxbU;zd1A-xrJ~f1z0Gb_hz)Hnh`B-6n>!|l z(RExZAUhzVosnc~8?w{RCZvDi*K6sR$r9&g6~skv&_t@z*!o9wq&BrfW{6UOGDJy+ zO6w<Cr7MgE%4=WJU_c`O(`~Gf-oTK#I__{!&p~jETzb6YPcSxdtFFQZXI3yIB?f zlUPW2=E3kAUuyJDy>H_R?zNPgP&i@u{F_PUp#|0E_Mx?dkJ`ImTTiy5v{5%DMb;K* z9)_eOp$Z(eXk|Jw&GXM}1x}M8ofu~FQ<%Q6gD6?NpC%N+hB(Wln3x${9Io8uyQFA> zDIlg6vnJ0K_L779+wb3=v4Ljno5UZ{u8;RtJWX%3;Zyi62&ZLeoMW=&kh}k!wuZXi zp~`n^jz87QY#yP160q>$`*10fYR_jEcRm{J&I+s~YSue;>>!~F@!2=$RJl{P%n3K} z@$t5%Q4CNSrswL9pbJRy&@F=9&4}l8z?nA{r#TwQ z;K}WCDJQDX=LlwMpeD$J5Pvl(krq&}j=sYUkS0o$G;j#rn9HUoL(|EQP}E80uD`iA5xYHw&ntIayIGX$MkL+4`+BIR|7mDmCJF*G#iFOhGrELzb7sdP) z;^xSltV!%>m9?3*O!h8zw`$1}y7{O$Uv4PffRGXw>&?!gNR5YcR{GrYvuHGVF4W(r z%&nc`o%m>$%|Ahz*a9zP8e2JK-Q-)EFQxNGjlNNE8@_KqY0J^m)@!|SPnVwSNf-0f zzClC>wc*LClGJUS3-|i-k&39YS|(9c(!Ag5ONA z3XOILHj?B;PMvZs_GaWB&Yl~>AcF;3HTf$lpR?W{lxxxKO$YDq7HF5h$U5h*y5apY z423M#TuS|g!L6SRQ;L~nna|^5)KRBk?8)~P8H>MGENVC{_fB2RY_aQBkRCpIA6hBt zMCweZsqW1Rr-RL@qr$<(R0@%sc)5~9#3Km67c(w;n~n6hKb79^Uffj*Wt9KD40)=4 za&Fx^An5$=$o?21rCV3A^a{?-w`+Q&%L5#Wt(#CS8f{1UZ0Ztwws-NOygA9|Aes#b zyq~X_LrQ`aV}n?vJYuLs`E;ViS_HTQvPOsPld{x>dYvU1!K5Se@9S=NbH$+QM}e$D zP!|Angv`p!65qOTr;b;c1i}L9#`)7OJGo~tFn9FAJT{U!FVO5yy8Yn zqSyYjL-KBI*0Hi3^UV4lpPy8HHXdlC4ooz^jEo$R-I4f8YxFBU}yef9T3))*` zKHkM?BXs$8*x78K8tL$cEMDbH)@A7z9JhR{*a?r0riCv%n-bd~Nq}hb;i{NrZxE;0 z0H&&B25un;)n{ZD=4J^+nl@xaCEp&WGM+qB$uvlR{;oq4^@C^pX5Y6SVG-^m6Dl`9 z3k-8EN;w3_zMko?6aariZkkrA#T{N0Hc=OXxYyke4WwcWFaA}U`E#Ss_@4$h?7MZmImA0bRRn`E-jf1Wz#QI z2>S25C{nAk|CptbSB&X1mjhJ|+q1_sOb22GTmIEsD`y&E{3AAdtoG;?p>fr)_`Hkv zd@8NmmJeAqLjIXE~|{5s{tGglQz`0ydj6G$*0prnbQt$dmO69UDKVj zK2KBmksh+Rh|JsUz74$x}A^w;0vSqSMk-MMfIbUj@p`qmw*Iuli?P7O5 z?v8EBpyMIExto1OrUzPkji&}Lh_H1hJ990U-@p02=^n8J`$v0@NsvfyN{5nfIaF!7 z-)Hr5+T?wlU99KYbcP;!m(+B0!dw%c@0TpNb^b~G4mo{&u6ebf2I>+i6X9W|2Tf;+ zR$f+2z5MY-XlUrblauQ)T@sIxkPp{$QxS-xb*{eiZSHD^*jGhGt=q3eZPq+Gg zAX0Jb|B@c@ZR-~MfzC@SWN&=Tsi6t{Dy%u_aD#tr&dut@_z%lyF^jL4i-yUf(qW6K zgN_S!Epq;oZ*N&`ocnNP?_M_zHQSe0%|VxA-|bIvf3254#$@2~9gJo^K7vm-Pu2Mf zm{qs(xR*38M6R2V7}o8j`cioU(e`*y$~<@zhjy@w!UF96R%aJXpE7g_|zCT|~KpKJ}iqm%btdXg{h9r@$UU(T<r)IYoL z4!o+NUi)RE(KK}W)V*nEc|+%!J9wZinL{ZlXKA4%QX=DW3ICtvkFT4x^z2$NsEU|d z^zM%3+C(C)$*L4E&y-SQNX^f=kmS8Q=KcZRrnnno9fLNb*FOZ61wEgb=yP?xSl~LX za^yABNYlXN)eL%>qQ0f>Q_n?-ZY7B`p@*ht<2@4Vli%iU7<28aom|{|X8XZ?36I*{ zg(^nMe!hFR+`td-(uT}R@zjv(a#{mJ;!5|Fhjdwn@q%sQ*SbcEZ}e1l$hx&uT&BxW zjn2fMi!hcvcXOkkAE!|H{$APX`z{_WC3#oxWVvI1Owx_1i%X3`dwoUdSycK4Gk2F? zXX;Bl^v<(3sF!Z%53_PTUH{oC&?P&$X1un;#)i3HJ|J^FJ;<+H!flFgBdefB4fRE~ z!ZJ2}O6cRkhar4lK5CQCPsx$fN7Xy}Kla)8-_N%zlFg|U5mmCXUb&q_@**jnII5A; zZ1Q(8NwqIpQ%hH>uPvM0@ly4^pI-ykbF#C+I~)B2-8+pl=!b)d?Xh>yzve$aY95tg zsqV9%EB5Siz4z2!@RLnSmZ*^D^p6~o%!85z$J0CXx1w3F^ln=Mh?`h~T@DybTj&Y;|HQv3_{`Qxz79We%@`=sZO8MycL)e~Fta|M=IswArRy`+ME z!V^!5e7q6EWb<6^1g){7p;(+zdXo9TXhAAdeV1U4nTtn~_llUco zBadTlaV84+mOaDtIa_b3`Z-xvjmzH+9U}Fu*!);*+*BBpmF%nzQt1g}-PZ>=mz!)#0H_kXzlx$(fFZ z-#%2u6=uw;!W*-}1`zX3n?#DN_L&|O34uxC?C<)$qFzF+IZ;yVjE(E=(q(R_zSQA6 zed?^nv8&Ixo`z%`DAYR>T5`EHG&OBr;gv~E9lKS}%E18?j*ogDZ#|eAc_5`D^nSo_ zZReG+e%Gn9N79+MZx{Z_0xcKi>A`s!)jAn#S{RtjjawDXJwNRA_c%-HJZs(WvEs+J zM*fs^u7VLR&V?x{B{kF8ol0sSN>(`hWRf?S8E)BaX5(=%%Hhj}otzalDv68qj-~LY zXE()|`t(`n@9FcYY*ydMAYYpxGyfusgUcfpoi@tMNAzxlsot^GJ$KXg3H@Jb^*7Jv z%6oo$uD-*;tYAxz25IL+@6NPyWib z&s=a{x^=wn!}{2)C`s2MGJAWwlxxGOXeP^fR?$Jx-QwNt6?I0H=DIhGzCHhtkk1dZ z5f-N6(J>BF%U0Q4?mx|L;X3Xw=J(@aoC0g5=hMS)#Tn<@EHE$0CU(8(e4FWTq{Oln z5`|7ff_iVbq^9@-MOV$vipVlHW>DHj<_ox)L;HJG_owU5`k0c()~{2pD0prd*dsG~ zJ6W{%-{XPL-OJN+VeIiB`KaenY{*9Wxs*({k^u?_n2f$6lS7d!Fy7C`cQ> z%awxV!vTW5T`#*NiZijVvt07`@?V2@OPRm!ByQ#@kQs}90m9%p4!4@N#x<8vF1_g& z3$mY|Vf4uL9G&eIVXH(p!900|D@KctNownRPHIMFoG%`)ZkcOTWdd|vOO$&t7Tg-ADrtin9$#vsy@`7lUhCpZ9BzG(H;DTXL(? zj56zB^;$jIml)k2A4;ynV2;T3R`I=G#wtzZ7H8r-rVBf0F47p^YAkSI560v^)QiFw zyIHZVo8jWs-^aMJC`!5GrmGOBQux{TX7EATQW`o9H#CG2Zg%?gugF7L2BpM#i2c4$7_qJ zxnh=*@dCm%Q9>0uT!My46&&UN+SiRS352KoX!zczPiz<0Km^d-spw!wrCDi^b<`z};+6IPkD`TZGz;)Xr+<@j%FSCE_@30cvm z-kjCF@W3C`;QctFW^DyI9bU>X?zEHtbd}FJb$AR+l0yo`I-E;KinE|t#8NrBb}hLD z#mxri{ViW~I~b3rk@MrT@;Wj%^pMqDT=LVZ!d;>Y)AREc^f3aZZr1$EYr1gV*tqhQ zNZ%H6>oq#p*7^^<66!{|5yPV*2~T->%cc1*ywqV0G=$C9GRmz)vYv5%nHQV5RKp6A zm-TBCND5{r>$v!-5Z;yX&t(>|Fybe8zpqf?M2zilL?dZS2&a|f}9RLDv3-PpW>FWW5+tiGD1G3Ho~~91N2E4W%13fne2+mx-NQu3)C`q!@Y z)iL}1`1NZ&CB!MS%7w)z@dhmsJVYlI6b|e>@_D2xD&){5wn|{}qmx4dr2SDv7kc== zdJMc<80msDDcoL%_fghQFT1$SNqDw0IDR<=C+z#r=szMY=26hG*vr_3IO@^bPW1vbnZusW*7sQ-rxU-T8K>wkTy+vofBEY8R=Ou14%7Eub~=t; zDy|1!@(v#0Kk5@_}2yt_Y_h#F4xxHIX>q-vY_4ovpLiW|#Kg=NVCJ1#6_HF0%41siYtVxMR?*^EF!!yA{Aie5DouP2EwEDSqQ z>ks9&Ue$^BBEnH;e5q6GH1nJ5s{qF6^IDfy#MZk+didTsvzhCo;jOI zco&o@O#JC~)L%^+)A`A+{mF^6LMNX+gs&t36PKkfb7lOaE1^$i)ZM3gt;NpH?U6xw+T)MwgGi+*Y_qrBKi%|YsP+;1}*C%CBHff zr$2n;MC*Tj35D)Q*Upfy2p7qm9W3Jv(_*e=Y@wL13ZJTU7_kLH zghv-_b9?;Urn`Kg%e0S7zgT#^cy)*1d*?uEcV7ldPWS=kjpOd?)V806*m_5_tnO|s ztm@9`@AVsS&ZG9Q)d18xfD!G`g_mE$h*9eUvOC`n$msPoepJiomcf3gFWuI5NbhxP z?{NVJ#u7~{t7gMbQK`|uyZib0J`K1OJGRa3v$5JtQdu>hv(jtF($P7SbYb7;Kd>PjNX%QWX&>DO8=Xa<>H16P`>@#kaR&#q7)=CQs>HmZNimaYE2 zFM)585P7eqExL&yZq1T*9MqvQtF@&L+K;rC4%~6j*4)zQ@~em4YGwm5;jG>QlPbOZ zH^|b=Vml+XlyYJ{hYYJsP`Z%Qt_wqqvx`VJCd?al}&^l_7`Hr|CQ1I(Jub5a%R; z{!a!&on~XP8{;AUl3kaXwli!0G{ic~(p!rp8oS${N+ELCXjK%8Xw#w~TB}&_)8D|B zJ+rvj^iiYy{X!T5Jl`UPom^J$EJTZ`yU+F2bBVSoPB}`0KQpB%aG?#Fyn4ChbDyHN zPRKlidkPmfwNQ$6E6)((wW|KF0tn67=*G+}c-3v>IQhxH047U?6E#;=l>yq29- zQTYM~TX{7r>0ES)S<}n-JtNa+P_WCkZyH<8+KufkVpC^01atAarzd>)7RC$unyaIj zxT955y+dTA59t|Hw8t~b&8aTU{oIZGg*>2Af#C4zvhuRoVu7A5TkDnRauPAfk~A_$ zduD)I8vFjNjJn$6$X43VQc3{-bvGN;ZgvC=lOuBS$R)8*&ZxN`yLs`oMKZ7xM}s(; zgL^(vjv+iaPoEw^d*hRJ>(`yNb3Pu(esi4=wEl?8U{s-;x|G|~53J+bGIZlh#^S~7 zYq79_W*mDilzcmBBpsBDGy@QIrFeS=1*5H>apQD%>Orq;2lEV@v*2R3b-)QR$N(bm z`q{batocro-*~&=^d_1Oi6lP0GDW`2THLJm(t@>Q6xYss`R6{>haGuJ z(*jC77qR2tn2cZ1@~wQ!wrG?^pf`44rh#Gl$V|#|^+y!PstXkV%sFxMmP~Vt zw;bv?!?*uEK@EN|Kjfy@dj$3&tSSLsdQvu!WSt=S2k_ocJsnU83d;r1%57Y1(tr=_ z{DdS!ZA?pn+{5H74q5lF+ird>lF*)R6{;Snzgr?bV-y!Sa@HhE=kZh7HXZ%E%O$`; z3U{+O_79FB!u;a8VOaBeQU1~m2EQMKkEX|3`QmWlsLOI{3BF&q%v7 z9w+?Wwj1yGRT9 zIos`f>P^JPA;_W3rdw-7ydcL+i$_%}bo5pVP+JEU|7VqPt+&JDhxMSMRHnJNqWGdR zs8cfvhYMDxYW=ZXe8c+k9pIHlI%D`1ln65!XeK~QM2bqOjw(C%-bB;CUsJKjhEa_0 zBjzW!^cj*2k&Zkjbl)ToKoxR=G;H4Fmt*V%vZ~Nu$KlviAkvsE>-;NSHt-d(i3n$1 z9qrF<=N=-F`zOf%XU+u~0Q)O5cM|#!4#!5xV{6aKF!okIsjpRU-`~1I2wLWoO&M^% zea;L-{^@rhDN9>%1br*le^C~@Y1-_Scx+nbyPquHI^|f%!K0$e$&h%bW2wf<;rh67 zEZ1rh>Dn%jB{}NG$<6<*A5C>91Ia8lEA>0xO!5+{RECe?L$(ijwfsg$O~=AypZjtk zDkvYrthoE@O7}gxFY6z=^jbJXPrq~87+#+%^Ukw(3JxVbuab3UKOUT{t9i`8EgXVS zO(IS$z+Uijca4zmc2*+O!N^i>!z$&NmNiHoDvyv-3=j@MvO31|Iz6#ZXyFcj%0b?y z?rG^XF2Ql_;$Dp`$DxavnNl2^HrmtKcPY^!pOtV%sSb%3%5}Hjo*+Du+2Qjm6CWfn zklw_`S-W=BXQ<&k5(4epei7mBf3GB~mA0gq?Yf$_twts$1JL8kn67yyiOjAEGRG=D z2;S1B;j0L15dqwaj3h&w33sl%4Z4q6D`?bnnRr<=$I{WIaBqpPuPLRMt&Ftq}Z;~LDI|M=G~>z|lH)H+?mu5n@3-!yt*RY*#N zj9bX!;#Z5n#wGljeqlU3Yq59jR(7Lkco_o3SIne%(~*}GnF%>s?$VxRpr~ha(mu}b zpZ^{w{vG**Fz$y$w1PSQ9C!kR`~Ud((W>E3ASr@0n4YVW8xaKJrxVTLjko%(VBnRH zMi80rBRk3W*NzrU1Wgs}-=%B*r6>W8J-lRoSo!Q(9>NLfMB_U7ebzKqiEUI(4LQ^R zg#RVYMHx7`mhj2fTb<)9N7{Mu;0@ze>mvX`cd1!gChiAuEG@-oeUwuM24^J?AVS{A zX`7n7{9fVbIEDTqyL7T=>+y96-KLt2Ar<9O@ij4UmU_s0Q@-XeEk|*}}?d zD#-|yronM(ckBM+T)kbmjas{!Lq{h223RT_`&l#!`Lp z=hCu-j_xc2g;dST?FF+5%ZO_^?%nDt*!oyjkMZ0Yv7F-lyQDiu+VgC>nUK#ku6b${ znVVtf3DA__NCe9x0s;b=(-MzW78h*gJdlhPoN5s%L6Gmi;{u8v1f-AC`1N1Sh4c%0 z(q~b65c5D`tM9)2CQ^f%fyRf}-{&I(e#^2w(%dy2=0`F95xdpkRt--i;@QEu7&oVk ztr$lIl>a%)91{MT2+qj#OC|1i6tBH2PWgyttQ^NSS0x_m*^%qWE>HY;E5Pm`%gpbZ zpzt=Rd^;;^(+=iuZb%()j^jg|H>%}DNSOo>i2W;@XV3fdalieRtA?OWBT8t2jYW6v zxv!m)M)HMA(}_55?{3~~R5ciHN6H zRqHO)JfWY68_orkjm$siymE7{RY%W}5|{u`uI&Ms{rfrwT^BQ88$WI$W%jLQElHJu z@Nlb)VL~t^CuIw=!GRY<&=G>Pm= z=5wuHw=h~AqD|?5y?vh(>G>2O%WcN5h=(`vfrqY%NPIqHqOYZKWaOUt(tz8hwl-%2 zZi?RdWbnGyf7R_q`=4puTdDt65&yP!-T7bz$)PhPsGIBKhwADt4vl)+y$3`IY(=X3 z@a8QFg=-78sO;G8ySH;|7|3W>Xq~s2WE%VmdxBsxxN~P^G}T@z)_ssa9YaY9tjRRxc8F@hQ1k%3&v4^pw$LjXs?6{$o+Z;{g9X@@ zHIonipWh*Q)vvz6K}y=K9n9eb%?v^w$GnZ_&+loQHh4h@(%IN)$7S~XzrWj= zeH7Wa{-YG_Kc}2zmh$nsHk>|T^V*Ih7)GMfLt@0UNm8;dge zyd>o}?jVsS?;M6J{=Zl4VZK-{_!xe|VW+l0WqqJvAY*+&!A9ct|TJPtJ+m*2V>XS1B0e%los zV7E_z87$k3mZHj7T8iPpV=}iDa^0qfRT=+hS4r2l5wL@c7XK##BEHFt=fHs!C9P{`37_3ibg%#3XZFUa2x7`7Kl@vAx8A!_34RNe}CSzLwlf(>Be$Jevu# zL%br%?U<`mWsE&95v78AyF*;l-Ys=rhWiZB^kJr3+Z2CrapIBp9;b_w0|QwIS0mN(0}`|Y|D&oF@1($ z|L2sjJux~uD#8{`p9dLey_;^YDxvAn-Oa?#o(iq7l^mB4+cr|{i-(of)2<9-NK_+x~NUndeC3a_L?g>4y7R8gNfE;;M9ITW)^a0Y@uYtN9$PoY3VrqaN~ zrj`wj?U-XbKvaAB+&MA-Jw>~myHX9V4A9#Mj!g>4M{77O28TzpO{GJ#J!|roD*~(h$J6EV)bVZ1#-K1 z3Ja@Wp!}*G*BIZ-x%=;pU_bFn{EqkPAO1h_9+zb@ahmWltGu4eeq*gCS~5UT(wlqr zK5-XN^^ti;(0C?Hf{}P5oACamBVgBay{(Zlt*xQ)9E`{8G$XcpG;4~0Mj-ITnaLyk zIxplA6qpKjr}FRKeH;vlivDxeA3u(l^&=;gvM2*1Ypzt16dWny5bqY97IN5+InHV7CaD^DH^S@5C;4eS#%-AW=C& z^k|>}L;%J2$t_Gg620DcHVXWHp)pe@9m3BynVK%%lXVbN;raguM~x*raY0iS;JfX3cU=2SIsN7(B-6@ev^D zg_Vh>47cRg=mhxss)G^>e|y#ve?T74Xr;zoH0HfZL8VRD{|GcDz(9(^dAT`w6hK9( zK-!}k^Do$hFm63jj|PE@HQj|HCFXq47*T?H@1rVXA@VC7J{(m4n_KJK(K7LwQcTLzerbvA+Vn{^uqCU8S)kTLKhJrG0ATrdQr@(pPhgffTV zSHEQHXJRF1I4&J;%C}3*a&IyUowW#k{Mb*4QJOV@SLEe`X?5DG9D+rIrv?%bXGB74tm%vKtJ#IK+U%h|KI?G6-j6H_JF+MSf!N3Y z2Pi(~qN3$#7Hr2r>4=GNVbH)}&(2z)z-(^NRD3|~@B+OKP+Or5y%T?5of^mPz-nEl zrGB3|_9rk8!)NyV5N1+K+~&44Ey^{!UC9sTB~4jZh(erw*q75 z%DA+&PuU(bzphxKzFWS4hRm6^VXm?N+wGSXeqY~Pt-mk;rj*b)tpek^Nt8oKxR_Oj z<52Sx5A0zs`?76NN5HlVBUiqEuL`~-;;+fYu6>aF4Jz$gj55f5H^73-EK#nN}!-6@MEm}e*)fN-K9@O_~pn4pP6J`Hm z-Ji>pUw}}jHlD-it@=mwBSfNY(i+W2n~^H3U9(*C|DJX-Bv>X#IyxqrvC;q9MLO;4E?b)+q;JL?EGH>A7gYvI79!0t9>C^BvK9C7C zl`O}e_Lu$Jz$I__b;$8Rb;AQx-?X%}-b7<-z#!jo%}e6J*Ap8N!YapPwvoIT0TYq? z-v<9oLl2KM`G2n2ACkzQbOO*GZ4yn{hne!6bhnteeJ}&iCQpP%?)wp{{{^UPVz_X` z3{PDl0`pp_D5Bw-)}_?o9?~xp%DE_l)2Ix= zCU_!*R*-hcA!M`q3=i{2NL;|F@rNP_vTr-ac+x<`^>MH7R3`MuHj%2BG5Gh+-Rg(Qfwngp{(Y=PfMKt{M<(69?<7I-;bU zR&&0$F?rPlM3F#1GGzJ62P7mUl)v?bVR&WDW_XtFGt28fyf|w)mA`LlxXt_`H*_hI z68{>!q*tB`1NyOC$7*#O8P16Sw1kV>JOhW~kLfEa;>pRYOU>vn4w2-${{RjlC2Z8a zN+!_@1JU|_fuvWstz?wKNmiwTA0e0AUt8W|zYidjz_YsN`}c-qD#l@fZ9eKaF_s83 z5mJqyu<@HLPdYUYp$XPXsD#N{g-b=L$Slru$3ETlXZ!Eiui=(a2#gR&cyu#pmxHI_ zaI>a{#x3&>!*HL^IP-T^$TgxPfy|1o0my6B@(cZ3M*S>fib2 zMq<{Q`7nMrwQwxdvnrkpK`3Kpf9T6z`0DONeX2+J;VU=Ju5KFa)jrC`Iy4E<^&%+OlRCo(SfGZIoR_BqRI2RC zwJau?h>iZd5_e3S9fFAB%?kg|uoF0%Nh@_+x}(c_#=Y(e($uCDAYZPLDnxIPPdr0TC-XT4n?+JbOz<(b&g}{&9Hr3fwgAT|h)XoTOnVN4XjuF5(%%1`j;*l*Vol?v-l{8(DLHLUyc z!H+8?%SDnUM?A=cz(=_*86L{4y(mAcqbfqIF<6_-Y8~BH!#?e831mU%$Q+no*Aw938w)p!ItB4ilupyx1rdaix({h#B!3+(%z5uKZe+0YJ=2%#xsKL`^ZBSh>J^)n)9 zs7aq68d&`u%u33!|G;+xz#8}gcr_rI-+-)cSw+Q}AS8xzoJPn$dV1JKDnEUiNK*S= z1&407k_h8yMJ{&Gi|5_9uROaZXadOIxkwJG0Ej~r3en$p5RS;H%^_42P3L~R_;QHu zs=>(kHzG+UV%089wAY}(l#r;Mjtm_>ij^Mu@L??MXZ-W$@3EDtBfv(**7gJ z%F2478tL7A-?xtPoDJLDzq4yw7DCB5U3&D=eD4Fj%M4zspa-w_VLEtL>@+i<&SkSl zPo5+Lk_H~wn-TGdX7y%jpz$1>yzuqw+nJakR`-6}bL-Hcpl{Qw)2fWsp;?4S>iwz*KVvTz$eoF!YVokWm5jivOFc z!00An->?lGsze%pb_dE$&F0fe;n$McVDgFm4<>wb^vfrhC*X7tnTr~FDb9YvVHIGS zr%++cvFacPO_VX~|Aa<~P{vvw9v+4bkDaA@xjg4bFM1d0>NXA_G!e#Db?B=*iE=N_ zcb)n<86#aDWfc`IJazcZC@auVk_M?&g zE)6@P52A?m#IHbn!}Ye2E|bCXRR^K-u7-hLVf~WpJF3Hu9M5gF_5G55DX~%Cv#q-v zN`5Jba6~-jXV}_0b9MM6oqxq>Ws=i@XVo=18cXc)Sn$^38d*RW%?lTj6}c%IXa~~< z%Q?V+JrvA|NW~{GP;B{mKnFysB2&Bf?Nfl4Ju=CRI>_9h{J3VlM4*pP-Dr0OVHE<+ zPG^*?-oO%r=z-R!^T&R6HUuF8)LpV;{63T7ICKUp*cU4-l27hk?2N>{1`Z)~=4zC# z{P?k-@)(e$#6NEVDI>0W=;v<;t!!(}@2t1AV;5l+w9krkvF z1!QQ6SC|HM&817I`D`F1J;5CU2CSN=TjpG(LD+*Two4EB02`q^**s52%O%fp^dz(_ zDY_8R`PZ*sUGtlUs;_3TR3yE|+Yn3W3(&h3%e*hk=8D=~&$;MdWA)zPkL~pmI)^?z ze|Z&T0DZ8+tgs470fVuC48V|gH2*W!dy|i}Bp#)L zG)&b*crL^DJa0YNS;}1^Q31gy!@TyCr58_+ucEAMwnzayjM>IjY;k61aKhn& zWaiSiSi)u8Ue}ln5t!D06sFuIlHK13%Ruh^j4D9WOCyDx=rUjCMtaUQef*BUk-nzm zbN!K_^78V2FyUB!GF{=N&W1c8@w)tNQR8L7j{kuvlGofeZf+|+OdW&|D_XV}HLa08 zRafrMuFSaKmT{kRDog!?6b8dq2aN7!U(an>vDy|uPMX{T->Q|S({+B*9YB_A=RbvQ z*Vh&Lam%%j^rA2N)0!frr$G)d%SC~j;a8oKfsDfWDICt{0Kf)qm+l$CK$VX{U1Yxc zJ`6cBgIVWb@;1)xRGj>|-@KVQN5&p%yu5t-Jv<O zo~OvdBP66wY-wj|X;;!`mJ|l%6so=?tDp@awEPAIKtdmt63b3R6M6w}E7hVJV5csaL&;G8VH1gB2$Tt(O!#195nhpdau-PfMckfAn z*JDZE|Lel?61wg7VlSjw5j_T}tUjST{X#cb4P4!mNXE18Ts!^Zw1M|-Hu^ZW&2uEf z!k3>NZY@<(vkpE_0E5~p=q6ll){-G@O?r3kFeqC6et_py7bw7^r7-aALa{Q9$b-fo zQ*n1MB9QX99FjZ04M8gHBaqm948da^fr<^J>J!NF;nlhAN^T2LkW;Ed6w39I=jRNh z<`RIOQ=oV$Io)bUOHgK~o?Et#$d8wR>J;tf1z#uyA776knGQv#Nl95WJ9{qCJ0u}~ zv<;)r0|vxLaC^)gMMRL9yZ$MVy(B1Bt09RVL!Wf&YNT{nh+c{k*n-{$%4iugVR0qV zp=ed{T$X0M=@u>t2$!F*?V=I>zJJqm%J1APQfA6CmgcG4$7MK3@>}cqU;2B{ zTRKvH52o3cCwp*Xi0b3jte+iAi@BrC5CQjpa}+stD0Z^#IlSdGOpRa&xT8{#(h7gQ zYTZ{tOg>?9aBz@~;(?pw9aOLPk$MYjD0rwav+;Ye|K6~aGw`|Py4rH9H$qDO8f+eM zRG|lKkz)8bBEsC2rF(av^s||nnSD;w?WAzQ#jAu(m*0vQ8sS>c`HQV+>eU?Yt(kT? zijjj9Bx(|Tufgq&(w`8K6>z^*8Z#5qth;Sc>O!_@y*s%f683hOy0pl~K&ZJ+el-Ym zDV`~`?mC8;lE3qgJi2zlD0uBl+J%JmzQY8vGy3@0i@B{va)8fZ7Iya`03c*~C+7w- z-3Hx2odTCI=+v(3&e;ujLL{9b$pN~_C&P zGxCw}`c(k3kYfmn*7qyvz73bKOXwR%kvjJI^XIFhj!$=ogomdPUSY`b5X?aL)m;0r zFlc9qe0af0>@;*8b>?;7UYxYKKPESGN}sZzjM*}h_{m78>vkJCYA4I2~*!DY;sUF-z!2jjNFNmB8qO7AG zc8IkJZZNZnS+oN97f`YNeNY!|s7XtPmsw?@F2nK#Vl=)Gzl@!jut#mSADQCOJjRiA zz5fF1i;0E0g^)Jy`2$&MPiQihJ3qqo1sc<$yOyJo4R;A34_tH%*(2DSj#dQ0kd$reox-j&1J3j)JxMD)Rbpspcy07s z83F6fwyBObI>8&*JnYclU(w-F!0*fjB`#DgyhjVDzFjwN@@HfnLP-PnsRbwyr+?}Y z)Q2JYUE}rG+z(JtTSX;+>@oX8Qtk$klUyh1SO387r?t3W$4M)oNG61BV4jQ~J7@GK z!WIv9N9|a9F)Lv6CQ^)6$x>Gy`2+c&Qh!Top{xCwJz@24a4xU4CK3$E&Do}LNWGm3 zB5D@hoxS18Hwk2wV2pcL~`ND%_E2tk#8r^mBaju4Lm0=XcfOcjf_OXK{}HH zqyokDavg3s0jyWiYX_=)Sa^8<^vx?k+-I*4l?_W2TYxN@^K9Y>pKXQ;!Qi-ETkzw@ z*Pz}Vq$2B2sQ3|CeN=gp^$<(dP(q%g5D>!@A%s= zl+8Xn!G9ni&PL#*9vvhM)DTS}3MwoWIhpuI92((|pG*u5etOf>*+ zkGVb`2s;uVUCu(3F}S$elg?d1?;8=N*zDc`x=L{q;$667Q#fY@I`*hKOR{Ln691}o zcUOfUZ(Q{=y!r|fvC!u=9CX8_XHkh7+H0I+^R(lCfTy(reZP9nBqVO&*ne3_T} z#y`O2+D)QhY2bp~0g>$l5<~d{{}2UIbl!y>6Br&z=I7=8ur%M$Usy7cYh*dNTZz;Y8(tC7Ssj;Pxsd7@~LxTw_sBBhW$a z9h;tuSsiI5jt1FAhUpmMX0U1e?8+BzW|~v=D)xr$2o<9p+>Em0+{W&eLf-JbIZsQG zcRQq;r%HrM(A@G{Sb(utrdBSdj1$KLwN{V$X{ub`XNmQbkW61RIV^SmtXxNniyL6Y=zYU*OT!+&(QTG6Q#Anb|DJh@t!^VP)w> zOBZAYzl|Kd_tusgI50wfWk2R|9d}9;g7?*M=kq8LaU}`B6RD4 z2ami)>Yb=cl)GXs4)**1qv|{0sowwpwRc)7BkHC!L_$V6iW@1b5D}6Vva&J`8qy$L z4YEaMNcKFHqJcwJ*|K-yI2`N$e5?Dt|KEAs+oOBSIiK?$ukn1nUe6~zzrH|rT5csw zVbo?~XEC2TxAbkU<8UPs10NCCcoLuL2=ClE#G1CGO`U?%IHsb)IN%8bEUK?*t0lYy zIw!=Z4eMUagZGGHAo6kQFPM5hMAWA!Eif#f)~PNLyCeiJLW$=ljpn1@j0>F<6mBa@ z#&6MN&v^;U(y5Vv@Isj*3>>9_ul zWDgaL(Pk!O_fW^Rs51pa>g)^HtOCfqz0+juHqFmVdfxntu%=J3C1U(oFCFxKZ*nuZ#+io+Q10|93omEiI*Be|!$ZGjsz`oj1=D$6$X`nJH5 zDAZ`T))TniF%@kaQOSmGQw)mi7sDeP91^)#_$nK%wv2OYL}rV8k`_jZPLI=`k8GZa ze#VJGiCz0iiPi3)exH4gb;$9zNR{a=v;Hfz0Kmj}y*+$YVD+Yzirzx+e!JDp*N#8E z)cq#faQ+pC5AA+2`kLoS4siL#uKd_aaINuCuQuDz`VC4A;+=~gEM`~*rlK{Sl}qcs zKQUE-xSNxC%Ob0Ny#~=&F?oAqLDXkHoCa4u1UqjXtkatC;Ot}(o!HnCP#ObjSMu@^=|C9&q0yp!pkOf zlg*ishRD5l`)nh?sBVKSy7V(egc18Q9n~tP-iG_y0(ci^P0o1}+ch$kG+QE+Jd&kd zxOkoJ!$?xb&fL2`Chm60#R^pT9|r{qDsk;Qe+TT@MrR^babL$D^BYcjI7B`Q_d8@> z4@uIoyu}8g+u8txm}=dPM1kBjBgfMvSIV~Qp^1$8;vI1Qi=)SWxLS*q@c@s~2yK*; zU$IkMT)f%)LC-|9t@O=osUNn|W*mjF6i-qNVM-lDszbHd>hgOB%Ovv?V@9|OP3qFq z)+FVeLM|yi(U&4Ga|SUz2lc}R-^j<@iQ1nU)l#SO4kq=lbYXc=CYikYHd@i@>{&|I z?5710r~xIaK^ztb5JnSaIn?h+$pW|$Vzdo3YUaQar4VL){k#Q<_Mm{{rAqOv^zrqn*!6tLiLr_B31f)n}!c$Ut`e4`}%SfqCC?1SAAaWl(5n@7UBT zVq8cHjHXTbdDgP_5T9rLxG$`HJ^bqCtaQj3^#4?zJ}QxU0DC7{pcQ-lg*(MzSTU`RVyi~MQp-)rt9*@wq{{Bazh=(~af zZw4$g3fUhb8c>0F303=u>|!{)R74+1R1l0_MsVZG_uLPW%rITHDW*&VSD&@BL1~@Qav$5{TEv3T%aPeGfPMQWT}!d+ zH=J}raW4VhC>Isfe4BJcFn%66ZD1g`)pX<|rVdICnh|vL>v8^x-pei;uH;V@(sZCJqp3T>ZEz?%!8wEfog|yP?y_Y$hi)z!t@+$1&Rdty zVsUSB2vWB(L-SzDCt%eG#kG)&(5(*RCIIy4O7S5`Xe#B(x4lXI$f{i{sG%Rx#WCm_ zfxoVc%q><|1*@&d!GTKy4ayi`x=v^z%H;)x{$g*`pAdsA>ESg1w24lEe^Fqj6YYSg zsA$(Tw$v7irz+3(eYoU?u)(SAAH~Uq7r6=$mf3c{@B>{o2QQObLqkK{yjINTnCf(K zpoAI<_yM3?teI#;R8ERoO^c3!H62}pN@!dwUnZQ}JUp=or`V}4xd;OQgIsQwXfrrJ z`GHix-M0&(H6Uj1VHH?gdaIls`O0g0qq!`Nc(lg{5RwKv4`db29&Yb~4J!I7^uWTz zv5IdtLoJ?s)nm^^Y!=U+VJp_oJ+f^K#;awz-Eq5P&!7z^@YSnjw(wqEkLHCsUzw;W z+SXJ1!Y0P^TO?TTcOnP?gaw0!WTde7Eh;<1i~X)uAnqb&5=tdrs+-d_O(240R7pS#DZZCB1=3&AJ`UWeAMMU-*t0x)r{!3_&A zuj0fU=pS+->Cf_wt9}lb%Feh{I0u}&(^;;_C+=ZA`9xZF(>qD5wnzf)=euPp1J7e8 z7PJHf1-W;gX54@CRu>365jWlX5I*yFvRCG8v#a>e!R(1T-!vyFsi`c_EXH#oo21Ga z&bm)#n+0ZW`Ydi6GfMf+)wabWW>b8XZhV=S@cR2N8V7TNcQJ>0Y=Ri#J`AeMoMz?! znKsluLoYn$E5NB6wuEimepAs#0HzsIr-T;{0YwZr=kq@Y!9 za1>>(2(M8zY|7kN&nW4(4VbG!p6x)odW?a0JAv=$=_@_A#_XctJ{96z(Rh5PenmT7 zcyXvx2dz=wvJvq8g;#MS#n9JeOhuwTCw?H4-hvJsIhhOOnec#@_@BS?6 zLpqv+$mof`|2I%GNhmO1eMHT)HKiX*bfd?nGUi0#FZ{^VLIb#}KiL#%p4j28rH8!a z6cVIl109{Nj49ZKvx1i{UaamBB{hYhctmUHMjFmZ8nE&I=b!Snjz1%A4B@^Tr|bq{ zEF^FSmd{SUl^y-xbwRDMvQpES3P~}_Zs%OY{-0qJabHLu0t)5V%)uQ1vtwyY1+syB zbYC?_q;QSBfR|pWaI)L2ohX(T3@ot59=8^ik}Q~^w~HEDCFav z8k$%tQ@y}A!L)XRtfD#|>0njTWe2w%CBo{ZS0w^Y1A@TdT=NVD5s-KqoEu2EB2SFg z;KCz3D9ona6GzXPn3fc5=CK6Xio9%f;4lQFtmHXndi9Snb7wicUEpHE#Rh#vOKXD| z&$7(~ASMTzZS&ERglcv67BABNk5|BEX4#?Ng*>!oyg-u)l1yUVuVIQmRT*9}2y%1O-{ z37qBQahsVSgk!)pj}zn<9l9iI4n{Aa?yIxc3Gffq*p22H=5H10g$Nmgj~6<$_fTin~r zM?Lc#gN-zGc6QRCugK?jjXIS-O(JvozH;_5r1Zofkvoj@8+XVVWp1wd2|Xy?b=Sj z-x_;BM4}gq%Mg!2hR!s%NFSDfAV@bCvF|s%{1&wi)V)QuhOD1qAxZl;N|B$=C4a=d z!SM|cGdUUIO{WHt-W0%!5+h)@R5LRXFx1Su&+Y7&#k0OZ{oc15=>K``*M+7K?U& z^tRJw%xPFp>$l}etG@R*sF^Bq0`sfS0n92(MHQ#mF|L;r4A zoxrwjoZ<653%2Am2q~GO;8JGfo0^&$RkfUt@5{5P%wVvNoE=?>KtUAwY z_KTk%Z;MbDNZokQ&3EtQjHUd~k`8i86KI~w!sQEtD#bTy@u@kY1D(VVtNCpX=q4%=<3j(I6t416+exG`vXChNWpEu^^8$BxO9c-tDHA>iO4R=)Qjv$FO?dc4i zQR}tY@7z!=^x8be(?NkW;o=qj@T{CN78MoMbmb+! zQPZfdEOPi?903O;Z)Ra0HC$?*IOrxhklf$imzI5L#A>QT0^Zdht61$iJw7v1|4WP2 zl3CC@I%bx%l(CAwW6}>@r+95?wvr@Wl%y|^v<}mE38QBZXS>h|C59N!|4kRUi)x;H zmu6rn`}1@x6r_&HLYXm~_b)tj!d69E#oA>dHP`9K@+<~X0)W-Dk&`JFUHYe@ECC9d~hK63#PBq1CMVfJ=<8LknHp?eHez?=1wCEqZcTOArOHLe|CD{- zJTtA&v?i4Rf@z=!4E+nF^{OGqp#~RR<%u+UU;XOv1vF2Bk_N78~NhPQ%|vn z8ag^*m}2Gb{atFshu*FLj~{EaCs9Q&-P36Yw*6$2K?7J+gXLtrMA!Vg%%l;60J`W> zBh5p;zK~xf6%AsZA)r(SIws)Z@-mv_!De5Fd~J=7NhtmGcplrKJBZR%zRPO>^_o8bQ8+QrJp{ zRq3_=-kv^Io*^?%I{+j-8v7R0M*o+m1e&w)t_Z zX@7!H=An{+(^#RPLWEMg@~7iY4Ym)~J?q4qSBvAr_*$joCchL&vl>L6xH%pz+9WXY zz4O8?TZD@@TW*%S zM-y}N-~*f`831uxJ5)&$F!s$1_?00PN=!4qk0cREl2u$qqk3VJJD_E!IQps5cZ+q zpFmjJjtiS4jrK>7Pu{lueQ#Fh(~sS@`+t=$z@u7af2kYF9|*KfT9Y&pVL!+UtDbty z`*N>qz_C|tnm<2HQ_DI54xR*8#ms$x9Z}57>^Ssn;^5`2^?$Us^5AU!-!AdkcHdM`dQw@R1ZoG@FN}R%CvN+*MDyw8^B}IkO`FU% zY2M7}`(8Iv>(E4vXP2#iF&XSwI8T`0WKza>Wb~;y8fagsvvWs>M{hXDZCh7uUVF@Z^AKl?yF0zsV#*fmI+_#5h05y!_dk2L9hqXq>kG&2=rXM?quL;0 z0MoM<12k~r*M6R5C@YmCqx7PzUGw8Y%J&;wt!VT@`?e*S)@926bO%?5#s$J`^F0Fl zc=?uxLcMEFvWjn`OY4fH>x9wf=#q~V)F36E(L>wNJ`?GLTKef*dz{EB%)3#N?$*&0zGzV528X4EP7<_@CGPH$P>oJ$dq@J1P>lhdWS3;Sov-U?Sm^?yinr6t+4l%6nd11;=OiG*!ar1Q4m<#rb? zPNpebPh2a&F4%r0$-Erjp?mORKMsDs>73ztefq~i8uVC5>e4G(x?{|Z{j zCjRs5abXXV@E-f?fBjO?*xuh%F^>mNWnIKDe5zHs=w}yxc_1@S`3X;1p%-WG@Np5a zGtV;XX{oEn?9FH{fg$Y1EXN6OGw^cQy2OG|6jR7O7-}yBk}9hE_0usa%t9-3Tsl)n zT#9G(3+EBPhyF2O8mKTs#)o0zWL2AP1En3HyB&hcTtgJ@sYHX=A)H2jrS6I-Oe$h6 z)h(UsA3k?*Sl2Zmvh_(uD^=-h#7tngfkb21@R#mK+r5VCIyX5ys7?z`iXYM$9juGi zPSNiEz;RqAecLvNDk{5jm6ls>t>w3K%tZE)U&p_wv`X6kJZ@gn@O)2T)BaJ=^i%+p zwA1UcI^xjPoI=~G829+ukhZam`nUH~i+%g}7%UGL=KcYWi<2esN%<}%EzzN_=lbH! zrlk%2!nh|!hI44*nEtMui3((N&0O3#v3kc@vS~d}{0{EwyY>Cbu|D5OY<&ETibTy0 z^%UotMNX_wSy5AVoKmJ&-Wc=r@em7nLHkU&^UT4l#*V!k2Re_ayt~R>ogl7t_Lswe z#l)q=>ID7P6H>iV*KgXEhzE-|cU(Ul>?WL`6NT%vO)1Jz+WK91mYZ8~@1XsTxT{(3 z$lXh<8UESs_q4_l_z|0Xz<+&m(P1$08ap~rjUT;ijLA8WVy^!9Pb9LK-TU@^jYz;C zqgLJbpM~5}dmkJJ`7ans0&5rUV}yHp39HwawJxkkSKKG+Z9X-@zSqFP!;nh@gdTZA z7atV$8to4paBH5kRS;w(DmM>qBM|bNXYr>+Q-;^`sLk}{Dc;}PEYpJlJd`-Iqau+QJHzb zO-)ux+ow3_#Q{a{6Vz8af~?-AeJWa4RfDXqt+3f7b@Ih(H!jnqyEDPV0>9;yhv#~F zFh_m&%#iMzpEqFYRjAI6{qzrZUVgOHlQI@ZPi)5eci60C4mL#tdFn1%(JOwnQbz?8 zqY;yJlfK7=Dk{`UpV;pEx$cVkdzl?}P4hIUr7iQADnFf7^YQlU_qgcZ57k@$lgXMK z*5jh;+o)W^Aq>M2wC&dluGAEJ@3^s;hqBK&yNM=_bv$Hll`)HYaFx4FH@PtihStvd zW62eND=73#@bRF( zL#l}CyxV(Py&g<6?xvrL7w@8Q-^fsZbR4qGCJoxJX4}I}<5lYJ+f}?dTcmig(vTS0me^C3<0JIItJ1kHgL_KpYuQ@~vx*LL?^BHT)akgv3}KpOb+Kh=)x0?p zD|pD2=?ySTssi;B`oBvGN3o4Ha3Gz-W7U<8drCzUW}rVZw788&dhEsOF<;f`372>8 zC)QQ$JmstTI$G!cdP)LJ3ZYU9HIPZQC{Rc>YU?bytH*=_Zi@Z!NdStg5Fcmd&CV2hhGIu9c9@&sqEil0}`INFO4Z3n# z-}||w$2#2KM#0H#{KDs&dEDUvSH!2ah0dwRj}64BQNA1Y(m2z!0*8M-(Tb-|o5y_O zIVLbueK4>2dssXVF^Sjz#Ot`z8bL{WA0*54)d=VC9I!7o3q&AooCQWl*U%BJwHhhW zai`c*#<=ui^hZ2p$LbZvNE`4W<4h*~k;?U3@{4FATz7sVU{UwrcB|j*do6ym$j_&5 zxom103O95YHHj(=4(#};>+wOq@c~n9)G*;MWgqU7Vn)BH3Ob6uA=HbmAcSUon6fcF zNiFij+letWWW>thzPPS^Ioq)B+8u=^DLGJZR3(7WZ$<8Wvr^*@&x_ZVWc7exikJzl;^`f~H0n|4LP5hV8TRShxBaiEqpbEqNm*C{XGSRr_qQg(8Ac_3(+t$;b->>k_*-=BGyP1ZpUhLHMcH+s*TbheO27ukY zPn+sr8DLU*|APv6l7tMK#PqmO&*2Sw_6JMU*ypPjSkg4MTB?bc%DcI7$I*=#KObaB zxs14p-blPk;DY&A|G%!@7;bctR>d1uHSq-|AZQJ61usyXxS8o_>bJ9U``|yTkGq8@ z-fK-n4^cQTiLTs5&kT2TxNQZ>BsO-5SL);iK|C+z=0fvYgO>=F;{S;#>`8L_Qdz!L z@kWnZTTC(x_Ll`4Zgu1EcrzpC;>T@``#5Q{FUf#Y`(qo@jYyZI!`$qJ%_zag#4L+txjc6%Fvv~4n z8b=?N5ZFOg_H2rHS>D1Mc$KZQgjAcR5IcJL|EXGz6r(bPbN{-~Mn^+G$4(-FHrgZWl5W?u9@HdpvIhO-9ZfT=k1>6JDNR{s zf4@YCn&2zNtIfFN;tb4pXx7sUu)*Te?ArO0zKVf#%2aB#jZR)1NsQI?>h=EsS?(HS zuKC|ApoYj9WLPw;3w;Df!Zmi3okF;Y4YrOv?j6o)sis9nXtpEIr{<(MVrRi{#ZsL3-}w|Qt2Yx;OtaaY&$ zjov<+tYZrg%D}>JQ=0jx+nx675&s_Dv5}l(zY)ZcjC8liPxZ1vC7 z{(I?dXFLDwZ?7!O%gmh{u0<4OHMoSzr$8LAn-yz1dBtUf098!*Qg zR6Pt9RpX_tkS&;b4G)<`Ob^L8p6?Kp zfvb*uq;)mhK1&sWkU~A>B)o}T6<<=KmR9-Rz&*_qy82d!V;pFzKxvhG#h2`<^uu1U zCW0I(@|vNpzwRdI+Vyt0Pi>OY<#?qMn7$$KohET%#T|ez|jCV5C+XI^#*LGi{IDJ4@h0 z_2NYo6&+$S?!9@tuz^*kZyyG76+bhMDOHNNE`G~=?d%vbLKIDD9tC>=EW1Xb(noSLd zNg9=NPXP)&87o^vlrd`<>zC>a&~pCEOf~q9@XQevBM$M>!=bUb5`NQ=b!qSwfd*GP7OAT7-;w>_3^Efj_>A&_Sc7g=6ZRk%F?t#fc)9!SND==OTS-gk}~xoo{nRE8=PT^@|KlK zPB001ndf2&t#951y|Ov?0~SZ^`_jE#-T!=j-ddF{FW^}*`Y~0k&ms9)LRYf2cvUJhqCX`v zRzExqc6E(yo0JF%Twc1jmzO3IBV%f5P*GlbJb)30(jaxU*;NgC8iIH_xvzK267P0E zedG%0zPfz(82hQqxFUt=h=HUmuPpxN>HIKSMAOavh_My>L9tUuzITiAij?}`j~@wm zG3zMF$9HNZp7 zZw@>{FK&UhRl4^P8yF%uBd&@wO>U_`V0>|D~8eb|sfK60~;=So+TxE;T0Xculf z9o=4W>EOAg+LK$&E~pP4uzuI#;Hv9tE|D!te#Cs4Sdbb$~vbSCUwP2YP1s(yvhXP0(s)> z6V;aI^{cr~SS7uDOcNdS%DmWOP##4oEtn15FMTG3vas$yu&HG0(2<6U<4+nac<50q z{zGgr?Cdfl8!A}PcY^A{4icn<%Ue_dD_RFOM@U+Qb34_aO(pOpC>!m78v0Nsn0#SG zAE*}$GbblTPY^&8+?d+HScqVYO{jk~YKIa%)4UWH9U?_D#VOr(N<+`EA2Mk5F|#9n z?q^9Ro1@TV@)Ch!k6T5F_pk3*!&n3`_xhQ>kRRr@&a&n)`i}euhSc!}EtH+}F&L?P)((%kXLw@1};g ztvn`|t9%@d4(FRFlXMR#nCrwHwWDHBiKH1W>bSWZ&~Eaf6U+tLhhuhLWgn?3a{MN; zy-vTEJ`XHZEOo!nr zr~=0fJY&&IQy?FNxKd+BbT$}WiA&E;x9R-%ey#^ldkDGfUagujKm`y7;%Xlr0uP-SZD;1<6>r9Gy*r3n!RT!^(%Bl5Ei2!Mw9qd5*6qU%-!H ze{#fcugpJ9KDNf7>Qiq%X?c$`^XnBu>dHm!Tuck+L80nNQtsuKMf#2}K#Iu`4t$~Z z(&E;S|Lj7Tl$wly;I3UjL9Nr}T3h4sy#6{Bj);M&;Wqa{q}`@VGpF`U3A@fy$5$@f zDjF4UImqi|^5M1nl*ExLdO=Q+*K*du`n=wo?lzLsAtHzAEtAn59!}D@rB(r#gWR%a z>w>BT+w+Y?dRBppu9p711cFo^v#ZSFX;FF{lej$D@;XtI>4 zr4V*=wd2}Q+7MzD@VSg)esrWTPzsc}HN>SS`A|-NIhBCs*_XQ$@9dwZ<%30aS7VB{ z@4EFJ;~qO}_FI~!Bp`KiZQ9_42dIfvW2RTlD&n{wf-_iau9E>|;MA!&eZF-&;3s(d z2b|Bw(R(Cu-MeMc3DRJXIMq1o=^%+I+b|q;w zyM8>EMeP|Fe;g>VQ{jJ1a`SI+RP_VIM?y%_C$L-TZ-FCW@VurZv_^`kz%1UE1Vn^?(s|yrKV2v_dsH@W< zBoPMa-~?c#mUgy$O%gTp#==B;e_f6zT;oY=ZG#ufgcetzWCW{UUMSJDm$DlO=j4(5 zH+iEaP%vV^lJ?+3j`@;28wE1c<~<==<})~y04CZT86CG7(oSu62v}zw#J*|rB;P<| zg5r!sf!H9DnQK#Ln8y2X=A6v?xe4P-Tvd={OBvEnvCg6IQM-=f3EJ%pSxvSnyi-lf z5gtH@u_{`deUGcfGwH(jF~9*uJX;4jjYm4;eING*F`}G2wpr2ye7zipUYKPJ6#fFH z$Mpp%dv57735N#JQvU*gYq1CyoGw(E>hm}z2Z*Qi;7oH+CoT6lw@4X>KJv{m9qV^k zSmmbC=E~dAf32yvX`y9j3ozkjlAevF+=ez|op-OxL;a2I(B849vjxJiQWT3G+x|Kp z*O<@d^`c6rd2ckHwHs@FVaMuS6ue>w&#xxA}e{AzW34 z13PWXaoInanQq+%(&I;5Esdh=xc8fuwx*b^`4Hsnqyui-N+iU2_-gL!9AcZkIlkMU2U(hHS?XDooAD$c;-XEy0QUE}>?`93wv+ z%A&sC!DYT|Xpivjj_oW__-B?oR*kwsJkR`*O zR}xMeCaX03#o*wJP6BkQeECjDLh)*@?1+1Cv)0B6>YCs_@)9q-&kq|3OMd4!G;Qb@`cd=UyL`L7B6upG2JqtqBIQLeevEzh!)t4mH-m zgrHFdbt`f&ylQM3Km3N8hmCOh)qP|RlUbsohX?xR-kJmO#k`hhY%;(3#@=?fpTbC0 zA2!(|?5qk5Os_m17Ar(>#nv=bHgqB=&kxv^*bj~o*>)me-#C<|{(dPOI z@G8z@O@Cu%chs7hi7g^ol!S!&i4Qe~oYYi{3I*+{AyW;$0h@K!<$O1KoB?Z8p&+6{ zJ^od^#=vZ3Z84Fz*xn?2P&4LGv}UL zwtkODuy8_M>IqQcsUMEIbAP&dm?@<5Hs}Bz`U)$kNy6+@{3}JA$%JB871ij9R-W9y z;H@9zW-3f&`|SMIbp3XKqMMDjVW*M&Y#ZzxKw&)=7q*5Wt6=NvhK8Ng<*UuY77tPwb`ETr@P(V8ra7P_JNSmX@%-8Nx4gt=zP( zAzVIq;;MFCYm4+#?{JE2bbjeu7MF%`u`xBobHLc*Wh?1@`HlCwZ;$-l=HVNOR)j|w z!7cQba_adcAa2!s=ta~DZ7@3VKTEgkDmzSNjaH#Tw_wr>SppG#p#IiViUz${Jciq2 z)td4#FHDp{7P<8JPe{%R%7z4|9fJtS`(9^sm3SpjfDM{xA?^~~L3Ei24>t;-$9yd& zDnct$E*6fuAG(w`fv>ABq{k48t(Ql+B=FbE;9`pAQksv88D}_ zZ`?jUVVsgT+QL5BOsvN74z^25*(+-GI>&bmk>NXJK7UNisxOe5W?HNWlLiO z4FzY-ldp^XZ(<31z@pAtkvA3~&onMy*YPSp!KIypD6jyw{&Fgw-rB}x6Piz@K45_E zc~}a}QO3&uL#AfGF{8haQ1Ri_-0)4rn4i#2qn`cg+7zUQ8D;Vss1$Q&S`rZe^oG<- zqshT!sr!5A3?d2^hlZ>IW1SN890Q?VgsiI(>DQj4Um;Ia@h$REiy!Lenin!A`jfy0 z4<{++aq!GUJ^oZlU>+YbID7Th;q`?}*lA_08#7rdp`%t2rH1mwM)G$s?C}(g&8U7d zqU&xu-j14m#y`KfZ}}&`&@1a*0}g$ceLw%9=zr19ykUCCvryEg`T@hLN_FsEd2IVNG}n+0p9y0aXAEYV@U}mo|UPWJ+eUKGCw{)lsfH!Jv}kLzyo~}VkI9Sx;u3{0Oerhx!mR} z_1`8I+CtZ|@E4SaQm=eJ8IfIv#b(q7)OZiaT%#L6FgSjCYSCDq<@mh~G94z}uiL%z z4gdcIl1%ycq6@bEqpakM7QlD=gdq6y@f#W*ueCBz-0^Y6UrITJIQ98veHy!;Rp;HLMqS&CH7!HU@gB!Ldt+zUG?yhM; z{p?De(Ni1(;Bm*7E3Y&(GCCYlY^c&<1+CsfvjLk*9Y{BOwS%j|6dEX)_Z04gL=nVO z6+t}U8wqYzuz!I_e?Lp|4_5U18@c`4f8D|R_eSf9{=#2S2~aUw#*EuqrNv&IdE;O{ zg)TGJYH%TH%QgWA@wo8Z^bWhN4^QaRLv}{lc^0&!H9*}_S9}K^O>tupp`-vg@nLW4 zUfGOTEGOg&;vsUzx+1{{H|%tPcANJ;H}Pi$J)Os%G&Ei|ejM3RLKjDc!4tV2kv;SN zLpaFBwj!d&TY~udF5>UE4wn;pQM}w28vi@md3!Hpe?#kpKr0zGVV&e(fzz+?uqaSg z%|8rtC`!_&4`0AGa=RFUd1*D}7YF}8Z6c$vfk{;wjg6_FbcjY@6FtAXBT(FV^rgG8 zZBp$IH56P`TFnI0g5^n0WR`luX(>DH;lH%#R$M04#c>x)Vw`4a<0YqlH$b>=-z8Y= zp!|Glo8SBfdIn^Fb)o#g&PW_btA`zUUrEY=7Ff}KMWe%qy(gB5X>Yh~sMAV88co$0 z_ukpZAN=ypFy=fxFL$_VHQ9W7xR9Nf=U@86KL=3!4--*93H8swsir0dJu>xasIIcT}Qe%>5As4f}#~) z)26c+<)HnKdk&{S2{fGdUjTXbGu4g%)N)*2e)NYEr!g`^C+6sK9~iQe@F!jjG}Aw$(2^^eJo~|0YpUz>U_kxLYzxkx zuf^tG_6MQ4g!6ZMSL;8;TzS#R5m!(Wa|XH0mij{MRCCz|nby^fR+2o2J(P&zRJcEp zR6wUnsClsJr8m<=zSt-TMuSnDLhk`N{Iyi-K-M;h1V3IFno?UDUeUeWbY($-@JqWV z!T4)8Ex4h1(Pi_0JX9seZ>&PHgaLJfb#E@;o3ofX^a2G22%P{NjEh?uM# z=s2ft-TL+Z)v`jnMi*yqG+(xK?r6=b8^HxoAHC40P$om##6+YmcZL&ny67m9!5EHS+t`l2fTLmK; zaI?+MZ2Q9={riAQR_VyeU<-pMv71iAB@%?vNtb!z8l+HJTvNxsmTk#k zUD1PnnPMoMuJIMf3hl%XV2ECXL>Nz|e6-UL9V^@+N{58L^WD35sV*PxN4bVV){`+1 z{Ff4N-NLRE*0d0S2O?FJ3Z&R^y4ZcM^@x&TjBEF zacUfXj7{n9!g3oSDfMJ-%ccxeJBk~2^@o2?a`EYLIVJ$>uwwU(EcAwTaSK)Gj7;rn4mfliex*qYAl^iYt0@ zLq?uXGT1hD51`v&kKxdBlHyR$j3h(x5q6&lXCG4?0Er7AY&vB|4gvE>2e)Mmf`Bm| zZ8YQ|o6#~dx(>sw1nu<@c!Velbz&EVa(Ff_13#o9l0cd7ZaV(-e2UPnYUGNAxzPRD z0kUk;m}$-z@-*@8qwQv`g*;R!u@gEGp!S%DN;eUa?s&zqnOdr2ZybBHdggKB{+HW33J{_fd7 zGxOsdIPXTLm`(AKk$+=`5adh#u~5+MwFN^PCW}4P0h&c8Be2}cf6}mSmK7g5q)=~~ zZLsCe0gi0rw&d>JV@H+6c9o2^)_NW~W7BGRC6T_up6}*&dTp_yMsPtux_KA%B^~1z z|GnI6Ne5)wNCCSnnYH76%c;Gp6LbOb)>a+4!=;2}3Q~d6dE*^{x)~P1CeBJqcgghG zvu8>>{?C^sZzU%|3c_58@P zigb)Jfri-ePnq<;=YpLwCbAs5MEjHoAUW;XvgI(ev%?6ToYO=Q+TYdGWEhVLQI%63 z)(zaW>3#mf1wv6t$w>Mwb`s8eF%P4Hgbb@LU0^MVT|;d0k^Dct}9j%jZZT~OS)-#RHDLc5j$@sd>K!V$?pIs z`X9mwg2~OO(9FzCb@u=95Mlyr-Rx{l^gZG$lX8FE>jtrJVK=tgELG&&mHlMG5cG5+ z2KeO}Xj@_xmMoZkQ}+r(kn&?(OF0RQpD~!Ws+9jPz;fSybEy>Dsz{*Hfz^5tUj^Yr zVtJ8>ZI^ll3$5lOAer;DwfOIg{r+9EGSP`x{1sbjtgEJ?vO-BKfDob+PWDvj+qb4+ z(kC9%X#b;FCqF0o;tchI*TB&n@yY^>NGVVzwUZ6Ti?XvFfc?2|_dzT$zXfA|r*Kxg z(?C7?(7Q9hEKy{^)#-6`w1H@JR&X8fbeRjAmkJhWLPtp2896{jgYdXnR6q?WeJUc4{VGE$k_f+R4e&n_NM2; zBx<`lmK9Ong6oA+{#&z4e+{ggduUbu*pFouPGDZOLkQq;jTl$_`TZhTvRJ)_GrIyys=HckBzd?nriv+0m846TE^&XsJeDra zmHOsvDIvv0p$HM;TS<(!`i_9u#iZC@BudI^kd}Bo2vb~O3FqzwX)y#96e8%`W` zxG!Q)F;+`RyLpX*Fl}7ngqd9-%&TD1cDz3c$GPwq&NULT<&`wI8Fw3XJ^P&C*~HIk zDGSr&X|7XF35MiD|D5VAFsG|98)&3!36l}y85y6L{W;Y@M(lU*mZ}>F@~T0P;l;q1 zBS?)j39Z*3nlvK8@r}?vgIh#_JCyWt58MT-IiXtvDGSCCyTP0Y9^Y7zDC{@P)#q+o zR-Y&+?^9(lQg`?6-9j^5cESum*bm9@CN3BVB(xHkZ$XbFBvh&+Fe{`dMorvY3fdu3 zb~Aw$C*MV0MsRkutOAm6Igw_8F&s9PUKE*^{8`60oS?5XC8YJ)8DU5N6HHJEl04x{ zrr)rbU{T*cwh@!X=C`cD3pxTahtKCk44*#0bSY*wE7Q&O{*2yYnDi9J<6TC%LG1#D z9;K7u!x-W`VBe<>K89FxBo9^|)@P=oBO`an$mG54BiacA3_g=V74>a9MhK= zZk>H@$&y*c)E2ktPB&yu`c`ijL@|1_TxS39O?Qb*-(R0B)z4#CkJP`a!LC=k!`PO6 zIU$GH>eY%VW!T(n##Fqy>xB2+yCP5Z-||=*1!V)>#56KOr2XJ7x0#LVZ>ciwNjCO1 z<{hXk_`S2yoC7>_-|Xy>of-ErX_#6jIs38ABYuw_8Iy5%){1)w8K@w*@s`d9yFdlw z-t~ElSmthD_GIjU0Myq{4^=>S3FCEx^%B$pRn8luL4*Vzr6+%HBwp-t+@#ifYm>V4 zgn`^)Rek&K7qSP1MdfAA&wh{Wz=Dw*$;5P{btkO zY>VG%Y3skZabvFW)W?6ehV%M@{Qh4>2KQB07brw#dz|L_3??3xUd(!)@@{m4TMFCV zHos12Bj)2ZF~6T2^5n^p-(Tsk#XrXUpel1I#8Khvr$rl-{r7IEl$SOK(TqyS!S6cs z&!6LLLNV7{_zUDT^3xC1$+H|fTYqG}yY5&=pe6R1I#R9}#lS5^pULc#ZvLAv`$2uJ z5rQ?lL6X*+4p*}={Mn38-T@zduAL za%*>3#HgP>)_U@}to*MhI%zw%xrc4mc^vtA$Bz4Q>on!oysg{6<WzL6YHH z6(8I(KNXOjEnVRRKC{w~0xh!*lx#CNv*6v?ri5VQq_VPd=J9>7TB1)u#5H7`_7u~< zx(Ne>wdU0;bz=QMH^;A30sN!{g2vJU;hrp(#jRFklUrO1LtUEkVlx&65@(!VlnG_OmGxUbOhc ztPiTvHmrg{pzlvYGsji~iDg{Xq>&%{?-HVR)*#!~q-+%7NDYz8Ac9NFHV9o|u#k*f z=r%#Rs;nk8qpTDx!FZ@`-IYF>V2|M#@Ws!UE#4$;yVzmL;_~h_@2#DYC>J=njDgPd zVljX3w2*)j7J8+F3Hhi$4^)1^Pd^G@!#^FGF>xim*FZF)(hHYipvJ_iwY21$xQuj_ zTlwKXO6NiyY~NErTZAo>W)3nlO)aepFAb@Tssw{jh~+UMSnKeI*n8}BwV zZN6sXQU(t|2X6*q3VRKb)5%r*oaU_=sP(JZbEh>O4)s)bP5V{s^eJNg&CShof?ia# zSjA*#>%zaqB_x<9|B-3Y)YorlbCtO%VOEnUE+>V#c@^H{Xh5s2)di4HVIN;r+cx|% zA%Wre00v^uUf+}E<_XwW%`<1N@WhLBHhm41r`y=mXe)|RMCuuiqcy&vN63O)UQT@U za}u1xTIQJOk%}ho!EYDEAN%?BVxOYS!Na|bSq2|iZFa5V+9%&_I1-5B764O6R$=ne zXQPN59@n5B@s%qW(F4=ds;=Z9D=NH-iW^|sKAZJCcveJ}T>a7x1NGY(9)F>(MMy8I zqt!wadwP1T{BWY(S1~|ZmkL^eB+nfFu94+G5Xp0aQAM}Mm>j3155x9``YN< zde}-L^-jVA$WA5C!V?~mRO9{wdR0*4PwvkKQ$JJDOF9GwpZ0^+=K*LeI(?{Us>%}MBU|(nM z8woN>?umN)fqJMLO?>@{6p*lehZcR$r{*M2wmnia#nCV@hu|Y&%Bt88;TG@?ldRNN zwPdeNKN^g{rM+ay5};2H5usdSiRj_3fO_MdOzL(7D-{@=2Ls8jSsFp2+S4ZR7sBnZ z{>Z2(Qv}$BPB@JqzAutV-#g@>Ql$Y?Yl}2LyJTbnEOH}Ow$wT8XCEogr+HJ@M#Fb7fjg`xjylpi8BC95r$I-QtJA-M9%&F zk?OS20z9%?P+uyKP~@J%itAybkiFLDOZ!)FUwRF?tuQ0nAprPPnv4T&Lcr`r{u^#y zet<4|Sw~+#0W5n+K+HfEvV~dxGbkk`MdkW-!%hQ4g+Tiuq9l3w+GX%{6sEDc)rSVE zQO5rUui%`zQ`*{rl#G1X7Za>vPkok6PEI1(?B?PlaDZIh60~ve?(Rh2k*)}zp6=+- zQ>Y;5w);c5MK&*_?Q5)tKm(W_TX+FKlpYJO7fakB{KyvLSx4?1v@8XoC#_sf;{CJm zmoJsU1nS#1q1et=#tU*47|^32R`eN~mx%gLsO)mWe*(7gCy;eXNrITdf~nOyVbj~j zCoMZcdTz_8(&mE&93maQ4kJN7ebm-z@KeL zx=z5wsG;LOV@QS{w-eIK=8yPRHm_{WED(piBlN>fqiJbrL~SLR7b&5iffJ$A$sYT0 z-wr=cqT0n*GmAbLZu|>*g=7=h)`}MAK`zOpG)T%f5;ZvgcY9&7z^e3f{Ol4nG_US| zD!OkUzY=(O2%S>?{oDIeR1mOrj}0*S!TUf`cpN!|@9V^Pex$=SXl%R$fDrFc28E=Z zSOZMtD-zd^n+~xUj+3Dwp&3MG>}T`aDEfIM=is}Gfka>HTki7ma@UDrhLkciJ)n(% z@a9Y8Sc*|sBq`N|I4;$iqmu2ly*3qiLZ~{BOs0o$DRdK)5 z-zV4Rn35b8=6BwooRn4|WX}Vfa6(gMLxKPqo&F(`@qD&tvJmSr1cMz^; zowl3rL`BYn5G@r{#DwJ=JfI3Y1hC%(v%$u2uU~5W@{sJVrFN4b(Z3mTv&yszW`&?Z z8|&)T{z5cyXu=-$p%)39M70{?G(}#=ztO||5K#p0z^~HFm*iFz?E&lPSm_T=SAX#B zJaQc~)YCJ|UQbkRDjc4J-0j2r_c3vCpVvL#DJZyHGQwL{$d7R785ji}YFaD9=ieMp z1>{834tFPJzFVw)iz^JoM1;8GsgS-Y7Y%qoEF}CE&qd%4IZN^8zWwkEBh8+-Zp}A# z?5T`Z*nAOG@^gxiKL%X9dm3a?-CW`5F2T1K%4%Cf6Kxc0<6H+bMQE^9reM*!d*YL- zag64c%C&tO&M{L_@m}qYV|fw|wwq=D$}H$D#S^3>_y-2zBor^Pj0i*1!0k(WyPHlM zMCff`JiuGzLt!0-KLaNW;Q;%8T)hQYlxf&Kj$I(TYY{32ibYAct*C&A3Zj6@N|&^> z1?x(ffYhKONI4)St)PQS3^2gZpfm_b3?0Aofct&_@6WY+t-8p(?-O^N``qXD@rlZv zVNq$-kP0+z`}Qo79^kxDIHPkL3Ok1$i(9vza0T3UP|1tfpwi1OJxGE&p|*9*Z?k#E zLt7jZ6o_=l!KnlyuwF(LMG|1!KT@VOb0^Mc=5FCY*K~yF!4O`H_Xj&d9l{jHdK#?) z8Y1hZ8UuAAyWrM#BQgM7uhh!7JuMR941=-eP9@RgLTs)4qw5aaA3I@wrQE_1%G7r< z)q*AC&*%Sx7sZyixhJf7t8FfM=}QaDEi)n!LBs(^og1ImDfag&41U>8hS5Szs1m|x zn?0GgOCq+YnhvmM0#3>AXGuXf37}Ksa-P=q_C0cPnd(Wc?=K4xDQfZUDhVR0`QbE_ zE2dM4%lA3p+hC!IFy{nQnn~5`ACi|}B=MM_WXpaUVmE2l@NCf~a2Z}3ztkjrFYq$M zwVj-99LG2a0xGx}y0bPOrb`0#9IX+8MhvDuZS``IPoErJM_X~PqeBFSsejTSMH>C-JGHDCtwNEtlWi8Q_}6fn^Gd3bo({?9KpDW(Af5%dF6*BaJCJ`zy(&NR+XHj7m3TgfQdD3v=2lJ&Ldwa1?M7`x)n$c)DHzC(xqkh@T zqX(%15cHpzb~b>f+B96)u4XN#rPkzd*75;H0py7*h<^xx6;I+=+Msz_!}6NhitUYpMQvYD^U06M93ZYlHy4DS|NshUKE5TW-sGy+GJo9M#ah~Z- z(pFopghC@-T1l^Ezg_pa?LXha!#~{ok2uC-hO?HfYIR(>{+IFT4>2!*FYml}13WF` z>34-ftF&Lq)$@l==-j1VISsATf#)Q=g0M9#WNoV#nR6Ii1xYn1u3TVsF$5y$`{xeN z&YWj#2gH1MBuV z?k{8u7yRRsanM{kVO6WoK953bqXrxrg*XKrjOVp;T+&6wR%=(OxecyCt}?sQfm^qB zh*f{)&=Qpums>kJf(>+xi}vjy2(WINh0cdFr}IbpJ{+@gEcy4ZFJyc{TeL>hgqax8 zvD^p~j49KdHbn}C4@jeK68)nMEfk9G5}8Www~G^6{W_d?h6b^sM9K6`N*#g*vz2|& z|009P5F~q(nSTu~L&aO^N7k=dqrWl&r*&@FA#z4yej#U@qwId1EKYbj*hmLf5%UX_ zIR!JRa@C3eY*P@3MiC4;rXRN%b=4aO4Xg3$D$K?^(r{fk{A4(Dmc^O$Q2?CrWoiKc zfXSR6-;FXav96NiUAAFT>n;t3AiR#T%o#scSy@S2^D9XEm2G$XOGekzN)z)(yf%F^ zAXhJF7F`iiMbTtkC9 z_DKQOhF@Eme_}~H8Z$e(gdeV1v*y*txH zaj22HTBOyO;V+g0uz-5yHmd%6KQRnfy#Dv!yS8rK+L=-XzQddF@B`JEr& zRPBY{Ad-KRbC{orub>BdzSsexm0A(4Apv30=`J<*a@>`mNl5an%mX}RT5ONq9U^}XlEzbD2DppN;wb1$E@Kad(?MwF+_2c@6nsAF-H?j>%Q`fy zUM+p-Ixy>lmLnJ(Nrv?dR~%DASi5K6zH^uz_vPw80J2Q_>eF@At``**kr_h7555^S z!lVQ;_^64u6pnw-u3gJ{Tn3~(Au;SKwk%;#pK|Gw%hgs@ME!vlme`8VzuiZYc{IYH_f3xa@kI9?SiBR4H zVSk5XORVcT^-ba^Gs97lEWFeDSd?pw$?Tea`zmn5Brudu-;*~D+X%xz1R;^lu5b*k z1fd^;6B+t`3XuSm<8`xZdlbFoLe{KXSNWzUoNQmX%-HAUY{>}_$R$HBCXikbeP|ex zyDnvll9Css8XaF@)U%;{F~FMSENtMw>+tXod(RQZ1qKi)4|2c%$Ip)mCHD5DT(}2h ze&ZbbPAD}d0OuheT@~5UgE^pZED4H;xiP=l2x`&?4L94)C#y|S6Qt4(=_?5bS+ zlKDW2oJ@+oF})A7eBet~IQbNb75(~^&*>$fqm< zsWes@u;ZE@*{j%~8}S2pkp+f-kuh+kEu<33e8ag(c5Om@VVlX2s2P`{ywkAJmn46w zdxZHbPGCU8v$Cv*k+h|^p{{_VfwE{6267maH26#j>`C3!i#fEoM3L0aIo?y=wDoKo zT&H>Y&%Qyx+by9M2REt_;Iko|Xj!Vze&Zz||BEh>#TbHkSpMJ%q-)~-EiEnC1|U}4 zFC&%!MQzpX$Gjif?siDLw5A#s+vDnjO`6zfMdL%p0PIEw$S;tF11BU(fGcRBn76-Sqc=VhC5>}P zCKpq%s&Gi%EZxPt`G}c!Hq={u<1#QbOzZL)2G)?=$#rsn$(--T$Ul(Io|vlL8sSR9+Oi<5t=O5;UJys zNbZ9aT{HNNi7&aekuK_tJH0SR0J_H)N?XXF$@yUDVi%^$)VZ+p=1@@IFsVG?D*yxn zgf_;VPeHuP2@Z2(lh$L@#Wx`!#+PTa2lM%2sOmVun>iAUau{z1yg{Gqn|7KYx`vxKxE9?$;4{%eX zCqqEKW}`7C0p$#cQJjP)LGw3e#*wKf&W#=xZ!o@ht?GcoD>39;2z<9z8ez&4CK@Ab zz(l!;jrmCV8Be%4(S5wLg&8R7f)bjK9cHVSKiL3fdJ|LAMKd>~HwsF6LfUt^F}s2v zH`KV1hf#X*IWYVw#*Ce=FqRoV-xVI>VW3*Wfu9wt3IuUZvzu6Zx7@i!U@O_#cSRL5 z4xz8^nhIr7BH>M_MiUbgRskE|0UVU*vI}GW303yB3BAzSHQRUm=+T?SW`F&Qg9lOg z4_Xv>R6NvlDpG)mM6o95}L zP@oz^LbB3_=YUV$DY*hz-RF6uVMz#gLj31zqBQ-B#6%`;gy$ZokN7Hhr1)HC2XD}E(ZHN+6zK%&4u=6Uq4k2wlI4J}K207)T8H5Dp ziK4^oRf~D@R|&oUpp}YYF1vR9s*^;?0Z4)-ZX5j~#bscfi8ZgR?VGOZ`JGwbcX~y; zqhf1;jX^K%SC&60Or%UWVWvsyNZs4M_TRBMB#LNAqZcE<-1w@|cdc^O2WaL5M=hkJ zjRC8XxpFAzVZ6l0_SH8IaPWPxMJ((!EQ?z%JA1v*|M`fsE-w`tjFVw&*RH*5t0Dp{ z$~M#;1x>dCDh4#3FDT-pJF_9s{YIyc)|9ka8)GsPqJy*GVi-%>5{^6r&L2t3ogvk&Kc`cKu*uVIL}>gW~Uj+7T;QV-(|g)QK!rK zHL0liSQN>hy>uxSks29_29}OU?N~6k+&PE<0p6hP>w-H40x(#oC?{7}Aqfl+=TF?k zmVS9Z98ZhHocQ@vBagh{6XjE}cG1!f`gz+-1mOZWb-h$i70UqhFi`kXnv zBzUvi*j4>>a7=(3)n?sH8ra;?(P4@Kc?8|n>5<2ejrDyI45w8bLS~;8*|uJ6A$Y&n ze*9PxEGbeclI&X%tP!PcnoMSaC>Ux8sYPmUmIO5>DAKP~WAnXpP5Wl*-8C_0%-bID zg-Vd}oO9-hHTEka%j99-N-Ed>f|sT|ts*oIl9`4LDB$+W-ff9~9oBf^;zi?qAUh~D z7x6#N%3~J_i2bT|*=!%8EkIL7@LlM6at>a6o5xw>EiNV^4N?X_6tpffKK-%~BHxUo z1tPG8MuD-S1twobT`(Nww9-kdrcZXW(pLvtzaBKaH@Ks^=;ZvS87Vr57$RQKPBQSp z-^!{5cZG<-!-w)nS%iSLlBbgj(kYn}*5U+U%&Mi zv{A!NqE^;oila_u)%(Z`^KmnU>4OFn9Acu}kE4Mcmtm&B0W$xR?3wz@ut4I$-nq}U zr;Me8RnZo{`+&Inj0+=0Rj*$e=>n*xFUhc+YI`906tOHaiW5;a&8`$_5+&(7sg_`X z8m;5a+qZ8k@2FTHRMH@`$xUMsuPva~T{wS!3Hw1ZRtM+EK2xF7@R;nn7$g+KnM&}d zgnFv}BsX`v1xc-7K9VMpz@dO*;W9Uy&#LwfIPJ4j^x00J@Q))1-Hf3B!In8)qY_d2 ze-y;qbd(5|`x$?ZB9NrGh`kbb&}@9OXUJW23pVn+8Q2TEJ!`GmQZ66z^rL3>+{nN<6#{ zc+$qEHtR0<$Hx^B^p_omw4wgv8k0{V5H6D$WwfS8o+*UhctCo-XRlNUNWMFRT(*cm zRu^F?50vm}G*6eqR6(Cja#bWUBVy++jR%IZ%6R|jShJc80q`*AZG1mOriVvv`T83( zphyMf0;m-6zS61B^=&-W6S3(9zQb^Y8DBG{r4Z8xPNO zTQ2mrOnG$ASwV^X`v9Ltlbl0TzI%Zfx?Z8le=05+iWKG7IvUD5Tx6#b<`oDVla$!lrU%nqs z>C;4>4w-Y1F&~Nb>-7;KdJF7CLHZn$2ayr-tjJ7~ooA<&X3M3T!q^?S0Vjlx_0NuL zzCXq0ks#gh8zX7{X>IX-p1qsapF67yPR^ANBB}s~+*q-C#xTC!s0hA4r6_m=V>1fj zOs=l_d&~w6aEuAyCqL7A!6Z1;%@{IvMlQx)Em`fROwi&)dBCr^y$I~k!Q7a!sQqw0 zTOo=M{b}ZJ0O!aTh-}}UZ<$%em5;sMD=bXc(#v+T)1qN%$d^jX>UJ>l$yhPNV6Y_t zH<_Yn5ksYNI((h`j^=$I*ykel+I!@=(FIMaeasx(uO6_=H4yZv)LCW?93!Y5iSu<` zS?6M>QDJP?(&P7Z7%1rfS;cq^7!8Z4_=31!R8Ov<)G?Jns{)J zCBQAzc)ueM)zGCOq=Tx|!sK_yR(4ksrv|sWJevOEuov))6Xnr=l!NzkJ#lPi zWfoA6W0jLg<;NPNfWb0OOQ>K1^$UY+#fLQStMA1P*rhBR;{YFbLVZ6-$=&S(y*vEi z9k}$p%HV&nj~dU@qQX}MivBKH2o%j}Nr&q=f{=I~nR4y=`8h}?h|6@ZZcM{TFv2gL z`qDeZ5i3#52?gx;NN*zSf;u)|AH!S&rWXrGBXg|q#t=IM%@-AE0E!YAa@h-lq|t2H z)_-6^Br|e6zXy}NI@SkmW>}IPq+U(4cTdVjGvB9V-7{yJlXQR(;V<*r`N9cUr(f1J zN1#u*^)ZMQ`6EPs{dC7J`fSekJowa~E8o8gbuI?iL{M~eqCO!6?!ioX95J%`<2moA zQFoE_x6JHKm6S1@+jO?%)dofLrmJ_syhXh_3JA}+*lG<{0LUPxC{OsoGyzRfT?+VlrR+M@x$jv$d^_u@&b80`-( zP#j@)p;E6)1tuHWqKfh%VM2oJiJ9kd0I+-VRtdwSKOM6$RKGx$bnw#0bL1PWVx#-4E5nJD!7SoPzYU+mqMA zs60_s0LKIob@?0hSKi!L0T}hAxx)E@r;_+^VlxJ6?mlvL&ihxqnLt-~I7m)BJvacm zuJ0Ee!h}RJN{K%j73$?LrzV?bB~$1nB~_$M7(%5g!lt268i#TR^^bp|;B)Z7JV_~7 z(VIS`9{|e*0eMs)3^q#PbYKAq{ssI=HCEJ2IbPHgJQPAGKIYA}wX4ER_M^cL()0z` zM!Q#j{-~6W)PD>?4}$SLytv#!`TtswJQ9G-fwue+J{1(0 z0@H>GIrU{)d*Axr+LS`^?LJhup}|4SUv}y91CWBgrE5WLM`hQQf)_$YS^9XK0TTqi zHZokSP78DQYTg}%-BBC`nHYiPNz{WeX89o92G|~%&cqzS3V_>p5(~Y*#ImdWH4=3( zgB?KIN&-b+DZ^$i^!W~7by{ER^5u=?To7`)jI4`L>Oa_{G&=!$6JYrA$PRR1;>#m- zjS-E!^}S3mi1B6RDbvvuKR$MeO*+5cZL;^f(8nmkZu~B^fUWk(d>#B+oZ9pKN0Bam z7m7VEF*O?|#>yS%pbueWCy|oS9_DIt9Sv>bwy6iXwBnjrJ29S4iLL9Xy}1)@Mmo-R zlg~r*zgwXUdy)t`2t9Z`z9t$2xnwr_?i}s1PAAkDuy=C5uc7N-jIVk)5g#94iDaMx zC0@`NlE1uPBBvBkxI@`z^PLe{Pr>LES^|>8WQ(MlJZrZPHO*3-9h3DlQ38j9uX03< zO-kigt8cGRdaL%n5a+!2Q&Y^SY43!JZk_uxnPZUbqJEuDZB2!8qmWnvJ^Q zc4ZQp()-^rhy5xe;_C8r_OLax4}SEjbQ%zD9OdVmY+$!TiCCK+xY(XfA0FuBYKWoh?!B<1h|b3-RtL{26$zXME>`ebj!s&ow;8pAIHleNlm_9 zxITNLJ66{mSMz_ZO(`m$8~Vx@uzk4L?IV@i`El7h3HzVYJ9h54mpS)H(t=ae9%t8~ z-mg{x3)rOZOpVS??XBN|pB?Taa(He57k?n{*leqQQT@;ViqYh=o8x{@W;;|`eWj#{ z^XDoONi^?EvhNMDnp0YwdqPygNK+lYN|(P_YLkN&YO4Sw#3kEUtB(h723<*VG7#@6 zcme!6ld>84Nd=wcDz4vu7ytQH{8!(c$8NT5UER*kD|s3PpE3e}*SZ(mNp)W7?fEXW z26V{peHlZmmCxc(CibG4Ln3wZoy+G{TUoo6SFIO<48=3sJqN^GYo}`y^)s2K{tCI5 zxw%^wja<@5(sXzGz68bY4;9Bvx(^^v>8aA2I+U0=eMr_apzyTq|BF6u-|)A+J>va-?&nmBbs0Hdz6g=7nw@Gn$NwaZl+g+ewPj|rKa)BcH+QMG-OUpdOPZlO5`nN8H*x1T;Z?r~(#R85Wj*Q_gFf<*_F)z%9QrB>M zi8XGQ&%+0DQ;ANj!XTGX9hJ93HHHD?Ah`C`VIj~N^Y-@bbf*c@+xypFYu54B2^$r2Ah9x?otZvXy{qDiPhs2HlAkrQcu3{) zpp>3NiaY;jyRCNqp6@95K`apRm&45N>ECo?n(roouNz<9_x|}Z*dinPtLsh_A6zRd zD&Cy>djsWa2#~lX>@)8h>^e3@+Jjro5X|@7y>+WP(rYLE!u9&IcR2sPX^r=p`1`eTCi zjK9H_p>ojhjrqm5t z0#6}IJEFJwE~WXKbe3N77E#?b*xt%hh%H2ykvxpmZV9{aMBTGl-wZR;(sIqUDT?}$ zn!y0DaWt>RIWKi|xfz>Wp`|-qB<8kAfG(&LiL=nvrL?vi)2S_9l&x0DPw;W}=XKb! z`zdHI7x%57>!%rIuNx~DVQVwZ`k%-1NLuIjjzoAKlEY22L&E=AMz+#jc)X>d%CEOZ zF^q#Y2~6Hv2ELMet@!pS^)n)x48tOjBqUP4e&M2edk>&;bd;yh!BopMJO8`XiF0~- zmiy>miz~wq8Y>=_!Fbt=Vw@?-o=J=DnM`;;KlTS)N-qM}A>Pi5sb4 zk?qpwVA*7n+@o#_xb-_khK#(7PO3!cghakXy5sSW^KN$(Ah96k5Bw{~in5JhnwM}M@IC&jT zYYciD6qGG!?RFvd{JM3|bY1(Z6LsY5&6qGTdnHpT2Eg!mM7L#MJQ&#ik58{*g_L#c z7X#M1O`FWS`{UD2O4xP^MoNO_7ndmCla|X-D1$SXgr1^K*rEzkYIYu+Ki(ADAyw`4;+u(g zpPg*_HtVYX{?x+KV{g-AC_>4);ju0Hm12>{-xrd%^?3N{bAeap;=|ebFFt)*tlz%A z>)e^wY81r`oFa`n+G3AwW|Ge|&&5OPfyoc+Zi$RVjQo@OH0RYFzD=tgcC+QR;*A8AWie1SPiG~r zsGhq5D?i$yu#rj6urGVBp1-1A3|Pt^ zgeJV=Ur!#d<*qqOY#ojPF0PmXKe}c&_}I(H`2A`*#AXyF`#1asEp`q#ZdxsFS&BAhF+o*nyXQ>>SDh`!HR z8HfDv?+sqx!Ob%eQWhBv_-HrRQ1fBVarTxIkB3^n8f+@OmbeQimN>9)d-vFlS;cSL zp2;uF-hbBPx!bc9_~Z%`c~19%C~ zp{+2$6b-Su<^&u95yLC`_t=vhJIVfnYh)Ws8?}Vre@NEV)MUoZ03kBa&NVMGcdK(7@pDh^Og~a> zhDARgXQGjYu)IGrUYzz_ z2*xM*@a$CkrMW+1QD-@DH1So!hTa;B_oqNM`1BWV9bHR!x9+-xY;=tz(S_8H-b8IC zfH`BCJ55WT;p14SWC0j>jew_Q>;vpNTs=etYs4pgh3_jX!^U$Z(-Gx-)pA4VlH0qd z_T!VEsG(R}Zx*d;a*91iB3Atd(tc~i2Py_8pmhYLhDzT!1n`c8f+c&Nty@>zzc~Kd z!Lq5};kfhztvsC%&-=Xi+?LCDueqUYsAK*0^LHU`yimgSefefDuWM^NdHUo^huuL^{c1e>`BloJ1=cl9JkSd~BpcS3ZTL=9 z|1>Ol_Q=FB#>^_=pGMkE=|c(}o17kKDv=PQ=+_VX<_~TZvFvl2=eb$R<=^v3=;BhP z2|uw(E|s^3ptVjjGkNM@EhZUEN7^mTeni6QB}<~*D&z#Dn9Fw_J!WsbfwC2eMm*vJ z9tEdla@6$e&`kaG)2zdy_?VYjmnoaa&pp1GS_mJP@pG`yHOJu7KekAZ!(B5k3&9;- za*v$|-m`bloB%*%uthogHKGfJaxc%y{8E~B(=?7j_$}!#XV=|2H2EwF7T?m+{gShN ze-+B5t(dbNrN-B=-z*S3Hr5bj)c^Cf72|<@i8s7dz%p6Z;qoNYT6Ayi8;7; z!1+CAV{KzcLeUYqK9L+gyaGrW(dz{-JI1&=W%T7ZTbJ6PDZ3Vn?K-A6`x?y}Jd|MQ zezE-esqe?e9xA$%Z?rojwxuF%6iuLH^2`TgBGVPgx|rnlO0F+UgN!)6x4~ZAZE>>U zsD(~rYyiy2$>54-flnh~Q;&VwzFYL5Y1Las3iER8@?fZxYT&LqwLP<=an{1j7>y6E z({)8^uopq`pQ-7y%-pJ%hGAtM3#7Vp)xs(=G^@Jqt=l^n$H@3~qWV&SoMeZx@NAnf zk4W<|D&IDVxc;h3jFg|_9hE!YJs8kW!)8%%SmCtm9CacJW<1fes@QUw9N=uB+|!(j z!w+|=PCbjz*cy&zl+LjM`m-R}(zSytyzqb|YruY-PFk!mR(kkqJb?VgK;T3)wsZiY zC4|8WM3x4_E4MrZIi+gtryDlQ>RqFe1{rO6iAXwkBjvk}+xkFgBZ+Y*p5=zvxX*ML?`> zW~P)qGeLWpcLGj@tApg4K@u0}ikY7}#xrtrQD8mx=#frx=X1lUrO#pdyoUs-Zz@n) zmTYW+SAud@VycB;r5#g`+NhLUK78dq?pW-*mxrKE*_=j0G}V%MJ^#kgl$(yadd-ik zb1aJ^N1!% z-X=%6`s)i1Bq67OAJo9v?cU^jLBPG5CR%?*7Tt7TWKMv2w8!9Xqq@JU{#M?%7x zg;h9t^7I+FJCc`@f5{``R@Nq`GWD9?z+}@V-Y2GYy(_#n%<#Qn=kssH!>z{0td2cy zbaKvY;#(!?)XuFxjvbV+_-WqFvkq7IK^Y>jSOh9(HI{YXTE>KN<^U7yf2--c3ZtkO-|dc=DH{zMWlG6hPry!s7u3EKDsQQ%a%(zueSLN zDIiT%nD#oPGF3_SwkNJSI7b-vZd8$K-p2-H*oZ=Cu^6JxNQDCss=vO#h{UR&Sv{s0 zy%V__>E^DA>ckfB{ypWv;pa!nt5j!1;4r{K-Noc{Q*#B{I~vD{`Xk)OWs_a2y2kt`lzZrd5{1UQv|&o2#4CEqiMv+^8V&-e1^~`P$I6Fig)P zW(W&ORw8DXuw}Uxu^4Q(VKM$yjH~T%(4(1euM}%9w+dK}P`}ww=|TCIOJ3vkqkeQn zW?IP;(lQ?O^M%)D62AP~!<1^}eeFY%g%++G(-WSPPw)8VN2>7V>#PPsGyog;{E*BH zzxdd91JefyHN&_CcdFL&)~U6vd-kud??{~i62au3<85ww6vUVVN7~b(7L4$3<)& zzj|s@VvVO?`6;6H{R6l8_`e&5>6RfA9a8Pfex2)YFf_9q6DS?!1>6I(z}15^p4Fx) zFkIWiYuDte0l~9Xvcm&c84jQer%LU;Oz;#mgnf$O&+XN_c|Fwl0$swCNfPMp=&0E% z6^;m`23&t&O|gr8j3~cEc^py#c_95##cM_?g*8PjT%sYLZa8bp9H_eVT7~D;r2Dj~ zz6^FzF^Ny}=F!(sGd;L$#tmwadKMEi;)5GsD_lbdd!v3w}_k?%Jfj;m9$`` zD~NuVo&ITvSRB#!U3GfkDb~Y(y$(q>=iuJyGXtEiPOTcs<*yZl z&ZE!sw&&$~7)Vn%=E^F!mq9^1B-3Q-+Qc(^5a)yeklN$;W3)EOIW1%6r=i=VH;xH5 zgA9h%Ie)#P-T&c3($)O%@E+t~FlpiSGB;`I6EQaUiTZKzM2cIDJ{p0C5uBI-!Cpo_ z?3(Lra+hGO60YnfNP8H&39#k0KhvywZXkU_>2mmsrs5L9bVc`vEN;uW%~z^a-S>Pe zlt8@h&AM`RBhCgcoDb_dU6@(=BF!-cM;C{*eJ_vxrR&#^o@g=zOvX(OeiGc1)OcXd{xq;AQ){!FeTbWsTtTc4wqtOea3>JY%7GRN z?}FDf-?_5p7U_jD@tu?=mmZtR6ip;};)tkM5Bv3Wh&|MpnQ@A(;ZaMP$@p%Va5M{4mKJj=dtTmdEC8-{_u-9PPp(y0Dm= zt$&bg!g)Ug5ZbEf>@c3Dx=+VG*6wT)Wz(h@R=4@e^$&B?Ze&jT$|0WzRElIzqPwXV z@g$j4X)ho8u2j;;C!!G7e-Atcc8@CyXN z#rh%C%TjOZzjn=uhx4$s)s@^PrsR~6ww@l}aAs(Sdy;U@*fkBku(&TB=*WdSi9d8+}LwebjOX=DPLXIZ!!$+Mva_n4Mj@y!GpoIdbEYUia27j~0yj zaf{JN3gG{c&K!~}j=Pw^n zWytoAiszC{GkF1ye3nc)%?Uu$`ru8l{cIlY9Cy}*iA9XrFjS{Ad7Yl@R z<{$zGzKMD@?ySLYd+k^f%+6ZVw;Wx{ zk8aTn>3t1O!1(x;Nq@`R%o(R0{QQ#L(H_XMNKWIssm>1EA?0#{m!k_tY4&FSLmHlQ z%jGcVzKbo-ku z9!yz+6r0%b7-RBjNZHC@nM@IMd<%WDVNjx3^wsh4knaLOb+>JE3*pQ8J{psnU^v=m zkUjI^*7Q6XgH6y|Zw2|Z19F2je?gj07RKpbhI}+Q!|A?$tI`>uxDU=}S9jwiP zp=I2d9USZqmuh#J4rDM8bMb8YauW<(arj=hQDFq-&=Q15L!0$(!;$`E6gQ}7Q+0kl zo96oMX*6*-2Q81tg{oekvCd+0TpX1?f@i;#yDXO*%TcOJg1N!Ay~|t3W}0`BW$$6F zxc@?Ld-2zsp8^{Uu-g;)47DzJz1ul`$Sn82btYc8C~k#MOd^01K?Sp|Nm~2xNh?~k z$G|Tp#Rv%0DNzpYNHe79T+0}N;+J&p)iA5Y)@1Qog6t6&3TnN7qfQ-VUrQE*O?&A) zRm14`TyAqrciyHh;h=>CO?XOOlEJWmpld1ioqV!C7OTU|3w}1^DxmO1*T)j#KM< zz^vIbURdZxsqM6ttyEG}1r*jN;aMKdn?OJ@>~Bga>6ePTT&+#hThBx?33^ zm z=;ZuZGCjl_)kS~T5g^r-r6Uv&=GMJiUjZjMXR7-b1D6~f z@(GS5cSu(~nyK6Lh6EZ2p*j&Q#yIsW>SqCMdyDS^YHZ9n1{sN-sA|`=UVC zCvk^Z4d=hECheA$4Vn|&`(r0){9^G;xs40zfCw;H4b|1v;RWxzB7@07J{AYUjOaHe z(GqmXgMK#)6Ry$*L5j&!_}d5F(lBLz>noI|=yg?aIeXgmQ6%AaGcq#53xIPQ4cK%y zWcnq#V8s_un@Bl?t@gh&XSms=bE|%O_!Wx}!6rfhg=^33#$l%P8Wd0XB+6g^b`Hf# zUrnOUPlD6M=SbpB&3gA%rDPnyw@<<;lGhG)J5l-mIR*f)r?>RB5Fr2y31=sNdq4gY zZ>TMtJ*?lkyVS9xD9n5xxwZ6W1_?P)CNNNt>PFTv&@sc;@FQ}Gmr#XeA@#5kGkcc19?MXsuocdB@y6SG5OM7ekQuey3 z?_E*if1e1F)vH%_Xt;SixPrt3Y=*qQiON=BGulr&IEr9d7SuJ~=mLUUt)0!cOa*_J=F5%HSx5g|b?n0EIs`@Frey#MVsI2U2 zbl%A;5edtJAC>ZT@{fOEz_+HDN}HCg+It~zp*+DaCIwc)twfIsR!g*1{gCNVgBAAS zR`|BJ=zPCbCyMNWeS-3=3WoWG?PQrkRQ473&lSr`%Zb_E9syw8zEWTyN}G69|+#>zVGBS{{U5LLfDqE3p5?yR-|;4Q1FX9jh% z551)lgE*RLS)Y17a1LRIxv1lrE!5*-TKeGv9`=kFH@}qaK%w`<0)xR-JBd`H6keHN zB^k$#?m^B8BJL|hI?lzNgTs6)5o$NY)r#$>_4o*0@}5BA82<0SCn-sN{HVtYbo{x! zn$$gkX!`K*maJWaa!6qS^_SctCuy?6xT(4+6Yl^{ku?%g$J*@Jv6B=Uz{0zcEd{&} ztp*tKvz62U%cGa6V*Z03DKx^CCwGq%hF62OiAy`-Cjiz_|NHL^qzdi?@@e*470U+g z+K7wK$hxw96rJa(Bp(KF9vL+d?|eN}GZLHJl@=lI>zb030Z?*HPjr8@3O zai0Ku395dP7xOXn+yr3_s_A$>k`AC^UcQZ`lxF+f*ghKi5?YZE3dDznVDH^sM@u& zk5}`(mDn^3EP*7GRFM*RpS_~HqghKgkcTU=?(KLq&klOBjL+%I@_*&0D>Nd5<6)g0 z_q;$IZdI@6wOzFnUH3Q^DHXfs>D!D4)wC0Jp+NxO+*K35U-51ejO;Ek)7wB33832| zYk26|OCe-Ypw92v8EIHrQ?MP=ePO*xhc0*(vd{-2V&gE6whb&Mh6Jg@Ev@RxFh6wY zC1Kb+wnc_Aor;hLmY(PWiln+uS8BTDi(|7UE}c&?F;!AWPitfKyS4)TojYUPU1lF1 z;)7B>WuO#VC!Y)`51idaU@CRZr;B&aB3df#b*O(4D(4XM?nlL#H;`bvUI}Ipt>9YX zo2-{-!6e$VTlY1<{Q3&D)2G#PS;vnY5jUnphZaWy3%TiP5uw3MpyssmaPT~>dTX$k zTuBeHKbtbhB7}zq!Hyr>(t));uaU%b{Lv8Bkf~z64ETLCotiVT$0(RqqQ$sewMs}N zo>^JBguTsWvRCThq&Iy1V$-mh!!In-cZ?MsxaH_+C7&-dlROf4==bvG3B$GFO}>5M zYKn7%r7qJvRlz7X-|^KXg-0w!isiEA$>T#|@jISkcGhwI+6=nbQ||jOU%g^w*2pLp z6gG(lXHZ}GPt?!-ydXjq3o-EBJYJ#Y+dUzctClQtvpiZjcP!6RDO5Q92oR7lW1QYC zS#cL)N$nWSpc9Wt0eAbu78yBJ9OVTR>wt%WM#F^KHN5j$zKAlQ8*P^Sw1Qn3B`7E; z+??pfw}c>({$bGc&}_^${|YW-W8SNImDk@d7Op@#{-U1qKCu+>Z>nSEWK2GfbgB!s z?3NO9>rJC5ty6gQ=vBepNn4nuKgizxxohjbPcz$z)GJM}8g)g7W#?>)*`q)F}pD(XXx&`>pNq`w5JkfBWH0r%s$=NR(Vrw?UIa*M$q^(xKK z)hY(^Rw0{1L?AHJGCRdTN1>a0IG#E7p-0rK9F|83=KlQ^ zFn5}y8UjC-jpmh(Y?WzJjWX1O$rzsby{Pm5kro3`WThT&MQth?OS$Im zmflX^TLHR)S7x$($L&>d-Hk108I+QM%YXH~!$z+;9Wth`MaciO)B6$p5(D8?;bmxL z2qx0R^qM8QF9IM-^B>u2VaYkhoE|qdaHk8oV2L699V}q9|efygIy?UDO z#_Ln}6Kb+*r@H*qdi*~wdM=0 zU5MLIlE5?$djj`rM4mSP>i2&(%fo(WvYy3)1A$XB(TD8%Z$ul{eSM` z>c+$Pqru}VHw-XpkNasA0BUpT9uke9nnX)BM@@mJvVdeJ-4I{DE%?}_q#f;+(h6y| z7r$Jk_z%JmJyu;}I8~WuXBrI_#OaTG?l5EorUvcLz@)z?1Xrz##gtkq-+_7>yd;5z zZl6L{XyK$siZbc13rQ&}V#k9z+o}Ka@X4fQTvcwvDF^R(E{@L>#BIqQ?OW}ttr^9s zSZyxLJoW0;vxk8ZtAr9HXAc0j;TPTgRa}e^N?b}da}@#m?G&55@f)|!PQ3nr|2Hf{ z(k2+}MI(?6#i-bWD7sts{q$X+_Vx#X%c_gY!>dqt> zA`p2?$c`=qD{Ikzf!KE|Yu(kPF3a3mxM z>BEQBd-MV8{qNTn7qG^p&B-ya*d%ijH&vBNLu1C)a$Z;+sGJ6r3G8#WO}RUO?}7 zAlz+N*nBI-K{{*VQN&YJZ0~J2l_%SN+|MPZZ2R%CxH6q6{`i}15)^%0s~~~x`YsQK z(J~D_s2Yq|P=z6p!zxv^A@L*vs?i3i*Pm(I@(eWY7Ds-Hh%oSbg}elqt;clo2-h6$ z@!zX0r`qI${w!W_+2)vfJb4ZoU?EO+uAEU!}MtkdbECweBkZN{PL6 z-1lsMqpNpn3vKF_fQOM}@Prt_9tT6pVKopaV<73aXdo?VzPQ18DGz$pv@b<`JYFM;zqd(TQ-E%-Q?(A zCdMPJ|h%4P#?@Vutpngny{+k&&MgAvRL9NCHKp(X*fAlhe( zw6vSWz~Yx}lAZe0F)}(jB~kx+0b8kA$&=YleScX6U9*4QPO6(FG94R<07T|mq{Po} z%wpons#^5i6%EgIGtWItv+ZXb+1iv*`)tdVFE-+UfC%CD%w6{_Qz|Zif9{T7{v|+8 z$UgtnNwrI7txvx9dQ%Bm`GbrJ+!66^R)Ozk4(SINR%{qkvOzC`VxM(}u%Q?umZ1XI zXX{_Q5h~^!S;+Q>5NJ4F^xj0*x9kqaMg<#JAAf&Jx>qK&#D4S_q~HF+_(uhu-Mep5 z15glpuDhn>N*S0~RHCy)r1Gk}|64w`4_)LmG5(bcVPNf%An^{D=F1#AKsf33Q!?L* z-R0}AOP(Bm+pM;lBg#E&%n(hPY|Y8f9y^Ann$>+zah+{aB0BEU=B~5(oKE_6_LND) zY0wP6X;eVeuBTK>tld%F$=Au_M#J0=BM*XY80HKKc0{+_+i*9N9nyTQB+|>%AsIvM zazLH4F$?iua zv34yd@sQ3fVgg*ZHY#JjxrYAEP%+TyO@{P!9bZ%?yG8m6g2(gX)ZcfTT3= zw#Gga5MIcp%Gosg>rP|C#TRK!HElMt2_K^;zN(dQIy^Vzh+;s5bE`7sbRO}Ou0Hfo zk?ADB%KuKBavW!7Q!&8;5U`}~n$y4gk2#g5&hWVoKf?Ds#VGr}hRomz&etUnDB!9? z5ujsK0!|8bMWd$2C;-Jn0nQb3b5XhuAbP*b;3)YQSB-J6fWQjT?}5sT_68l>N07AT z9n>0^ne8E?2vCfci!ry%jiviBC(WgPMMhW!I9(u<0(YJc z+@cINz75N!i% z6^_Zml9CJR>W`Wv=cVM>YpJ=SMR))U3yVzde1A#k4!%nLt+9lvCK2K}Eg>my>3%EZ zxtQUEYv|Qb$7_2jjf)$} z85FFb;*p*1?ZMhX<7vlNQwl!^m&k$HnbBO=5zWh&f39OA6C*Hs<@Bv*5DUSeXJ>6p z;`x%4n0yyquK4CHMwu{IL>--oq;c_BOK(J9>F7#6^kj0^VJpKsJ3AkCuUg-C11gs^ zX{nt*8Ji0dBtFq-Jd_4V`e2Cj&MR{>KIToDktx#EkW}zN8=W1zU^j}Y?Gb(H_PW(} zLG=z{3lq_*V@lnOee|EQk0ShLW=17t{1!+x{Jjr|zbuxP-9 z9pju!+GLfL8l^xxQk=AP9l51@AE#xeXo1nYbonP*edc>DOh~4JBIWcwB8_8SIM3KN+2RCibVj* zi8AFriIeC~-Mgq+kNvI+1rrWgNW{6!21&Z2S8Mf&dgVBcZo7xu79S-3zx5F<7+p5< zMP{YV*vF{+(517JM!7NZT{z`c%Gar|yrWB%O>J)fD&w>ebE20oO8)EP#=*jNOQ>+{ zxV|nv`zZ-kfA?YEYk$*9tpX2z7vj_WnRw2NU0w8}6}J>0TlXM61*%M*vnGnWkqPOS zhNMCT9N^vrMR;eL`K~WuT*Hn^v0MT}>>v2Nm%HMA=4@5N;j z5MtyZoBb&?9S<|xxxXU@SYUJqL_@GAKI|Ao`)IZmnDioZ8j0rBe-dTV+heHh)-(+% zhSl2QxybjH9vbyTTd-#@9rh@6O;@3t)>9A?niKWJt!eo|7zac{jetb}!WC$yo({gW ztIV;50qX2%`s1eqH&H9tSb8g8`eV z>}g!C#MHN?P_frWa7S(cE#(P)KE=AD%u!lz9FnIm|Jvoh6}^j(_$|q3-BO_|FOh|4 z+ELeO)$^ot{Efu_87lJodg#x@oecMwm^|_g;;w7uSHJzN{X8CuT{(68Fnfht% z9#Rgxx*du~$7N-QYX{)D$DjY!G!^q~+DZQ-v1=njV$=PlP}77;J<#{nYHg5^-X{z8 z;$284_a}l%M668ctwmr07vrM|oqmu|B5k57HXrD!V7AFfm(;_4fX?kTbZ);!S}OCc z=3c}?$T2p$+T7w)$j}4?)RNjb7`o>^M|{{FJ*G}CllpN)vp3rhVc!ZDh|khpy%eR7 zNC%SKq&8npg-V89jiHmC-i?7bv+H#ln5()3FP|s z5=xa{A~Fyl&w<^k&@A)jyL*5KXM)Asfi&?Gx&I|>Vmo&1h>%hU%Q$qoq{I@=Im+x} zsgd4Ka$EE(q`Y}(Mo^23zwEJj=W40U@@ZeWv8bndOH!r{qC$J_?2Wt6am0pt?wC*u zx))wNy&=c%P^1cQLGQX6@|&kd@tF#Zjv5XxT6V5>n8V*n^ebW=Cv`)8>VaZ}iMuw& z{6sLY@7rk(kA^ya74DwMgy!-e;R`npO$r?evkBt~n`xp2UWUuD=?&wOp3j|5P1 z%-4FlU1e9ctLsh<&hD#g-p{rHrUnwOMt%X+2Wzb^Yf0 z*x`b4Sgrb1)*MG4IoGvANkh{bma^B8nr<8tsMd771_H!Q&Ox(P1H9%wT)54NA_?8%2vAKSBtPiKVk@=EOA-D8R zo?cGuP;+qpw9i7tU#B#UGs+CVD!N#@q!s=}>F(|((oIqjUz0}E3?~8w7TZ5-c~LqZN9!`%pTaLh&ZHe<{SC^hy+yNi1z@$<63pO+|tu>&Ly~Cx2lti z;`KiRUJke$)INI0kN&t*&9qJtBs5nR8@-d3hVW#69Lv#*L8B1kz=kr)pltm=!MWEg zR;oS3l=GvBc0&VJJtCfHs)>k-W_-xsCLxjCxWXdhA!}|`xp_y__wV1${Yjc&9}VqZ z^jAU1pgp;iZ!X*L>z>8xEG78Lj~>GJtuzwssv+~%(B|uz%fikMH*X2EKp<)j`En>E zty}tM?0G1hLB13bDbBzcw9N;&G!S@Ve)^rBEfGw+%7Xll+yfuc8kt&x}}U3Q+g9&8d{#Kk!W4$Rl)i?iIV$9Y_DoN~qkA+TO}`%%Caa@_k8@WGtcHNfRiwaMCb@9XNADVxRNH1%|N86Q=26n)2mb7* z|Mb89;etYz;6qlV#)ZS>uOf+U@sY#K+AhZ(=Fsk6mU zmRW5lE`xIYu+IAduNKR6(f|%gvY3>Vlq0&{aN0Tx$r@|`L7Rdh4hjhpUk(gpH+0Gb zCzj97Gd&k{ZQh2k8QTc6_zLK%#D0L6f{kGNo6F$hy?3T{^Z)mb?{bKb7V+g#YxL?O{b*RbE?>PFX4&r6kt((a6Oasi+8*Ej=J3rP}TamN?#UB2C z@aG?q0Yd$bJgcvXdWytEgy{GaUoC{vdn6@uPVwEZ0>S`+%%icyn-%hY+js1+!4{zp z@i(vaZuU1#A>H~6n(8wRo$+jws!$7#ff^5(2`bR{BzCV*3}$H-X_&b+AyY~AKL=u;_o)zz)-4S z$>qg@YE}4J69~l#|sAElSnZnX0 z_Bcm{^PT5$X)p@A4$qTzp>LI11(0w(5+rC+4hFu-Sg*w7O{^v2Yk{e9L&M5SIHcrm zK3ADABoA@%99&#P{V0D1J?bYGyeAr}nTXK|Rm>VLyvG&)hxrq;nC(m41r(detVizr z@xn0GDildtMNSJJAK%%NC;wS?7{WymnwUrhxbpyy2@6UB8opt4=kQ$l=3^m6Z#E`m zRJE_InM!OByJuTq!iBUP?YU8K-NjZRi_MSR6tv(ukeSK%)ThQLDEbPTHay2)Z}8s~ zYIaH_>>#3@-kj~6Z?|bu#1%G6uhn9Ur;lBaXyL`o(DhUS-lPjPMSy+cxP-{TCn6#u zE;+XvU|xe;^XI6zMhEG~$`}=%(0Ew|1=DdWQc{8D!Xlp)Ukew5J;cHng8!#}lu*RQ zR3J2eNzg!$pbst=CSeMCA&F4ewqnmVIH{=;6EWD(M4*SHZKVtzA*?A-h)X2`n7}1x zo!&ry3j4{KlPAMS^$gDvo8xIHn!$9&`s-Ph&64(aZi~CmG2mJ#veJK@xedN@!8oR` z5p<(qAvCUDtphP}q?iU!UyWq9BwEt|uW_UiHGIf059ji^N;X;gG{QLHyr()c)uY~9 zq5)-BJc3YaITIc5RT$9@IUDrzZh<_SY4&snr&r5ROD-w<#i}O!joTYJSC80@ESVfh zHe=POPGbrAucrxP_FEq@2boEA0^qXyZpHn8O-4XlnkBLO7_3f$33#osAaQU*1MR^j zJMow&E+4{bf_|HhIFv4l9B8yWPE<&JURa&5Y_)3|)WbijP6W%2qNh>tp$BYCPM-XO zzw1J3y#OM)@Dzwx?@&d?{Q`E=M9tc$85?-k5)tgL)ND+a-3UphwAA5!A|Pn;PJH%7 zr!eL_HdQ&C)%kia5KKeUax(Bd3|gd?O^U#lCvb$7Mw%>hS|n?b`{Hk#UK;$cu9_V8 zz!ID!uB0U!(%m2pwYQXjAY%fo>E43n;NUGfBE(QBXBh27kuYw&F7Oyaa-oDVuEKJ~4?@XG3y)Fa z8pv62+3^wLs86w1%&aTt54IU0+4I+j2p5WG$k$Bg1Emn{8SWDbesc*k&t-Bsb1pnw zr&yt9Ni3wah{ikzJX(p1l)UbzVK_j(qV~fq`i{3 z=DG-BPs(U?Z8{X8ml=!Ew3wl2hVx6y1Zz-#A0q?9ir|2Q2+5MNp~sqIB0h;ZeEEhd z(Kz3S^Ghtk3bg0=X6Jd`%fnlEwCjnR5?h6pAiFFPOFx#J5`muYn!#+enLMf@9Q}@Q z+9fd4`VoFy3;rlOiipKPyyoz4co?v(yYv#R6-6<{i0QoA0dl%gp`B6;^DI?KfCkM3 zBaTSu+njANx;BF-%!LH;Jnm%aYRI%39I(^QJ$X9!?y18AwN4EPl&)3PmM zTZU>QAry(ZnD3B#PB()68aGD8Hv%18=g73NH!eMA%UqJyw!3Vfq`X0Wlip#I!NuFo z`ZxZieQ4cB?D!{ok^L3-#J9ZUA22^*MV77VV}$w5Lq$-$_0)kdgpmH$sGWT{;LpW3 zr&)w!TB^NKAn))tz-uv|`*{y#M+q5St+c4fxUx5AZbYtPW;uurYKn4lMia(+!Ab>A zCRjz*9sC2wY!<^$1R+Kb609;I7aTb5pc_tmMoG(lWN<&;TZ#onLqP^_($dn-Q3Zn2 zb;%xe`?i5{C_-bLbMg-TuP+rsKzXi%?hFM{ze!c^#tj?rhW>?LUv$z!8)5N9z1N?Y zOQ}1STYC-&ZV+qO?M2B%4g++}77v)gW-BsVM@MIAikiI23Wi{JIJYl$GBM)j zHV)WJjM)>nB-4SlVmQrBlC5ZARItRqqBXS7F^M?;=pv4f;`L<&8NU*Y)CL6$fpi`~ zw4@$L4bXcG$B^T1tI@9);yOQ_KSK;(APif@{CE6GrB`jhAa)$!xFMM9o!c@b>J3s& zfm;6}A1=y=hQt24Bh(}WKSA>EA@qG&i8;k-#C;?d1f~zdN4y|8HWD<>oZ$ex^{N#5 z#6+qRHoOytnOI~xB0$LWnPhyiLm=on|FSelC|Ig9Vtv8SGOG5f%7u1xLgKB+$_?>uiA6X3aTy8>J>h8HW7AGsn4 zz+s}i&M21Lpn2qL{+$0wRPY*xIbSqnKl1xv3Q1gChie64B7sfCC0DpSTIvLz53D)1 z!^;OBLLz6gS)}ZHI63o^`r*+jK=T`!Sjk7s(S{OXq6@8_SOC}v7D)_ZQMBk&G;JL8 z@L?p(FIH~J#it= zzbbVc|Ix7hUk{7&-WaSP5;Ow8hb*w^>Z@rGGK8|WY-E7(9Bjuc5M|!?^9u)EcWSLC z5V;^xZ{}8B6NU`5xc^&u)oxj^zW5QDIt;>$Qp$x z`=#amD5OG?;xMV*WX&KdtpIuhA>S#tCy!I`n>YZT6|lP=`serVd=|5{-HEhqKtF59 zRqWpqHZERaBO2Idpj_Sfl!`=B&KKJ~jvWs8Jb?)-m?4Q&31_FAFY05+oeV{lw-$@@ zxaw?t-W)kFiqqifqF)4yj()OQ(4W!?YO&<8|I*urPUgU-_ZF!J(Zg$><=iwD3?f#Y zct@7x<0I#I$nb@=4;TNO33UPN^Heh`%Iz)OxZaMTzAgAa=7fd)EhK{LWJ(ZJDS#u zo8WuUJpS_%HgcYfcP01_Xbe!Z39aWzQp?pO#1J)b3CJbC$j;Wnp``5W?3^a|D?kzM z*R(n-8CowXp#dRDP$KSLyCpH4PUVsmkt8$}TE`{+(2s%=X!xSC^u~WxE%o8R<2{=0 zT;K=^k#*2a71o%IF721>t=qZy+naE!N+d+&X?Rzq!-blwn# z#E)07vV$f=o7L>Q!=YUsJULZ6LwHu(-3F>wGugZlCJ=8rkse=t>C0jlXRI5V;{k};clo)Qn4QRwXlTcPhCTS>qlg#e4ov`lsuC|5^lW;tM?s_;(5`O;`~jdK zdwsSNwfC4sp;EWE=+1>y^ZA#07d?qI85!MZ!iVrPV~?L}W}G$|)pm{fg8DmW!?Pt& zM9N(e^+NB24-proxGDd=wUfWqT$s3?>rlx__CTrx<=r!fs`Nn!MnH)HPpA8`y%hN@bmXa{&@Ev&3;mCY1c&Ha{Jt2Y>3Jc zvN$FGr`Y*cS)!^Mt3`bstN^MIu8m^;Fq3!Mp*YwATH5-6M}4S zH-C#_<*BUvwDGIJo`Vg~@bALbk-^W)%j;@hn71PzN$D@D{j+GgFR?VMgrrGtyIW8` zkFBzURM{nr_T+A|2!z*-dv+tO&iQid?Ce;T_d#kKAKUsBD2?omPdgF4bVKFrXPWjK z`CM#f^tAm&&pmP5ql!SuZ)8U<%Ru$^N02gw@^EKQUCc~3`OmLz$IM9Y#uxDmr$fbh zY`WUibW4Au*r#{AaLFihxZZ? zEhnM*0Mzo+1%q_Cx0p`g?wF4BPWCmi4xC?=iPmOt-)MJD`9liD(4J^E+UAR24PF6- zK4OmmRq(Ynx-kBGEilp%3-bkH-iU(?*38F!@1t0a2};~CzfgaJ$9V6FH62+C^RtoT z@Ta_y$$@tYYkUr_|9i=~zaXbGT;~swC3L5w5j6UX^ZfJ;#<3qAuLjL%IkTB%wL^Q7 z_FDX1o05*F;5E(&F7UO+-fXM@Yjoc&*3U;EGxZ zjnRj9y~mMHceUrt9hsdR6!O9&Y`cH=(ewKJhNDtHK}9@8d332q;n<^j*YDo7OaF{p zB-R}O(V;pmgnWpGi$lb!fA7X1^A!aAQ8bNn`^~(`n1}YBL%{j{v?ZD59*fSnMZ zVM^>b>-8OnU_DPW7q9mcYGNYo1MpOEoT!Dxo&pH*URy*?69EBq34QqU_gYZtAG^8) z5XrjcdE6?riH`3u-yRZpo(q*Wvry9TXB2NXR@)4*k*|0J93iXUq|M69vvy6+=Hq+o z>`NXyXTTtF<=W{f-vEJkOqo#Ks79;LwG}#e{_;A0Xh@0RErPr$X-P&z0ii$U;@LKK zbOou97H81%lOVqebu+o}o`0Y0A6jG_016N$MZf&I??pch;>r<;T})Q=lLG#Xx`sj=dB0I9_5w0lWT;lgwf9%NTCY_Ql?J{wnn289^3=G^Q5W zjzU5yV|L(^i{91C(A0wW2I(@pdmK9k82bSRD?*6k)6=X5y+=F2V6SmO4*}Ep$II6+ zqRBQxwzID`N_|mc*5r&N8k6oo3K41%z7YuU1ogLeqR1oQl^Sm~v?Z6QBcOpR(iYTh zq9wzh_=TD?pS*sK`p+@GFzk-MsiRwz4#{ogr{>@sq<`q0!Wm9voplPSDD=3aTKx-s_ImmKY}Q!LmhD6*p`?{>dr5`%&HOLEsXJ9Y-T(#Z%A@d$U?g zNnTz)?befXP?6({%|W{`QEHZ`&!#mFquD9G`gl2MQ_~TK2Cs{c3EI2ShpiL&1UhXx zfi1KyUD{nq&7!q!-we9M8TV*nj$1@!p$iyAN;-EeXDPpeR2$$|j{` zkgimsKbO1apL$2e$=Uvn*vZ~;m;P~=Z0(?EV;^z8p!wk$mQ7|6ad;Y4eS!H364O@E zQVN%+dm?sXsa{-zseQ*uC_z^F(ds_;3MxPVP8tPuyV>w^LJ) zQdWSn&W8wDzYBTp^!I@6SR*bf+Yi0#yy;G=W6)ut*xC#mqn?>>hu6!_e9b^x%HigmL8GLySjX|?Zx1n-bG!Pp@L2fXTm@4 z<|^xe<#B4#KnQ&Hy5GN#@y0{CWPNp)_fkq@Zirn|?atEZ*jO*PtPm2ShY?^B9TXvW zfyR9N`0?0`K4z|a+C*SX#d~46%6NHjXLD@gFfcB8=AU9TSgqwO&|TeoT83SGo5|e! z(NY-;Sy^f6xe$gGU>}0ux?Ozn~x%1Sg&>rfJ)7fD=Yg7|Z2}T0N!ewgUFe&1u#OiRXje z-S?{t4w~mAeE1OE3+aoUR0qvHDB56VoK?{b$jEM)=~b#`)V7q3neBU1DiPfqtDee9 zRa?9eDX#KuQ9iqQc+AGogYXl6j)2>6h#dSa+4}+QS^+5ET{bvZtSs3D%#nA0$;w%xliyiVA620~4yA&8D$0 zJwqjjMuq!c#Y8f~ftGUEV#K`#Gu1jH;wRlyM7 zrHepQ5LzO%O6}MtXl{+@Ybjr9ql-CY6-yjo3&(To)5Bm}!fJ z!E4i#R5>+Pp=gwlsZcx8ZV;lAZS!GM|BD56f9=N&Ey>C%;geIvuP+U^i47d{0$Z<8 zY;FpEmXG-KObwmhE!Q?0UJLa4X_~#UhSQh6UAx&>BNU@<$>U|^s0^cEn@7ZgZLBuv5LlpWyKDJ7+iMAPa7;J+p~j*wf= z!X%buUrKr9VV#FKxXGlW=p0#K?(V9u{&DF`9Lye0_mgB?Xo%N7L>*HarmCC5-Oc_` zzK`TV1<5u~vKwsQ-uiAOyC-()D&t#$L1xn~`_!ymrJc%Jp5fx17FJ5SE3%JuPj$Kd zHjI^2fEpJYFh!#t9kTvo`A*1ri%is1Hnq3MwE24Q+zID80bcrqkG$)QQr4;Aqgm*v zPk8!rUtIP~;K#yJ#n~y$8+#bc1~xkO*ZEiE6TXdYBdXIu*8`ZvX1Z2I_ufqOnhINb z(kR4~S!`jiwpUDC#=-H&A3o@`y+a6$CBcTUAtCwoL6^zd<`0u*={tc*ZlR|qD-ibF zB?>Os%V{s!KR$Nfgj;n}q)jL^?t(%VJ}J6vfS#IRT>}8;%?sn&3!C7D>*?+e-!sWg zWwrAZ`%)vykyiNt~e zyhX&{yVfuiDaPa^6z13*`EDXKS*33Ftf%ST!>U-dH<7hlPhW!zI+;{^PF8%ox`xNN zp6l5SzJ0sd>U%azoXkl38irOJ@+zbDJ+%JK+6!}Vs&UYT5od|MMKN(vCO7NJ#Jj!Q z@;A4Tlj`a$zKlBfSQ)!^@1_T1&y8&#kEA5IgDXoX^)1c5y%oQJ zS#?S|W&6m;=nni0OpW@!y8caA)BehFcgXQ#(}*HT+p`BTf!41LWNmG24^4S!OP~7k zkyS!6Q#&WG#0GJ!K)`IDo{w2In~8WXteuXWt6hDbq^WpyU2*b{XMqA&wOx1HsQT0j zdtF`KeVQ5kkYD`+>*F?~mi@K~WGKR8WruY>-rW{$HdBJiSN6~io}>&$N||$)JI*I( z6?~-XY=I2~70uugd5NB$*Vfn~QS+Z{i|*j5{AX5Aji*OyM%&oUY?{eHeK<@fBb`SM zL(If*V7$q$T3T8`8Lj*(gCnmuB>;k|8HXtd(@~J!$tL5vITJ zUbj=_LA;k_oa_GX5sKR~lb7~;*=G9_i<`t8=F%dFN+70i1*vOiM8ji_wqq=n5d0SY zMY3~8aZ_ez6kT5aom6s;AA2hoqvfscn)RyIk!mHeA(VOiQv|TbU+;h~HcZ4TDe4b5 z#Y$>1O^OehXU+P*h$vh}5j`hgJbP+lZm{hk`@As2{`pHDy_{?|_I)WqX5+MMoMSg% zH|-0`OopQOF3)T0w`|UFwXjK?BsXxj-Dt=53qDsa^B$1o>Gu82ZGFl!OC#f^*EY9$ zB`^fMaZ?qt{gjryP~Ipm1pDOWICrmY zT%3Oyzqy`X{MB_%cqST_?~1xYNAE=dU(0whFT#FXj{DJO_D=);*1KtMe0{x$CW`Mf zsn1;osS$-3y4wr$z0JO4T67&{{#(Ruzth&LSPZ!(#m%(U-9o2zO2^xq&bcepmx!=q zzAc0=YQIPcjoj;V;T?Uc)E1Li;^{M(E>qr4p{%NlHy@muSqk97Q<{c&Qo?Rc*SPs`l6Bem z7AJHt!^g{g2?edF&=A|u2x9m{anmY|S6zJK*tzwwGu{gd3Sx%q-3J^_kFC#l(WzR1?3T*JkXOE@cssqs0W9G+JaU$KO))QwQZSYje$-9LqTk6 z<72_e-kow6zC4|^Gt9}#%Ho`TGtm=c0R|K&^+gCd7^lAYxYP{zWX|*$ZO1v^J@R8x zP{L$3AG~-B#*`em!YzgVJU+|UFhn$l3YlqXK>qQbzrVkAG`Z}pcXJiss;*B?uupR( zD=c3pCbr#Ik_Z({#Zjuo^6_$bt5=9<3<-9o6kUGYBBkas%CDzDK|(E7J}3u3t`6!+ zvT%%}j8XpcxUn52Z##T5?~=dfLqRx7GJv#8+qcIkOGNJ8CC|nehmE6v+KKgMlh@4kJWn4(GP&2t?>vi{gc#wY=@jBma1Mjw!Ju$!I9f)qb-a#)Mb zKb#k<^S+&+=yk3 zDtOFI%m_G5EgFjg5?dkTVN&s!Tz9yS=<9BmH62mhokamGBu6ceZ$8g<`Fl05Nl&Mm zrq#mJ(1Hlrf}7$}Sc3x+t&0!a_gpRhWCf*~(>5)C4=r&GL}))^e!wntx+6Xj{IfqriER zYJ6*!I&^DItJ}TM6ZNzpb8c!!MU|$_*9R7@s|sWw8(S{(N)CBx>r1|z8V*k-B(!(# z9E~iN51L7zDjn2F&K36o_)s|xGvp2Q^m-vSx)lc${{H-s$81Ezd#a$i=;8jLaHKHg zfs~^jwfK@sy?gK8my2=aAi(bDW~#6?Du{qK4E#N6qg~;Jt;yAbc_}@C_U&J*FI3d4 zU$`8uz||S-tZW?n(%2#g^C15NW3hr4ASH&Nrlt+?Su<*Ltxs<0sN2Xll!f0ANWTs>H=;ix$`-S|RPD`h(gt|fESH<9o4%wurUbN{KRX>F|>m)om- zXLexr+@ayTHu~Ye%JdL*_CW+4;S=77 zXLJFuOJsg(ldWU0<>$_dDsT<*{c{fSZQs60IRj8f&`TpUS)7mVWpB2S0Z!)a?vAv< zbLDYH8Ql)a`GmC6!>!Lb9P z->sG4IxVL;2n1z+ToA4jak>19Q8}y6g(^`tchcw7Rg%)Qaert00m%fJ&1jX&Bq-5f zG4td2w(?=YP_b$Dmir?kUr(htUG^}N@vMkJdj!0%`OGa6|XUfrhCcbN(*jxsFc_%M83~0}>eQmytbQrS2zj zCu9U$@abypL!(8?)g8D)o5aiCu7svei{MFq+5cXc{vJaHGZb%>s|Dw`{5%54C-y^( zU!GO`{NFQE&v&%llGG?OJP@^$Y*alCKnTS&T;Jd3ii~5)C>rC3%$>X@9gCpxtzMQ} zw#=@5-oXHf8n=5Uz7dz4A66brt+K%i{q@q zdRmJt3NV@Q^!L&u7P6n#PpX|#ewd*JfMK8N0ZG7Y*09#L?{NU&XH#dDLZ`6v8K5SU z;Kq3EnBGZjWe7EYeHJCG0N54_3Nco#Uu57YI4mQWo(jlf%HK8MkJ*7lySYxgxI3{A z9)u_tlvuZSe`465kyA{2F5Q+po*JlxX3fnhR}VfXs;IwSWd{MLq__I|(o#Ag<^by& z2{z-7CmTIWl5lcYrfiklKBF68wjuv9;n zH@Y|!j_B$Zx;mQ8R3PZ2|Mh7AzxG`TW&IuO#)nv;{WV!3SyE={4 zdmsv%4VzCiDAtCbONfFBwz5qcb&jFV4pcEZwnM@ubI4bdnHd~#I!4IR z@{q;Pa$_?hh$I!qXn0x#M_hJC9TR3pM@L7*Z3d{y@B@1y~_8KGg9{J{oGo}d-5x8jbb4ir^2C@sjg5zR=tqv56acL zmo7c?nw*;|2zt4jd-v_9Pwzz!7YjMZe-&RC6;t_8u;P3EIXxF%Y}1M}!nU8MwWkex zG5|@&3=+c-F2#T?RIArCAPGAyr{=r@){oWc>d6LYJC9xvVwrG)`f=0f6F=^ZJPXq< zw^QM*x4GDG>K^eJ@FAP12YM$udTIbi8Xf}Ls9W7=p6eCeOXh*gRPOTPILGhzvo5V#J7~Bpcndx(YRkzUTPUtBren6`4$L$(*0&sujIN9JArbA)^U)HLf z+bqLwbMfB;23IBJ^Dw~rH~t49A*@PK3LfNw+}*88B=!!Z&kiv0UXb46^l=+KJq=D% zQWSqs)g56$x#UP<0Z(?( z*DAmzhr`*auWCE9=0*ymds*UgBwXHHl=TzP@$hIX)P5YmYz6>TjypZAmYQPP7?|NQ z@`+2mkI8zLRnqjJ-MmURGePozPft%y z89MPX^NYH+#mx+)l0k4oL=A60DhdXuW6Mo^7lR7tQS7Ld)#S9_Tq-eZ>@+de7L+vz zWJu}V-XG73{1N87%rYaIomw4>z>yK~mybK0=wsTEw;93WrzVb&H6 zw)L?V7?6rd<9vZ?Ek*###Z8V_DoI=A0!w@Yt)?7wl(L6fg2axdHp&mPM83a z#$Rn+WZ&6WD!^||qMA*NEiCGUfsJX_naK<~;emLUF7)Q)v%4(Jsv^2m+p32r zVNsM&kgq4{=oD1)YKKu{Eqd>&2O{s_ll zW#<1lJGkyv6ce}(i!s(Vr?ys{#V*>w2fHOhW@n!#_ETN425Z!syk(c4vTZ-ddd^5D zREGeMN%8*J*K~7_hB$Zp92oprrrr;%QL58WuJZ>NMf+h`|F0x#@$anvzy1B7A9q+h z;564=mJ;PW02+W?gi6jmiIDqu^uj`{Y1wvIX>HbJTG4UwXX1|*N#t*|P=u?q2UCES z@6vua_Rr$n9222sJU`!T>>ngAq$+e4t{3rdCe-eutH))HGqU?07YhpuY3V*x@lSzc z4u5#Nix)3)kjmm3B5Goyp{2!!CIeb&(cs4$O3`s3@07ab*!|4^0{WB^th>vVs|D6~ zBCS3uotJleW1HL3_3r5S=AtrFqpUV+S z7-J1?dcrq~bcNj((H+~d_G>*U@XaLDJ6`{Q4{`B#qJ9^s+AcS$+ z3hB5-)8)<&2()!@Lud`WM>2cbi{#0N&nPP&MVyj_z>vVq?(PKIR+_xEcJqdS0F#B& za5KFP!0LHioHyEuQAz!6<(5A%67=-i?eL1>~bBya}W812DvmSt~6O zajrpe=0ZuZNP>CGd*y0anDz1^@@E}TLG=paS)9tZwvp**H-dj^MNFs4z+u1Ci^^ zqeGW1%j4;Q4y{|@{?d?BNQws!xitf=N%k)#M8E}fOnRdn$cJhW8d-%i7+<5Kj70!jhkMFP(doQaGQ3j1Wm2nwOCU3j8K$EF%M zgqsBNP@fs-@&pR9p|S^C`n}!~GhIxF?9EY&d$4nR-B^A9Anup*d~m!|`(9oUy{Eo& zNp7a6Ck2w6xpp3lt|zy^E)a9p73<-w3|^o%FUV(DQ@T##uE;7$N(X&Kcra`90>aVW zGCed(<{>#`OKq@}tE2l|0%XB7z5OBJG1(bm0AR*rU0*DU1*-E9qijNmDDB4{M7k?+JM7!FuLPgQtX^}D^`L^x^2Iz<%dlwc|tkU#hkr$6}9b26$)~i=xNxa1-nko zIus5bgX`0l*-SO{oA%(z76#ZT+8zK7inazMoGfZD`9CXmoy=_Z+8dX)9j#2h5)3FR zKqBDIs*Rj^5Egg}-B`3?(38ar*l`gE%}6JR-e$dEdKA8nWe`E^t-mc1$pg7h)H;X9 zxm!*)=XBK6r0c?@FvPtpI@nO;OERxebQ$pjw(h1tSbqZ3#lxsHosYUjrwun@yub-da>Q3^kfKn4M;UDCq2Zocl&MW zcD_WbYz_2+him7!h#jiHM)4udqAjpUX#MwK_)CSHZi~ZS*PArDG;9zSwTiQyTC{yy z2ais3WDYxZ0d{R2Gzmqg-&n(#|A<$|!NflpRxmqEG|+YT1FI~H0?h;K52M@qafgp5 zn&hIWOA_4Of%D~o=f8uCiwosL+#&aOuCdH?WCmLA_Z{NQMxQWm zMVHu{`^i)ydy`TdEgKH|>ssp??Ww;#*c~xV^DlRX_zO-J@s;MG6@Xfqh85U9Z>FM$DQxG-qzp6iqO1O}oaghJP5K$J`*9f0?C%_;@92 zRn1w@f5{cgU;PVBCmvuMeQ5XBW>b`;T4zpV0l{m-f;AeRC+~!>JkXN;VbEn#EX0bU zp9t!}c9TPZ2tI!P+*8hs#fQ>x00;+HlQJB1wnhO{+wb%zYOZ}X5rwo|(M=*ABT^zU z+n1yuf|AK8r~%56#C>d=%Z8jF(7w-lVs8-mx#g{g#M8&(VlRTvMh|eoF4uM~yTl>L zZq*tKJvdA!&QAiD$E)4e!UG93t&a@7HS;<?UM*Uc<`gNkd;$vPT{~LX!?QTG!jq6wAZ;%6N;_RyD}=qdkHp1HGCugHGfJ-efNx z2B|g=tg_KFY27bk;*R%jSw`6peHL#tA19^r>g02rF2rIiUm}9z;LQk&2HJKNSUNj8 zCNk`3)j+q$hFHTScEhkmr(;FR;4ChGMmAuSh@yf-fdDF16UrFQKOuCBDOr)D5%q>= z%SE|yyPP>k#9Obe7xKz%;9sA;Kt{s}DIk0m7ouS(S&U#cc--|sn!Tqr7}7bZZ={>0rB4}1SmW#FJwrx*rIqXv2^ zBIIx;PnyIAyf_qfuW3-qYVK^>Tb@sX4ZNfcNpN+0BUdEVu@paWIO`u=S67$TpHg4m z;Vs(p{Ks^oFr%O8wA$8AeBK5UWxGjSBmhdZ-s8)hvFe4Oae*(k4uhpLJ<=XMjD6q& z`tW4^bChrpW}gwh1WWSaVl$V-+7coBfHs=>WJ8IW6&x6{f@lNT>sX;Oa@=O)@jag4 z44ExVm__%JD007N#a09b=ti&X5N8L8tkR~<%5WcOUu;lNeI()RFoy&Cwn=@;PTLif zu`Ss61}pMqZ3+y!r|YY${on{*00;Ojh!qS*F`U+mPn$yVBJ_=)5j(AHL9wPMpvP?U zf=dr_zE!a|kK%DMV3$1ZWd<@wL}Bx8ugo_7fsyjy-Aa)WaVl{dn`4w?4K4Z!rzpwd zFjG{zo07_l=xEtBnuPEM>=G9CL6as87J4ZA?T-`t=C-sI&(jO1e{${CKPblW!)A6K z?o5C8=;a|T3Y^u%Jey~8ZqC?JFw}{%5apf?E#tD z%UNTG*-L^icLa!OEIi|J-i;o=IZpO^KmPT;P9uJy#y0(A_K`t$E_!h`rWHwteuWi+ z?I3$5uBY9d2EdZ{#}yY9d6JC|Dj0TYW^FcT{k^=!Y3Lo9qC2S>DE1>nY#f*-aA;Cw z$S|I8J~~Qu5ag1^KY>-P($XYeqb;jNWUE-XE9_dii-jsS3{vi*)kod9x8AK-w;!$m zHjNL%WZSq8h!3acnz1Wf21Pc=I;FM=hFBGUA2JyaD0SlOuI@iWCXAYSXJRPTSXvsf zn)Yo-Equ_!*{&?c{acxHgLSR|Yj&sSWQm9HITB5gfhp{B?~kbv*>t8?6J! z9LTJfyF1PgK^yV&@2|7QNh0p4uiVy2QaFGXqG*c`WKhk3XH+ZBjN{^i>diCCo~&Ym z2l=SlK%A>J=Pzd^5bRwhVbyLkwx~;KSEh8qEMj{O04xs zrdUOd2i<%_Lz|z>;<(}lwY?QZP}2#a9v+P2da=G&Q&FxnQT81HdcH2s6o)XfC^Y|0 zeG$F`eWUq^+FE5KXYSF=t`%I{$?~AzeY~f_=!t$`V;m7w-b8U9KGAT#kjr22GXXQCbJn4z{Izb ziPAHlnmrz?3S&}S$6MQM%Xc4!yO+ugl(X^a79C@0M!>|HqGHF7VE?~p!i+n429eCS z47DJH!y1=^cRyVXx*+zn!i++*#=+w#Imb4c$uWkchz_K+VTX@9<^=<#l-$mxQmC@m zk=W~tLyGJC#A1IuZM>~H$^L@aPEoTI!dzdIN#PnmVdd0)B?Z}xxsY<*ef`R&^&bjH) zwpY39Jt`E$_9Nvj>vLJhY)6z(&H&Kka^|2|Tfq}to@HSoHAg<_@&D~QKcnSkO&vM8 zh5-wR5xyEd`*j*`O`F=I(gqn3#n{<*bS`(TyT$gSU=S-;Vh|PbaH50^sNvTaY3&cy zThUae80$FpMJ99KiSjb%$YE{5wXF;6A53M~CrSgrNwkJKzhJ*r7JiH5jXGf3M!P8+ zrO*i!V^s0ccNtXc5KW@G#Al!SdMD3|F$}W z@zDx{Vi@vPY#*}AO}QajkTg_0f#fK3tY$!X9m2egNE{U!;KF3$(5XE?;!k2`>vPWN zV4Ku|CQxjgNPkjAU{MItlHxdV93B_I*Nzs)Ok>EsMQ8Tf6o0iqyeyPyrvv!H0>+F{ z?*)Y#t(AKAsUO4+l{CaiN#ei~L((6P266#AB!valfL*xb+c60gSr{1^WsV*_S}sG2 zkZ3bzY#XTpqMd=9+I(awD`$q2;84qzI?iyIU0fDSLZ?B;W)E`Bv2iSTxU^H4tio5zeqcELO`{Qh z3U`oWif_LVCE?;oSVaBR^tA1v83{b+;2hcKCD5^&58~j4%hiLHZ*miCxt z4d=x>#McBcnA!E~Cfy{C1GjXu4H9>>{oc_LN&8gpv~O1-YS4FA54c@E#5~F(_!O{t zlBgNm*{|2kfh(Z2*6qhECP^IU6Otv8TfKmo3P56}l4CX63CPCt)PwsDewgG4ayl)D zrp@eM??f3pzX+MQP_G&>X?^9KgM2-gfwf&Rry6 z+XN61#7W{85HKZR%GacrsAI4rzjhL?l7eu{GSVaSl&SeFhL8E z-4w^wpX!PvG%doxX9AO`JpSME^Ibx9lDb8{O{gtUCuS$B4U?gNOp6Q+Vs04%sl^lU zWbsu-o~{&+4HPRmM;hM0e@~1rf5JipsPfRXV&1!J7f1#L1?ZzC@Ws74k~uBD>i#Cz zs&EejOO`!wC27ohs8oOqJTu;_WW+4`4U{WsWo6~^o&Szo5_|}7JaJ>(Hl=~vC71Do zzKnNu(|oO7{_hpyJ5q}OlDKFe+yAd%z~bNUqDbgm+&mw}JwF?tEQ>B_>y56np&9=@ z-@`&shEjE=HiyT>5$2+s>C^1_K^g&q_9plovBh=r+j1Z!$c zmO3*o_n#XPr2p9c8!3>M?@X#q?{pC%jSHad$NUp7ZFDZFYd&N~m@MC8`Lts1ulW)t zzs%CXjkL*hZ>W2TIy{&&zCV{%;=SY)f-M(b( zcLahO0Q^a@wfmq$gvnO{`Hm9NQIF^n5mF?sADpr%zw~c`UFud0`k&B*l#dLEtmpmE z!=34^pryo1*9&hw>d8b;@b{b*QmH$UpaA53<{M>HogEz5jLtCQhO~qaK7@={XAh=8 zW!8d=v@}8afJ9~*8O&u>O_(Xz=D6JR4{eU@MDafl$5>FT4KM@%S?98b=0+`sw%w8j zRE_SPzM8{kQ6Oc|U=6V0rKR&~52@czxrAJ*}f0{#;y{XUXTEE!) zxw$K$NR5lZ8$#z;z7TgvMVu%;Gg91amdca6a@Zu|>-lst~sMfNw0Vw#OxT!h7iknB9)!(I!g*3ONrbVKe zuEC+++!_jo$kFX8d-Gu*{l0cs{~GV)Zr;LTuV)Qq_5eN`(i zCbR;s5c>-~Z9(;Xt=Gy*WVs&KJR1a)^~X!9bO^oX`4FKYl==N}p8%SXqz~!$g;3`S z+2R~Drvhzbg}|Yu2fMbK8Z&g@R5dI=?c_z29-++`rat6HZ7jo7*8yD;w63A1(1^d; zNX1&Hd4wuB$af)cp4J`qx)HsN{z7#|(tfkpGUPSpWA~PKFjr?L5%yF9`sNs+P@qLXG5T7+sm zkr?WQhKsJ|9M?o6H0GVICN(=8ms7y&HscsF=*4M!0cAjomK%)uU0=feTuj{ZR(-cV zjOdXU1QF~6nWtv>d3bmPni)_mQqVNRS_QkU?yF||jm}U-O?J>W214|b9vt;+uQEDz@% zUA6Nw)j2d$IGG4#hoL1T45oHa5hLmqjOd5?KWu#oJd|r6w$e$CBB_+MDB6%@9a~Y7 z5R$TnO7`sgmdZ|2WG5skl6^l}lQm1SPK=!)>tLAq{`csd_nhzhp5O0%J2B1kJooZn zuj{&hKqaj=-vH#UQUJ*f17QT(@HWtT6Q&*0g_sx>*nW4*p1q~;=pv${4+F#_oCVIz z)iEUit?m%Qcyt={>6AKJPPxwf7>4Ehl-0^&pNi>2K#-(ofkg2re})Oz3e%zJ#ma@Q z``sXlSXY=;^jg_Xgyg=Ptw;gz-uXr2pRke#v&##P7HD=;4o+Wsh%)M4XQE8m?~d++ zAkPBn}Qsac|%ld7hK z7MVFLE-ttA>SjLB!SE!o83`pVfV)Seibpk-3;ww#pjV7&SMgpwZffe;J9Gy55B zfMW=FjXIMOK#PbGl*aRlL?AT-epBg)9+>?JZBPRH#|0+&p}H+Rjc{}#(DSaviRH*8 z3fm0Vyn??)tidlX!34G|YA(RLgX*?532BqUVw5ifk zLIB~DNdTN-WuuvC3yD}r+9*iS1gi`S=~@UgnsD$6^Iy0tLjijsj|JG7cZiKuAlgiZ}Lg4$<=enU4%kNZc+5jmAm^3%bh&8CkT?Q#1C?!0D2B*Fi1$=2B zaQk@)WJu}i-N@tk`y(`_nbl%sv+}Id%dFYD4w$NSv(hP6utXwwVw)U8`UgGmi?2kM z?Q+JGeALt>MUXjFu2O@=w`2jNUw(U%(c0YF z8U`GPvzACn3$@Zx!*(12eUfrroYgrBV9|>lsRKDxVoq~tMAa2}pVgF2sxEj~5b4S7ovHBqn9TM5deZPBoS_6lenyAKv z(6#>liJMF5jNyaI={8aE6=r}qnsNo!9esBcSe=aV{ZTJ!y1I2)``(!wfd@biGu?xf zRWfxo9yK#veW36z23pP?W4{fjpcl#g^Y4&1>2{w}eKeG`VVgUc?g3YbM)>CR_6a%w z!FaczJz4`?d1$v>h^66J1U_BcX z0-{nd0!!B^-Jsv)4z7Ga?#Kh2XDVusNT(K@1T}GZn|&Ix-QV#5D4SYGxJR%Rj&p*p zofDnQ2)(_Xp5mqAm`Sv>2Dh%riE?W82hd`$0haY8@U-n|@RRsd=!ioG&5~=<8j27%TT2%C zXV)YkoSw3DM_>f))|FOlg|zn|0@?Qg?^2Z&3&31JdUoF(03ZKD`*v})6pO?tau~?| z={NxpPrinR{T2(G-As-m=Y_&&@$vBoAB#`cFvUvEkvCd2O$Yq7bFrSpv??_eBIPD_ z`}@P&-*AfCl?dnXc@Lfd7Y+a-W<)GPspTuX+F2v%Q3WVJiI z2sx^zso6DC{vRj}B@vNAV(G-C#cj_4-9b&E^eWE($SY=lB2|De%m{l~#~TMzU|vw@ z;g{g=FM4Y5fd=y{&^{B6^l#AS2RUrcd;%bAb@XBZ+8&!w zDI`2Q(StzqK*6YS=+g<$vbX?&tSA|IAT8#ZQ^uMqf=vt#owhwhc z&~fola?v+KJ{-84Bryot5EmFOZ^s{eliQPc&H00WC&qjrWf>4H)ZujjaqChL-=-aX z(8_|CC4`(~`*C3%zgaO^gMaD^ZBLM;1U#VSES%e`RB{GBpTyb-cqCR}G#=Me&vmdc z;FY!J`I{ZrR*L`wdGpW`evU{-K!<)lTNJysWPd+LTWtZu2$GoEf>YYt^*QxJnM{l6 zg9&lm+JIBm8sM>tq_yhX%`Iet0T~02>1{RATqq)pk#V1R_S+Fle0z3u(rf1}@D|YU z`rU*z#?^br8{cKo($K9NE^+ zDLrl!I;{)*d&(s%+OAta0Ey>u^+s6Zufc0~ty8&TeBx9Vk{LENX0(c&atZp_)%%q1 z`774iGLT>vnfpI-wqF%H#b-v6YrHhwplbic)7CY51pYuLvieH^WXdvUz*8F=*Xr2dL+F6|g?-@Ie1Rr3IP5M#spQ7ilfxp6 zHOpdcMmQc+dS35`xVvcPQ)BN923Z4C#Vdy%9|zMG(^}tcGo*+#Mh>J7yj;WkCm|rr zGtXh6efSrP=>S~RjdV}vw)IX zKN&lCO$fm@-oCqPefc3Iu~VVcOSfUU&2aZ@4hK*2)(_Beg66}!h5Vd%?v{v=K#L(X zP0r_R<5Vu$vMX?(q*zudS9|0aA#c>ML*T(i{?aqxFuRkJXHvP?neMF0%~6qb)oTM($eCu&;IUH!#8_#3_oU4@rNnRD~82~Vn^fo z;aW8VgGwc0cXxL^Qqh4{m-W6nRGJ2|+0oxA9dq>gpg!=qi%Jo%u@UN-eFOV@rWVuz%0ALD^FWQSEmFuTazGl9V&LHPLkt1Kg=@} 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