//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2024 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 namespace ripple { namespace test { struct Remit_test : public beast::unit_test::suite { // testDebug("PRE", env, { alice, bob }, {}); void testDebug( std::string const& testNumber, jtx::Env const& env, std::vector const& accounts, std::vector const& ious) { std::cout << "DEBUG: " << testNumber << "\n"; for (std::size_t a = 0; a < accounts.size(); ++a) { auto const bal = env.balance(accounts[a]); std::cout << "account: " << accounts[a].human() << "BAL: " << bal << "\n"; for (std::size_t i = 0; i < ious.size(); ++i) { auto const iouBal = env.balance(accounts[a], ious[i]); std::cout << "account: " << accounts[a].human() << "IOU: " << iouBal << "\n"; } } } static bool inOwnerDir( ReadView const& view, jtx::Account const& acct, uint256 const& tid) { auto const uritSle = view.read({ltURI_TOKEN, tid}); ripple::Dir const ownerDir(view, keylet::ownerDir(acct.id())); return std::find(ownerDir.begin(), ownerDir.end(), uritSle) != ownerDir.end(); } static std::size_t ownerDirCount(ReadView const& view, jtx::Account const& acct) { ripple::Dir const ownerDir(view, keylet::ownerDir(acct.id())); return std::distance(ownerDir.begin(), ownerDir.end()); }; static AccountID tokenOwner(ReadView const& view, uint256 const& id) { auto const slep = view.read({ltURI_TOKEN, id}); if (!slep) return AccountID(); return slep->getAccountID(sfOwner); } static AccountID tokenIsser(ReadView const& view, uint256 const& id) { auto const slep = view.read({ltURI_TOKEN, id}); if (!slep) return AccountID(); return slep->getAccountID(sfIssuer); } static STAmount lineBalance( jtx::Env const& env, jtx::Account const& account, jtx::Account const& gw, jtx::IOU const& iou) { auto const sle = env.le(keylet::line(account, gw, iou.currency)); if (sle && sle->isFieldPresent(sfBalance)) return (*sle)[sfBalance]; return STAmount(iou, 0); } static bool validateSequence( jtx::Env const& env, jtx::Account const& account, std::uint32_t const& sequence) { auto const sle = env.le(keylet::account(account)); if (sle && sle->isFieldPresent(sfSequence)) return (*sle)[sfSequence] == sequence; return false; } static std::pair> uriTokenKeyAndSle( ReadView const& view, jtx::Account const& account, std::string const& uri) { auto const k = keylet::uritoken(account, Blob(uri.begin(), uri.end())); return {k.key, view.read(k)}; } void testEnabled(FeatureBitset features) { 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 testPreflightInvalid(FeatureBitset features) { testcase("preflight invalid"); 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.fund(XRP(1000), alice, bob, gw); env.close(); //---------------------------------------------------------------------- // preflight // temINVALID_FLAG - Invalid flags set. { env(remit::remit(alice, bob), txflags(tfBurnable), ter(temINVALID_FLAG)); env.close(); } // temREDUNDANT - Remit to self. { env(remit::remit(alice, alice), ter(temREDUNDANT)); env.close(); } // temMALFORMED - blob was more than 128kib { ripple::Blob blob; blob.resize(129 * 1024); env(remit::remit(alice, bob), remit::blob(strHex(blob)), ter(temMALFORMED)); } // temMALFORMED - AmountEntrys Exceeds Limit { std::vector amts_; // Remove the const qualifier for (size_t i = 0; i < 33; i++) { auto const USD = gw["USD"]; amts_.emplace_back(USD(1)); } env(remit::remit(alice, bob), remit::amts(amts_), ter(temMALFORMED)); env.close(); } // temMALFORMED - Expected AmountEntry. { auto tx = remit::remit(alice, bob); std::vector const amts_ = {XRP(1)}; auto& ja = tx[sfAmounts.getJsonName()]; for (std::size_t i = 0; i < amts_.size(); ++i) { ja[i][sfGenesisMint.jsonName] = Json::Value{}; ja[i][sfGenesisMint.jsonName][jss::Amount] = amts_[i].getJson(JsonOptions::none); ja[i][sfGenesisMint.jsonName][jss::Destination] = bob.human(); } tx[sfAmounts.jsonName] = ja; env(tx, ter(temMALFORMED)); env.close(); } // temBAD_AMOUNT - Bad amount { env(remit::remit(alice, bob), remit::amts({XRP(-1)}), ter(temBAD_AMOUNT)); env.close(); } // temBAD_CURRENCY - Bad currency. { auto const bXAH = gw["XaH"]; env(remit::remit(alice, bob), remit::amts({bXAH(100)}), ter(temBAD_CURRENCY)); env.close(); } // temMALFORMED - Native Currency appears more than once { env(remit::remit(alice, bob), remit::amts({XRP(1), XRP(1)}), ter(temMALFORMED)); env.close(); // not adjacent env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1), XRP(1)}), ter(temMALFORMED)); env.close(); } // temMALFORMED - Issued Currency appears more than once. { env(remit::remit(alice, bob), remit::amts({USD(1), USD(1)}), ter(temMALFORMED)); env.close(); // not adjacent env(remit::remit(alice, bob), remit::amts({USD(1), XRP(1), USD(1)}), ter(temMALFORMED)); env.close(); } // temMALFORMED - URI field was not provided. // DA: Template/Submit Failure // { // auto tx = remit::remit(alice, bob); // tx[sfMintURIToken.jsonName] = Json::Value{}; // tx[sfMintURIToken.jsonName][sfFlags.fieldName] = 1; // env(tx, ter(temMALFORMED)); // env.close(); // } // temMALFORMED - Field found in disallowed location. // DA: Template/Submit Failure // { // std::string const uri(0, '?'); // auto tx = remit::remit(alice, bob); // tx[sfMintURIToken.jsonName] = Json::Value{}; // std::string const digestval = // "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5CCC"; // tx[sfMintURIToken.jsonName][sfURI.fieldName] = strHex(uri); // tx[sfMintURIToken.jsonName][sfHookOn.fieldName] = digestval; // env(tx, ter(temMALFORMED)); // env.close(); // } // temMALFORMED - URI was too short/long. (short) { std::string const uri(0, '?'); env(remit::remit(alice, bob), remit::uri(uri), ter(temMALFORMED)); env.close(); } // temMALFORMED - URI was too short/long. (long) { std::string const uri(maxTokenURILength + 1, '?'); env(remit::remit(alice, bob), remit::uri(uri), ter(temMALFORMED)); env.close(); } // temMALFORMED - Invalid UTF8 inside MintURIToken. // temINVALID_FLAG - Invalid URI Mint Flag { std::string const uri(maxTokenURILength, '?'); env(remit::remit(alice, bob), remit::uri(uri, tfAllowXRP), ter(temINVALID_FLAG)); env.close(); } // temMALFORMED - URITokenIDs < 1 { std::vector token_ids; env(remit::remit(alice, bob), remit::token_ids(token_ids), ter(temMALFORMED)); env.close(); } // temMALFORMED - URITokenIDs Exceeds Limit { std::vector token_ids; for (size_t i = 0; i < 33; i++) { std::string const uri(i, '?'); auto const tid = uritoken::tokenid(alice, uri); token_ids.emplace_back(strHex(tid)); } env(remit::remit(alice, bob), remit::token_ids(token_ids), ter(temMALFORMED)); env.close(); } // temMALFORMED - Duplicate URITokenID. { std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(remit::remit(alice, bob), remit::token_ids({strHex(tid1), strHex(tid1)}), ter(temMALFORMED)); env.close(); // not adj std::string const uri2(maxTokenURILength, 'a'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::token_ids({strHex(tid1), strHex(tid2), strHex(tid1)}), ter(temMALFORMED)); env.close(); } } void testDoApplyInvalid(FeatureBitset features) { testcase("doApply invalid"); 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"]; //---------------------------------------------------------------------- // doApply // terNO_ACCOUNT - account doesnt exist { auto const carol = Account("carol"); Env env{*this, features}; env.memoize(carol); auto tx = remit::remit(carol, bob); tx[jss::Sequence] = 0; env(tx, carol, ter(terNO_ACCOUNT)); env.close(); } // tecNO_TARGET - inform acct doesnt exist { auto const carol = Account("carol"); Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); env.memoize(carol); auto tx = remit::remit(alice, bob); tx[sfInform.jsonName] = carol.human(); env(tx, alice, ter(tecNO_TARGET)); env.close(); } // tecNO_PERMISSION - lsfDisallowIncomingRemit // DA: see testAllowIncoming // tecDST_TAG_NEEDED // DA: see testDstTag // tecDUPLICATE { Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); std::string const uri(maxTokenURILength, '?'); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env(remit::remit(alice, bob), remit::uri(uri), ter(tecDUPLICATE)); env.close(); } // tecDIR_FULL - account directory full // DA: impossible test // tecNO_ENTRY { Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tecNO_ENTRY)); env.close(); } // tecINTERNAL - not actually a uritoken. // DA: impossible test // tecNO_PERMISSION - not owned by sender. { Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(bob, uri); env(uritoken::mint(bob, uri), ter(tesSUCCESS)); env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tecNO_PERMISSION)); env.close(); } // tefBAD_LEDGER - invalid owner directory { } // tecDIR_FULL - destination directory full // DA: impossible test // tecUNFUNDED_PAYMENT { Env env{*this, features}; env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); env.close(); env(pay(gw, alice, USD(1000))); env(pay(gw, bob, USD(1000))); env.close(); env(remit::remit(alice, bob), remit::amts({USD(1001)}), ter(tecUNFUNDED_PAYMENT)); env.close(); } // tecINTERNAL - negative XRP // DA: impossible test // tecUNFUNDED_PAYMENT { Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1001)}), ter(tecUNFUNDED_PAYMENT)); env.close(); } // tecINTERNAL // DA: impossible test // tecINTERNAL // DA: impossible test // tecINSUFFICIENT_RESERVE { Env env{*this, features}; env.fund(XRP(250), alice, bob); env.close(); std::string const uri(maxTokenURILength, '?'); env(remit::remit(alice, bob), remit::uri(uri), ter(tecINSUFFICIENT_RESERVE)); env.close(); } } void testDisallowXRP(FeatureBitset features) { // auth amount defaults to balance if not present testcase("disallow xrp"); using namespace jtx; using namespace std::literals::chrono_literals; auto const alice = Account("alice"); auto const bob = Account("bob"); { // Make a remit where dst disallows XRP Env env(*this, features - featureDepositAuth); env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); } { // Make a remit where dst disallows XRP. Ignore that flag, // since it's just advisory. Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); } } void testDstTag(FeatureBitset features) { // auth amount defaults to balance if not present testcase("dest tag"); using namespace jtx; using namespace std::literals::chrono_literals; Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); env(fset(bob, asfRequireDest)); env.close(); { env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tecDST_TAG_NEEDED)); } { env(remit::remit(alice, bob, 1), remit::amts({XRP(1)}), ter(tesSUCCESS)); } } void testDisallowIncoming(FeatureBitset features) { testcase("disallow incoming"); using namespace jtx; using namespace std::literals::chrono_literals; { Env env{*this, features - featureRemit}; Account const alice{"alice"}; env.fund(XRP(10000), alice); env(fset(alice, asfDisallowIncomingRemit)); env.close(); auto const sle = env.le(alice); uint32_t flags = sle->getFlags(); BEAST_EXPECT(!(flags & lsfDisallowIncomingRemit)); } // setup env auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); Env env{*this, features}; env.fund(XRP(1000), alice, bob, carol); env.close(); // set flag on bob only env(fset(bob, asfDisallowIncomingRemit)); env.close(); // remit from alice to bob is not allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tecNO_PERMISSION)); } // set flag on alice also env(fset(alice, asfDisallowIncomingRemit)); env.close(); // remit from bob to alice is not allowed { env(remit::remit(bob, alice), remit::amts({XRP(1)}), ter(tecNO_PERMISSION)); } // remove flag from bob env(fclear(bob, asfDisallowIncomingRemit)); env.close(); // now remit between alice and bob allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); } // a remit from carol to alice is not allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), ter(tecNO_PERMISSION)); } // remove flag from alice env(fclear(alice, asfDisallowIncomingRemit)); env.close(); // now a remit from carol to alice is allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), ter(tesSUCCESS)); } } void testDestExistsTLExists(FeatureBitset features) { testcase("dest exists and trustline exists"); using namespace jtx; using namespace std::literals::chrono_literals; // REMIT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob); } // REMIT: XAH { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } // REMIT: USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit with uritoken id env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit xah + uritoken id env(remit::remit(alice, bob), remit::amts({XRP(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: USD + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id env(remit::remit(alice, bob), remit::amts({USD(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify usd BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH/USD + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah & usd BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(1000), alice, bob); env.close(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({XRP(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); // verify xah BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } // REMIT: USD + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({USD(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify usd BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - (feeReserve * 2)); BEAST_EXPECT(env.balance(bob) == preBob + (feeReserve * 2)); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; 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(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify xah & usd BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } } void testDestExistsTLNotExist(FeatureBitset features) { testcase("dest exists and trustline does not exist"); using namespace jtx; using namespace std::literals::chrono_literals; // REMIT: USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); BEAST_EXPECT(preBob == XRP(1000)); BEAST_EXPECT(preBobUSD == USD(0)); // remit env(remit::remit(alice, bob), remit::amts({USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); BEAST_EXPECT(preBob == XRP(1000)); BEAST_EXPECT(preBobUSD == USD(0)); // remit env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } } void testDestNotExistTLNotExist(FeatureBitset features) { testcase("dest does not exist and trustline does not exist"); using namespace jtx; using namespace std::literals::chrono_literals; // REMIT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); bool const withXahau = env.current()->rules().enabled(featureXahauGenesis); BEAST_EXPECT(validateSequence( env, bob, withXahau ? 10 : env.closed()->info().seq)); BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - accountReserve); BEAST_EXPECT(env.balance(bob) == preBob + accountReserve); } // REMIT: XAH { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + accountReserve); } // REMIT: USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT( env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + accountReserve + increment); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); // setup auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit with uritoken id env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // validate BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT( env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); // setup auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); env.close(); BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT( env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit xah + uritoken id env(remit::remit(alice, bob), remit::amts({XRP(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + accountReserve + increment); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: USD + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id env(remit::remit(alice, bob), remit::amts({USD(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify usd BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( env.balance(bob) == preBob + (increment * 2) + accountReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH/USD + URITOKEN XFER { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify uri transfer BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah & usd BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // REMIT: XAH + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); env.memoize(bob); env.fund(XRP(1000), alice); env.close(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({XRP(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); // verify xah BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } // REMIT: USD + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({USD(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify usd BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - (increment * 3) - accountReserve); BEAST_EXPECT( env.balance(bob) == preBob + (increment * 3) + accountReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT { // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const increment = env.current()->fees().increment; auto const accountReserve = env.current()->fees().accountReserve(0); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; env.memoize(bob); env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), remit::token_ids({strHex(tid1)}), remit::uri(uri2), ter(tesSUCCESS)); env.close(); // verify uri mint BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); // verify uri transfer BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify xah & usd BEAST_EXPECT( env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 3) - accountReserve); BEAST_EXPECT( env.balance(bob) == preBob + XRP(1) + (increment * 3) + accountReserve); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } } void testGateway(FeatureBitset features) { testcase("gateway"); using namespace test::jtx; using namespace std::literals; struct TestAccountData { Account src; Account dst; bool hasTrustline; bool negative; }; std::array gwSrcTests = {{ // src > dst && src > issuer && dst no trustline {Account("gw0"), Account{"alice2"}, false, true}, // // src < dst && src < issuer && dst no trustline {Account("gw1"), Account{"carol0"}, false, false}, // // // // dst > src && dst > issuer && dst no trustline {Account("gw0"), Account{"dan1"}, false, true}, // // // // dst < src && dst < issuer && dst no trustline {Account("gw1"), Account{"bob0"}, false, false}, // // // src > dst && src > issuer && dst has trustline {Account("gw0"), Account{"alice2"}, true, true}, // // // src < dst && src < issuer && dst has trustline {Account("gw1"), Account{"carol0"}, true, false}, // // // dst > src && dst > issuer && dst has trustline {Account("gw0"), Account{"dan1"}, true, true}, // // // dst < src && dst < issuer && dst has trustline {Account("gw1"), Account{"bob0"}, true, false}, }}; for (auto const& t : gwSrcTests) { Env env{*this, features}; auto const USD = t.src["USD"]; env.fund(XRP(5000), t.dst, t.src); env.close(); if (t.hasTrustline) { env.trust(USD(100000), t.dst); env.close(); } if (t.hasTrustline) { env(pay(t.src, t.dst, USD(10000))); env.close(); } auto const preAmount = t.hasTrustline ? 10000 : 0; auto const preDst = lineBalance(env, t.dst, t.src, USD); // issuer can remit env(remit::remit(t.src, t.dst), remit::amts({USD(100)}), ter(tesSUCCESS)); env.close(); auto const postAmount = t.hasTrustline ? 10100 : 100; BEAST_EXPECT( preDst == (t.negative ? -USD(preAmount) : USD(preAmount))); BEAST_EXPECT( lineBalance(env, t.dst, t.src, USD) == (t.negative ? -USD(postAmount) : USD(postAmount))); BEAST_EXPECT(lineBalance(env, t.src, t.src, USD) == USD(0)); } std::array gwDstTests = {{ // // // src > dst && src > issuer && dst has trustline {Account("alice2"), Account{"gw0"}, true, true}, // // // src > dst && src > issuer && dst has trustline {Account("alice2"), Account{"gw0"}, false, true}, // // // src < dst && src < issuer && dst has trustline {Account("carol0"), Account{"gw1"}, true, false}, // // // src < dst && src < issuer && dst has trustline {Account("carol0"), Account{"gw1"}, false, false}, // // // // dst > src && dst > issuer && dst has trustline {Account("dan1"), Account{"gw0"}, true, true}, // // // // dst > src && dst > issuer && dst has trustline {Account("dan1"), Account{"gw0"}, false, true}, // // // // dst < src && dst < issuer && dst has trustline {Account("bob0"), Account{"gw1"}, true, false}, // // // // dst < src && dst < issuer && dst has trustline {Account("bob0"), Account{"gw1"}, false, false}, }}; for (auto const& t : gwDstTests) { Env env{*this, features}; auto const USD = t.dst["USD"]; env.fund(XRP(5000), t.src, t.dst); env.close(); if (t.hasTrustline) { env.trust(USD(100000), t.src); env.close(); } if (t.hasTrustline) { env(pay(t.dst, t.src, USD(10000))); env.close(); } auto const preSrc = lineBalance(env, t.src, t.dst, USD); // src can remit to issuer env(remit::remit(t.src, t.dst), remit::amts({USD(100)}), t.hasTrustline ? ter(tesSUCCESS) : ter(tecUNFUNDED_PAYMENT)); env.close(); if (t.hasTrustline) { auto const preAmount = 10000; BEAST_EXPECT( preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); auto const postAmount = 9900; BEAST_EXPECT( lineBalance(env, t.src, t.dst, USD) == (t.negative ? -USD(postAmount) : USD(postAmount))); BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); } else { BEAST_EXPECT(preSrc == USD(0)); BEAST_EXPECT(lineBalance(env, t.src, t.dst, USD) == USD(0)); BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); } } } void testTransferRate(FeatureBitset features) { testcase("transfer rate"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; struct TestRateData { double rate; STAmount delta; std::string result; TER code; }; // We test only rates that that can fit in a STI_UINT32. // Negative rates can't be serdes so there is no need to test them. std::array testCases = {{ {0.0, USD(100), "900", tesSUCCESS}, {0.9, USD(100), "900", temBAD_TRANSFER_RATE}, {1.0, USD(100), "900", tesSUCCESS}, {1.1, USD(100), "890", tesSUCCESS}, {1.0005, USD(100), "899.95", tesSUCCESS}, {1.005, USD(100), "899.5000001", tesSUCCESS}, {1.25, USD(100), "875", tesSUCCESS}, {2.0, USD(100), "800", tesSUCCESS}, {2.1, USD(100), "900", temBAD_TRANSFER_RATE}, }}; for (auto const& tc : testCases) { Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); env(rate(gw, tc.rate), ter(tc.code)); env.close(); env.trust(USD(100000), alice, bob); env.close(); env(pay(gw, alice, USD(1000))); env(pay(gw, bob, USD(1000))); env.close(); auto const preBob = env.balance(bob, USD.issue()); auto const delta = USD(100); env(remit::remit(alice, bob), remit::amts({delta})); env.close(); auto const postAlice = env.balance(alice, USD.issue()); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); BEAST_EXPECT(to_string(postAlice.value()) == tc.result); } // test rate change { Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); env(rate(gw, 1.25)); env.close(); env.trust(USD(100000), alice, bob); env.close(); env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); // setup auto const delta = USD(100); auto preAlice = env.balance(alice, USD.issue()); auto preBob = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({delta})); env.close(); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAlice - delta - USD(25)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); // issuer changes rate lower env(rate(gw, 1.00)); env.close(); preAlice = env.balance(alice, USD.issue()); preBob = env.balance(bob, USD.issue()); // remit env(remit::remit(alice, bob), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); } // test issuer doesnt pay own rate { Env env{*this, features}; env.fund(XRP(10000), alice, gw); env(rate(gw, 1.25)); env.close(); env.trust(USD(100000), alice); env.close(); env(pay(gw, alice, USD(10000))); env.close(); auto const delta = USD(100); auto const preAlice = env.balance(alice, USD.issue()); // remit env(remit::remit(gw, alice), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); } } void testRequireAuth(FeatureBitset features) { testcase("require auth"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; auto const aliceUSD = alice["USD"]; auto const bobUSD = bob["USD"]; // test asfRequireAuth { Env env{*this, features}; env.fund(XRP(1000), alice, bob, gw); env(fset(gw, asfRequireAuth)); env.close(); env(trust(gw, aliceUSD(10000)), txflags(tfSetfAuth)); env(trust(alice, USD(10000))); env.close(); env(pay(gw, alice, USD(1000))); env.close(); // alice cannot remit because bob's trustline is not authorized // all parties must be authorized env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tecNO_AUTH)); env.close(); env(trust(gw, bobUSD(10000)), txflags(tfSetfAuth)); env(trust(bob, USD(10000))); env.close(); env(pay(gw, bob, USD(1000))); env.close(); // alice can now remit because bob's trustline is authorized env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); } } void testDepositAuth(FeatureBitset features) { testcase("deposit authorization"); using namespace jtx; using namespace std::literals::chrono_literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; { Env env{*this, features}; auto const feeDrops = env.current()->fees().base; env.fund(XRP(10000), 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(); auto const preBobXrp = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); auto const preAliceXrp = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); env(fset(bob, asfDepositAuth)); env.close(); // Since alice is not preauthorized, remit fails. env(remit::remit(alice, bob), remit::amts({USD(100)}), ter(tecNO_PERMISSION)); env.close(); // Bob preauthorizes alice for deposit, remit success. env(deposit::auth(bob, alice)); env.close(); env(remit::remit(alice, bob), remit::amts({USD(100)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops)); BEAST_EXPECT(env.balance(bob) == preBobXrp - (2 * feeDrops)); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(100)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(100)); // bob removes preauthorization of alice. env(deposit::unauth(bob, alice)); env.close(); // alice remits and fails since she is no longer preauthorized. env(remit::remit(alice, bob), remit::amts({USD(100)}), ter(tecNO_PERMISSION)); env.close(); // bob clears lsfDepositAuth. env(fclear(bob, asfDepositAuth)); env.close(); // alice remits successfully. env(remit::remit(alice, bob), remit::amts({USD(100)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice) == preAliceXrp - (4 * feeDrops)); BEAST_EXPECT(env.balance(bob) == preBobXrp - (4 * feeDrops)); BEAST_EXPECT( env.balance(alice, USD.issue()) == preAliceUSD - USD(200)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(200)); } } void testTLFreeze(FeatureBitset features) { testcase("trustline freeze"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; auto const aliceUSD = alice["USD"]; auto const bobUSD = bob["USD"]; // test Global Freeze { Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); env.close(); env.trust(USD(100000), alice); env.trust(USD(100000), bob); env.close(); env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); env(fset(gw, asfGlobalFreeze)); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tecFROZEN)); env.close(); env(fclear(gw, asfGlobalFreeze)); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); } // test Individual Freeze { // Env Setup Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); env.close(); env(trust(alice, USD(100000))); env(trust(bob, USD(100000))); env.close(); env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); // set freeze on alice trustline env(trust(gw, USD(100000), alice, tfSetFreeze)); env.close(); // outgoing remit fails - frozen trustline env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tecFROZEN)); env.close(); // incoming remit success - frozen trustline // env(remit::remit(bob, alice), // remit::amts({XRP(1), USD(1)}), // ter(tesSUCCESS)); // env.close(); // clear freeze on alice trustline env(trust(gw, USD(100000), alice, tfClearFreeze)); env.close(); // remit success env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); // incoming remit success env(remit::remit(bob, alice), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); } } void testTLDeepFreeze(FeatureBitset features) { testcase("trustline deep freeze"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; auto const aliceUSD = alice["USD"]; auto const bobUSD = bob["USD"]; // test Deep Freeze { // Env Setup Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); env.close(); env(trust(alice, USD(100000))); env(trust(bob, USD(100000))); env.close(); env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); // set deep freeze on alice trustline env(trust(gw, USD(100000), alice, tfSetFreeze | tfSetDeepFreeze)); env.close(); // outgoing remit fails - deep frozen trustline env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tecFROZEN)); env.close(); // incoming remit fails - deep frozen trustline env(remit::remit(bob, alice), remit::amts({XRP(1), USD(1)}), ter(tecFROZEN)); env.close(); // clear freeze on alice trustline env(trust( gw, USD(100000), alice, tfClearFreeze | tfClearDeepFreeze)); env.close(); // outgoing remit success env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); // incoming remit success env(remit::remit(bob, alice), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); env.close(); } } void validateNoRipple( jtx::Env& env, jtx::Account const& acct, jtx::Account const& peer, bool const& result) { Json::Value params; params[jss::account] = acct.human(); params[jss::peer] = peer.human(); auto lines = env.rpc("json", "account_lines", to_string(params)); auto const& line = lines[jss::result][jss::lines][0u]; BEAST_EXPECT(line[jss::no_ripple_peer].asBool() == result); } void testRippling(FeatureBitset features) { testcase("rippling"); using namespace test::jtx; using namespace std::literals; // rippling enabled { Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const USDA = alice["USD"]; auto const USDB = bob["USD"]; auto const USDC = carol["USD"]; env.fund(XRP(10000), alice, bob, carol); env.close(); // alice trusts USD bob & carol env(trust(alice, USDB(100))); env(trust(alice, USDC(100))); // bob trusts USD alice & carol env(trust(bob, USDA(100))); env(trust(bob, USDC(100))); // carol trusts USD alice & bob env(trust(carol, USDA(100))); env(trust(carol, USDB(100))); env.close(); // alice pays bob USDA env(pay(alice, bob, USDA(10))); // carol pays alice USDC env(pay(carol, alice, USDC(10))); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); validateNoRipple(env, alice, bob, false); validateNoRipple(env, alice, carol, false); validateNoRipple(env, bob, alice, false); validateNoRipple(env, bob, carol, false); validateNoRipple(env, carol, alice, false); validateNoRipple(env, carol, bob, false); // alice cannot create to carol with USDB env(remit::remit(alice, carol), remit::amts({USDB(10)}), ter(tecUNFUNDED_PAYMENT)); env.close(); // negative direction destination // bob can remit to carol with USDA env(remit::remit(bob, carol), remit::amts({USDA(10)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); } // rippling not enabled { Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); auto const USDA = alice["USD"]; auto const USDB = bob["USD"]; auto const USDC = carol["USD"]; env.fund(XRP(10000), alice, bob, carol); env.close(); // alice trusts USD bob & carol env(trust(alice, USDB(100), bob, tfSetNoRipple)); env(trust(alice, USDC(100), carol, tfSetNoRipple)); // bob trusts USD alice & carol env(trust(bob, USDA(100), alice, tfSetNoRipple)); env(trust(bob, USDC(100), carol, tfSetNoRipple)); // carol trusts USD alice & bob env(trust(carol, USDA(100), alice, tfSetNoRipple)); env(trust(carol, USDB(100), bob, tfSetNoRipple)); env.close(); // alice pays bob USDA env(pay(alice, bob, USDA(10))); // carol pays alice USDC env(pay(carol, alice, USDC(10))); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); validateNoRipple(env, alice, bob, true); validateNoRipple(env, alice, carol, true); validateNoRipple(env, bob, alice, true); validateNoRipple(env, bob, carol, true); validateNoRipple(env, carol, alice, true); validateNoRipple(env, carol, bob, true); // alice cannot create to carol with USDB env(remit::remit(alice, carol), remit::amts({USDB(10)}), ter(tecPATH_DRY)); env.close(); // negative direction destination // bob can not remit to carol with USDA env(remit::remit(bob, carol), remit::amts({USDA(10)}), ter(tecPATH_DRY)); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); } } void testURIToken(FeatureBitset features) { testcase("uritoken"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); Env env{*this, features}; env.fund(XRP(1000), alice, bob); env.close(); // cannot mint and transfer same token { std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::uri(uri), remit::token_ids({strHex(tid)}), ter(tecNO_PERMISSION)); env.close(); } // mint and xfer in same ledger { std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); // mint uritoken env(uritoken::mint(alice, uri), ter(tesSUCCESS)); // remit env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // confirm offer (amount/dest) is removed on xfer { std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); // mint uritoken env(uritoken::mint(alice, uri), ter(tesSUCCESS)); // sell offer env(uritoken::sell(alice, strHex(tid)), uritoken::amt(XRP(1)), uritoken::dest(bob), ter(tesSUCCESS)); env.close(); // verify amount and destination auto const [urikey1, uriSle1] = uriTokenKeyAndSle(*env.current(), alice, uri); BEAST_EXPECT(uriSle1->getAccountID(sfDestination) == bob.id()); BEAST_EXPECT((*uriSle1)[sfAmount] == XRP(1)); // xfer the uritoken env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); // verify amount and destination was removed auto const [urikey2, uriSle2] = uriTokenKeyAndSle(*env.current(), alice, uri); BEAST_EXPECT(uriSle2->isFieldPresent(sfDestination) == false); BEAST_EXPECT(uriSle2->isFieldPresent(sfAmount) == false); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // test digest { std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); std::string const digestval = "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5C" "CC"; // mint the uritoken w/ digest env(remit::remit(alice, bob), remit::uri(uri, 0, digestval), ter(tesSUCCESS)); env.close(); auto const [urikey, uriSle] = uriTokenKeyAndSle(*env.current(), alice, uri); BEAST_EXPECT( to_string(uriSle->getFieldH256(sfDigest)) == digestval); // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); } // test xfer multiple { std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); std::string const uri2(maxTokenURILength - 1, '?'); auto const tid2 = uritoken::tokenid(alice, uri2); env(uritoken::mint(alice, uri2), ter(tesSUCCESS)); env.close(); // xfer multiple tokens env(remit::remit(alice, bob), remit::token_ids({strHex(tid1), strHex(tid2)}), ter(tesSUCCESS)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); // clean up test env(uritoken::burn(bob, strHex(tid1))); env(uritoken::burn(bob, strHex(tid2))); env.close(); } // test sell/xfer in same ledger { std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); // mint uritoken env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); // sell uritoken env(uritoken::sell(alice, strHex(tid1)), uritoken::amt(XRP(1)), uritoken::dest(bob), ter(tesSUCCESS)); // buy uritoken env(uritoken::buy(bob, strHex(tid1)), uritoken::amt(XRP(1)), ter(tesSUCCESS)); env.close(); // xfer uritoken env(remit::remit(alice, bob), remit::token_ids({strHex(tid1)}), ter(tecNO_PERMISSION)); env.close(); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); // clean up test env(uritoken::burn(bob, strHex(tid1))); env.close(); } } void testOptionals(FeatureBitset features) { testcase("optionals"); using namespace test::jtx; using namespace std::literals; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); Env env{*this, features}; auto const feeDrops = env.current()->fees().base; env.fund(XRP(1000), alice, bob, carol); env.close(); // inform { env(remit::remit(alice, bob), remit::inform(carol), ter(tesSUCCESS)); env.close(); } // blob { ripple::Blob blob; blob.resize(128 * 1024); XRPAmount const extraFee = XRPAmount{static_cast(blob.size())}; env(remit::remit(alice, bob), remit::blob(strHex(blob)), fee(feeDrops + extraFee), ter(tesSUCCESS)); env.close(); } // invoice { env(remit::remit(alice, bob), invoice_id(uint256{4}), ter(tesSUCCESS)); env.close(); } } void testWithFeats(FeatureBitset features) { testEnabled(features); testPreflightInvalid(features); testDoApplyInvalid(features); testDisallowXRP(features); testDstTag(features); testDisallowIncoming(features); testDestExistsTLExists(features); testDestExistsTLNotExist(features); testDestNotExistTLNotExist(features); testGateway(features); testTransferRate(features); testRequireAuth(features); testDepositAuth(features); testTLFreeze(features); testTLDeepFreeze(features); testRippling(features); testURIToken(features); testOptionals(features); } public: void run() override { using namespace test::jtx; auto const sa = supported_amendments(); testWithFeats(sa - featureXahauGenesis); testWithFeats(sa); } }; BEAST_DEFINE_TESTSUITE(Remit, app, ripple); } // namespace test } // namespace ripple