177 using namespace std::chrono_literals;
180 auto baseFee = env.
current()->fees().base.drops();
187 stream[jss::streams].append(
"transactions");
188 auto jv = wsc->invoke(
"subscribe", stream);
189 if (wsc->version() == 2)
192 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
194 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
195 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
197 BEAST_EXPECT(jv[jss::status] ==
"success");
205 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
206 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"]
207 [
"NewFields"][jss::Account]
208 == Account(
"alice").human() &&
209 jv[jss::transaction][jss::TransactionType]
211 jv[jss::transaction][jss::DeliverMax]
212 == std::to_string(10000000000 + baseFee) &&
213 jv[jss::transaction][jss::Fee]
214 == std::to_string(baseFee) &&
215 jv[jss::transaction][jss::Sequence]
220 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
221 return jv[jss::meta][
"AffectedNodes"][0u][
"ModifiedNode"]
222 [
"FinalFields"][jss::Account] ==
223 Account(
"alice").human();
230 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
231 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"]
232 [
"NewFields"][jss::Account]
233 == Account(
"bob").human() &&
234 jv[jss::transaction][jss::TransactionType]
236 jv[jss::transaction][jss::DeliverMax]
237 == std::to_string(10000000000 + baseFee) &&
238 jv[jss::transaction][jss::Fee]
239 == std::to_string(baseFee) &&
240 jv[jss::transaction][jss::Sequence]
245 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
246 return jv[jss::meta][
"AffectedNodes"][0u][
"ModifiedNode"]
247 [
"FinalFields"][jss::Account] ==
248 Account(
"bob").human();
254 auto jv = wsc->invoke(
"unsubscribe", stream);
255 if (wsc->version() == 2)
258 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
260 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
261 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
263 BEAST_EXPECT(jv[jss::status] ==
"success");
270 stream[jss::accounts].append(
Account(
"alice").human());
271 auto jv = wsc->invoke(
"subscribe", stream);
272 if (wsc->version() == 2)
275 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
277 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
278 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
280 BEAST_EXPECT(jv[jss::status] ==
"success");
287 BEAST_EXPECT(!wsc->getMsg(10ms));
294 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
295 return jv[jss::meta][
"AffectedNodes"][1u][
"ModifiedNode"]
296 [
"FinalFields"][jss::Account] ==
297 Account(
"alice").human();
300 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
301 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"]
302 [
"NewFields"][
"LowLimit"][jss::issuer] ==
303 Account(
"alice").human();
308 auto jv = wsc->invoke(
"unsubscribe", stream);
309 if (wsc->version() == 2)
312 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
314 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
315 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
317 BEAST_EXPECT(jv[jss::status] ==
"success");
445 auto& cfg = env.app().config();
446 if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty()))
448 auto const parsedseed =
449 parseBase58<Seed>(cfg.section(SECTION_VALIDATION_SEED).values()[0]);
450 if (!BEAST_EXPECT(parsedseed))
465 stream[jss::streams].append(
"validations");
466 auto jv = wsc->invoke(
"subscribe", stream);
467 if (wsc->version() == 2)
470 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
472 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
473 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
475 BEAST_EXPECT(jv[jss::status] ==
"success");
480 auto validValidationFields = [&env, &valPublicKey](
482 if (jv[jss::type] !=
"validationReceived")
485 if (jv[jss::validation_public_key].asString() != valPublicKey)
488 if (jv[jss::ledger_hash] !=
492 if (jv[jss::ledger_index] !=
499 if (jv[jss::full] !=
true)
502 if (jv.isMember(jss::load_fee))
505 if (!jv.isMember(jss::signature))
508 if (!jv.isMember(jss::signing_time))
511 if (!jv.isMember(jss::cookie))
514 if (!jv.isMember(jss::validated_hash))
517 uint32_t netID = env.app().config().NETWORK_ID;
518 if (!jv.isMember(jss::network_id) ||
519 jv[jss::network_id] != netID)
524 (env.closed()->info().seq + 1) % 256 == 0;
540 while (env.closed()->info().seq < 300)
543 using namespace std::chrono_literals;
544 BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
549 auto jv = wsc->invoke(
"unsubscribe", stream);
550 if (wsc->version() == 2)
553 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
555 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
556 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
558 BEAST_EXPECT(jv[jss::status] ==
"success");
594 auto const method = subscribe ?
"subscribe" :
"unsubscribe";
595 testcase <<
"Error cases for " << method;
601 auto jr = env.rpc(
"json", method,
"{}")[jss::result];
602 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
603 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
608 jv[jss::url] =
"not-a-url";
609 jv[jss::username] =
"admin";
610 jv[jss::password] =
"password";
611 auto jr = env.rpc(
"json", method,
to_string(jv))[jss::result];
614 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
615 BEAST_EXPECT(jr[jss::error_message] ==
"Failed to parse url.");
623 jv[jss::url] =
"ftp://scheme.not.supported.tld";
624 auto jr = env.rpc(
"json", method,
to_string(jv))[jss::result];
627 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
629 jr[jss::error_message] ==
630 "Only http and https is supported.");
637 jv[jss::url] =
"no-url";
639 env_nonadmin.rpc(
"json", method,
to_string(jv))[jss::result];
640 BEAST_EXPECT(jr[jss::error] ==
"noPermission");
642 jr[jss::error_message] ==
643 "You don't have permission for this command.");
655 for (
auto const& f : {jss::accounts_proposed, jss::accounts})
657 for (
auto const& nonArray : nonArrays)
661 auto jr = wsc->invoke(method, jv)[jss::result];
662 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
663 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
669 auto jr = wsc->invoke(method, jv)[jss::result];
670 BEAST_EXPECT(jr[jss::error] ==
"actMalformed");
671 BEAST_EXPECT(jr[jss::error_message] ==
"Account malformed.");
675 for (
auto const& nonArray : nonArrays)
678 jv[jss::books] = nonArray;
679 auto jr = wsc->invoke(method, jv)[jss::result];
680 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
681 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
687 jv[jss::books][0u] = 1;
688 auto jr = wsc->invoke(method, jv)[jss::result];
689 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
690 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
699 auto jr = wsc->invoke(method, jv)[jss::result];
700 BEAST_EXPECT(jr[jss::error] ==
"srcCurMalformed");
702 jr[jss::error_message] ==
"Source currency is malformed.");
711 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"ZZZZ";
712 auto jr = wsc->invoke(method, jv)[jss::result];
713 BEAST_EXPECT(jr[jss::error] ==
"srcCurMalformed");
715 jr[jss::error_message] ==
"Source currency is malformed.");
724 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"USD";
725 jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
726 auto jr = wsc->invoke(method, jv)[jss::result];
727 BEAST_EXPECT(jr[jss::error] ==
"srcIsrMalformed");
729 jr[jss::error_message] ==
"Source issuer is malformed.");
738 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"USD";
739 jv[jss::books][0u][jss::taker_pays][jss::issuer] =
741 auto jr = wsc->invoke(method, jv)[jss::result];
742 BEAST_EXPECT(jr[jss::error] ==
"srcIsrMalformed");
744 jr[jss::error_message] ==
"Source issuer is malformed.");
751 jv[jss::books][0u][jss::taker_pays] =
752 Account{
"gateway"}[
"USD"](1).value().getJson(
755 auto jr = wsc->invoke(method, jv)[jss::result];
758 BEAST_EXPECT(jr[jss::error] ==
"dstAmtMalformed");
760 jr[jss::error_message] ==
761 "Destination amount/currency/issuer is malformed.");
768 jv[jss::books][0u][jss::taker_pays] =
769 Account{
"gateway"}[
"USD"](1).value().getJson(
771 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"ZZZZ";
772 auto jr = wsc->invoke(method, jv)[jss::result];
775 BEAST_EXPECT(jr[jss::error] ==
"dstAmtMalformed");
777 jr[jss::error_message] ==
778 "Destination amount/currency/issuer is malformed.");
785 jv[jss::books][0u][jss::taker_pays] =
786 Account{
"gateway"}[
"USD"](1).value().getJson(
788 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"USD";
789 jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
790 auto jr = wsc->invoke(method, jv)[jss::result];
791 BEAST_EXPECT(jr[jss::error] ==
"dstIsrMalformed");
793 jr[jss::error_message] ==
"Destination issuer is malformed.");
800 jv[jss::books][0u][jss::taker_pays] =
801 Account{
"gateway"}[
"USD"](1).value().getJson(
803 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"USD";
804 jv[jss::books][0u][jss::taker_gets][jss::issuer] =
806 auto jr = wsc->invoke(method, jv)[jss::result];
807 BEAST_EXPECT(jr[jss::error] ==
"dstIsrMalformed");
809 jr[jss::error_message] ==
"Destination issuer is malformed.");
816 jv[jss::books][0u][jss::taker_pays] =
817 Account{
"gateway"}[
"USD"](1).value().getJson(
819 jv[jss::books][0u][jss::taker_gets] =
820 Account{
"gateway"}[
"USD"](1).value().getJson(
822 auto jr = wsc->invoke(method, jv)[jss::result];
823 BEAST_EXPECT(jr[jss::error] ==
"badMarket");
824 BEAST_EXPECT(jr[jss::error_message] ==
"No such market.");
827 for (
auto const& nonArray : nonArrays)
830 jv[jss::streams] = nonArray;
831 auto jr = wsc->invoke(method, jv)[jss::result];
832 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
833 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
839 jv[jss::streams][0u] = 1;
840 auto jr = wsc->invoke(method, jv)[jss::result];
841 BEAST_EXPECT(jr[jss::error] ==
"malformedStream");
842 BEAST_EXPECT(jr[jss::error_message] ==
"Stream malformed.");
848 jv[jss::streams][0u] =
"not_a_stream";
849 auto jr = wsc->invoke(method, jv)[jss::result];
850 BEAST_EXPECT(jr[jss::error] ==
"malformedStream");
851 BEAST_EXPECT(jr[jss::error_message] ==
"Stream malformed.");
860 using namespace std::chrono_literals;
873 auto goodSubRPC = [](
Json::Value const& subReply) ->
bool {
874 return subReply.isMember(jss::result) &&
875 subReply[jss::result].isMember(jss::status) &&
876 subReply[jss::result][jss::status] == jss::success;
887 bool first_flag =
false;
889 for (
int i = 0; i < numReplies; ++i)
892 auto reply = wsc.
getMsg(100ms);
896 if (r.isMember(jss::account_history_tx_index))
897 idx = r[jss::account_history_tx_index].asInt();
898 if (r.isMember(jss::account_history_tx_first))
900 bool boundary = r.isMember(jss::account_history_boundary);
901 int ledger_idx = r[jss::ledger_index].asInt();
902 if (r.isMember(jss::transaction) &&
903 r[jss::transaction].isMember(jss::hash))
905 auto t{r[jss::transaction]};
907 idx, t[jss::hash].asString(), boundary, ledger_idx);
911 return {
false, first_flag};
914 return {
true, first_flag};
921 auto sendPayments = [](
Env& env,
929 for (
int i = 0; i < newTxns; ++i)
931 auto& from = (i % 2 == 0) ? a : b;
932 auto& to = (i % 2 == 0) ? b : a;
939 for (
int i = 0; i < ledgersToClose; ++i)
949 auto hashCompare = [](IdxHashVec
const& accountVec,
950 IdxHashVec
const& txHistoryVec,
951 bool sizeCompare) ->
bool {
952 if (accountVec.empty() || txHistoryVec.empty())
954 if (sizeCompare && accountVec.size() != (txHistoryVec.size()))
958 for (
auto const& tx : txHistoryVec)
964 if (i >= accountVec.size())
967 if (it == txHistoryMap.
end())
972 auto firstHistoryIndex = getHistoryIndex(0);
973 if (!firstHistoryIndex)
975 for (
std::size_t i = 1; i < accountVec.size(); ++i)
977 if (
auto idx = getHistoryIndex(i);
978 !idx || *idx != *firstHistoryIndex + i)
1014 auto checkBoundary = [](IdxHashVec
const& vec,
bool ) {
1015 size_t num_tx = vec.size();
1016 for (
size_t i = 0; i < num_tx; ++i)
1018 auto [idx, hash, boundary, ledger] = vec[i];
1019 if ((i + 1 == num_tx || ledger !=
std::get<3>(vec[i + 1])) !=
1039 request[jss::account_history_tx_stream][jss::account] =
1041 auto jv = wscTxHistory->invoke(
"subscribe", request);
1042 if (!BEAST_EXPECT(goodSubRPC(jv)))
1045 jv = wscTxHistory->invoke(
"subscribe", request);
1046 BEAST_EXPECT(!goodSubRPC(jv));
1051 request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
1053 jv = wscTxHistory->invoke(
"unsubscribe", request);
1054 if (!BEAST_EXPECT(goodSubRPC(jv)))
1057 sendPayments(env, env.
master, alice, 1, 1, 123456);
1060 auto r = getTxHash(*wscTxHistory, vec, 1);
1061 if (!BEAST_EXPECT(r.first && r.second))
1067 request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
1069 jv = wscTxHistory->invoke(
"unsubscribe", request);
1070 BEAST_EXPECT(goodSubRPC(jv));
1072 sendPayments(env, env.
master, alice, 1, 1);
1073 r = getTxHash(*wscTxHistory, vec, 1);
1074 BEAST_EXPECT(!r.first);
1085 request[jss::account_history_tx_stream][jss::account] =
1086 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1087 auto jv = wscTxHistory->invoke(
"subscribe", request);
1088 if (!BEAST_EXPECT(goodSubRPC(jv)))
1090 IdxHashVec genesisFullHistoryVec;
1092 !getTxHash(*wscTxHistory, genesisFullHistoryVec, 1).first))
1099 sendPayments(env, env.
master, bob, 1, 1, 654321);
1101 auto r = getTxHash(*wscTxHistory, genesisFullHistoryVec, 1);
1102 if (!BEAST_EXPECT(r.first && r.second))
1105 request[jss::account_history_tx_stream][jss::account] = bob.
human();
1106 jv = wscTxHistory->invoke(
"subscribe", request);
1107 if (!BEAST_EXPECT(goodSubRPC(jv)))
1109 IdxHashVec bobFullHistoryVec;
1110 r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1);
1111 if (!BEAST_EXPECT(r.first && r.second))
1120 jv = wscTxHistory->invoke(
"unsubscribe", request);
1121 if (!BEAST_EXPECT(goodSubRPC(jv)))
1123 request[jss::account_history_tx_stream][jss::account] =
1124 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1125 jv = wscTxHistory->invoke(
"unsubscribe", request);
1126 BEAST_EXPECT(goodSubRPC(jv));
1132 sendPayments(env, env.
master, bob, 30, 300);
1134 request[jss::account_history_tx_stream][jss::account] = bob.
human();
1135 jv = wscTxHistory->invoke(
"subscribe", request);
1137 bobFullHistoryVec.
clear();
1139 getTxHash(*wscTxHistory, bobFullHistoryVec, 31).second);
1140 jv = wscTxHistory->invoke(
"unsubscribe", request);
1142 request[jss::account_history_tx_stream][jss::account] =
1143 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1144 jv = wscTxHistory->invoke(
"subscribe", request);
1145 genesisFullHistoryVec.
clear();
1147 getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second);
1148 jv = wscTxHistory->invoke(
"unsubscribe", request);
1165 env.
fund(
XRP(222222), accounts);
1171 stream[jss::accounts].append(alice.
human());
1172 auto jv = wscAccount->invoke(
"subscribe", stream);
1174 sendPayments(env, alice, bob, 5, 1);
1175 sendPayments(env, alice, bob, 5, 1);
1176 IdxHashVec accountVec;
1177 if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1183 request[jss::account_history_tx_stream][jss::account] =
1185 jv = wscTxHistory->invoke(
"subscribe", request);
1188 IdxHashVec txHistoryVec;
1189 if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1191 if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec,
true)))
1196 if (!BEAST_EXPECT(checkBoundary(txHistoryVec,
false)))
1201 IdxHashVec initFundTxns;
1203 getTxHash(*wscTxHistory, initFundTxns, 10).second) ||
1204 !BEAST_EXPECT(checkBoundary(initFundTxns,
false)))
1209 sendPayments(env, alice, bob, 10, 1);
1210 if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1212 if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1214 if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec,
true)))
1219 if (!BEAST_EXPECT(checkBoundary(txHistoryVec,
false)))
1222 wscTxHistory->invoke(
"unsubscribe", request);
1223 wscAccount->invoke(
"unsubscribe", stream);
1232 auto const USD_a = alice[
"USD"];
1235 env.
fund(
XRP(333333), accounts);
1236 env.
trust(USD_a(20000), carol);
1239 auto mixedPayments = [&]() ->
int {
1240 sendPayments(env, alice, carol, 1, 0);
1241 env(
pay(alice, carol, USD_a(100)));
1249 request[jss::account_history_tx_stream][jss::account] =
1252 auto jv = ws->invoke(
"subscribe", request);
1256 getTxHash(*ws, tempVec, 100);
1259 auto count = mixedPayments();
1261 if (!BEAST_EXPECT(getTxHash(*ws, vec1, count).first))
1263 ws->invoke(
"unsubscribe", request);
1272 env.
fund(
XRP(444444), accounts);
1276 auto oneRound = [&](
int numPayments) {
1277 return sendPayments(env, alice, carol, numPayments, 300);
1283 request[jss::account_history_tx_stream][jss::account] =
1286 auto jv = wscLong->invoke(
"subscribe", request);
1290 getTxHash(*wscLong, tempVec, 100);
1294 for (
int kk = 2; kk < 10; ++kk)
1296 auto count = oneRound(kk);
1298 if (!BEAST_EXPECT(getTxHash(*wscLong, vec1, count).first))
1303 auto jv = wscShort->invoke(
"subscribe", request);
1305 if (!BEAST_EXPECT(getTxHash(*wscShort, vec2, count).first))
1307 if (!BEAST_EXPECT(hashCompare(vec1, vec2,
true)))
1309 wscShort->invoke(
"unsubscribe", request);
1384 testcase(
"Test synthetic fields from Subscribe response");
1386 using namespace test::jtx;
1387 using namespace std::chrono_literals;
1391 Account const broker{
"broker"};
1393 Env env{*
this, features};
1394 env.fund(
XRP(10000), alice, bob, broker);
1400 stream[jss::streams].append(
"transactions");
1401 auto jv = wsc->invoke(
"subscribe", stream);
1406 auto verifyNFTokenID = [&](
uint256 const& actualNftID) {
1407 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1410 nftID.parseHex(jv[jss::meta][jss::nftoken_id].asString()));
1411 return nftID == actualNftID;
1417 auto verifyNFTokenIDsInCancelOffer =
1419 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1420 std::vector<uint256> metaIDs;
1422 jv[jss::meta][jss::nftoken_ids].begin(),
1423 jv[jss::meta][jss::nftoken_ids].end(),
1424 std::back_inserter(metaIDs),
1425 [this](Json::Value id) {
1427 BEAST_EXPECT(nftID.parseHex(id.asString()));
1431 std::sort(metaIDs.begin(), metaIDs.end());
1432 std::sort(actualNftIDs.begin(), actualNftIDs.end());
1435 BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
1439 for (
size_t i = 0; i < metaIDs.size(); ++i)
1440 BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
1447 auto verifyNFTokenOfferID = [&](
uint256 const& offerID) {
1448 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1449 uint256 metaOfferID;
1450 BEAST_EXPECT(metaOfferID.parseHex(
1451 jv[jss::meta][jss::offer_id].asString()));
1452 return metaOfferID == offerID;
1464 verifyNFTokenID(nftId1);
1470 verifyNFTokenID(nftId2);
1475 uint256 const aliceOfferIndex1 =
1480 verifyNFTokenOfferID(aliceOfferIndex1);
1482 uint256 const aliceOfferIndex2 =
1487 verifyNFTokenOfferID(aliceOfferIndex2);
1493 alice, {aliceOfferIndex1, aliceOfferIndex2}));
1495 verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
1499 auto const bobBuyOfferIndex =
1503 verifyNFTokenOfferID(bobBuyOfferIndex);
1509 verifyNFTokenID(nftId1);
1519 verifyNFTokenID(nftId);
1522 uint256 const offerAliceToBroker =
1525 token::destination(broker),
1528 verifyNFTokenOfferID(offerAliceToBroker);
1531 uint256 const offerBobToBroker =
1535 verifyNFTokenOfferID(offerBobToBroker);
1539 broker, offerBobToBroker, offerAliceToBroker));
1541 verifyNFTokenID(nftId);
1552 verifyNFTokenID(nftId);
1555 uint256 const aliceOfferIndex1 =
1560 verifyNFTokenOfferID(aliceOfferIndex1);
1562 uint256 const aliceOfferIndex2 =
1567 verifyNFTokenOfferID(aliceOfferIndex2);
1572 alice, {aliceOfferIndex1, aliceOfferIndex2}));
1574 verifyNFTokenIDsInCancelOffer({nftId});
1577 if (features[featureNFTokenMintOffer])
1579 uint256 const aliceMintWithOfferIndex1 =
1583 verifyNFTokenOfferID(aliceMintWithOfferIndex1);