From ff1e51456c849361734367de0c17399f759a11ea Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 9 May 2025 12:55:56 +0200 Subject: [PATCH 1/6] Prevent native funds when seller deposit is 0 --- contracts/protocol/facets/Offer.sol | 2 ++ test/protocol/offerFacet.ts | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/contracts/protocol/facets/Offer.sol b/contracts/protocol/facets/Offer.sol index 3709d77b..b86f89e0 100644 --- a/contracts/protocol/facets/Offer.sol +++ b/contracts/protocol/facets/Offer.sol @@ -635,6 +635,8 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents } uint256 bosonSellerId = FermionStorage.protocolStatus().bosonSellerId; BOSON_PROTOCOL.depositFunds{ value: msgValue }(bosonSellerId, _exchangeToken, _sellerDeposit); + } else if (msg.value != 0) { + revert FundsErrors.NativeNotAllowed(); } } diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index 300e28ce..5086732d 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -3870,6 +3870,14 @@ describe("Offer", function () { const newBosonProtocolBalance = await mockToken.balanceOf(bosonProtocolAddress); expect(newBosonProtocolBalance).to.equal(bosonProtocolBalance + fullPrice - openSeaFee); }); + + context("Revert reasons", function () { + it("Native funds cannot be sent if seller deposit is 0", async function () { + await expect( + offerFacet.unwrapNFT(tokenId, WrapType.OS_AUCTION, buyerAdvancedOrder, { value: parseEther("1") }), + ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); + }); + }); }); context("unwrapToSelf", function () { @@ -3976,6 +3984,13 @@ describe("Offer", function () { .withArgs(minimalPrice, minimalPrice - 1n); await mockToken.setBurnAmount(0); }); + + it("Native funds cannot be sent if seller deposit is 0", async function () { + // await mockToken.approve(fermionProtocolAddress, minimalPrice); + await expect( + offerFacet.unwrapNFT(tokenId, WrapType.SELF_SALE, selfSaleData, { value: parseEther("1") }), + ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); + }); }); }); }); @@ -4122,6 +4137,13 @@ describe("Offer", function () { offerFacet.unwrapNFT(tokenId, WrapType.OS_AUCTION, buyerAdvancedOrder), ).to.be.revertedWithCustomError(fermionErrors, "InvalidUnwrap"); }); + + it("Native funds cannot be sent if seller deposit is 0", async function () { + // await mockToken.approve(fermionProtocolAddress, minimalPrice); + await expect( + offerFacet.unwrapNFT(tokenId, WrapType.OS_FIXED_PRICE, encodedPrice, { value: parseEther("1") }), + ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); + }); }); }); }); From 5ef432f328a139a40842c37156d128b70defbce2 Mon Sep 17 00:00:00 2001 From: zajck Date: Fri, 9 May 2025 13:33:07 +0200 Subject: [PATCH 2/6] Cover also native self-sale unwrap --- contracts/protocol/facets/Offer.sol | 13 +++++++++---- test/protocol/offerFacet.ts | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/facets/Offer.sol b/contracts/protocol/facets/Offer.sol index b86f89e0..76c4df43 100644 --- a/contracts/protocol/facets/Offer.sol +++ b/contracts/protocol/facets/Offer.sol @@ -367,6 +367,9 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents * * Emits VerificationInitiated and ItemPriceObserved events * + * N.B. If unwrapping using selfSale for an offer that was set up with both verifier fee and seller deposit, the seller deposit must be + * paid in advance using the depositFunds method. + * * Reverts if: * - Caller is not the seller's assistant or facilitator * - If seller deposit is non zero and there are not enough funds to cover it @@ -419,7 +422,10 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents EntityLib.validateSellerAssistantOrFacilitator(sellerId, offer.facilitatorId); handleBosonSellerDeposit(sellerId, exchangeToken, offer.sellerDeposit); - // WrapType wrapType = _wrapType; + if (_wrapType != FermionTypes.WrapType.SELF_SALE && offer.sellerDeposit == 0 && msg.value != 0) { + revert FundsErrors.NativeNotAllowed(); + } + deriveAndValidatePriceDiscoveryData(tokenId, _priceDiscovery, exchangeToken, _data); uint256 bosonProtocolFee = getBosonProtocolFee(exchangeToken, _priceDiscovery.price); @@ -497,6 +503,7 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents if (customItemPrice == 0) { revert InvalidCustomItemPrice(); } + if (exchangeAmount > 0) { validateIncomingPayment(exchangeToken, exchangeAmount); transferERC20FromProtocol(exchangeToken, payable(_priceDiscovery.priceDiscoveryContract), exchangeAmount); @@ -585,7 +592,7 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents /** * Handle Boson seller deposit * - * If the seller deposit is non zero, the amount must be deposited into Boson so unwrapping can succed. + * If the seller deposit is non zero, the amount must be deposited into Boson so unwrapping can succeed. * It the seller has some available funds in Fermion, they are used first. * Otherwise, the seller must provide the missing amount. * @@ -635,8 +642,6 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents } uint256 bosonSellerId = FermionStorage.protocolStatus().bosonSellerId; BOSON_PROTOCOL.depositFunds{ value: msgValue }(bosonSellerId, _exchangeToken, _sellerDeposit); - } else if (msg.value != 0) { - revert FundsErrors.NativeNotAllowed(); } } diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index 5086732d..9d3247af 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -3947,7 +3947,6 @@ describe("Offer", function () { bosonProtocolBalance = await ethers.provider.getBalance(bosonProtocolAddress); - // await mockToken.approve(fermionProtocolAddress, minimalPrice); const tx = await offerFacet.unwrapNFT(tokenId, WrapType.SELF_SALE, selfSaleData, { value: minimalPrice, }); From 7a55ec29a348028822d79f00924832599e0f308f Mon Sep 17 00:00:00 2001 From: Klemen <64400885+zajck@users.noreply.github.com> Date: Fri, 9 May 2025 13:48:37 +0200 Subject: [PATCH 3/6] Update test/protocol/offerFacet.ts Co-authored-by: 0xlucian <96285542+0xlucian@users.noreply.github.com> --- test/protocol/offerFacet.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index 9d3247af..837b73dd 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -4138,7 +4138,6 @@ describe("Offer", function () { }); it("Native funds cannot be sent if seller deposit is 0", async function () { - // await mockToken.approve(fermionProtocolAddress, minimalPrice); await expect( offerFacet.unwrapNFT(tokenId, WrapType.OS_FIXED_PRICE, encodedPrice, { value: parseEther("1") }), ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); From 383a14316df89e96458d627db05c55f32f909818 Mon Sep 17 00:00:00 2001 From: Klemen <64400885+zajck@users.noreply.github.com> Date: Fri, 9 May 2025 13:48:47 +0200 Subject: [PATCH 4/6] Update test/protocol/offerFacet.ts Co-authored-by: 0xlucian <96285542+0xlucian@users.noreply.github.com> --- test/protocol/offerFacet.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index 837b73dd..f71aae99 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -3985,7 +3985,6 @@ describe("Offer", function () { }); it("Native funds cannot be sent if seller deposit is 0", async function () { - // await mockToken.approve(fermionProtocolAddress, minimalPrice); await expect( offerFacet.unwrapNFT(tokenId, WrapType.SELF_SALE, selfSaleData, { value: parseEther("1") }), ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); From 01aec3edbadb407ad336319cc47564ac15d3685b Mon Sep 17 00:00:00 2001 From: zajck Date: Mon, 12 May 2025 19:54:14 +0200 Subject: [PATCH 5/6] Prevent native funds when erc20 offer and sellerDeposit covered from the available funds --- contracts/protocol/facets/Offer.sol | 1 + test/protocol/offerFacet.ts | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/facets/Offer.sol b/contracts/protocol/facets/Offer.sol index 76c4df43..322106c9 100644 --- a/contracts/protocol/facets/Offer.sol +++ b/contracts/protocol/facets/Offer.sol @@ -616,6 +616,7 @@ contract OfferFacet is Context, OfferErrors, Access, FundsManager, IOfferEvents ]; if (availableFunds >= _sellerDeposit) { + if (_exchangeToken != address(0) && msg.value > 0) revert FundsErrors.NativeNotAllowed(); decreaseAvailableFunds(_sellerId, _exchangeToken, _sellerDeposit); } else { // For offers in native token, the seller deposit cannot be sent at the time of unwrapping. diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index f71aae99..7debfb56 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -1482,7 +1482,7 @@ describe("Offer", function () { }); }); - context("unwrapping", function () { + context.only("unwrapping", function () { const bosonOfferId = 1n; const quantity = 15n; const verifierFee = parseEther("0.01"); @@ -2275,6 +2275,25 @@ describe("Offer", function () { }); }); + it("Native sent to ERC20 offer and seller deposit fully covered", async function () { + await fundsFacet.depositFunds(sellerId, exchangeToken, sellerDeposit); + + await expect( + offerFacet.unwrapNFT(tokenId, WrapType.OS_AUCTION, buyerAdvancedOrder, { value: sellerDeposit }), + ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); + }); + + it("Native sent to ERC20 offer and seller deposit partially covered", async function () { + const remainder = sellerDeposit / 10n; + await fundsFacet.depositFunds(sellerId, exchangeToken, sellerDeposit - remainder); + + await mockToken.approve(fermionProtocolAddress, remainder); + + await expect( + offerFacet.unwrapNFT(tokenId, WrapType.OS_AUCTION, buyerAdvancedOrder, { value: sellerDeposit }), + ).to.be.revertedWithCustomError(fermionErrors, "NativeNotAllowed"); + }); + it("Price does not cover the verifier fee", async function () { const minimalPriceNew = calculateMinimalPrice( verifierFee, From bb3e4f65a4221501b392adc6bbcfb719fcdc3798 Mon Sep 17 00:00:00 2001 From: Klemen <64400885+zajck@users.noreply.github.com> Date: Wed, 14 May 2025 10:31:08 +0200 Subject: [PATCH 6/6] Update test/protocol/offerFacet.ts Co-authored-by: 0xlucian <96285542+0xlucian@users.noreply.github.com> --- test/protocol/offerFacet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/protocol/offerFacet.ts b/test/protocol/offerFacet.ts index 7debfb56..226b650b 100644 --- a/test/protocol/offerFacet.ts +++ b/test/protocol/offerFacet.ts @@ -1482,7 +1482,7 @@ describe("Offer", function () { }); }); - context.only("unwrapping", function () { + context("unwrapping", function () { const bosonOfferId = 1n; const quantity = 15n; const verifierFee = parseEther("0.01");