diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 010affad9..af283fb81 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -295,11 +295,13 @@ EscrowCreate::doApply() // Create escrow in ledger. Note that we we use the value from the // sequence or ticket. For more explanation see comments in SeqProxy.h. + auto xferRate = transferRate(view(), amount.getIssuer()); Keylet const escrowKeylet = keylet::escrow(account, ctx_.tx.getSeqProxy().value()); auto const slep = std::make_shared(escrowKeylet); (*slep)[sfAmount] = ctx_.tx[sfAmount]; (*slep)[sfAccount] = account; + (*slep)[sfTransferRate] = xferRate.value; (*slep)[~sfCondition] = ctx_.tx[~sfCondition]; (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag]; (*slep)[sfDestination] = ctx_.tx[sfDestination]; @@ -560,6 +562,13 @@ EscrowFinish::doApply() if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) return temDISABLED; + Rate lockedRate = ripple::Rate(slep->getFieldU32(sfTransferRate)); + auto issuerAccID = amount.getIssuer(); + auto const xferRate = transferRate(view(), issuerAccID); + // update if issuer rate is less than locked rate + if (xferRate < lockedRate) + lockedRate = xferRate; + // perform a dry run of the transfer before we // change anything on-ledger TER result = trustTransferLockedBalance( @@ -569,6 +578,7 @@ EscrowFinish::doApply() sled, // dst account amount, // xfer amount -1, + lockedRate, j_, DryRun // dry run ); @@ -607,6 +617,14 @@ EscrowFinish::doApply() (*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount]; else { + // compute transfer fee, if any + Rate lockedRate = ripple::Rate(slep->getFieldU32(sfTransferRate)); + auto issuerAccID = amount.getIssuer(); + auto const xferRate = transferRate(view(), issuerAccID); + // update if issuer rate is less than locked rate + if (xferRate < lockedRate) + lockedRate = xferRate; + // all the significant complexity of checking the validity of this // transfer and ensuring the lines exist etc is hidden away in this // function, all we need to do is call it and return if unsuccessful. @@ -617,6 +635,7 @@ EscrowFinish::doApply() sled, // dst account amount, // xfer amount -1, + lockedRate, j_, WetRun // wet run; ); diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index 2ec75c153..0e7bc164b 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -335,6 +335,7 @@ PayChanCreate::doApply() STAmount const amount{ctx_.tx[sfAmount]}; bool isIssuer = amount.getIssuer() == account; + auto xferRate = transferRate(view(), amount.getIssuer()); // Create PayChan in ledger. // @@ -351,6 +352,7 @@ PayChanCreate::doApply() (*slep)[sfAccount] = account; (*slep)[sfDestination] = dst; (*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay]; + (*slep)[sfTransferRate] = xferRate.value; (*slep)[sfPublicKey] = ctx_.tx[sfPublicKey]; (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag]; @@ -468,6 +470,19 @@ PayChanFund::doApply() auto const txAccount = ctx_.tx[sfAccount]; auto const expiration = (*slep)[~sfExpiration]; bool isIssuer = amount.getIssuer() == txAccount; + // auto const chanFunds = (*slep)[sfAmount]; + + // adjust transfer rate + Rate lockedRate = ripple::Rate(slep->getFieldU32(sfTransferRate)); + auto issuerAccID = amount.getIssuer(); + auto const xferRate = transferRate(view(), issuerAccID); + // update if issuer rate less than locked rate + if (xferRate < lockedRate) + (*slep)[sfTransferRate] = xferRate.value; + // throw if issuer rate greater than locked rate + if (xferRate > lockedRate) + return temBAD_TRANSFER_RATE; + // if this is a Fund operation on an IOU then perform a dry run here if (!isXRP(amount) && ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens)) @@ -727,6 +742,17 @@ PayChanClaim::doApply() } } + // compute transfer fee, if any + Rate lockedRate = ripple::Rate(slep->getFieldU32(sfTransferRate)); + auto issuerAccID = chanFunds.getIssuer(); + auto const xferRate = transferRate(view(), issuerAccID); + // update if issuer rate is less than locked rate + if (xferRate < lockedRate) + { + (*slep)[sfTransferRate] = xferRate.value; + lockedRate = xferRate; + } + (*slep)[sfBalance] = ctx_.tx[sfBalance]; STAmount const reqDelta = reqBalance - chanBalance; assert(reqDelta >= beast::zero); @@ -748,6 +774,7 @@ PayChanClaim::doApply() sled, reqDelta, 0, + lockedRate, ctx_.journal, WetRun); diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 5e672e5aa..cbf8de356 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -490,7 +490,6 @@ trustAdjustLockedBalance( // dry runs are explicit in code, but really the view type determines // what occurs here, so this combination is invalid. - static_assert(!(std::is_same::value && !dryRun)); if (!view.rules().enabled(featurePaychanAndEscrowForTokens)) @@ -772,6 +771,7 @@ trustTransferLockedBalance( S& sleDstAcc, STAmount const& amount, // issuer, currency are in this field int deltaLockCount, // -1 decrement, +1 increment, 0 unchanged + Rate const& xferRate, // TransferRate beast::Journal const& j, R dryRun) { @@ -829,7 +829,7 @@ trustTransferLockedBalance( Keylet klSrcLine{keylet::line(srcAccID, issuerAccID, currency)}; SLEPtr sleSrcLine = peek(klSrcLine); - // source account IS issuer + // source account is not issuer use locked balance if (!isIssuer) { if (!sleSrcLine) @@ -919,16 +919,18 @@ trustTransferLockedBalance( // dstLow XNOR srcLow tells us if we need to flip the balance amount // on the destination line bool flipDstAmt = !((dstHigh && srcHigh) || (!dstHigh && !srcHigh)); - - // compute transfer fee, if any - auto xferRate = transferRate(view, issuerAccID); - - // the destination will sometimes get less depending on xfer rate - // with any difference in tokens burned - auto dstAmt = xferRate == parityRate - ? amount - : multiplyRound(amount, xferRate, amount.issue(), true); - + + // default to amount + auto dstAmt = amount; + // if transfer rate + if (xferRate != parityRate) + { + // compute transfer fee, if any + auto const xferFee = + amount.value() - divideRound(amount, xferRate, amount.issue(), true); + // compute balance to transfer + dstAmt = amount.value() - xferFee; + } // check for a destination line Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency); SLEPtr sleDstLine = peek(klDstLine); @@ -945,7 +947,6 @@ trustTransferLockedBalance( if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)}; dstBalanceDrops < view.fees().accountReserve(ownerCount + 1)) return tecNO_LINE_INSUF_RESERVE; - // yes we can... we will auto const finalDstAmt = diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 311a85445..ca58a66ec 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -116,15 +116,16 @@ LedgerFormats::LedgerFormats() {sfAccount, soeREQUIRED}, {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, + {sfTransferRate, soeREQUIRED}, {sfCondition, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL}, {sfFinishAfter, soeOPTIONAL}, {sfSourceTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL}, + {sfDestinationNode, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfDestinationNode, soeOPTIONAL}, }, commonFields); @@ -189,14 +190,15 @@ LedgerFormats::LedgerFormats() {sfBalance, soeREQUIRED}, {sfPublicKey, soeREQUIRED}, {sfSettleDelay, soeREQUIRED}, + {sfTransferRate, soeREQUIRED}, {sfExpiration, soeOPTIONAL}, {sfCancelAfter, soeOPTIONAL}, {sfSourceTag, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL}, + {sfDestinationNode, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED}, - {sfDestinationNode, soeOPTIONAL}, }, commonFields); diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index bbffbe5de..35a7b028d 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -202,6 +202,15 @@ struct Escrow_test : public beast::unit_test::suite return STAmount(iou, 0); } + static Rate + escrowRate(jtx::Env const& env, jtx::Account const& account, uint32_t const& seq) + { + auto const sle = env.le(keylet::escrow(account.id(), seq)); + if (sle->isFieldPresent(sfTransferRate)) + return ripple::Rate((*sle)[sfTransferRate]); + return Rate{0}; + } + void testEnablement(FeatureBitset features) { @@ -210,7 +219,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 1s)); env.close(); @@ -246,7 +255,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: Finish Only"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -268,7 +277,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: Cancel Only"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -300,7 +309,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: Finish and Cancel -> Finish"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -334,7 +343,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: Finish and Cancel -> Cancel"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -383,7 +392,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -432,7 +441,7 @@ struct Escrow_test : public beast::unit_test::suite { // Ignore the "asfDisallowXRP" account flag, which we should // have been doing before. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "bob", "george"); env(fset("george", asfDisallowXRP)); @@ -483,7 +492,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Implied Finish Time (with fix1571)"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); env.close(); @@ -517,7 +526,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -651,7 +660,7 @@ struct Escrow_test : public beast::unit_test::suite { // Unconditional - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); auto const seq = env.seq("alice"); env(escrow("alice", "alice", XRP(1000)), @@ -675,7 +684,7 @@ struct Escrow_test : public beast::unit_test::suite // Unconditionally pay from Alice to Bob. Zelda (neither source nor // destination) signs all cancels and finishes. This shows that // Escrow will make a payment to Bob with no intervention from Bob. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "zelda"); auto const seq = env.seq("alice"); env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s)); @@ -700,7 +709,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Bob sets DepositAuth so only Bob can finish the escrow. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "zelda"); env(fset("bob", asfDepositAuth)); @@ -738,7 +747,7 @@ struct Escrow_test : public beast::unit_test::suite { // Bob sets DepositAuth but preauthorizes Zelda, so Zelda can // finish the escrow. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "zelda"); env(fset("bob", asfDepositAuth)); @@ -765,7 +774,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Conditional - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); auto const seq = env.seq("alice"); env(escrow("alice", "alice", XRP(1000)), @@ -806,7 +815,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Self-escrowed conditional with DepositAuth. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); auto const seq = env.seq("alice"); @@ -842,7 +851,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Self-escrowed conditional with DepositAuth and DepositPreauth. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "zelda"); auto const seq = env.seq("alice"); @@ -893,7 +902,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace std::chrono; { // Test cryptoconditions - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); auto const seq = env.seq("alice"); BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0); @@ -966,7 +975,7 @@ struct Escrow_test : public beast::unit_test::suite env(cancel("bob", "carol", 1), ter(tecNO_TARGET)); } { // Test cancel when condition is present - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); auto const seq = env.seq("alice"); BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0); @@ -982,7 +991,7 @@ struct Escrow_test : public beast::unit_test::suite BEAST_EXPECT(!env.le(keylet::escrow(Account("alice").id(), seq))); } { - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); env.close(); auto const seq = env.seq("alice"); @@ -1004,7 +1013,7 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance("carol", XRP(5000))); } { // Test long & short conditions during creation - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); std::vector v; @@ -1061,7 +1070,7 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance("carol", XRP(6000))); } { // Test long and short conditions & fulfillments during finish - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); std::vector cv; @@ -1207,7 +1216,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Test empty condition during creation and // empty condition & fulfillment during finish - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob", "carol"); env(escrow("alice", "carol", XRP(1000)), @@ -1253,7 +1262,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Test a condition other than PreimageSha256, which // would require a separate amendment - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), "alice", "bob"); std::array cb = { @@ -1285,7 +1294,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Metadata to self"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bruce, carol); auto const aseq = env.seq(alice); auto const bseq = env.seq(bruce); @@ -1360,7 +1369,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Metadata to other"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bruce, carol); auto const aseq = env.seq(alice); auto const bseq = env.seq(bruce); @@ -1456,7 +1465,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; env.memoize("alice"); env.memoize("bob"); @@ -1521,7 +1530,7 @@ struct Escrow_test : public beast::unit_test::suite { // Create escrow and finish using tickets. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob); env.close(); @@ -1582,7 +1591,7 @@ struct Escrow_test : public beast::unit_test::suite { // Create escrow and cancel using tickets. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob); env.close(); @@ -1656,7 +1665,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1703,7 +1712,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: IC Finish Only"); - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1732,7 +1741,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: IC Cancel Only"); - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1773,7 +1782,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: IC Finish and Cancel -> Finish"); - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1816,7 +1825,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Timing: IC Finish and Cancel -> Cancel"); - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1874,7 +1883,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -1937,7 +1946,7 @@ struct Escrow_test : public beast::unit_test::suite { // Ignore the "asfDisallowXRP" account flag, which we should // have been doing before. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), bob, george, gw); env.close(); env.trust(USD(10000), bob, george); @@ -2003,7 +2012,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("IC Implied Finish Time (with fix1571)"); - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2048,7 +2057,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -2206,7 +2215,7 @@ struct Escrow_test : public beast::unit_test::suite { // Unconditional - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -2245,7 +2254,7 @@ struct Escrow_test : public beast::unit_test::suite // Unconditionally pay from Alice to Bob. Zelda (neither source nor // destination) signs all cancels and finishes. This shows that // Escrow will make a payment to Bob with no intervention from Bob. - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2291,7 +2300,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Bob sets DepositAuth so only Bob can finish the escrow. - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2352,7 +2361,7 @@ struct Escrow_test : public beast::unit_test::suite { // Bob sets DepositAuth but preauthorizes Zelda, so Zelda can // finish the escrow. - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2400,7 +2409,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Conditional - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -2456,7 +2465,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Self-escrowed conditional with DepositAuth. - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -2507,7 +2516,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Self-escrowed conditional with DepositAuth and DepositPreauth. - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2576,7 +2585,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace std::chrono; { // Test cryptoconditions - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2666,7 +2675,7 @@ struct Escrow_test : public beast::unit_test::suite env(cancel(bob, carol, 1), ter(tecNO_TARGET)); } { // Test cancel when condition is present - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2702,7 +2711,7 @@ struct Escrow_test : public beast::unit_test::suite BEAST_EXPECT(!env.le(keylet::escrow(Account(alice).id(), seq))); } { - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2735,7 +2744,7 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance(carol, USD(5000))); } { // Test long & short conditions during creation - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2810,7 +2819,7 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance(carol, USD(6000))); } { // Test long and short conditions & fulfillments during finish - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -2973,7 +2982,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Test empty condition during creation and // empty condition & fulfillment during finish - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -3034,7 +3043,7 @@ struct Escrow_test : public beast::unit_test::suite } { // Test a condition other than PreimageSha256, which // would require a separate amendment - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account{"gateway"}; @@ -3077,7 +3086,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("IC Metadata to self"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob, carol, gw); env.close(); env.trust(USD(10000), alice, bob, carol); @@ -3158,7 +3167,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("IC Metadata to other"); - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob, carol, gw); env.close(); env.trust(USD(10000), alice, bob, carol); @@ -3261,7 +3270,7 @@ struct Escrow_test : public beast::unit_test::suite using namespace jtx; using namespace std::chrono; - Env env(*this, features); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3342,7 +3351,7 @@ struct Escrow_test : public beast::unit_test::suite { // Create escrow and finish using tickets. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob, gw); env.close(); env.trust(USD(10000), alice, bob); @@ -3407,7 +3416,7 @@ struct Escrow_test : public beast::unit_test::suite { // Create escrow and cancel using tickets. - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, bob, gw); env.close(); env.trust(USD(10000), alice, bob); @@ -3492,7 +3501,7 @@ struct Escrow_test : public beast::unit_test::suite // test create escrow from issuer with ic // test with dest tl // test finish from destination account - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, gw); env.close(); env.trust(USD(10000), alice); @@ -3517,7 +3526,7 @@ struct Escrow_test : public beast::unit_test::suite } { // setup env - Env env(*this, features); + Env env{*this, features}; env.fund(XRP(5000), alice, gw); env.close(); @@ -3541,6 +3550,126 @@ struct Escrow_test : public beast::unit_test::suite } } + void + testICLockedRate(FeatureBitset features) + { + testcase("IC Locked Rate"); + 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 TransferRate + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + 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(); + auto const preAlice = env.balance(alice, USD.issue()); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + env(escrow(alice, bob, delta), + condition(cb1), + finish_time(env.now() + 1s), + fee(1500)); + env.close(); + auto const transferRate = escrowRate(env, alice, seq1); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + env(finish(bob, alice, seq1), + condition(cb1), + fulfillment(fb1), + fee(1500)); + env.close(); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + // issuer changes to higher rate + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + 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(); + auto const preAlice = env.balance(alice, USD.issue()); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + env(escrow(alice, bob, delta), + condition(cb1), + finish_time(env.now() + 1s), + fee(1500)); + env.close(); + auto transferRate = escrowRate(env, alice, seq1); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + env(rate(gw, 1.26)); + env.close(); + + env(finish(bob, alice, seq1), + condition(cb1), + fulfillment(fb1), + fee(1500)); + env.close(); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + // issuer changes to lower rate + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + 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(); + auto const preAlice = env.balance(alice, USD.issue()); + auto const seq1 = env.seq(alice); + auto const delta = USD(125); + env(escrow(alice, bob, delta), + condition(cb1), + finish_time(env.now() + 1s), + fee(1500)); + env.close(); + auto transferRate = escrowRate(env, alice, seq1); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + env(rate(gw, 1.00)); + env.close(); + env(finish(bob, alice, seq1), + condition(cb1), + fulfillment(fb1), + fee(1500)); + env.close(); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(0)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + std::cout << "BOB BAL: " << env.balance(bob, USD.issue()) << "\n"; + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10125)); + } + } + void testWithFeats(FeatureBitset features) { @@ -3567,6 +3696,7 @@ struct Escrow_test : public beast::unit_test::suite testICConsequences(features); testICEscrowWithTickets(features); testICGateway(features); + testICLockedRate(features); } public: diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 84fcbb9e4..968a2c0aa 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -111,6 +111,15 @@ struct PayChan_test : public beast::unit_test::suite return (*slep)[sfAmount]; } + static Rate + channelRate(ReadView const& view, uint256 const& chan) + { + auto const slep = view.read({ltPAYCHAN, chan}); + if (!slep) + return Rate{0}; + return ripple::Rate((*slep)[sfTransferRate]); + } + static STAmount lockedAmount( jtx::Env const& env, @@ -4641,6 +4650,7 @@ struct PayChan_test : public beast::unit_test::suite using namespace std::literals; auto const alice = Account("alice"); + auto const bob = Account("bob"); auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; { @@ -4656,6 +4666,63 @@ struct PayChan_test : public beast::unit_test::suite env(create(gw, alice, USD(1000), settleDelay, pk)); env.close(); + // gw can not claim + auto const preAlice = env.balance(alice, USD.issue()); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(500); + auto const reqBal = chanBal + delta; + auto const authAmt = reqBal + USD(100); + // env(claim(gw, chan, reqBal, authAmt), ter(tecNO_LINE)); + + auto const sig = signClaimICAuth(gw.pk(), gw.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), gw.pk())); + env.close(); + } + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice); + env.trust(USD(1000), bob); + env.close(); + env(pay(gw, alice, USD(1000))); + env(pay(gw, bob, USD(1000))); + env.close(); + + 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); + auto const preLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(preLocked == USD(1000)); + env(claim(alice, chan, reqBal, authAmt)); + } + { + // test create paychan from issuer with ic + // test where dest has no tl + // test claim from issuer account + Env env(*this, features); + env.fund(XRP(10000), alice, gw); + env.close(); + auto const pk = gw.pk(); + auto const settleDelay = 100s; + auto const chan = channel(gw, alice, env.seq(gw)); + env(create(gw, alice, USD(1000), settleDelay, pk)); + env.close(); + // gw can not claim auto const preAlice = env.balance(alice, USD.issue()); auto chanBal = channelBalance(*env.current(), chan); @@ -4754,6 +4821,114 @@ struct PayChan_test : public beast::unit_test::suite } } + void + testICLockedRate(FeatureBitset features) + { + testcase("IC Locked Rate"); + 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 TransferRate + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + 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(); + auto const preAlice = env.balance(alice, USD.issue()); + 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(); + auto const transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(125); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(500); + // alice can claim + env(claim(alice, chan, reqBal, authAmt)); + env.close(); + auto const postLocked = lockedAmount(env, alice, gw, USD); + BEAST_EXPECT(postLocked == USD(875)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + // test rate locked in + // test fund fail + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, 1.25)); + 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(); + + 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(); + auto transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + + auto const preAlice = env.balance(alice, USD.issue()); + auto chanBal = channelBalance(*env.current(), chan); + auto chanAmt = channelAmount(*env.current(), chan); + auto const delta = USD(100); + auto reqBal = chanBal + delta; + auto authAmt = reqBal + USD(500); + + // alice can fund paychan at rate + // no change in rate + env(fund(alice, chan, USD(1000))); + env.close(); + transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.25)); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // alice can fund after issuer rate change + env(fund(alice, chan, USD(1000))); + env.close(); + transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.00)); + + // issuer changes rate higher + env(rate(gw, 1.01)); + env.close(); + + // alice cant fund after issuer rate change + // issuer rate stays the same + env(fund(alice, chan, USD(1000)), ter(temBAD_TRANSFER_RATE)); + env.close(); + transferRate = channelRate(*env.current(), chan); + BEAST_EXPECT(transferRate.value == std::uint32_t(1000000000 * 1.00)); + } + } + void testICTLFeatures(FeatureBitset features) { @@ -4854,64 +5029,11 @@ struct PayChan_test : public beast::unit_test::suite // alice can claim env(claim(alice, chan, reqBal, authAmt)); env.close(); + // bob can claim - // auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, - // authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), - // alice.pk())); env.close(); - } - // test TransferRate - { - Env env(*this, features); - env.fund(XRP(10000), alice, bob, gw); - env(rate(gw, 1.25)); + auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); + env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); 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(pay(alice, bob, USD(100)), sendmax(USD(125))); - // env(pay(alice, bob, USD(100)), txflags(tfPartialPayment)); - // env.close(); - // env.require( - // balance(alice, xrpMinusFee(env, 10000 - 50)), - // balance(bob, USD(2.5)), // owner pays transfer fee - // balance(carol, USD(50))); - - 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(); - - auto chanBal = channelBalance(*env.current(), chan); - auto chanAmt = channelAmount(*env.current(), chan); - auto const delta = USD(100); - auto reqBal = chanBal + delta; - auto authAmt = reqBal + USD(200); - // alice can claim - env(claim(alice, chan, reqBal, authAmt)); - env.close(); - - // bob can claim, increasing the limit amount - // auto const preBobLimit = limitAmount(env, bob, gw, USD); - // auto const sig = signClaimICAuth(alice.pk(), alice.sk(), chan, - // authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), - // alice.pk())); env.close(); - - auto const postLocked = lockedAmount(env, alice, gw, USD); - auto const aliceLimit = limitAmount(env, alice, gw, USD); - auto const bobLimit = limitAmount(env, bob, gw, USD); - // std::cout << "ALICE AMOUNT: " << env.balance(alice, USD.issue()) - // << "\n"; std::cout << "BOB AMOUNT: " << env.balance(bob, - // USD.issue()) << "\n"; std::cout << "ALICE LIMIT: " << aliceLimit - // << "\n"; std::cout << "BOB LIMIT: " << bobLimit << "\n"; - // std::cout << "POST LOCKED: " << postLocked << "\n"; - // std::cout << "CHAN BAL: " << channelBalance(*env.current(), chan) - // << "\n"; std::cout << "CHAN AUTH: " << - // channelAmount(*env.current(), chan) << "\n"; } // test Global Freeze { @@ -4924,53 +5046,38 @@ struct PayChan_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); - env(fset(gw, asfGlobalFreeze)); env.close(); - auto const pk = alice.pk(); auto const settleDelay = 100s; auto chan = channel(alice, bob, env.seq(alice)); env(create(alice, bob, USD(1000), settleDelay, pk), ter(tecFROZEN)); env.close(); - env(fclear(gw, asfGlobalFreeze)); env.close(); - chan = channel(alice, bob, env.seq(alice)); env(create(alice, bob, USD(1000), settleDelay, pk)); env.close(); - env(fset(gw, asfGlobalFreeze)); env.close(); - auto chanBal = channelBalance(*env.current(), chan); auto chanAmt = channelAmount(*env.current(), chan); - auto const delta = USD(10); auto reqBal = chanBal + delta; auto authAmt = reqBal + USD(100); - // alice cannot claim - tl global freeze env(claim(alice, chan, reqBal, authAmt), ter(tecFROZEN)); - // bob cannot claim - tl global freeze auto sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk()), ter(tecFROZEN)); env.close(); - env(fclear(gw, asfGlobalFreeze)); env.close(); - - // alice can claim env(claim(alice, chan, reqBal, authAmt)); env.close(); - - // update channel values for claim chanBal = channelBalance(*env.current(), chan); chanAmt = channelAmount(*env.current(), chan); reqBal = chanBal + delta; authAmt = reqBal + USD(100); - // bob can claim sig = signClaimICAuth(alice.pk(), alice.sk(), chan, authAmt); env(claim(bob, chan, reqBal, authAmt, Slice(sig), alice.pk())); env.close(); @@ -5042,6 +5149,41 @@ struct PayChan_test : public beast::unit_test::suite } } + void + testICMismatchFunding(FeatureBitset features) + { + testcase("IC Mismatch Funding"); + 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"]; + auto const USDC = gw["USDC"]; + + { + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.trust(USDC(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, alice, USDC(10000))); + env.close(); + + 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(); + env(fund(alice, chan, USDC(1000))); + } + } + void testWithFeats(FeatureBitset features) { @@ -5085,7 +5227,9 @@ struct PayChan_test : public beast::unit_test::suite testICUsingTickets(features); testICAutoTL(features); testICGateway(features); + testICLockedRate(features); testICTLFeatures(features); + testICMismatchFunding(features); } public: