Skip to content

Commit

Permalink
Price discovery ask flow update (#913)
Browse files Browse the repository at this point in the history
* Change ask flow escrow handling

* Unit tests

* Refactor Price Discovery Client

* Fix failing unit tests
  • Loading branch information
zajck authored Feb 19, 2024
1 parent 74d610f commit 6fa4d9b
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 282 deletions.
3 changes: 3 additions & 0 deletions contracts/mock/PriceDiscovery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ contract PriceDiscoveryTransferElsewhere is PriceDiscoveryMock, IERC721Receiver
* @dev invoke fulfilBuyOrder on itself, making it the msg.sender
*/
function fulfilBuyOrderElsewhere(Order memory _order) public payable {
if (_order.exchangeToken != address(0)) {
IERC20(_order.exchangeToken).transferFrom(msg.sender, address(this), _order.price);
}
this.fulfilBuyOrder(_order);
}

Expand Down
18 changes: 17 additions & 1 deletion contracts/protocol/bases/PriceDiscoveryBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,16 @@ contract PriceDiscoveryBase is ProtocolBase {
protocolStatus().incomingVoucherCloneAddress = address(bosonVoucher);

if (_priceDiscovery.side == Side.Ask) {
return fulfilAskOrder(_tokenId, _offer.id, _offer.exchangeToken, _priceDiscovery, _buyer, bosonVoucher);
return
fulfilAskOrder(
_tokenId,
_offer.id,
_offer.exchangeToken,
_priceDiscovery,
_seller,
_buyer,
bosonVoucher
);
} else if (_priceDiscovery.side == Side.Bid) {
return fulfilBidOrder(_tokenId, _offer.exchangeToken, _priceDiscovery, _seller, bosonVoucher);
} else {
Expand All @@ -95,6 +104,7 @@ contract PriceDiscoveryBase is ProtocolBase {
* @param _offerId - the id of the offer
* @param _exchangeToken - the address of the exchange contract
* @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 _bosonVoucher - the boson voucher contract
* @return actualPrice - the actual price of the order
Expand All @@ -104,13 +114,15 @@ contract PriceDiscoveryBase is ProtocolBase {
uint256 _offerId,
address _exchangeToken,
PriceDiscovery calldata _priceDiscovery,
address _seller,
address _buyer,
IBosonVoucher _bosonVoucher
) internal returns (uint256 actualPrice) {
// Cache price discovery contract address
address bosonPriceDiscovery = protocolAddresses().priceDiscovery;

// Transfer buyers funds to protocol and forward them to price discovery contract
if (_exchangeToken == address(0)) _exchangeToken = address(wNative);
FundsLib.validateIncomingPayment(_exchangeToken, _priceDiscovery.price);
FundsLib.transferFundsFromProtocol(_exchangeToken, payable(bosonPriceDiscovery), _priceDiscovery.price);

Expand All @@ -131,6 +143,10 @@ contract PriceDiscoveryBase is ProtocolBase {

// Transfer voucher to buyer
_bosonVoucher.safeTransferFrom(bosonPriceDiscovery, _buyer, _tokenId);

// Price discovery should send funds to the seller.
// The seller must approve the protocol to transfer the funds before the order is fulfilled.
FundsLib.transferFundsToProtocol(_exchangeToken, _seller, actualPrice);
}

/**
Expand Down
107 changes: 62 additions & 45 deletions contracts/protocol/clients/priceDiscovery/BosonPriceDiscovery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,14 @@ contract BosonPriceDiscovery is ERC165, IBosonPriceDiscovery, BosonErrors {
IERC20(_exchangeToken).forceApprove(_priceDiscovery.conduit, _priceDiscovery.price);
}

uint256 thisBalanceBefore = getBalance(_exchangeToken);

// Call the price discovery contract
incomingTokenAddress = address(_bosonVoucher);
_priceDiscovery.priceDiscoveryContract.functionCallWithValue(
_priceDiscovery.priceDiscoveryData,
_exchangeToken == address(0) ? _priceDiscovery.price : 0
(uint256 thisBalanceBefore, uint256 thisBalanceAfter) = callPriceDiscoveryAndTrackBalances(
_priceDiscovery,
_exchangeToken,
_msgSender,
0
);

uint256 thisBalanceAfter = getBalance(_exchangeToken);
if (thisBalanceBefore < thisBalanceAfter) revert NegativePriceNotAllowed();
unchecked {
actualPrice = thisBalanceBefore - thisBalanceAfter;
Expand Down Expand Up @@ -133,29 +131,12 @@ contract BosonPriceDiscovery is ERC165, IBosonPriceDiscovery, BosonErrors {
_bosonVoucher.approve(_priceDiscovery.conduit, _tokenId);
if (_exchangeToken == address(0)) _exchangeToken = address(wNative);

// 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 thisNativeBalanceBefore = getBalance(address(0)) - msg.value;

// Get protocol balance before calling price discovery contract
uint256 thisBalanceBefore = getBalance(_exchangeToken);

// Call the price discovery contract
_priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, msg.value);

// Get protocol balance after calling price discovery contract
uint256 thisBalanceAfter = getBalance(_exchangeToken);

// Check the native balance and return the surplus to seller
uint256 thisNativeBalanceAfter = getBalance(address(0));
if (thisNativeBalanceAfter > thisNativeBalanceBefore) {
// Return the surplus to seller
FundsLib.transferFundsFromProtocol(
address(0),
payable(_seller),
thisNativeBalanceAfter - thisNativeBalanceBefore
);
}
(uint256 thisBalanceBefore, uint256 thisBalanceAfter) = callPriceDiscoveryAndTrackBalances(
_priceDiscovery,
_exchangeToken,
_seller,
msg.value
);

// Calculate actual price
if (thisBalanceAfter < thisBalanceBefore) revert NegativePriceNotAllowed();
Expand Down Expand Up @@ -197,22 +178,13 @@ contract BosonPriceDiscovery is ERC165, IBosonPriceDiscovery, BosonErrors {
// Check balance before calling wrapper
bool isNative = _exchangeToken == address(0);
if (isNative) _exchangeToken = address(wNative);
uint256 thisBalanceBefore = getBalance(_exchangeToken);

// 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 thisNativeBalanceBefore = getBalance(address(0)) - 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 thisNativeBalanceAfter = getBalance(address(0));
if (thisNativeBalanceAfter != thisNativeBalanceBefore) revert NativeNotAllowed();

// Check balance after the price discovery call
uint256 thisBalanceAfter = getBalance(_exchangeToken);
(uint256 thisBalanceBefore, uint256 thisBalanceAfter) = callPriceDiscoveryAndTrackBalances(
_priceDiscovery,
_exchangeToken,
address(0),
msg.value
);

// Verify that actual price is within the expected range
if (thisBalanceAfter < thisBalanceBefore) revert NegativePriceNotAllowed();
Expand All @@ -231,6 +203,51 @@ contract BosonPriceDiscovery is ERC165, IBosonPriceDiscovery, BosonErrors {
}
}

/**
* @notice Call price discovery method and track balances
*
* Reverts if:
* - Call to price discovery reverts
*
* @param _priceDiscovery - the fully populated BosonTypes.PriceDiscovery struct
* @param _exchangeToken - the address of the exchange contract
* @param _msgSender - the address of the caller, as seen in boson protocol
* @param _msgValue - the value sent with the call
*/
function callPriceDiscoveryAndTrackBalances(
BosonTypes.PriceDiscovery calldata _priceDiscovery,
address _exchangeToken,
address _msgSender,
uint256 _msgValue
) internal returns (uint256 thisBalanceBefore, uint256 thisBalanceAfter) {
// Track native balance just in case if the sender sends some native currency or price discovery contract does
// This is the balance that protocol had, before commit to offer was called
uint256 thisNativeBalanceBefore = getBalance(address(0)) - _msgValue;

// Get protocol balance before calling price discovery contract
thisBalanceBefore = getBalance(_exchangeToken);

// Call the price discovery contract
_priceDiscovery.priceDiscoveryContract.functionCallWithValue(_priceDiscovery.priceDiscoveryData, _msgValue);

// Get protocol balance after calling price discovery contract
thisBalanceAfter = getBalance(_exchangeToken);

// Check the native balance and return the surplus to the sender
uint256 thisNativeBalanceAfter = getBalance(address(0));
if (thisNativeBalanceAfter > thisNativeBalanceBefore) {
// _msgSender==address(0) represents the wrapper, where it's not allowed to return the surplus
if (_msgSender == address(0)) revert NativeNotAllowed();

// Return the surplus to the sender
FundsLib.transferFundsFromProtocol(
address(0),
payable(_msgSender),
thisNativeBalanceAfter - thisNativeBalanceBefore
);
}
}

/**
* @notice Returns the balance of the protocol for the given token address
*
Expand Down
17 changes: 4 additions & 13 deletions contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,12 @@ contract PriceDiscoveryHandlerFacet is IBosonPriceDiscoveryHandler, PriceDiscove

(, 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());
// 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
}
}
Expand Down
28 changes: 6 additions & 22 deletions contracts/protocol/facets/SequentialCommitHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,13 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis
immediatePayout = thisExchangeCost.price - additionalEscrowAmount;
}

if (_priceDiscovery.side == Side.Ask) {
if (additionalEscrowAmount > 0) {
// Price discovery should send funds to the seller
// Nothing in escrow, need to pull everything from seller
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
FundsLib.transferFundsToProtocol(address(wNative), seller, additionalEscrowAmount);
// But since protocol otherwise normally operates with native currency, needs to unwrap it (i.e. withdraw)
wNative.withdraw(additionalEscrowAmount);
} else {
FundsLib.transferFundsToProtocol(exchangeToken, seller, additionalEscrowAmount);
}
}
} else {
// when bid side, we have full proceeds in escrow. Keep minimal in, return the difference
if (thisExchangeCost.price > 0 && exchangeToken == address(0)) {
wNative.withdraw(thisExchangeCost.price);
}
// we have full proceeds in escrow. Keep minimal in, return the difference
if (thisExchangeCost.price > 0 && exchangeToken == address(0)) {
wNative.withdraw(thisExchangeCost.price);
}

if (immediatePayout > 0) {
FundsLib.transferFundsFromProtocol(exchangeToken, payable(seller), immediatePayout);
}
if (immediatePayout > 0) {
FundsLib.transferFundsFromProtocol(exchangeToken, payable(seller), immediatePayout);
}
}

Expand Down
Loading

0 comments on commit 6fa4d9b

Please sign in to comment.