diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index 7e058d06e39..10b69fd582d 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -78,11 +78,15 @@ Payment::preflight(PreflightContext const& ctx) else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else + { + auto const& asset = saDstAmount.getAsset(); + auto const iss = asset.isMPT() ? Issue{asset} : Issue{asset, account}; maxSourceAmount = STAmount( - {saDstAmount.getAsset(), account}, + iss, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); + } auto const& uSrcCurrency = maxSourceAmount.getAsset(); auto const& uDstCurrency = saDstAmount.getAsset(); @@ -314,11 +318,15 @@ Payment::doApply() else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else + { + auto const& asset = saDstAmount.getAsset(); + auto const iss = asset.isMPT() ? Issue{asset} : Issue{asset, account_}; maxSourceAmount = STAmount( - {saDstAmount.getAsset(), account_}, + iss, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); + } JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() << " saDstAmount=" << saDstAmount.getFullText(); diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 82bb98ef2e2..9364d611c8a 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -239,12 +239,14 @@ accountHolds( if (asset.isMPT()) { - Issue iss{asset, issuer}; + Issue iss{asset}; if (auto const sle = view.read(keylet::mptoken(asset, account))) - return STAmount{ - iss, - sle->getFieldU64(sfMPTAmount) - - sle->getFieldU64(sfLockedAmount)}; + { + auto const amt = sle->getFieldU64(sfMPTAmount); + auto const locked = sle->getFieldU64(sfLockedAmount); + if (amt > locked) + return STAmount{iss, amt - locked}; + } return STAmount{iss, 0}; } @@ -1734,10 +1736,18 @@ rippleMPTCredit( auto const mptokenID = keylet::mptoken(mptID.key, uSenderID); if (auto sle = view.peek(mptokenID)) { - sle->setFieldU64( - sfMPTAmount, - sle->getFieldU64(sfMPTAmount) - saAmount.mpt().mpt()); - view.update(sle); + auto const amt = sle->getFieldU64(sfMPTAmount); + auto const pay = saAmount.mpt().mpt(); + if (amt >= pay) + { + if (amt == pay) + sle->makeFieldAbsent(sfMPTAmount); + else + sle->setFieldU64(sfMPTAmount, amt - pay); + view.update(sle); + } + else + return tecINSUFFICIENT_FUNDS; } } @@ -1745,10 +1755,15 @@ rippleMPTCredit( { if (auto sle = view.peek(mptID)) { - sle->setFieldU64( - sfOutstandingAmount, - sle->getFieldU64(sfOutstandingAmount) - saAmount.mpt().mpt()); - view.update(sle); + auto const outstanding = sle->getFieldU64(sfOutstandingAmount); + auto const redeem = saAmount.mpt().mpt(); + if (outstanding >= redeem) + { + sle->setFieldU64(sfOutstandingAmount, outstanding - redeem); + view.update(sle); + } + else + return tecINSUFFICIENT_FUNDS; } else return tecINTERNAL; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 4ccd4068bbb..aae5d66f028 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -40,6 +40,20 @@ class MPToken_test : public beast::unit_test::suite return amount == expectedAmount; } + [[nodiscard]] bool + checkMPTokenOutstandingAmount( + test::jtx::Env const& env, + ripple::uint256 const mptIssuanceid, + std::uint64_t expectedAmount) + { + auto const sleMpt = env.le(keylet::mptIssuance(mptIssuanceid)); + if (!sleMpt) + return false; + + std::uint64_t const amount = (*sleMpt)[sfOutstandingAmount]; + return amount == expectedAmount; + } + [[nodiscard]] bool checkMPTokenIssuanceFlags( test::jtx::Env const& env, @@ -894,8 +908,9 @@ class MPToken_test : public beast::unit_test::suite Env env{*this, features}; Account const alice("alice"); // issuer Account const bob("bob"); // holder + Account const carol("carol"); // holder - env.fund(XRP(10000), alice, bob); + env.fund(XRP(10000), alice, bob, carol); env.close(); BEAST_EXPECT(env.ownerCount(alice) == 0); @@ -909,18 +924,44 @@ class MPToken_test : public beast::unit_test::suite BEAST_EXPECT(env.ownerCount(alice) == 1); BEAST_EXPECT(env.ownerCount(bob) == 0); + BEAST_EXPECT(env.ownerCount(carol) == 0); // env(mpt::authorize(alice, id.key, std::nullopt)); // env.close(); env(mpt::authorize(bob, id, std::nullopt)); env.close(); + env(mpt::authorize(carol, id, std::nullopt)); + env.close(); + // issuer to holder env(pay( alice, bob, ripple::test::jtx::MPT(alice.name(), mpt)(100))); env.close(); BEAST_EXPECT( checkMPTokenAmount(env, keylet::mptIssuance(id).key, bob, 100)); + BEAST_EXPECT(checkMPTokenOutstandingAmount( + env, keylet::mptIssuance(id).key, 100)); + + // holder to issuer + env(pay(bob, alice, ripple::test::jtx::MPT(bob.name(), mpt)(100))); + env.close(); + BEAST_EXPECT( + checkMPTokenAmount(env, keylet::mptIssuance(id).key, bob, 0)); + BEAST_EXPECT(checkMPTokenOutstandingAmount( + env, keylet::mptIssuance(id).key, 0)); + + // holder to holder + env(pay( + alice, bob, ripple::test::jtx::MPT(alice.name(), mpt)(100))); + env(pay(bob, carol, ripple::test::jtx::MPT(alice.name(), mpt)(50))); + env.close(); + BEAST_EXPECT( + checkMPTokenAmount(env, keylet::mptIssuance(id).key, bob, 50)); + BEAST_EXPECT(checkMPTokenAmount( + env, keylet::mptIssuance(id).key, carol, 50)); + BEAST_EXPECT(checkMPTokenOutstandingAmount( + env, keylet::mptIssuance(id).key, 100)); } }