diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 9cdd2f0d4..c3fd51bc7 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -741,6 +741,7 @@ if (tests) src/test/app/RCLCensorshipDetector_test.cpp src/test/app/RCLValidations_test.cpp src/test/app/Regression_test.cpp + src/test/app/Remit_test.cpp src/test/app/SHAMapStore_test.cpp src/test/app/SetAuth_test.cpp src/test/app/SetRegularKey_test.cpp @@ -890,6 +891,7 @@ if (tests) src/test/jtx/impl/rate.cpp src/test/jtx/impl/regkey.cpp src/test/jtx/impl/reward.cpp + src/test/jtx/impl/remit.cpp src/test/jtx/impl/sendmax.cpp src/test/jtx/impl/seq.cpp src/test/jtx/impl/sig.cpp diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp new file mode 100644 index 000000000..e16f6b48c --- /dev/null +++ b/src/test/app/Remit_test.cpp @@ -0,0 +1,380 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL-Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Destination Exists - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | N | N | N | N | +// A | B | Y | Y | N | N | N | N | +// A | B | Y | N | Y | Y | N | N | +// A | B | Y | Y | Y | Y | N | N | +// A | B | Y | N | N | N | Y | N | +// A | B | Y | N | N | N | N | Y | +// A | B | Y | Y | N | N | Y | N | +// A | B | Y | Y | Y | Y | Y | N | +// A | B | Y | Y | N | N | Y | Y | +// A | B | Y | Y | Y | Y | Y | Y | + +/* +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | Y | N | N | N | +// A | B | Y | Y | Y | N | N | N | +// A | B | Y | Y | Y | N | Y | N | +// A | B | Y | Y | Y | N | Y | Y | + +// Destination Does Not Exist - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | N | N | N | N | +// A | B | N | Y | N | N | N | N | +// A | B | N | N | Y | Y | N | N | +// A | B | N | Y | Y | Y | N | N | +// A | B | N | N | N | N | Y | N | +// A | B | N | N | N | N | N | Y | +// A | B | N | Y | N | N | Y | N | +// A | B | N | Y | Y | Y | Y | N | +// A | B | N | Y | N | N | Y | Y | +// A | B | N | Y | Y | Y | Y | Y | + +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | Y | N | N | N | +// A | B | N | Y | Y | N | N | N | +// A | B | N | Y | Y | N | Y | N | +// A | B | N | Y | Y | N | Y | Y | +*/ + +namespace ripple { +namespace test { +struct Remit_test : public beast::unit_test::suite +{ + + void + testEnabled(FeatureBitset features) + { + // 0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA + + testcase("enabled"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + for (bool const withRemit : {true, false}) + { + auto const amend = + withRemit ? features : features - featureRemit; + + Env env{*this, amend}; + + env.fund(XRP(1000), alice, bob); + + auto const txResult = + withRemit ? ter(tesSUCCESS) : ter(temDISABLED); + + // REMIT + env(remit::remit(alice, bob), txResult); + env.close(); + } + } + + void + testDestExistsTLExists(FeatureBitset features) + { + testcase("dest exists and trustline exists"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // REMIT + { + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + // auto const preAlice = env.balance(alice, USD.issue()); + } + + // REMIT: XAH + { + auto const preAlice = env.balance(alice); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + env.close(); + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + XRP(1) - feeDrops); + } + + // REMIT: XAH + USD + { + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'A'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit with uritoken id + env(remit::remit(alice, bob), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: URITOKEN MINT + { + std::string const uri(maxTokenURILength, 'B'); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'C'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit xah + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'D'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + URITOKEN MINT + { + // mint uri token + std::string const uri1(maxTokenURILength, 'E'); + std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, 'E'); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({tid}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // mint uri token + std::string const uri1(maxTokenURILength, 'F'); + std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, 'F'); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({tid}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + } + } + + void + testDestDoesNotExists(FeatureBitset features) + { + testcase("dest does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({ XRP(1) }), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), txResult); + // env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testTLDoesNotExists(FeatureBitset features) + { + testcase("trust line does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({ XRP(1) }), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), txResult); + // env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testWithFeats(FeatureBitset features) + { + testEnabled(features); + testDestExistsTLExists(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Remit, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/jtx.h b/src/test/jtx.h index 39d4a9662..0fcc62239 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/impl/remit.cpp b/src/test/jtx/impl/remit.cpp new file mode 100644 index 000000000..8ae158eff --- /dev/null +++ b/src/test/jtx/impl/remit.cpp @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace ripple { +namespace test { +namespace jtx { +namespace remit { + +Json::Value +remit(jtx::Account const& account, jtx::Account const& dest) +{ + using namespace jtx; + Json::Value jv; + jv[jss::TransactionType] = jss::Remit; + jv[jss::Account] = account.human(); + jv[jss::Destination] = dest.human(); + return jv; +} + +void +amts::operator()(Env& env, JTx& jt) const +{ + auto& ja = jt.jv[sfAmounts.getJsonName()]; + for (std::size_t i = 0; i < amts_.size(); ++i) + { + ja[i][sfAmountEntry.jsonName] = Json::Value{}; + ja[i][sfAmountEntry.jsonName][jss::Amount] = + amts_[i].getJson(JsonOptions::none); + } + jt.jv[sfAmounts.jsonName] = ja; +} + +void +blob::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfBlob.jsonName] = blob_; +} + +void +inform::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfInform.jsonName] = inform_.human(); +} + +void +token_ids::operator()(Env& env, JTx& jt) const +{ + for (std::size_t i = 0; i < token_ids_.size(); ++i) + { + jt.jv[sfURITokenIDs.jsonName] = Json::arrayValue; + jt.jv[sfURITokenIDs.jsonName][i] = token_ids_[i]; + } +} + +void +uri::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfMintURIToken.jsonName] = Json::Value{}; + jt.jv[sfMintURIToken.jsonName][sfURI.jsonName] = strHex(uri_);; +} + +} // namespace remit +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/remit.h b/src/test/jtx/remit.h new file mode 100644 index 000000000..4ad4bafbf --- /dev/null +++ b/src/test/jtx/remit.h @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 XRPL Labs + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_REMIT_H_INCLUDED +#define RIPPLE_TEST_JTX_REMIT_H_INCLUDED + +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace remit { + +Json::Value +remit(jtx::Account const& account, jtx::Account const& dest); + +/** Sets the optional Amount on a JTx. */ +class amts +{ +private: + std::vector amts_; + +public: + explicit amts(std::vector const& amts) : amts_(amts) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Set the optional "Blob" on a JTx */ +class blob +{ +private: + std::string blob_; + +public: + explicit blob(std::string const& blob) : blob_(blob) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Sets the optional "Inform" on a JTx. */ +class inform +{ +private: + jtx::Account inform_; + +public: + explicit inform(jtx::Account const& inform) : inform_(inform) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Sets the optional "URITokenIDs" on a JTx. */ +class token_ids +{ +private: + std::vector token_ids_; + +public: + explicit token_ids(std::vector const& token_ids) : token_ids_(token_ids) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Set the optional "sfMintURIToken" on a JTx */ +class uri +{ +private: + std::string uri_; + +public: + explicit uri(std::string const& uri) : uri_(uri) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +} // namespace remit + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif // RIPPLE_TEST_JTX_REMIT_H_INCLUDED