mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 19:15:54 +00:00
Add nftoken_id, nftoken_ids, offer_id to meta for transaction stream (#5230)
This commit is contained in:
@@ -28,6 +28,8 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace RPC {
|
||||
|
||||
/**
|
||||
Adds common synthetic fields to transaction-related JSON responses
|
||||
|
||||
@@ -40,6 +42,7 @@ insertNFTSyntheticInJson(
|
||||
TxMeta const&);
|
||||
/** @} */
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
void
|
||||
insertNFTSyntheticInJson(
|
||||
@@ -39,4 +40,5 @@ insertNFTSyntheticInJson(
|
||||
insertNFTokenOfferID(response[jss::meta], transaction, transactionMeta);
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
@@ -1354,6 +1354,225 @@ public:
|
||||
}));
|
||||
}
|
||||
|
||||
void
|
||||
testNFToken(FeatureBitset features)
|
||||
{
|
||||
// `nftoken_id` is added for `transaction` stream in the `subscribe`
|
||||
// response for NFTokenMint and NFTokenAcceptOffer.
|
||||
//
|
||||
// `nftoken_ids` is added for `transaction` stream in the `subscribe`
|
||||
// response for NFTokenCancelOffer
|
||||
//
|
||||
// `offer_id` is added for `transaction` stream in the `subscribe`
|
||||
// response for NFTokenCreateOffer
|
||||
//
|
||||
// The values of these fields are dependent on the NFTokenID/OfferID
|
||||
// changed in its corresponding transaction. We want to validate each
|
||||
// response to make sure the synethic fields hold the right values.
|
||||
|
||||
testcase("Test synthetic fields from Subscribe response");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const broker{"broker"};
|
||||
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice, bob, broker);
|
||||
env.close();
|
||||
|
||||
auto wsc = test::makeWSClient(env.app().config());
|
||||
Json::Value stream;
|
||||
stream[jss::streams] = Json::arrayValue;
|
||||
stream[jss::streams].append("transactions");
|
||||
auto jv = wsc->invoke("subscribe", stream);
|
||||
|
||||
// Verify `nftoken_id` value equals to the NFTokenID that was
|
||||
// changed in the most recent NFTokenMint or NFTokenAcceptOffer
|
||||
// transaction
|
||||
auto verifyNFTokenID = [&](uint256 const& actualNftID) {
|
||||
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
|
||||
uint256 nftID;
|
||||
BEAST_EXPECT(
|
||||
nftID.parseHex(jv[jss::meta][jss::nftoken_id].asString()));
|
||||
return nftID == actualNftID;
|
||||
}));
|
||||
};
|
||||
|
||||
// Verify `nftoken_ids` value equals to the NFTokenIDs that were
|
||||
// changed in the most recent NFTokenCancelOffer transaction
|
||||
auto verifyNFTokenIDsInCancelOffer =
|
||||
[&](std::vector<uint256> actualNftIDs) {
|
||||
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
|
||||
std::vector<uint256> metaIDs;
|
||||
std::transform(
|
||||
jv[jss::meta][jss::nftoken_ids].begin(),
|
||||
jv[jss::meta][jss::nftoken_ids].end(),
|
||||
std::back_inserter(metaIDs),
|
||||
[this](Json::Value id) {
|
||||
uint256 nftID;
|
||||
BEAST_EXPECT(nftID.parseHex(id.asString()));
|
||||
return nftID;
|
||||
});
|
||||
// Sort both array to prepare for comparison
|
||||
std::sort(metaIDs.begin(), metaIDs.end());
|
||||
std::sort(actualNftIDs.begin(), actualNftIDs.end());
|
||||
|
||||
// Make sure the expect number of NFTs is correct
|
||||
BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
|
||||
|
||||
// Check the value of NFT ID in the meta with the
|
||||
// actual values
|
||||
for (size_t i = 0; i < metaIDs.size(); ++i)
|
||||
BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
|
||||
return true;
|
||||
}));
|
||||
};
|
||||
|
||||
// Verify `offer_id` value equals to the offerID that was
|
||||
// changed in the most recent NFTokenCreateOffer tx
|
||||
auto verifyNFTokenOfferID = [&](uint256 const& offerID) {
|
||||
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
|
||||
uint256 metaOfferID;
|
||||
BEAST_EXPECT(metaOfferID.parseHex(
|
||||
jv[jss::meta][jss::offer_id].asString()));
|
||||
return metaOfferID == offerID;
|
||||
}));
|
||||
};
|
||||
|
||||
// Check new fields in tx meta when for all NFTtransactions
|
||||
{
|
||||
// Alice mints 2 NFTs
|
||||
// Verify the NFTokenIDs are correct in the NFTokenMint tx meta
|
||||
uint256 const nftId1{
|
||||
token::getNextID(env, alice, 0u, tfTransferable)};
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId1);
|
||||
|
||||
uint256 const nftId2{
|
||||
token::getNextID(env, alice, 0u, tfTransferable)};
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId2);
|
||||
|
||||
// Alice creates one sell offer for each NFT
|
||||
// Verify the offer indexes are correct in the NFTokenCreateOffer tx
|
||||
// meta
|
||||
uint256 const aliceOfferIndex1 =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftId1, drops(1)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(aliceOfferIndex1);
|
||||
|
||||
uint256 const aliceOfferIndex2 =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftId2, drops(1)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(aliceOfferIndex2);
|
||||
|
||||
// Alice cancels two offers she created
|
||||
// Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
|
||||
// meta
|
||||
env(token::cancelOffer(
|
||||
alice, {aliceOfferIndex1, aliceOfferIndex2}));
|
||||
env.close();
|
||||
verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
|
||||
|
||||
// Bobs creates a buy offer for nftId1
|
||||
// Verify the offer id is correct in the NFTokenCreateOffer tx meta
|
||||
auto const bobBuyOfferIndex =
|
||||
keylet::nftoffer(bob, env.seq(bob)).key;
|
||||
env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(bobBuyOfferIndex);
|
||||
|
||||
// Alice accepts bob's buy offer
|
||||
// Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
|
||||
env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId1);
|
||||
}
|
||||
|
||||
// Check `nftoken_ids` in brokered mode
|
||||
{
|
||||
// Alice mints a NFT
|
||||
uint256 const nftId{
|
||||
token::getNextID(env, alice, 0u, tfTransferable)};
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId);
|
||||
|
||||
// Alice creates sell offer and set broker as destination
|
||||
uint256 const offerAliceToBroker =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftId, drops(1)),
|
||||
token::destination(broker),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(offerAliceToBroker);
|
||||
|
||||
// Bob creates buy offer
|
||||
uint256 const offerBobToBroker =
|
||||
keylet::nftoffer(bob, env.seq(bob)).key;
|
||||
env(token::createOffer(bob, nftId, drops(1)), token::owner(alice));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(offerBobToBroker);
|
||||
|
||||
// Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
|
||||
env(token::brokerOffers(
|
||||
broker, offerBobToBroker, offerAliceToBroker));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId);
|
||||
}
|
||||
|
||||
// Check if there are no duplicate nft id in Cancel transactions where
|
||||
// multiple offers are cancelled for the same NFT
|
||||
{
|
||||
// Alice mints a NFT
|
||||
uint256 const nftId{
|
||||
token::getNextID(env, alice, 0u, tfTransferable)};
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env.close();
|
||||
verifyNFTokenID(nftId);
|
||||
|
||||
// Alice creates 2 sell offers for the same NFT
|
||||
uint256 const aliceOfferIndex1 =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftId, drops(1)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(aliceOfferIndex1);
|
||||
|
||||
uint256 const aliceOfferIndex2 =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::createOffer(alice, nftId, drops(1)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(aliceOfferIndex2);
|
||||
|
||||
// Make sure the metadata only has 1 nft id, since both offers are
|
||||
// for the same nft
|
||||
env(token::cancelOffer(
|
||||
alice, {aliceOfferIndex1, aliceOfferIndex2}));
|
||||
env.close();
|
||||
verifyNFTokenIDsInCancelOffer({nftId});
|
||||
}
|
||||
|
||||
if (features[featureNFTokenMintOffer])
|
||||
{
|
||||
uint256 const aliceMintWithOfferIndex1 =
|
||||
keylet::nftoffer(alice, env.seq(alice)).key;
|
||||
env(token::mint(alice), token::amount(XRP(0)));
|
||||
env.close();
|
||||
verifyNFTokenOfferID(aliceMintWithOfferIndex1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1373,6 +1592,8 @@ public:
|
||||
testSubByUrl();
|
||||
testHistoryTxStream();
|
||||
testSubBookChanges();
|
||||
testNFToken(all);
|
||||
testNFToken(all - featureNFTokenMintOffer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include <xrpl/protocol/BuildInfo.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/protocol/NFTSyntheticSerializer.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -3258,6 +3259,7 @@ NetworkOPsImp::transJson(
|
||||
jvObj[jss::meta] = meta->get().getJson(JsonOptions::none);
|
||||
RPC::insertDeliveredAmount(
|
||||
jvObj[jss::meta], *ledger, transaction, meta->get());
|
||||
RPC::insertNFTSyntheticInJson(jvObj, transaction, meta->get());
|
||||
RPC::insertMPTokenIssuanceID(
|
||||
jvObj[jss::meta], transaction, meta->get());
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ populateJsonResponse(
|
||||
txnMeta->getJson(JsonOptions::include_date);
|
||||
insertDeliveredAmount(
|
||||
jvObj[jss::meta], context, txn, *txnMeta);
|
||||
insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
|
||||
RPC::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
|
||||
RPC::insertMPTokenIssuanceID(
|
||||
jvObj[jss::meta], sttx, *txnMeta);
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ populateJsonResponse(
|
||||
response[jss::meta] = meta->getJson(JsonOptions::none);
|
||||
insertDeliveredAmount(
|
||||
response[jss::meta], context, result.txn, *meta);
|
||||
insertNFTSyntheticInJson(response, sttx, *meta);
|
||||
RPC::insertNFTSyntheticInJson(response, sttx, *meta);
|
||||
RPC::insertMPTokenIssuanceID(response[jss::meta], sttx, *meta);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user