diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 0f92212288d..6bc6a3f51c6 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -1316,6 +1316,157 @@ class MPToken_test : public beast::unit_test::suite } } + void + testDepositPreauth() + { + testcase("DepositPreauth"); + + using namespace test::jtx; + Account const alice("alice"); // issuer + Account const bob("bob"); // holder + Account const diana("diana"); + Account const dpIssuer("dpIssuer"); // holder + + const char credType[] = "abcde"; + + { + Env env(*this); + + env.fund(XRP(50000), diana, dpIssuer); + env.close(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + env(pay(diana, bob, XRP(500))); + env.close(); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // Bob authorize alice + env(deposit::auth(bob, alice)); + env.close(); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + env.close(); + + // Create credentials + env(credentials::create(alice, dpIssuer, credType)); + env.close(); + env(credentials::accept(alice, dpIssuer, credType)); + env.close(); + auto const jv = + credentials::ledgerEntry(env, alice, dpIssuer, credType); + std::string const credIdx = jv[jss::result][jss::index].asString(); + + // alice sends 100 MPT to bob with credentials which aren't required + mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}}); + env.close(); + + // Bob revoke authorization + env(deposit::unauth(bob, alice)); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice sends 100 MPT to bob with credentials, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION, {{credIdx}}); + env.close(); + + // Bob authorize credentials + env(deposit::authCredentials(bob, {{dpIssuer, credType}})); + env.close(); + + // alice try to send 100 MPT to bob, not authorized + mptAlice.pay(alice, bob, 100, tecNO_PERMISSION); + env.close(); + + // alice sends 100 MPT to bob with credentials + mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}}); + env.close(); + } + + testcase("DepositPreauth disabled featureCredentials"); + { + Env env(*this, supported_amendments() - featureCredentials); + + std::string const credIdx = + "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6" + "E2"; + + env.fund(XRP(50000), diana, dpIssuer); + env.close(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer}); + + env(pay(diana, bob, XRP(500))); + env.close(); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // Bob require preauthorization + env(fset(bob, asfDepositAuth)); + env.close(); + + // alice try to send 100 MPT to bob, authorization is not checked + mptAlice.pay(alice, bob, 100); + env.close(); + + // alice try to send 100 MPT to bob with credentials, amendment + // disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + + // Bob authorize alice + env(deposit::auth(bob, alice)); + env.close(); + + // alice sends 100 MPT to bob, authorization is not checked + mptAlice.pay(alice, bob, 100); + env.close(); + + // alice sends 100 MPT to bob with credentials, amendment disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + + // Bob revoke authorization + env(deposit::unauth(bob, alice)); + env.close(); + + // alice try to send 100 MPT to bob, authorization is not checked + mptAlice.pay(alice, bob, 100); + env.close(); + + // alice sends 100 MPT to bob with credentials, amendment disabled + mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}}); + env.close(); + } + } + void testMPTInvalidInTx(FeatureBitset features) { @@ -1979,6 +2130,7 @@ class MPToken_test : public beast::unit_test::suite // Test Direct Payment testPayment(all); + testDepositPreauth(); // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index d3611efe462..ead6a47c25e 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -301,14 +301,23 @@ MPTTester::pay( Account const& src, Account const& dest, std::int64_t amount, - std::optional err) + std::optional err, + std::optional> credentials) { if (!id_) Throw("MPT has not been created"); auto const srcAmt = getBalance(src); auto const destAmt = getBalance(dest); auto const outstnAmt = getBalance(issuer_); - env_(jtx::pay(src, dest, mpt(amount)), ter(err.value_or(tesSUCCESS))); + + if (credentials) + env_( + jtx::pay(src, dest, mpt(amount)), + ter(err.value_or(tesSUCCESS)), + credentials::ids(*credentials)); + else + env_(jtx::pay(src, dest, mpt(amount)), ter(err.value_or(tesSUCCESS))); + if (env_.ter() != tesSUCCESS) amount = 0; if (close_) diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 16a08d8bad9..12b9d74d27c 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -186,7 +186,8 @@ class MPTTester pay(Account const& src, Account const& dest, std::int64_t amount, - std::optional err = std::nullopt); + std::optional err = std::nullopt, + std::optional> credentials = std::nullopt); void claw( diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 9072881ef02..ad749d8211f 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -470,6 +470,14 @@ Payment::doApply() ter != tesSUCCESS) return ter; + if (view().rules().enabled(featureCredentials)) + { + if (auto err = credentials::verify( + ctx_, account_, dstAccountID, reqDepositAuth); + !isTesSuccess(err)) + return err; + } + auto const& issuer = mptIssue.getIssuer(); // Transfer rate diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index e4c7c432e4f..2eda79e64cc 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -151,7 +151,7 @@ doLedgerEntry(RPC::JsonContext& context) } else { - auto const& ac(dp[jss::authorize_credentials]); + auto const& ac(dp[jss::authorized_credentials]); STArray const arr = parseAuthorizeCredentials(ac); if (arr.empty() || (arr.size() > maxCredentialsArraySize))