diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 7cedbe79ec2..ac7648c4a01 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -1167,8 +1167,20 @@ rippleSend( saAmount, transferRateMPT( view, static_cast(saAmount.getAsset()))); - return rippleMPTCredit(view, uSenderID, uReceiverID, saActual, j); + + JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " + << to_string(uReceiverID) + << " : deliver=" << saAmount.getFullText() + << " cost=" << saActual.getFullText(); + + if (auto const terResult = + rippleMPTCredit(view, issuer, uReceiverID, saAmount, j); + terResult != tesSUCCESS) + return terResult; + else + return rippleMPTCredit(view, uSenderID, issuer, saActual, j); } + return tecINTERNAL; } diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 5af78b749b9..b634d334616 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -772,6 +772,38 @@ class MPToken_test : public beast::unit_test::suite // // issuer tries to exceed max amount // mptAlice.pay(alice, bob, 1, tecMPT_MAX_AMOUNT_EXCEEDED); // } + + // Transfer fee + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {&bob, &carol}}); + + // Transfer fee is 10% + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .transferFee = 10'000, + .flags = tfMPTCanTransfer}); + + // Holders create MPToken + mptAlice.authorize({.account = &bob}); + mptAlice.authorize({.account = &carol}); + + // Payment between the issuer and the holder, no transfer fee. + mptAlice.pay(alice, bob, 2'000); + + // Payment between the holder and the issuer, no transfer fee. + mptAlice.pay(alice, bob, 1'000); + + // Payment between the holders. The sender doesn't have + // enough funds to cover the transfer fee. + mptAlice.pay(bob, carol, 1'000); + + // Payment between the holders. The sender pays 10% transfer fee. + std::cout << "test transfer" << std::endl; + mptAlice.pay(bob, carol, 100); + } } void diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 3dc52fd87a6..540b0414615 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -46,6 +46,12 @@ mptpay::operator()(Env& env) const env.test.expect(amount_ == tester_.getAmount(account_)); } +void +requireAny::operator()(Env& env) const +{ + env.test.expect(cb_()); +} + std::unordered_map MPTTester::makeHolders(std::vector const& holders) { @@ -163,22 +169,37 @@ MPTTester::authorize(MPTAuthorize const& arg) jv[sfMPTokenHolder.jsonName] = arg.holder->human(); if (submit(arg, jv) == tesSUCCESS) { + // Issuer authorizes if (arg.account == nullptr || *arg.account == issuer_) { + auto const flags = getFlags(arg.holder); // issuer un-authorizes the holder if (arg.flags.value_or(0) == tfMPTUnauthorize) - env_.require(mptflags(*this, 0, arg.holder)); + env_.require(mptflags(*this, flags, arg.holder)); // issuer authorizes the holder else - env_.require(mptflags(*this, lsfMPTAuthorized, arg.holder)); + env_.require( + mptflags(*this, flags | lsfMPTAuthorized, arg.holder)); } + // Holder authorizes else if (arg.flags.value_or(0) == 0) { + auto const flags = getFlags(arg.account); // holder creates a token - env_.require(mptflags(*this, 0, arg.account)); + env_.require(mptflags(*this, flags, arg.account)); env_.require(mptpay(*this, *arg.account, 0)); } } + else if ( + arg.account != nullptr && *arg.account != issuer_ && + arg.flags.value_or(0) == 0) + { + // Verify MPToken doesn't exist if holder failed authorizing + env_.require(requireAny([&]() -> bool { + return env_.le(keylet::mptoken(*issuanceKey_, arg.account->id())) == + nullptr; + })); + } } void @@ -201,14 +222,8 @@ MPTTester::set(MPTSet const& arg) jv[sfMPTokenHolder.jsonName] = arg.holder->human(); if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0)) { - std::uint32_t flags = 0; auto require = [&](AccountP holder, bool unchanged) { - assert(forObject( - [&](SLEP const& sle) { - flags = sle->getFlags(); - return true; - }, - holder)); + auto flags = getFlags(holder); if (!unchanged) { if (*arg.flags & tfMPTLock) @@ -262,17 +277,9 @@ MPTTester::checkMPTokenOutstandingAmount(std::uint64_t expectedAmount) const } [[nodiscard]] bool -MPTTester::checkFlags(uint32_t const expectedFlags, AccountP holder_) const +MPTTester::checkFlags(uint32_t const expectedFlags, AccountP holder) const { - return forObject( - [&](SLEP const& sle) { - auto const flags = sle->getFlags(); - if (flags != expectedFlags) - std::cout << flags << " " << expectedFlags << " " - << (std::uint64_t)holder_ << std::endl; - return expectedFlags == flags; - }, - holder_); + return expectedFlags == getFlags(holder); } void @@ -285,6 +292,7 @@ MPTTester::pay( assert(mpt_); auto const srcAmt = getAmount(src); auto const destAmt = getAmount(dest); + auto const outstnAmt = getAmount(issuer_); if (err) env_(jtx::pay(src, dest, mpt(amount)), ter(*err)); else @@ -305,8 +313,15 @@ MPTTester::pay( } else { - env_.require(mptpay(*this, src, srcAmt - amount)); + STAmount const saAmount = {Issue{*mpt_}, amount}; + STAmount const saActual = + multiply(saAmount, transferRateMPT(*env_.current(), *mpt_)); + // Sender pays the transfer fee if any + env_.require(mptpay(*this, src, srcAmt - saActual.mpt().mpt())); env_.require(mptpay(*this, dest, destAmt + amount)); + // Outstanding amount is reduced by the transfer fee if any + env_.require(mptpay( + *this, issuer_, outstnAmt - (saActual - saAmount).mpt().mpt())); } } @@ -334,6 +349,19 @@ MPTTester::getAmount(Account const& account) const return 0; } +std::uint32_t +MPTTester::getFlags(ripple::test::jtx::AccountP holder) const +{ + std::uint32_t flags = 0; + assert(forObject( + [&](SLEP const& sle) { + flags = sle->getFlags(); + return true; + }, + holder)); + return flags; +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index b7a90391640..12b384c2902 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -72,6 +72,20 @@ class mptpay operator()(Env& env) const; }; +class requireAny +{ +private: + std::function cb_; + +public: + requireAny(std::function const& cb) : cb_(cb) + { + } + + void + operator()(Env& env) const; +}; + struct MPTConstr { std::vector holders = {}; @@ -232,6 +246,9 @@ class MPTTester std::unordered_map makeHolders(std::vector const& holders); + + std::uint32_t + getFlags(AccountP holder) const; }; } // namespace jtx