@@ -301,6 +301,60 @@ NFTokenAcceptOffer::pay(
301
301
return tesSUCCESS;
302
302
}
303
303
304
+ TER
305
+ NFTokenAcceptOffer::transferNFToken (
306
+ AccountID const & buyer,
307
+ AccountID const & seller,
308
+ uint256 const & nftokenID)
309
+ {
310
+ auto tokenAndPage = nft::findTokenAndPage (view (), seller, nftokenID);
311
+
312
+ if (!tokenAndPage)
313
+ return tecINTERNAL;
314
+
315
+ if (auto const ret = nft::removeToken (
316
+ view (), seller, nftokenID, std::move (tokenAndPage->page ));
317
+ !isTesSuccess (ret))
318
+ return ret;
319
+
320
+ auto const sleBuyer = view ().read (keylet::account (buyer));
321
+ if (!sleBuyer)
322
+ return tecINTERNAL;
323
+
324
+ std::uint32_t const buyerOwnerCountBefore =
325
+ sleBuyer->getFieldU32 (sfOwnerCount);
326
+
327
+ auto const insertRet =
328
+ nft::insertToken (view (), buyer, std::move (tokenAndPage->token ));
329
+
330
+ // if fixNFTokenReserve is enabled, check if the buyer has sufficient
331
+ // reserve to own a new object, if their OwnerCount changed.
332
+ //
333
+ // There was an issue where the buyer accepts a sell offer, the ledger
334
+ // didn't check if the buyer has enough reserve, meaning that buyer can get
335
+ // NFTs free of reserve.
336
+ if (view ().rules ().enabled (fixNFTokenReserve))
337
+ {
338
+ // To check if there is sufficient reserve, we cannot use mPriorBalance
339
+ // because NFT is sold for a price. So we must use the balance after
340
+ // the deduction of the potential offer price. A small caveat here is
341
+ // that the balance has already deducted the transaction fee, meaning
342
+ // that the reserve requirement is a few drops higher.
343
+ auto const buyerBalance = sleBuyer->getFieldAmount (sfBalance);
344
+
345
+ auto const buyerOwnerCountAfter = sleBuyer->getFieldU32 (sfOwnerCount);
346
+ if (buyerOwnerCountAfter > buyerOwnerCountBefore)
347
+ {
348
+ if (auto const reserve =
349
+ view ().fees ().accountReserve (buyerOwnerCountAfter);
350
+ buyerBalance < reserve)
351
+ return tecINSUFFICIENT_RESERVE;
352
+ }
353
+ }
354
+
355
+ return insertRet;
356
+ }
357
+
304
358
TER
305
359
NFTokenAcceptOffer::acceptOffer (std::shared_ptr<SLE> const & offer)
306
360
{
@@ -333,17 +387,7 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
333
387
}
334
388
335
389
// Now transfer the NFT:
336
- auto tokenAndPage = nft::findTokenAndPage (view (), seller, nftokenID);
337
-
338
- if (!tokenAndPage)
339
- return tecINTERNAL;
340
-
341
- if (auto const ret = nft::removeToken (
342
- view (), seller, nftokenID, std::move (tokenAndPage->page ));
343
- !isTesSuccess (ret))
344
- return ret;
345
-
346
- return nft::insertToken (view (), buyer, std::move (tokenAndPage->token ));
390
+ return transferNFToken (buyer, seller, nftokenID);
347
391
}
348
392
349
393
TER
@@ -431,17 +475,8 @@ NFTokenAcceptOffer::doApply()
431
475
return r;
432
476
}
433
477
434
- auto tokenAndPage = nft::findTokenAndPage (view (), seller, nftokenID);
435
-
436
- if (!tokenAndPage)
437
- return tecINTERNAL;
438
-
439
- if (auto const ret = nft::removeToken (
440
- view (), seller, nftokenID, std::move (tokenAndPage->page ));
441
- !isTesSuccess (ret))
442
- return ret;
443
-
444
- return nft::insertToken (view (), buyer, std::move (tokenAndPage->token ));
478
+ // Now transfer the NFT:
479
+ return transferNFToken (buyer, seller, nftokenID);
445
480
}
446
481
447
482
if (bo)
0 commit comments