mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-04 20:05:51 +00:00
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -24,10 +24,17 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: clio
|
||||
#our self-host mac has conan-non-prod configured
|
||||
|
||||
- name: List conan artifactory
|
||||
run: |
|
||||
conan search
|
||||
conan remote list
|
||||
if [[ $(conan remote list |grep conan-non-prod| wc -c) -ne 0 ]]; then
|
||||
echo "conan-non-prod is available"
|
||||
else
|
||||
echo "adding conan-non-prod"
|
||||
conan remote add conan-non-prod http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <util/Profiler.h>
|
||||
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/NFTSyntheticSerializer.h>
|
||||
#include <ripple/protocol/nftPageMask.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
@@ -190,12 +191,26 @@ toJson(ripple::STBase const& obj)
|
||||
}
|
||||
|
||||
std::pair<boost::json::object, boost::json::object>
|
||||
toExpandedJson(Backend::TransactionAndMetadata const& blobs)
|
||||
toExpandedJson(Backend::TransactionAndMetadata const& blobs, NFTokenjson nftEnabled)
|
||||
{
|
||||
auto [txn, meta] = deserializeTxPlusMeta(blobs, blobs.ledgerSequence);
|
||||
auto txnJson = toJson(*txn);
|
||||
auto metaJson = toJson(*meta);
|
||||
insertDeliveredAmount(metaJson, txn, meta, blobs.date);
|
||||
|
||||
if (nftEnabled == NFTokenjson::ENABLE)
|
||||
{
|
||||
Json::Value nftJson;
|
||||
ripple::insertNFTSyntheticInJson(nftJson, txn, *meta);
|
||||
// if there is no nft fields, the nftJson will be {"meta":null}
|
||||
auto const nftBoostJson = toBoostJson(nftJson).as_object();
|
||||
if (nftBoostJson.contains(JS(meta)) and nftBoostJson.at(JS(meta)).is_object())
|
||||
{
|
||||
for (auto const& [k, v] : nftBoostJson.at(JS(meta)).as_object())
|
||||
metaJson.insert_or_assign(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
return {txnJson, metaJson};
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
|
||||
namespace RPC {
|
||||
|
||||
enum class NFTokenjson { ENABLE, DISABLE };
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromStringStrict(std::string const& account);
|
||||
|
||||
@@ -59,7 +61,7 @@ std::pair<std::shared_ptr<ripple::STTx const>, std::shared_ptr<ripple::TxMeta co
|
||||
deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs, std::uint32_t seq);
|
||||
|
||||
std::pair<boost::json::object, boost::json::object>
|
||||
toExpandedJson(Backend::TransactionAndMetadata const& blobs);
|
||||
toExpandedJson(Backend::TransactionAndMetadata const& blobs, NFTokenjson includeNFTIDs = NFTokenjson::DISABLE);
|
||||
|
||||
bool
|
||||
insertDeliveredAmount(
|
||||
|
||||
@@ -113,7 +113,7 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
|
||||
boost::json::object obj;
|
||||
if (!input.binary)
|
||||
{
|
||||
auto [txn, meta] = toExpandedJson(txnPlusMeta);
|
||||
auto [txn, meta] = toExpandedJson(txnPlusMeta, NFTokenjson::ENABLE);
|
||||
obj[JS(meta)] = std::move(meta);
|
||||
obj[JS(tx)] = std::move(txn);
|
||||
obj[JS(tx)].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
|
||||
|
||||
@@ -58,7 +58,7 @@ TxHandler::process(Input input, Context const& ctx) const
|
||||
// clio does not implement 'inLedger' which is a deprecated field
|
||||
if (!input.binary)
|
||||
{
|
||||
auto const [txn, meta] = toExpandedJson(*dbResponse);
|
||||
auto const [txn, meta] = toExpandedJson(*dbResponse, NFTokenjson::ENABLE);
|
||||
output.tx = txn;
|
||||
output.meta = meta;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ constexpr static auto MAXSEQ = 30;
|
||||
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr static auto NFTID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
|
||||
constexpr static auto NFTID2 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA";
|
||||
constexpr static auto NFTID3 = "15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
|
||||
|
||||
class RPCAccountTxHandlerTest : public HandlerBaseTest
|
||||
{
|
||||
@@ -222,7 +225,9 @@ TEST_P(AccountTxParameterTest, InvalidParams)
|
||||
});
|
||||
}
|
||||
|
||||
static std::vector<TransactionAndMetadata>
|
||||
namespace {
|
||||
|
||||
std::vector<TransactionAndMetadata>
|
||||
genTransactions(uint32_t seq1, uint32_t seq2)
|
||||
{
|
||||
auto transactions = std::vector<TransactionAndMetadata>{};
|
||||
@@ -246,6 +251,34 @@ genTransactions(uint32_t seq1, uint32_t seq2)
|
||||
return transactions;
|
||||
}
|
||||
|
||||
std::vector<TransactionAndMetadata>
|
||||
genNFTTransactions(uint32_t seq)
|
||||
{
|
||||
auto transactions = std::vector<TransactionAndMetadata>{};
|
||||
|
||||
auto trans1 = CreateMintNFTTxWithMetadata(ACCOUNT, 1, 50, 123, NFTID);
|
||||
trans1.ledgerSequence = seq;
|
||||
trans1.date = 1;
|
||||
transactions.push_back(trans1);
|
||||
|
||||
auto trans2 = CreateAcceptNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID2);
|
||||
trans2.ledgerSequence = seq;
|
||||
trans2.date = 2;
|
||||
transactions.push_back(trans2);
|
||||
|
||||
auto trans3 = CreateCancelNFTOffersTxWithMetadata(ACCOUNT, 1, 50, std::vector<std::string>{NFTID2, NFTID3});
|
||||
trans3.ledgerSequence = seq;
|
||||
trans3.date = 3;
|
||||
transactions.push_back(trans3);
|
||||
|
||||
auto trans4 = CreateCreateNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID, 123, NFTID2);
|
||||
trans4.ledgerSequence = seq;
|
||||
trans4.date = 4;
|
||||
transactions.push_back(trans4);
|
||||
return transactions;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(RPCAccountTxHandlerTest, IndexSpecificForwardTrue)
|
||||
{
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
@@ -803,3 +836,228 @@ TEST_F(RPCAccountTxHandlerTest, LimitLessThanMin)
|
||||
EXPECT_EQ(output->at("transactions").as_array().size(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCAccountTxHandlerTest, NFTTxs)
|
||||
{
|
||||
auto const OUT = R"({
|
||||
"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"ledger_index_min": 10,
|
||||
"ledger_index_max": 30,
|
||||
"transactions": [
|
||||
{
|
||||
"meta": {
|
||||
"AffectedNodes":
|
||||
[
|
||||
{
|
||||
"ModifiedNode":
|
||||
{
|
||||
"FinalFields":
|
||||
{
|
||||
"NFTokens":
|
||||
[
|
||||
{
|
||||
"NFToken":
|
||||
{
|
||||
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF",
|
||||
"URI": "7465737475726C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"NFToken":
|
||||
{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"LedgerEntryType": "NFTokenPage",
|
||||
"PreviousFields":
|
||||
{
|
||||
"NFTokens":
|
||||
[
|
||||
{
|
||||
"NFToken":
|
||||
{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS",
|
||||
"nftoken_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
|
||||
},
|
||||
"tx":
|
||||
{
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "50",
|
||||
"NFTokenTaxon": 123,
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "NFTokenMint",
|
||||
"hash": "C74463F49CFDCBEF3E9902672719918CDE5042DC7E7660BEBD1D1105C4B6DFF4",
|
||||
"ledger_index": 11,
|
||||
"date": 1
|
||||
},
|
||||
"validated": true
|
||||
},
|
||||
{
|
||||
"meta":
|
||||
{
|
||||
"AffectedNodes":
|
||||
[
|
||||
{
|
||||
"DeletedNode":
|
||||
{
|
||||
"FinalFields":
|
||||
{
|
||||
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
|
||||
},
|
||||
"LedgerEntryType": "NFTokenOffer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS",
|
||||
"nftoken_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
|
||||
},
|
||||
"tx":
|
||||
{
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "50",
|
||||
"NFTokenBuyOffer": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "NFTokenAcceptOffer",
|
||||
"hash": "7682BE6BCDE62F8142915DD852936623B68FC3839A8A424A6064B898702B0CDF",
|
||||
"ledger_index": 11,
|
||||
"date": 2
|
||||
},
|
||||
"validated": true
|
||||
},
|
||||
{
|
||||
"meta":
|
||||
{
|
||||
"AffectedNodes":
|
||||
[
|
||||
{
|
||||
"DeletedNode": {
|
||||
"FinalFields":
|
||||
{
|
||||
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
|
||||
},
|
||||
"LedgerEntryType": "NFTokenOffer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"DeletedNode":
|
||||
{
|
||||
"FinalFields":
|
||||
{
|
||||
"NFTokenID": "15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
|
||||
},
|
||||
"LedgerEntryType": "NFTokenOffer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS",
|
||||
"nftoken_ids":
|
||||
[
|
||||
"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA",
|
||||
"15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
|
||||
]
|
||||
},
|
||||
"tx":
|
||||
{
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "50",
|
||||
"NFTokenOffers":
|
||||
[
|
||||
"05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA",
|
||||
"15FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF"
|
||||
],
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "NFTokenCancelOffer",
|
||||
"hash": "9F82743EEB30065FB9CB92C61F0F064B5859C5A590FA811FAAAD9C988E5B47DB",
|
||||
"ledger_index": 11,
|
||||
"date": 3
|
||||
},
|
||||
"validated": true
|
||||
},
|
||||
{
|
||||
"meta":
|
||||
{
|
||||
"AffectedNodes":
|
||||
[
|
||||
{
|
||||
"CreatedNode":
|
||||
{
|
||||
"LedgerEntryType": "NFTokenOffer",
|
||||
"LedgerIndex": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS",
|
||||
"offer_id": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA"
|
||||
},
|
||||
"tx":
|
||||
{
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "123",
|
||||
"Fee": "50",
|
||||
"NFTokenID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF",
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "NFTokenCreateOffer",
|
||||
"hash": "ECB1837EB7C7C0AC22ECDCCE59FDD4795C70E0B9D8F4E1C9A9408BB7EC75DA5C",
|
||||
"ledger_index": 11,
|
||||
"date": 4
|
||||
},
|
||||
"validated": true
|
||||
}
|
||||
],
|
||||
"validated": true,
|
||||
"marker":
|
||||
{
|
||||
"ledger": 12,
|
||||
"seq": 34
|
||||
}
|
||||
})";
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
MockBackend* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
auto const transactions = genNFTTransactions(MINSEQ + 1);
|
||||
auto const transCursor = TransactionsAndCursor{transactions, TransactionsCursor{12, 34}};
|
||||
ON_CALL(*rawBackendPtr, fetchAccountTransactions).WillByDefault(Return(transCursor));
|
||||
EXPECT_CALL(
|
||||
*rawBackendPtr,
|
||||
fetchAccountTransactions(
|
||||
testing::_, testing::_, false, testing::Optional(testing::Eq(TransactionsCursor{10, 11})), testing::_))
|
||||
.Times(1);
|
||||
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
|
||||
auto const static input = boost::json::parse(fmt::format(
|
||||
R"({{
|
||||
"account": "{}",
|
||||
"ledger_index_min": {},
|
||||
"ledger_index_max": {},
|
||||
"forward": false,
|
||||
"marker": {{"ledger": 10, "seq": 11}}
|
||||
}})",
|
||||
ACCOUNT,
|
||||
-1,
|
||||
-1));
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output, boost::json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace json = boost::json;
|
||||
using namespace testing;
|
||||
|
||||
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
|
||||
constexpr static auto NFTID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DF";
|
||||
constexpr static auto NFTID2 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DA";
|
||||
constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
||||
constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
||||
constexpr static auto CURRENCY = "0158415500000000C1F76FF6ECB0BAC600000000";
|
||||
@@ -252,3 +254,155 @@ TEST_F(RPCTxTest, ReturnBinary)
|
||||
EXPECT_EQ(*output, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCTxTest, MintNFT)
|
||||
{
|
||||
auto const static OUT = fmt::format(
|
||||
R"({{
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "50",
|
||||
"NFTokenTaxon": 123,
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "NFTokenMint",
|
||||
"hash": "C74463F49CFDCBEF3E9902672719918CDE5042DC7E7660BEBD1D1105C4B6DFF4",
|
||||
"meta": {{
|
||||
"AffectedNodes": [
|
||||
{{
|
||||
"ModifiedNode": {{
|
||||
"FinalFields": {{
|
||||
"NFTokens": [
|
||||
{{
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "{}",
|
||||
"URI": "7465737475726C"
|
||||
}}
|
||||
}},
|
||||
{{
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}},
|
||||
"LedgerEntryType": "NFTokenPage",
|
||||
"PreviousFields": {{
|
||||
"NFTokens": [
|
||||
{{
|
||||
"NFToken":
|
||||
{{
|
||||
"NFTokenID": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC",
|
||||
"URI": "7465737475726C"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS",
|
||||
"nftoken_id": "{}"
|
||||
}},
|
||||
"validated": true,
|
||||
"date": 123456,
|
||||
"ledger_index": 100
|
||||
}})",
|
||||
NFTID,
|
||||
NFTID);
|
||||
TransactionAndMetadata tx = CreateMintNFTTxWithMetadata(ACCOUNT, 1, 50, 123, NFTID);
|
||||
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
tx.date = 123456;
|
||||
tx.ledgerSequence = 100;
|
||||
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
|
||||
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
|
||||
runSpawn([this](auto& yield) {
|
||||
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
TXNID));
|
||||
auto const output = handler.process(req, Context{std::ref(yield)});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCTxTest, NFTAcceptOffer)
|
||||
{
|
||||
TransactionAndMetadata tx = CreateAcceptNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID);
|
||||
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
tx.date = 123456;
|
||||
tx.ledgerSequence = 100;
|
||||
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
|
||||
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
|
||||
runSpawn([this](auto& yield) {
|
||||
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
TXNID));
|
||||
auto const output = handler.process(req, Context{std::ref(yield)});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(output->at("meta").at("nftoken_id").as_string(), NFTID);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCTxTest, NFTCancelOffer)
|
||||
{
|
||||
std::vector<std::string> ids{NFTID, NFTID2};
|
||||
TransactionAndMetadata tx = CreateCancelNFTOffersTxWithMetadata(ACCOUNT, 1, 50, ids);
|
||||
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
tx.date = 123456;
|
||||
tx.ledgerSequence = 100;
|
||||
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
|
||||
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
|
||||
runSpawn([this, &ids](auto& yield) {
|
||||
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
TXNID));
|
||||
auto const output = handler.process(req, Context{std::ref(yield)});
|
||||
ASSERT_TRUE(output);
|
||||
|
||||
for (auto const& id : output->at("meta").at("nftoken_ids").as_array())
|
||||
{
|
||||
auto const idStr = id.as_string();
|
||||
ids.erase(std::find(ids.begin(), ids.end(), std::string{idStr.c_str(), idStr.size()}));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(ids.empty());
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCTxTest, NFTCreateOffer)
|
||||
{
|
||||
TransactionAndMetadata tx = CreateCreateNFTOfferTxWithMetadata(ACCOUNT, 1, 50, NFTID, 123, NFTID2);
|
||||
auto const rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
|
||||
tx.date = 123456;
|
||||
tx.ledgerSequence = 100;
|
||||
ON_CALL(*rawBackendPtr, fetchTransaction(ripple::uint256{TXNID}, _)).WillByDefault(Return(tx));
|
||||
EXPECT_CALL(*rawBackendPtr, fetchTransaction).Times(1);
|
||||
runSpawn([this](auto& yield) {
|
||||
auto const handler = AnyHandler{TxHandler{mockBackendPtr}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"command": "tx",
|
||||
"transaction": "{}"
|
||||
}})",
|
||||
TXNID));
|
||||
auto const output = handler.process(req, Context{std::ref(yield)});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output->at("meta").at("offer_id").as_string() == NFTID2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,12 +19,15 @@
|
||||
|
||||
#include "TestObject.h"
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
#include <ripple/protocol/STArray.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
||||
|
||||
ripple::AccountID
|
||||
GetAccountIDWithString(std::string_view id)
|
||||
{
|
||||
@@ -529,3 +532,200 @@ CreateNFTTokenPage(
|
||||
tokenPage.setFieldArray(ripple::sfNFTokens, list);
|
||||
return tokenPage;
|
||||
}
|
||||
|
||||
Backend::TransactionAndMetadata
|
||||
CreateMintNFTTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
uint32_t nfTokenTaxon,
|
||||
std::string_view nftID)
|
||||
{
|
||||
// tx
|
||||
ripple::STObject tx(ripple::sfTransaction);
|
||||
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_MINT);
|
||||
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
|
||||
tx.setAccountID(ripple::sfAccount, account.value());
|
||||
auto amount = ripple::STAmount(fee, false);
|
||||
tx.setFieldAmount(ripple::sfFee, amount);
|
||||
// required field for ttNFTOKEN_MINT
|
||||
tx.setFieldU32(ripple::sfNFTokenTaxon, nfTokenTaxon);
|
||||
tx.setFieldU32(ripple::sfSequence, seq);
|
||||
const char* key = "test";
|
||||
ripple::Slice slice(key, 4);
|
||||
tx.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||
|
||||
// meta
|
||||
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||
ripple::STArray metaArray{1};
|
||||
ripple::STObject node(ripple::sfModifiedNode);
|
||||
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_PAGE);
|
||||
|
||||
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||
ripple::STArray NFTArray1{2};
|
||||
|
||||
// finalFields contain new NFT while previousFields does not
|
||||
auto entry = ripple::STObject(ripple::sfNFToken);
|
||||
entry.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftID});
|
||||
const char* url = "testurl";
|
||||
entry.setFieldVL(ripple::sfURI, ripple::Slice(url, 7));
|
||||
NFTArray1.push_back(entry);
|
||||
|
||||
auto entry2 = ripple::STObject(ripple::sfNFToken);
|
||||
entry2.setFieldH256(ripple::sfNFTokenID, ripple::uint256{INDEX1});
|
||||
entry2.setFieldVL(ripple::sfURI, ripple::Slice(url, 7));
|
||||
NFTArray1.push_back(entry2);
|
||||
|
||||
finalFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
|
||||
|
||||
NFTArray1.erase(NFTArray1.begin());
|
||||
ripple::STObject previousFields(ripple::sfPreviousFields);
|
||||
previousFields.setFieldArray(ripple::sfNFTokens, NFTArray1);
|
||||
|
||||
node.emplace_back(std::move(finalFields));
|
||||
node.emplace_back(std::move(previousFields));
|
||||
metaArray.push_back(node);
|
||||
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
|
||||
|
||||
Backend::TransactionAndMetadata ret;
|
||||
ret.transaction = tx.getSerializer().peekData();
|
||||
ret.metadata = metaObj.getSerializer().peekData();
|
||||
return ret;
|
||||
}
|
||||
|
||||
Backend::TransactionAndMetadata
|
||||
CreateAcceptNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view nftId)
|
||||
{
|
||||
// tx
|
||||
ripple::STObject tx(ripple::sfTransaction);
|
||||
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_ACCEPT_OFFER);
|
||||
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
|
||||
tx.setAccountID(ripple::sfAccount, account.value());
|
||||
auto amount = ripple::STAmount(fee, false);
|
||||
tx.setFieldAmount(ripple::sfFee, amount);
|
||||
tx.setFieldU32(ripple::sfSequence, seq);
|
||||
tx.setFieldH256(ripple::sfNFTokenBuyOffer, ripple::uint256{INDEX1});
|
||||
const char* key = "test";
|
||||
ripple::Slice slice(key, 4);
|
||||
tx.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||
|
||||
// meta
|
||||
// create deletedNode with ltNFTOKEN_OFFER
|
||||
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||
ripple::STArray metaArray{1};
|
||||
ripple::STObject node(ripple::sfDeletedNode);
|
||||
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
|
||||
|
||||
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||
finalFields.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
|
||||
|
||||
node.emplace_back(std::move(finalFields));
|
||||
metaArray.push_back(node);
|
||||
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
|
||||
|
||||
Backend::TransactionAndMetadata ret;
|
||||
ret.transaction = tx.getSerializer().peekData();
|
||||
ret.metadata = metaObj.getSerializer().peekData();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NFTokenCancelOffer can be used to cancel multiple offers
|
||||
Backend::TransactionAndMetadata
|
||||
CreateCancelNFTOffersTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
std::vector<std::string> const& nftOffers)
|
||||
{
|
||||
// tx
|
||||
ripple::STObject tx(ripple::sfTransaction);
|
||||
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_CANCEL_OFFER);
|
||||
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
|
||||
tx.setAccountID(ripple::sfAccount, account.value());
|
||||
auto amount = ripple::STAmount(fee, false);
|
||||
tx.setFieldAmount(ripple::sfFee, amount);
|
||||
tx.setFieldU32(ripple::sfSequence, seq);
|
||||
ripple::STVector256 offers;
|
||||
offers.resize(2);
|
||||
std::transform(nftOffers.cbegin(), nftOffers.cend(), offers.begin(), [&](auto const& nftId) {
|
||||
return ripple::uint256{nftId.c_str()};
|
||||
});
|
||||
tx.setFieldV256(ripple::sfNFTokenOffers, offers);
|
||||
const char* key = "test";
|
||||
ripple::Slice slice(key, 4);
|
||||
tx.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||
|
||||
// meta
|
||||
// create deletedNode with ltNFTOKEN_OFFER
|
||||
// reuse the offer id as nft id
|
||||
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||
ripple::STArray metaArray{nftOffers.size()};
|
||||
for (auto const& nftId : nftOffers)
|
||||
{
|
||||
ripple::STObject node(ripple::sfDeletedNode);
|
||||
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
|
||||
|
||||
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||
finalFields.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId.c_str()});
|
||||
|
||||
node.emplace_back(std::move(finalFields));
|
||||
metaArray.push_back(node);
|
||||
}
|
||||
|
||||
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
|
||||
|
||||
Backend::TransactionAndMetadata ret;
|
||||
ret.transaction = tx.getSerializer().peekData();
|
||||
ret.metadata = metaObj.getSerializer().peekData();
|
||||
return ret;
|
||||
}
|
||||
|
||||
Backend::TransactionAndMetadata
|
||||
CreateCreateNFTOfferTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
std::string_view nftId,
|
||||
std::uint32_t offerPrice,
|
||||
std::string_view offerId)
|
||||
{
|
||||
// tx
|
||||
ripple::STObject tx(ripple::sfTransaction);
|
||||
tx.setFieldU16(ripple::sfTransactionType, ripple::ttNFTOKEN_CREATE_OFFER);
|
||||
auto account = ripple::parseBase58<ripple::AccountID>(std::string(accountId));
|
||||
tx.setAccountID(ripple::sfAccount, account.value());
|
||||
auto amount = ripple::STAmount(fee, false);
|
||||
tx.setFieldAmount(ripple::sfFee, amount);
|
||||
auto price = ripple::STAmount(offerPrice, false);
|
||||
tx.setFieldAmount(ripple::sfAmount, price);
|
||||
tx.setFieldU32(ripple::sfSequence, seq);
|
||||
tx.setFieldH256(ripple::sfNFTokenID, ripple::uint256{nftId});
|
||||
const char* key = "test";
|
||||
ripple::Slice slice(key, 4);
|
||||
tx.setFieldVL(ripple::sfSigningPubKey, slice);
|
||||
|
||||
// meta
|
||||
// create createdNode with LedgerIndex
|
||||
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||
ripple::STArray metaArray{1};
|
||||
|
||||
ripple::STObject node(ripple::sfCreatedNode);
|
||||
node.setFieldU16(ripple::sfLedgerEntryType, ripple::ltNFTOKEN_OFFER);
|
||||
node.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{offerId});
|
||||
|
||||
metaArray.push_back(node);
|
||||
metaObj.setFieldArray(ripple::sfAffectedNodes, metaArray);
|
||||
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
|
||||
|
||||
Backend::TransactionAndMetadata ret;
|
||||
ret.transaction = tx.getSerializer().peekData();
|
||||
ret.metadata = metaObj.getSerializer().peekData();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -228,3 +228,33 @@ CreateSignerLists(std::vector<std::pair<std::string, uint32_t>> const& signers);
|
||||
CreateNFTTokenPage(
|
||||
std::vector<std::pair<std::string, std::string>> const& tokens,
|
||||
std::optional<ripple::uint256> previousPage);
|
||||
|
||||
[[nodiscard]] Backend::TransactionAndMetadata
|
||||
CreateMintNFTTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
uint32_t nfTokenTaxon,
|
||||
std::string_view nftID);
|
||||
|
||||
[[nodiscard]] Backend::TransactionAndMetadata
|
||||
CreateAcceptNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view offerId);
|
||||
|
||||
[[nodiscard]] Backend::TransactionAndMetadata
|
||||
CreateCancelNFTOffersTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
std::vector<std::string> const& nftIds);
|
||||
|
||||
[[nodiscard]] Backend::TransactionAndMetadata
|
||||
CreateCreateNFTOfferTxWithMetadata(std::string_view accountId, uint32_t seq, uint32_t fee, std::string_view offerId);
|
||||
|
||||
[[nodiscard]] Backend::TransactionAndMetadata
|
||||
CreateCreateNFTOfferTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
uint32_t seq,
|
||||
uint32_t fee,
|
||||
std::string_view nftId,
|
||||
std::uint32_t offerPrice,
|
||||
std::string_view offerId);
|
||||
|
||||
Reference in New Issue
Block a user