Skip to content

Commit

Permalink
more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnxie999 committed Oct 16, 2023
1 parent 5e41a5e commit 3669793
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ NFTokenAcceptOffer::checkBuyerReserve(
std::shared_ptr<SLE const> const& sleBuyer,
std::uint32_t const buyerOwnerCountBefore)
{
// There was an issue where the buyer makes accepts a sell offer, the ledger
// There was an issue where the buyer accepts a sell offer, the ledger
// didn't check if the buyer has enough reserve, meaning that buyer can get
// NFTs free of reserve.
//
Expand Down
218 changes: 175 additions & 43 deletions src/test/app/NFToken_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6801,24 +6801,47 @@ class NFToken0_test : public beast::unit_test::suite
void
testFixNFTokenBuyerReserve(FeatureBitset features)
{
testcase("Test buyer reserve when accepting offer");
testcase("Test buyer reserve when accepting an offer");

using namespace test::jtx;

// Lambda that mints an NFT and then creates a sell offer
auto mintAndCreateSellOffer = [](test::jtx::Env& env, test::jtx::Account const& act, STAmount const amt) -> std::tuple<uint256, uint256> {
// act mints a NFT
uint256 const nftId{
token::getNextID(env, act, 0u, tfTransferable)};
env(token::mint(act, 0u), txflags(tfTransferable));
env.close();

// act sells NFT for 0 XRP
uint256 const sellOfferIndex =
keylet::nftoffer(act, env.seq(act)).key;
env(token::createOffer(act, nftId, amt),
txflags(tfSellNFToken));
env.close();

return {nftId, sellOfferIndex};
};

// Test the behavior when the seller accepts a buy offer
// The behavior should not change regardless whether fixNFTokenReserve
// is enabled, since the ledger is able to guard against
// free NFTokenPages when buy offer is accepted.
{
Account const alice{"alice"};
Account const bob{"bob"};

Env env{*this, features};
auto const acctReserve = env.current()->fees().accountReserve(0);
auto const incReserve = env.current()->fees().increment;
auto const txFee = env.current()->fees().base;

env.fund(XRP(10000), alice);
env.close();

// Bob is funded with minimum XRP reserve
env.fund(acctReserve, bob);
// Bob is funded with account reserve + increment reserve + tx fee
// increment reserve is for the buy offer, and tx fee is for the fee
// burnt from creating the buy offer
env.fund(acctReserve + incReserve + drops(10), bob);
env.close();

// Alice mints a NFT
Expand All @@ -6827,12 +6850,46 @@ class NFToken0_test : public beast::unit_test::suite
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();

// Alice sells NFT for 0 XRP
uint256 const sellOfferIndex =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(0)),
txflags(tfSellNFToken));
// Bob makes a buy offer for 1 XRP
auto const buyOfferIndex =
keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId, XRP(1)), token::owner(alice));
env.close();

// accept the buy offer fails because bob doesn't have the spendable 1XRP
// that he put up the offerfor
env(token::acceptBuyOffer(alice, buyOfferIndex), ter(tecINSUFFICIENT_FUNDS));
env.close();

// send Bob 1XRP
env(pay(env.master, bob, XRP(1)));
env.close();

// Now bob can buy the offer
env(token::acceptBuyOffer(alice, buyOfferIndex));
env.close();
}

// Test the behaviors when the buyer makes an accept offer, both before
// and after enabling the amendment. Exercises the precise number of reserve in drops
// that's required to accept the offer
{
Account const alice{"alice"};
Account const bob{"bob"};

Env env{*this, features};
auto const acctReserve = env.current()->fees().accountReserve(0);
auto const incReserve = env.current()->fees().increment;

env.fund(XRP(10000), alice);
env.close();

// Bob is funded with minimum XRP reserve
env.fund(acctReserve, bob);
env.close();

// alice mints an NFT and create a sell offer for 0 XRP
auto const [nftId, sellOfferIndex] = mintAndCreateSellOffer(env, alice, XRP(0));

// Bob owns no object
BEAST_EXPECT(ownerCount(env, bob) == 0);
Expand Down Expand Up @@ -6860,19 +6917,19 @@ class NFToken0_test : public beast::unit_test::suite
else
{
// Bob is not able to accept the offer with only the account
// reserve (200000000 drops)
// reserve (200,000,000 drops)
env(token::acceptSellOffer(bob, sellOfferIndex),
ter(tecINSUFFICIENT_RESERVE));
env.close();

// after prev transaction, Bob owns 199999990 drops due to burnt
// after prev transaction, Bob owns 199,999,990 drops due to burnt
// tx fee

BEAST_EXPECT(ownerCount(env, bob) == 0);

// Send bob an increment reserve and 10 drops (to make up for
// the transaction fee burnt from the prev failed tx) Bob now
// owns 250000000 drops
// owns 250,000,000 drops
env(pay(env.master, bob, incReserve + drops(10)));
env.close();

Expand All @@ -6883,7 +6940,7 @@ class NFToken0_test : public beast::unit_test::suite
env.close();

// Send bob an increment reserve and 20 drops
// Bob now owns 250000010 drops
// Bob now owns 250,000,010 drops
env(pay(env.master, bob, drops(20)));
env.close();

Expand All @@ -6894,41 +6951,116 @@ class NFToken0_test : public beast::unit_test::suite
BEAST_EXPECT(ownerCount(env, bob) == 1);
}
}

// Now exercise the scenario when the account accepts
// many sell offers
{
Account const alice{"alice"};
Account const bob{"bob"};

Env env{*this, features};
auto const acctReserve = env.current()->fees().accountReserve(0);
auto const incReserve = env.current()->fees().increment;

env.fund(XRP(10000), alice);
env.close();

env.fund(acctReserve + XRP(1), bob);
env.close();

if (!features[fixNFTokenReserve])
{
// Bob can accept many NFTs without having a single reserve!
for (size_t i = 0; i < 200; i++){
// alice mints an NFT and creates a sell offer for 0 XRP
auto const [nftId, sellOfferIndex] = mintAndCreateSellOffer(env, alice, XRP(0));

// Bob is able to accept the offer
env(token::acceptSellOffer(bob, sellOfferIndex));
env.close();
}
}
else{
// alice mints the first NFT and creates a sell offer for 0 XRP
auto const [nftId1, sellOfferIndex1] = mintAndCreateSellOffer(env, alice, XRP(0));

// Bob cannot accept this offer because he doesn't have the reserve for the NFT
env(token::acceptSellOffer(bob, sellOfferIndex1), ter(tecINSUFFICIENT_RESERVE));
env.close();

// Give bob enough reserve
env(pay(env.master, bob, drops(incReserve)));
env.close();

// Bob now owns his first NFT
env(token::acceptSellOffer(bob, sellOfferIndex1));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1);

// alice now mints 31 more NFTs and creates an offer for each NFT
for (size_t i = 0; i < 31; i++){
// alice mints an NFT and creates a sell offer for 0 XRP
auto const [nftId, sellOfferIndex] = mintAndCreateSellOffer(env, alice, XRP(0));

// Bob can accept the offer because the new NFT is stored in an existing NFTokenPage
// so no new reserve is requried
env(token::acceptSellOffer(bob, sellOfferIndex));
env.close();
}
BEAST_EXPECT(ownerCount(env, bob) == 1);

// alice now mints the 33rd NFT and creates an sell offer for 0 XRP
auto const [nftId33, sellOfferIndex33] = mintAndCreateSellOffer(env, alice, XRP(0));

// Bob fails to accept this NFT because he does not have enough reserve for a new NFTokenPage
env(token::acceptSellOffer(bob, sellOfferIndex33), ter(tecINSUFFICIENT_RESERVE));
env.close();

// Send bob incremental reserve
env(pay(env.master, bob, drops(incReserve)));
env.close();

// Bob now has enough reserve to accept the offer which causes him to own one more NFTokenPage
env(token::acceptSellOffer(bob, sellOfferIndex33));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 2);
}
}
}

void
testWithFeats(FeatureBitset features)
{
testEnabled(features);
testMintReserve(features);
testMintMaxTokens(features);
testMintInvalid(features);
testBurnInvalid(features);
testCreateOfferInvalid(features);
testCancelOfferInvalid(features);
testAcceptOfferInvalid(features);
testMintFlagBurnable(features);
testMintFlagOnlyXRP(features);
testMintFlagCreateTrustLine(features);
testMintFlagTransferable(features);
testMintTransferFee(features);
testMintTaxon(features);
testMintURI(features);
testCreateOfferDestination(features);
testCreateOfferDestinationDisallowIncoming(features);
testCreateOfferExpiration(features);
testCancelOffers(features);
testCancelTooManyOffers(features);
testBrokeredAccept(features);
testNFTokenOfferOwner(features);
testNFTokenWithTickets(features);
testNFTokenDeleteAccount(features);
testNftXxxOffers(features);
testFixNFTokenNegOffer(features);
testIOUWithTransferFee(features);
testBrokeredSaleToSelf(features);
testFixNFTokenRemint(features);
testTxJsonMetaFields(features);
// testEnabled(features);
// testMintReserve(features);
// testMintMaxTokens(features);
// testMintInvalid(features);
// testBurnInvalid(features);
// testCreateOfferInvalid(features);
// testCancelOfferInvalid(features);
// testAcceptOfferInvalid(features);
// testMintFlagBurnable(features);
// testMintFlagOnlyXRP(features);
// testMintFlagCreateTrustLine(features);
// testMintFlagTransferable(features);
// testMintTransferFee(features);
// testMintTaxon(features);
// testMintURI(features);
// testCreateOfferDestination(features);
// testCreateOfferDestinationDisallowIncoming(features);
// testCreateOfferExpiration(features);
// testCancelOffers(features);
// testCancelTooManyOffers(features);
// testBrokeredAccept(features);
// testNFTokenOfferOwner(features);
// testNFTokenWithTickets(features);
// testNFTokenDeleteAccount(features);
// testNftXxxOffers(features);
// testFixNFTokenNegOffer(features);
// testIOUWithTransferFee(features);
// testBrokeredSaleToSelf(features);
// testFixNFTokenRemint(features);
// testTxJsonMetaFields(features);
testFixNFTokenBuyerReserve(features);
}

Expand Down

0 comments on commit 3669793

Please sign in to comment.