diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index f0f210bac..e18fe2b1d 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -113,10 +113,16 @@ struct PayChan_test : public beast::unit_test::suite } static STAmount - lockedAmount(jtx::Env const& env, jtx::Account const& account, jtx::Account const& gw, jtx::IOU const& iou) + lockedAmount( + jtx::Env const& env, + jtx::Account const& account, + jtx::Account const& gw, + jtx::IOU const& iou) { - auto const tl = env.le(keylet::line(account, gw, iou.currency)); - return (*tl)[sfLockedBalance]; + auto const sle = env.le(keylet::line(account, gw, iou.currency)); + if (sle->isFieldPresent(sfLockedBalance)) + return -(*sle)[sfLockedBalance]; + return STAmount(iou, 0); } static std::optional @@ -609,6 +615,7 @@ struct PayChan_test : public beast::unit_test::suite // receiver can still claim auto const chanBal = channelBalance(*env.current(), chan); auto const chanAmt = channelAmount(*env.current(), chan); + auto preAlice = env.balance(bob); auto preBob = env.balance(bob); auto const delta = XRP(500); auto const reqBal = chanBal + delta; @@ -2145,11 +2152,16 @@ struct PayChan_test : public beast::unit_test::suite // <- setup tests 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.fund(XRP(10000), alice, bob, gw); + env.fund(XRP(10000), alice, bob, carol, 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(); // -> run code auto const pk = alice.pk(); @@ -2160,10 +2172,15 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); { - auto const preAlice = lockedAmount(env, alice, gw, USD); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); + auto const preLocked = lockedAmount(env, alice, gw, USD); env(fund(alice, chan, USD(1000))); - auto const postAlice = lockedAmount(env, alice, gw, USD); - BEAST_EXPECT(postAlice == preAlice - USD(1000)); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice.value() - preLocked) + USD(1000)); + BEAST_EXPECT(postLocked == preLocked + USD(1000)); } auto chanBal = channelBalance(*env.current(), chan); @@ -2213,19 +2230,23 @@ struct PayChan_test : public beast::unit_test::suite { // No signature needed since the owner is claiming auto const preBob = env.balance(bob, USD.issue()); + auto const preLocked = lockedAmount(env, alice, gw, USD); auto const delta = USD(500); auto const reqBal = chanBal + delta; auto const authAmt = reqBal + USD(100); assert(reqBal <= chanAmt); env(claim(alice, chan, reqBal, authAmt)); + auto const postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(postLocked == preLocked - delta); chanBal = reqBal; } { // Claim with signature auto preBob = env.balance(bob, USD.issue()); + auto preLocked = lockedAmount(env, alice, gw, USD); auto const delta = USD(500); auto const reqBal = chanBal + delta; auto const authAmt = reqBal + USD(100); @@ -2233,22 +2254,28 @@ struct PayChan_test : public beast::unit_test::suite auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + auto postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(postLocked == preLocked - delta); chanBal = reqBal; // claim again preBob = env.balance(bob, USD.issue()); + preLocked = lockedAmount(env, alice, gw, USD); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ter(tecUNFUNDED_PAYMENT)); + postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); + BEAST_EXPECT(postLocked == preLocked); } { // Try to claim more than authorized auto const preBob = env.balance(bob, USD.issue()); + auto const preLocked = lockedAmount(env, alice, gw, USD); STAmount const authAmt = chanBal + USD(500); STAmount const reqAmt = authAmt + USD(1); assert(reqAmt <= chanAmt); @@ -2256,9 +2283,11 @@ struct PayChan_test : public beast::unit_test::suite signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqAmt, authAmt, Slice(sig), alice.pk()), ter(temBAD_AMOUNT)); + auto const postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); + BEAST_EXPECT(postLocked == preLocked); } // Dst tries to fund the channel @@ -2298,12 +2327,18 @@ struct PayChan_test : public beast::unit_test::suite // Dst closes channel auto const preAlice = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); env(claim(bob, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); auto const delta = chanAmt - chanBal; assert(delta > beast::zero); - // BEAST_EXPECT(env.balance(alice, USD.issue()).value() == preAlice.value() + delta); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice.value() - preLocked) + delta); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); + BEAST_EXPECT(postLocked == USD(0)); } } @@ -2323,8 +2358,13 @@ struct PayChan_test : public beast::unit_test::suite // If dst claims after cancel after, channel closes Env env(*this, features); 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 pk = alice.pk(); auto const settleDelay = 100s; NetClock::time_point const cancelAfter = @@ -2345,29 +2385,35 @@ struct PayChan_test : public beast::unit_test::suite auto const chanBal = channelBalance(*env.current(), chan); auto const chanAmt = channelAmount(*env.current(), chan); auto preAlice = env.balance(alice, USD.issue()); - auto preBob = env.balance(bob, USD.issue()); + auto preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); auto const delta = USD(500); - // std::cout << chanBal << "\n"; - // std::cout << chanAmt << "\n"; - // std::cout << delta << "\n"; - // auto const reqBal = chanBal + delta; - // auto const authAmt = reqBal + USD(100); - // assert(reqBal <= chanAmt); - // auto const sig = - // signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); - // env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - // // auto const feeDrops = env.current()->fees().base; - // BEAST_EXPECT(!channelExists(*env.current(), chan)); - // // BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - feeDrops); - // BEAST_EXPECT(env.balance(alice, USD.issue()).value() == preAlice.value() + channelFunds); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice.value() - preLocked) + channelFunds); + BEAST_EXPECT(preLocked == channelFunds); + BEAST_EXPECT(postLocked == USD(0)); } } { // Third party can close after cancel after Env env(*this, features); env.fund(XRP(10000), alice, bob, carol, gw); + env.close(); env.trust(USD(100000), alice, bob, carol); + env.close(); env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); auto const pk = alice.pk(); auto const settleDelay = 100s; NetClock::time_point const cancelAfter = @@ -2379,12 +2425,97 @@ struct PayChan_test : public beast::unit_test::suite // third party close before cancelAfter env(claim(carol, chan), txflags(tfClose), ter(tecNO_PERMISSION)); BEAST_EXPECT(channelExists(*env.current(), chan)); - // env.close(cancelAfter); - // // third party close after cancelAfter - // auto const preAlice = env.balance(alice, USD.issue()); - // env(claim(carol, chan), txflags(tfClose)); - // BEAST_EXPECT(!channelExists(*env.current(), chan)); - // BEAST_EXPECT(env.balance(alice, USD.issue()).value() == preAlice.value() + channelFunds); + env.close(cancelAfter); + // third party close after cancelAfter + auto const preAlice = env.balance(alice, USD.issue()); + auto const preLocked = lockedAmount(env, alice, gw, USD); + env(claim(carol, chan), txflags(tfClose)); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(preLocked == channelFunds); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()).value() == (preAlice.value() - preLocked) + channelFunds); + } + } + + void + testICSettleDelay() + { + testcase("ic settle delay"); + using namespace jtx; + using namespace std::literals::chrono_literals; + Env env(*this); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + 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 pk = alice.pk(); + auto const settleDelay = 3600s; + NetClock::time_point const settleTimepoint = + env.current()->info().parentCloseTime + settleDelay; + auto const channelFunds = USD(1000); + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, channelFunds, settleDelay, pk)); + BEAST_EXPECT(channelExists(*env.current(), chan)); + // Owner closes, will close after settleDelay + env(claim(alice, chan), txflags(tfClose)); + BEAST_EXPECT(channelExists(*env.current(), chan)); + env.close(settleTimepoint - settleDelay / 2); + { + // receiver can still claim + auto const chanBal = channelBalance(*env.current(), chan); + auto const chanAmt = channelAmount(*env.current(), chan); + auto preAlice = env.balance(alice, USD.issue()); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + auto const feeDrops = env.current()->fees().base; + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(preLocked == channelFunds); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == delta); + } + env.close(settleTimepoint); + { + // past settleTime, channel will close + auto const chanBal = channelBalance(*env.current(), chan); + auto const chanAmt = channelAmount(*env.current(), chan); + auto const preAlice = env.balance(alice, USD.issue()); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXRP = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(bob) == preBobXRP - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice - preLocked) + chanAmt - chanBal); } } @@ -2400,13 +2531,14 @@ struct PayChan_test : public beast::unit_test::suite auto const carol = Account("carol"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; - env.fund(XRP(10000), alice, bob, carol, gw); + env.close(); env.trust(USD(100000), alice, bob, carol); + env.close(); env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env(pay(gw, carol, USD(10000))); - + env.close(); auto const pk = alice.pk(); auto const settleDelay = 3600s; auto const closeTime = env.current()->info().parentCloseTime; @@ -2469,15 +2601,19 @@ struct PayChan_test : public beast::unit_test::suite using namespace jtx; using namespace std::literals::chrono_literals; Env env(*this, features); + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; 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 pk = alice.pk(); auto const settleDelay = 3600s; @@ -2490,44 +2626,54 @@ struct PayChan_test : public beast::unit_test::suite // Owner closes, will close after settleDelay env(claim(alice, chan), txflags(tfClose)); BEAST_EXPECT(channelExists(*env.current(), chan)); - // env.close(settleTimepoint - settleDelay / 2); - // { - // // receiver can still claim - // auto const chanBal = channelBalance(*env.current(), chan); - // auto const chanAmt = channelAmount(*env.current(), chan); - // auto preBob = env.balance(bob); - // auto const delta = USD(500); - // auto const reqBal = chanBal + delta; - // auto const authAmt = reqBal + USD(100); - // assert(reqBal <= chanAmt); - // auto const sig = - // signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); - // env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - // BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - // BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - // // auto const feeDrops = env.current()->fees().base; - // // BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); - // BEAST_EXPECT(env.balance(bob).value() == preBob.value() + delta); - // } - // env.close(settleTimepoint); - // { - // // past settleTime, channel will close - // auto const chanBal = channelBalance(*env.current(), chan); - // auto const chanAmt = channelAmount(*env.current(), chan); - // auto const preAlice = env.balance(alice); - // auto preBob = env.balance(bob); - // auto const delta = USD(500); - // auto const reqBal = chanBal + delta; - // auto const authAmt = reqBal + USD(100); - // assert(reqBal <= chanAmt); - // auto const sig = - // signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); - // env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); - // BEAST_EXPECT(!channelExists(*env.current(), chan)); - // auto const feeDrops = env.current()->fees().base; - // BEAST_EXPECT(env.balance(alice) == preAlice + chanAmt - chanBal); - // BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); - // } + env.close(settleTimepoint - settleDelay / 2); + { + // receiver can still claim + auto const chanBal = channelBalance(*env.current(), chan); + auto const chanAmt = channelAmount(*env.current(), chan); + auto preAlice = env.balance(alice, USD.issue()); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); + BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + auto const feeDrops = env.current()->fees().base; + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(preLocked == channelFunds); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == delta); + } + env.close(settleTimepoint); + { + // past settleTime, channel will close + auto const chanBal = channelBalance(*env.current(), chan); + auto const chanAmt = channelAmount(*env.current(), chan); + auto const preAlice = env.balance(alice, USD.issue()); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXRP = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(bob) == preBobXRP - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice - preLocked) + chanAmt - chanBal); + } } void @@ -2541,12 +2687,13 @@ struct PayChan_test : public beast::unit_test::suite auto const bob = Account("bob"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; - 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 pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = USD(1000); @@ -2558,18 +2705,19 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelExists(*env.current(), chan)); { // claim the entire amount - auto const preBob = env.balance(bob); + auto const preBob = env.balance(bob, USD.issue()); env(claim(alice, chan, channelFunds.value(), channelFunds.value())); BEAST_EXPECT(channelBalance(*env.current(), chan) == channelFunds); - BEAST_EXPECT(env.balance(bob).value() == preBob.value() + channelFunds); + BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + channelFunds); } - auto const preAlice = env.balance(alice); + auto const preAliceUsd = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); // Channel is now dry, can close before expiration date env(claim(alice, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); - // auto const feeDrops = env.current()->fees().base; - // BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); - BEAST_EXPECT(env.balance(alice).value() == preAlice.value()); + auto const feeDrops = env.current()->fees().base; + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()).value() == preAliceUsd.value()); } void @@ -2584,12 +2732,13 @@ struct PayChan_test : public beast::unit_test::suite auto const bob = Account("bob"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; - 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 pk = alice.pk(); auto const settleDelay = 3600s; auto const channelFunds = USD(1000); @@ -2602,7 +2751,9 @@ struct PayChan_test : public beast::unit_test::suite { auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - auto const preBob = env.balance(bob); + auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); auto const delta = USD(500); auto const reqBal = chanBal + delta; @@ -2610,17 +2761,22 @@ struct PayChan_test : public beast::unit_test::suite auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, reqBal); env(claim(bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - // auto const feeDrops = env.current()->fees().base; - // BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); - BEAST_EXPECT(env.balance(bob).value() == preBob.value() + delta); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(preLocked == channelFunds); + BEAST_EXPECT(postLocked == USD(500)); chanBal = reqBal; } { // Claim again auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - auto const preBob = env.balance(bob); + auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); auto const delta = USD(500); auto const reqBal = chanBal + delta; @@ -2628,10 +2784,13 @@ struct PayChan_test : public beast::unit_test::suite auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, reqBal); env(claim(bob, chan, reqBal, std::nullopt, Slice(sig), alice.pk())); + auto const feeDrops = env.current()->fees().base; + auto const postLocked = lockedAmount(env, alice, gw, USD); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); - // auto const feeDrops = env.current()->fees().base; - // BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); - BEAST_EXPECT(env.balance(bob).value() == preBob.value() + delta); + BEAST_EXPECT(env.balance(alice) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()).value() == preBob.value() + delta); + BEAST_EXPECT(preLocked == (chanAmt - chanBal)); + BEAST_EXPECT(postLocked == USD(0)); chanBal = reqBal; } } @@ -2700,18 +2859,28 @@ struct PayChan_test : public beast::unit_test::suite testICDstTag(FeatureBitset features) { // auth amount defaults to balance if not present - testcase("Dst Tag"); + testcase("IC Dst Tag"); using namespace jtx; using namespace std::literals::chrono_literals; // Create a channel where dst disallows XRP Env env(*this, features); auto const alice = Account("alice"); auto const bob = Account("bob"); - env.fund(XRP(10000), alice, bob); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + + 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(); + env(fset(bob, asfRequireDest)); auto const pk = alice.pk(); auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); { auto const chan = channel(alice, bob, env.seq(alice)); env(create(alice, bob, channelFunds, settleDelay, pk), @@ -2729,44 +2898,53 @@ struct PayChan_test : public beast::unit_test::suite void testICDepositAuth(FeatureBitset features) { - testcase("Deposit Authorization"); + testcase("IC 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); - env.fund(XRP(10000), alice, bob, carol); - + env.fund(XRP(10000), alice, bob, carol, gw); + env.close(); + env.trust(USD(100000), alice, bob, carol); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); env(fset(bob, asfDepositAuth)); env.close(); - auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); // alice can add more funds to the channel even though bob has // asfDepositAuth set. - env(fund(alice, chan, XRP(1000))); + env(fund(alice, chan, USD(1000))); env.close(); // alice claims. Fails because bob's lsfDepositAuth flag is set. - env(claim(alice, chan, XRP(500).value(), XRP(500).value()), + env(claim(alice, chan, USD(500).value(), USD(500).value()), ter(tecNO_PERMISSION)); env.close(); // Claim with signature auto const baseFee = env.current()->fees().base; - auto const preBob = env.balance(bob); + auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); { - auto const delta = XRP(500).value(); + auto const delta = USD(500).value(); auto const sig = signClaimICAuth(pk, alice.sk(), chan, delta); // alice claims with signature. Fails since bob has @@ -2774,7 +2952,8 @@ struct PayChan_test : public beast::unit_test::suite env(claim(alice, chan, delta, delta, Slice(sig), pk), ter(tecNO_PERMISSION)); env.close(); - BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob) == preBobXrp); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); // bob claims but omits the signature. Fails because only // alice can claim without a signature. @@ -2786,11 +2965,12 @@ struct PayChan_test : public beast::unit_test::suite // transaction. env(claim(bob, chan, delta, delta, Slice(sig), pk)); env.close(); - BEAST_EXPECT(env.balance(bob) == preBob + delta - baseFee); + BEAST_EXPECT(env.balance(bob) == preBobXrp - baseFee); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); } { // Explore the limits of deposit preauthorization. - auto const delta = XRP(600).value(); + auto const delta = USD(600).value(); auto const sig = signClaimICAuth(pk, alice.sk(), chan, delta); // carol claims and fails. Only channel participants (bob or @@ -2821,13 +3001,13 @@ struct PayChan_test : public beast::unit_test::suite env(claim(alice, chan, delta, delta, Slice(sig), pk)); env.close(); - BEAST_EXPECT( - env.balance(bob) == preBob + delta - (3 * baseFee)); + BEAST_EXPECT(env.balance(bob) == preBobXrp - (3 * baseFee)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); } { // bob removes preauthorization of alice. Once again she // cannot submit a claim. - auto const delta = XRP(800).value(); + auto const delta = USD(800).value(); env(deposit::unauth(bob, alice)); env.close(); @@ -2843,8 +3023,8 @@ struct PayChan_test : public beast::unit_test::suite // alice claims successfully. env(claim(alice, chan, delta, delta)); env.close(); - BEAST_EXPECT( - env.balance(bob) == preBob + XRP(800) - (5 * baseFee)); + BEAST_EXPECT(env.balance(bob) == preBobXrp - (5 * baseFee)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + USD(800)); } } } @@ -2853,16 +3033,24 @@ struct PayChan_test : public beast::unit_test::suite testICMultiple(FeatureBitset features) { // auth amount defaults to balance if not present - testcase("Multiple channels to the same account"); + testcase("IC Multiple channels to the same account"); 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); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + 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 pk = alice.pk(); auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); auto const chan1 = channel(alice, bob, env.seq(alice)); env(create(alice, bob, channelFunds, settleDelay, pk)); BEAST_EXPECT(channelExists(*env.current(), chan1)); @@ -2883,10 +3071,19 @@ struct PayChan_test : public beast::unit_test::suite auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); - env.fund(XRP(10000), alice, bob, charlie); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, bob, charlie, gw); + env.close(); + env.trust(USD(100000), alice, bob, charlie); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, charlie, USD(10000))); + env.close(); auto const pk = alice.pk(); auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); env(create(alice, bob, channelFunds, settleDelay, pk)); env.close(); @@ -2930,10 +3127,12 @@ struct PayChan_test : public beast::unit_test::suite void testICAccountChannelsRPCMarkers(FeatureBitset features) { - testcase("Account channels RPC markers"); + testcase("IC Account channels RPC markers"); using namespace test::jtx; using namespace std::literals; + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; auto const alice = Account("alice"); auto const bobs = []() -> std::vector { @@ -2948,17 +3147,26 @@ struct PayChan_test : public beast::unit_test::suite }(); Env env(*this, features); - env.fund(XRP(10000), alice); + env.fund(XRP(10000), alice, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); for (auto const& a : bobs) { - env.fund(XRP(10000), a); + env.fund(XRP(10000), a, gw); + env.close(); + env.trust(USD(100000), a); + env.close(); + env(pay(gw, a, USD(10000))); env.close(); } { // create a channel from alice to every bob account auto const settleDelay = 3600s; - auto const channelFunds = XRP(1); + auto const channelFunds = USD(1); for (auto const& b : bobs) { env(create(alice, b, channelFunds, settleDelay, alice.pk())); @@ -3048,7 +3256,7 @@ struct PayChan_test : public beast::unit_test::suite { // Check that the account_channels command only returns channels owned // by the account - testcase("Account channels RPC owner only"); + testcase("IC Account channels RPC owner only"); using namespace test::jtx; using namespace std::literals; @@ -3056,13 +3264,21 @@ struct PayChan_test : public beast::unit_test::suite auto const alice = Account("alice"); auto const bob = Account("bob"); Env env(*this, features); - env.fund(XRP(10000), alice, bob); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + 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(); // Create a channel from alice to bob and from bob to alice // When retrieving alice's channels, it should only retrieve the // channels where alice is the source, not the destination auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); env(create(alice, bob, channelFunds, settleDelay, alice.pk())); env(create(bob, alice, channelFunds, settleDelay, bob.pk())); @@ -3083,17 +3299,26 @@ struct PayChan_test : public beast::unit_test::suite void testICAuthVerifyRPC(FeatureBitset features) { - testcase("PayChan Auth/Verify RPC"); + testcase("IC PayChan Auth/Verify RPC"); using namespace jtx; using namespace std::literals::chrono_literals; Env env(*this, features); auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); - env.fund(XRP(10000), alice, bob, charlie); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, bob, charlie, gw); + env.close(); + env.trust(USD(100000), alice, bob, charlie); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, charlie, USD(10000))); + env.close(); auto const pk = alice.pk(); auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); auto const chan1Str = to_string(channel(alice, bob, env.seq(alice))); env(create(alice, bob, channelFunds, settleDelay, pk)); env.close(); @@ -3423,7 +3648,7 @@ struct PayChan_test : public beast::unit_test::suite void testICOptionalFields(FeatureBitset features) { - testcase("Optional Fields"); + testcase("IC Optional Fields"); using namespace jtx; using namespace std::literals::chrono_literals; Env env(*this, features); @@ -3431,10 +3656,20 @@ struct PayChan_test : public beast::unit_test::suite auto const bob = Account("bob"); auto const carol = Account("carol"); auto const dan = Account("dan"); - env.fund(XRP(10000), alice, bob, carol, dan); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, bob, carol, dan, gw); + env.close(); + env.trust(USD(100000), alice, bob, carol, dan); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env(pay(gw, dan, USD(10000))); + env.close(); auto const pk = alice.pk(); auto const settleDelay = 3600s; - auto const channelFunds = XRP(1000); + auto const channelFunds = USD(1000); std::optional cancelAfter; @@ -3474,18 +3709,26 @@ struct PayChan_test : public beast::unit_test::suite void testICMalformedPK(FeatureBitset features) { - testcase("malformed pk"); + testcase("ic malformed pk"); 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); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + 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 pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); - auto jv = create(alice, bob, XRP(1000), settleDelay, pk); + auto jv = create(alice, bob, USD(1000), settleDelay, pk); auto const pkHex = strHex(pk.slice()); jv["PublicKey"] = pkHex.substr(2, pkHex.size() - 2); env(jv, ter(temMALFORMED)); @@ -3500,7 +3743,7 @@ struct PayChan_test : public beast::unit_test::suite jv["PublicKey"] = pkHex; env(jv); - auto const authAmt = XRP(100); + auto const authAmt = USD(100); auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); jv = claim( bob, @@ -3543,13 +3786,14 @@ struct PayChan_test : public beast::unit_test::suite void testICMetaAndOwnership(FeatureBitset features) { - testcase("Metadata & Ownership"); + testcase("IC Metadata & Ownership"); using namespace jtx; using namespace std::literals::chrono_literals; - auto const alice = Account("alice"); auto const bob = Account("bob"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; auto const settleDelay = 100s; auto const pk = alice.pk(); @@ -3572,43 +3816,60 @@ struct PayChan_test : public beast::unit_test::suite // directory Env env( *this, supported_amendments() - fixPayChanRecipientOwnerDir); - env.fund(XRP(10000), alice, bob); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + 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(); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(lockedAmount(env, alice, gw, USD) == USD(1000)); + // close the channel env(claim(bob, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(lockedAmount(env, alice, gw, USD) == USD(0)); } { // Test with adding the paychan to the recipient's owner directory Env env(*this, features); - env.fund(XRP(10000), alice, bob); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + 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(); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2); BEAST_EXPECT(inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(lockedAmount(env, alice, gw, USD) == USD(1000)); // close the channel env(claim(bob, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(lockedAmount(env, alice, gw, USD) == USD(0)); } { @@ -3616,16 +3877,22 @@ struct PayChan_test : public beast::unit_test::suite // owner directory Env env( *this, supported_amendments() - fixPayChanRecipientOwnerDir); - env.fund(XRP(10000), alice, bob); + 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(); // create the channel before the amendment activates - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); auto const [chan, chanSle] = channelKeyAndSle(*env.current(), alice, bob); BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); env.enableFeature(fixPayChanRecipientOwnerDir); env.close(); BEAST_EXPECT( @@ -3636,22 +3903,22 @@ struct PayChan_test : public beast::unit_test::suite // different set with the ledger closes (tho I haven't dug into it) BEAST_EXPECT(inOwnerDir(*env.current(), alice, chanSle)); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); // close the channel after the amendment activates env(claim(bob, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); BEAST_EXPECT(!inOwnerDir(*env.current(), alice, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); - BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); } } void testICAccountDelete(FeatureBitset features) { - testcase("Account Delete"); + testcase("IC Account Delete"); using namespace test::jtx; using namespace std::literals::chrono_literals; auto rmAccount = [this]( @@ -3679,6 +3946,8 @@ struct PayChan_test : public beast::unit_test::suite 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"]; for (bool const withOwnerDirFix : {false, true}) { @@ -3686,7 +3955,16 @@ struct PayChan_test : public beast::unit_test::suite ? supported_amendments() : supported_amendments() - fixPayChanRecipientOwnerDir; Env env{*this, amd}; - env.fund(XRP(10000), alice, bob, carol); + + env.fund(XRP(10000), alice, bob, carol, gw); + env.close(); + env.trust(USD(100000), alice, bob, carol); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); + env.close(); auto const feeDrops = env.current()->fees().base; @@ -3694,13 +3972,15 @@ struct PayChan_test : public beast::unit_test::suite auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); rmAccount(env, alice, carol, tecHAS_OBLIGATIONS); // can only remove bob if the channel isn't in their owner direcotry + env(pay(gw, bob, USD(10000))); + env.trust(USD(0), bob); rmAccount( env, bob, @@ -3709,13 +3989,14 @@ struct PayChan_test : public beast::unit_test::suite auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - BEAST_EXPECT(chanBal == XRP(0)); - BEAST_EXPECT(chanAmt == XRP(1000)); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); - auto preBob = env.balance(bob); - auto const delta = XRP(50); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXrp = env.balance(bob); + auto const delta = USD(50); auto reqBal = chanBal + delta; - auto authAmt = reqBal + XRP(100); + auto authAmt = reqBal + USD(100); assert(reqBal <= chanAmt); // claim should fail if the dst was removed @@ -3725,38 +4006,43 @@ struct PayChan_test : public beast::unit_test::suite env.close(); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob + delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); chanBal = reqBal; } else { - auto const preAlice = env.balance(alice); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); env(claim(alice, chan, reqBal, authAmt), ter(tecNO_DST)); env.close(); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice.value()); } // fund should fail if the dst was removed if (withOwnerDirFix) { - auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000))); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); + env(fund(alice, chan, USD(1000))); env.close(); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice) == preAlice - USD(1000)); BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1000) - feeDrops); - BEAST_EXPECT( - channelAmount(*env.current(), chan) == chanAmt + XRP(1000)); - chanAmt = chanAmt + XRP(1000); + channelAmount(*env.current(), chan) == chanAmt + USD(1000)); + chanAmt = chanAmt + USD(1000); } else { - auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ter(tecNO_DST)); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); + env(fund(alice, chan, USD(1000)), ter(tecNO_DST)); env.close(); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); } @@ -3778,18 +4064,26 @@ struct PayChan_test : public beast::unit_test::suite // test resurrected account Env env{ *this, supported_amendments() - fixPayChanRecipientOwnerDir}; - env.fund(XRP(10000), alice, bob, carol); + + env.fund(XRP(10000), alice, bob, carol, gw); env.close(); + env.trust(USD(100000), alice, bob, carol); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); + auto const feeDrops = env.current()->fees().base; // Create a channel from alice to bob auto const pk = alice.pk(); auto const settleDelay = 100s; auto const chan = channel(alice, bob, env.seq(alice)); - env(create(alice, bob, XRP(1000), settleDelay, pk)); + env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); // Since `fixPayChanRecipientOwnerDir` is not active, can remove bob rmAccount(env, bob, carol); @@ -3797,36 +4091,40 @@ struct PayChan_test : public beast::unit_test::suite auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - BEAST_EXPECT(chanBal == XRP(0)); - BEAST_EXPECT(chanAmt == XRP(1000)); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); auto preBob = env.balance(bob); - auto const delta = XRP(50); + auto const delta = USD(50); auto reqBal = chanBal + delta; - auto authAmt = reqBal + XRP(100); + auto authAmt = reqBal + USD(100); assert(reqBal <= chanAmt); { // claim should fail, since bob doesn't exist - auto const preAlice = env.balance(alice); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); env(claim(alice, chan, reqBal, authAmt), ter(tecNO_DST)); env.close(); BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice); } { // fund should fail, sincebob doesn't exist - auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ter(tecNO_DST)); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); + env(fund(alice, chan, USD(1000)), ter(tecNO_DST)); + env.close(); BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); } // resurrect bob - env(pay(alice, bob, XRP(20))); + env(pay(alice, bob, USD(20))); env.close(); BEAST_EXPECT(env.closed()->exists(keylet::account(bob.id()))); @@ -3834,7 +4132,7 @@ struct PayChan_test : public beast::unit_test::suite // alice should be able to claim preBob = env.balance(bob); reqBal = chanBal + delta; - authAmt = reqBal + XRP(100); + authAmt = reqBal + USD(100); env(claim(alice, chan, reqBal, authAmt)); BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); @@ -3846,7 +4144,7 @@ struct PayChan_test : public beast::unit_test::suite // bob should be able to claim preBob = env.balance(bob); reqBal = chanBal + delta; - authAmt = reqBal + XRP(100); + authAmt = reqBal + USD(100); auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); @@ -3859,12 +4157,12 @@ struct PayChan_test : public beast::unit_test::suite { // alice should be able to fund auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000))); + env(fund(alice, chan, USD(1000))); BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1000) - feeDrops); + env.balance(alice) == preAlice - USD(1000) - feeDrops); BEAST_EXPECT( - channelAmount(*env.current(), chan) == chanAmt + XRP(1000)); - chanAmt = chanAmt + XRP(1000); + channelAmount(*env.current(), chan) == chanAmt + USD(1000)); + chanAmt = chanAmt + USD(1000); } { @@ -3885,14 +4183,21 @@ struct PayChan_test : public beast::unit_test::suite void testICUsingTickets(FeatureBitset features) { - testcase("using tickets"); + testcase("ic using tickets"); 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); - + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + 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(); // alice and bob grab enough tickets for all of the following // transactions. Note that once the tickets are acquired alice's // and bob's account sequence numbers should not advance. @@ -3908,37 +4213,40 @@ struct PayChan_test : public beast::unit_test::suite auto const settleDelay = 100s; auto const chan = channel(alice, bob, aliceTicketSeq); - env(create(alice, bob, XRP(1000), settleDelay, pk), + env(create(alice, bob, USD(1000), settleDelay, pk), ticket::use(aliceTicketSeq++)); env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); - BEAST_EXPECT(channelBalance(*env.current(), chan) == XRP(0)); - BEAST_EXPECT(channelAmount(*env.current(), chan) == XRP(1000)); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); { - auto const preAlice = env.balance(alice); - env(fund(alice, chan, XRP(1000)), ticket::use(aliceTicketSeq++)); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preAliceXrp = env.balance(alice); + auto const preLocked = lockedAmount(env, alice, gw, USD); + env(fund(alice, chan, USD(1000)), ticket::use(aliceTicketSeq++)); env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); auto const feeDrops = env.current()->fees().base; - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - feeDrops); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice.value() - preLocked) + USD(1000)); } auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - BEAST_EXPECT(chanBal == XRP(0)); - BEAST_EXPECT(chanAmt == XRP(2000)); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(2000)); { // No signature needed since the owner is claiming - auto const preBob = env.balance(bob); - auto const delta = XRP(500); + auto const preBob = env.balance(bob, USD.issue()); + auto const delta = USD(500); auto const reqBal = chanBal + delta; - auto const authAmt = reqBal + XRP(100); + auto const authAmt = reqBal + USD(100); assert(reqBal <= chanAmt); env(claim(alice, chan, reqBal, authAmt), ticket::use(aliceTicketSeq++)); @@ -3948,18 +4256,19 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob + delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); chanBal = reqBal; } { // Claim with signature - auto preBob = env.balance(bob); - auto const delta = XRP(500); + auto preBob = env.balance(bob, USD.issue()); + auto preBobXrp = env.balance(bob); + auto const delta = USD(500); auto const reqBal = chanBal + delta; - auto const authAmt = reqBal + XRP(100); + auto const authAmt = reqBal + USD(100); assert(reqBal <= chanAmt); auto const sig = - signClaimAuth(alice.pk(), alice.sk(), chan, authAmt); + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ticket::use(bobTicketSeq++)); @@ -3969,11 +4278,13 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelBalance(*env.current(), chan) == reqBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); auto const feeDrops = env.current()->fees().base; - BEAST_EXPECT(env.balance(bob) == preBob + delta - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); chanBal = reqBal; // claim again - preBob = env.balance(bob); + preBob = env.balance(bob, USD.issue()); + preBobXrp = env.balance(bob); // A transaction that generates a tec still consumes its ticket. env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ticket::use(bobTicketSeq++), @@ -3984,13 +4295,14 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); } { // Try to claim more than authorized - auto const preBob = env.balance(bob); - STAmount const authAmt = chanBal + XRP(500); - STAmount const reqAmt = authAmt + drops(1); + auto const preBob = env.balance(bob, USD.issue()); + STAmount const authAmt = chanBal + USD(500); + STAmount const reqAmt = authAmt + USD(1); assert(reqAmt <= chanAmt); // Note that since claim() returns a tem (neither tec nor tes), // the ticket is not consumed. So we don't increment bobTicket. @@ -4005,11 +4317,11 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); } // Dst tries to fund the channel - env(fund(bob, chan, XRP(1000)), + env(fund(bob, chan, USD(1000)), ticket::use(bobTicketSeq++), ter(tecNO_PERMISSION)); @@ -4018,11 +4330,12 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); - { // Dst closes channel - auto const preAlice = env.balance(alice); - auto const preBob = env.balance(bob); + auto const preAlice = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); + auto const preLocked = lockedAmount(env, alice, gw, USD); env(claim(bob, chan), txflags(tfClose), ticket::use(bobTicketSeq++)); @@ -4034,8 +4347,9 @@ struct PayChan_test : public beast::unit_test::suite auto const feeDrops = env.current()->fees().base; auto const delta = chanAmt - chanBal; assert(delta > beast::zero); - BEAST_EXPECT(env.balance(alice) == preAlice + delta); - BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == (preAlice.value() - preLocked) + delta); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value()); } env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); BEAST_EXPECT(env.seq(alice) == aliceSeq); @@ -4043,6 +4357,78 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(bob) == bobSeq); } + void + testICAutoTL(FeatureBitset features) + { + testcase("IC Auto Trust Line"); + 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"]; + + { + // test trust line missing + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + auto const feeDrops = env.current()->fees().base; + // Create a channel from alice to bob + auto const pk = alice.pk(); + auto const settleDelay = 100s; + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, USD(1000), settleDelay, pk)); + env.close(); + BEAST_EXPECT(channelBalance(*env.current(), chan) == USD(0)); + BEAST_EXPECT(channelAmount(*env.current(), chan) == USD(1000)); + + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + BEAST_EXPECT(chanBal == USD(0)); + BEAST_EXPECT(chanAmt == USD(1000)); + auto preBob = env.balance(bob); + auto const delta = USD(50); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(100); + assert(reqBal <= chanAmt); + { + // claim should fail, since bob doesn't have tl + // and alice cannot create tl for bob + auto const preAlice = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob, USD.issue()); + auto const preAliceXrp = env.balance(alice); + env(claim(alice, chan, reqBal, authAmt), ter(tecNO_LINE)); + env.close(); + BEAST_EXPECT(channelBalance(*env.current(), chan) == chanBal); + BEAST_EXPECT(channelAmount(*env.current(), chan) == chanAmt); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob); + BEAST_EXPECT(env.balance(alice) == preAliceXrp - feeDrops); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice); + } + { + // claim should succeed, since the tl will auto add + auto const preAlice = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob, USD.issue()); + auto const preBobXrp = env.balance(bob); + auto const sig = + signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); + env.close(); + BEAST_EXPECT( preBob.value() == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob) == preBobXrp - feeDrops); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob.value() + delta); + } + } + } + void testWithFeats(FeatureBitset features) { @@ -4066,25 +4452,34 @@ struct PayChan_test : public beast::unit_test::suite testAccountDelete(features); testUsingTickets(features); - // testICSimple(FeatureBitset features); - // testICCancelAfter(FeatureBitset features); - // testICSettleDelay(FeatureBitset features); - // testICExpiration(FeatureBitset features); - // testICCloseDry(FeatureBitset features); - // testICDefaultAmount(FeatureBitset features); - // testICDisallowXRP(FeatureBitset features); - // testICDstTag(FeatureBitset features); - // testICDepositAuth(FeatureBitset features); - // testICMultiple(FeatureBitset features); - // testICAccountChannelsRPC(FeatureBitset features); - // testICAccountChannelsRPCMarkers(FeatureBitset features); - // testICAccountChannelsRPCSenderOnly(FeatureBitset features); - // testICAuthVerifyRPC(FeatureBitset features); - // testICOptionalFields(FeatureBitset features); - // testICMalformedPK(FeatureBitset features); - // testICMetaAndOwnership(FeatureBitset features); - // testICAccountDelete(FeatureBitset features); - // testICUsingTickets(FeatureBitset features); + testICSimple(features); + testICCancelAfter(features); + testICSettleDelay(features); + testICExpiration(features); + testICCloseDry(features); + testICDefaultAmount(features); + // testICDisallowXRP(features); + testICDstTag(features); + testICDepositAuth(features); + testICMultiple(features); + // testICAccountChannelsRPC(features); + // testICAccountChannelsRPCMarkers(features); + // testICAccountChannelsRPCSenderOnly(features); + // testICAuthVerifyRPC(features); + testICOptionalFields(features); + testICMalformedPK(features); + testICMetaAndOwnership(features); + // testICAccountDelete(features); + testICUsingTickets(features); + + /* + // testICAccountChannelsRPC(); + // testICAccountChannelsRPCMarkers(); + // testICAccountChannelsRPCSenderOnly(); + // testICAuthVerifyRPC(); + */ + // testICAccountDelete(); + testICAutoTL(features); } public: void @@ -4095,6 +4490,7 @@ public: testWithFeats(all - disallowIncoming); testWithFeats(all); } + }; BEAST_DEFINE_TESTSUITE(PayChan, app, ripple);