From 4c7119b92f88116a7f8de8fdc5673343200cba44 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 20 Mar 2025 15:28:09 +0000 Subject: [PATCH] Add support for Destination field in VaultWithdraw --- src/test/app/Vault_test.cpp | 16 +++++++- src/xrpld/app/tx/detail/VaultWithdraw.cpp | 48 ++++++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 47ac39cd34..0bb53670a2 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -785,8 +785,9 @@ class Vault_test : public beast::unit_test::suite Env env{*this}; Account const owner{"owner"}; Account const issuer{"issuer"}; + Account const charlie{"charlie"}; auto vault = env.vault(); - env.fund(XRP(1000), issuer, owner); + env.fund(XRP(1000), issuer, owner, charlie); env.close(); PrettyAsset asset = issuer["IOU"]; @@ -840,6 +841,19 @@ class Vault_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(owner, issue) == asset(160)); BEAST_EXPECT(vaultBalance() == asset(40)); } + + { + testcase("zero IOU fee on withdraw for 3rd party"); + auto tx = vault.withdraw( + {.depositor = owner, .id = keylet.key, .amount = asset(40)}); + tx[sfDestination] = charlie.human(); + env(tx); + env.close(); + + BEAST_EXPECT(env.balance(owner, issue) == asset(160)); + BEAST_EXPECT(env.balance(charlie, issue) == asset(40)); + BEAST_EXPECT(vaultBalance() == asset(0)); + } } public: diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index 567d447956..d4a032d9af 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,13 @@ VaultWithdraw::preflight(PreflightContext const& ctx) if (ctx.tx[sfAmount] <= beast::zero) return temBAD_AMOUNT; + if (ctx.tx.isFieldPresent(sfDestination)) + { + if (auto const dstAccountID = ctx.tx.getAccountID(sfDestination); + dstAccountID == beast::zero) + return temMALFORMED; + } + return preflight2(ctx); } @@ -54,6 +62,14 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) if (!vault) return tecOBJECT_NOT_FOUND; + if (ctx.tx.isFieldPresent(sfDestination)) + { + auto const dstAccountID = ctx.tx.getAccountID(sfDestination); + if (auto const sleDst = ctx.view.read(keylet::account(dstAccountID)); + sleDst == nullptr) + return tecNO_DST; + } + // Enforce valid withdrawal policy if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe) return tefINTERNAL; @@ -65,8 +81,24 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) return tecWRONG_ASSET; auto const account = ctx.tx[sfAccount]; - // Cannot withdraw from a Vault an Asset frozen for the account - if (isFrozen(ctx.view, account, asset)) + auto const dstAcct = [&]() -> AccountID { + if (ctx.tx.isFieldPresent(sfDestination)) + return ctx.tx.getAccountID(sfDestination); + return account; + }(); + + if (account != dstAcct && assets.holds()) + { + auto mptID = assets.get().getMptID(); + auto issuance = ctx.view.read(keylet::mptIssuance(mptID)); + if (!issuance) + return tecNO_ENTRY; + if ((issuance->getFlags() & lsfMPTCanTransfer) == 0) + return tecNO_AUTH; + } + + // Cannot withdraw from a Vault an Asset frozen for the destination account + if (isFrozen(ctx.view, dstAcct, asset)) return tecFROZEN; if (isFrozen(ctx.view, account, share)) @@ -80,7 +112,7 @@ VaultWithdraw::doApply() { auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); if (!vault) - return tecOBJECT_NOT_FOUND; + return tefINTERNAL; // Enforced in preclaim auto const mptIssuanceID = (*vault)[sfMPTokenIssuanceID]; auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID)); @@ -138,9 +170,15 @@ VaultWithdraw::doApply() view(), account_, vaultAccount, shares, j_, WaiveTransferFee::Yes)) return ter; - // Transfer assets from vault to depositor. + auto const dstAcct = [&]() -> AccountID { + if (ctx_.tx.isFieldPresent(sfDestination)) + return ctx_.tx.getAccountID(sfDestination); + return account_; + }(); + + // Transfer assets from vault to depositor or destination account. if (auto ter = accountSend( - view(), vaultAccount, account_, assets, j_, WaiveTransferFee::Yes)) + view(), vaultAccount, dstAcct, assets, j_, WaiveTransferFee::Yes)) return ter; return tesSUCCESS;