mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
VaultWithdraw destination account bugfix (#5572)
#5224 added (among other things) a `VaultWithdraw` transaction that allows setting the recipient of the withdrawn funds in the `Destination` transaction field. This technically turns this transaction into a payment, and in some respect the implementation does follow payment rules, e.g. enforcement of `lsfRequireDestTag` or `lsfDepositAuth`, or that MPT transfer has destination `MPToken`. However for IOUs, it missed verification that the destination account has a trust line to the asset issuer. Since the default behavior of `accountSendIOU` is to create this trust line (if missing), this is what `VaultWithdraw` currently does. This is incorrect, since the `Destination` might not be interested in holding the asset in question; this basically enables spammy transfers. This change, therefore, removes automatic creation of a trust line to the `Destination` account in `VaultWithdraw`.
This commit is contained in:
@@ -509,6 +509,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({
|
|||||||
{sfVaultID, soeREQUIRED},
|
{sfVaultID, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||||
{sfDestination, soeOPTIONAL},
|
{sfDestination, soeOPTIONAL},
|
||||||
|
{sfDestinationTag, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction claws back tokens from a vault. */
|
/** This transaction claws back tokens from a vault. */
|
||||||
|
|||||||
@@ -234,6 +234,28 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
env(tx, ter{tecNO_PERMISSION});
|
env(tx, ter{tecNO_PERMISSION});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase(prefix + " fail to withdraw to zero destination");
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
tx[sfDestination] = "0";
|
||||||
|
env(tx, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase(
|
||||||
|
prefix +
|
||||||
|
" fail to withdraw with tag but without destination");
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
tx[sfDestinationTag] = "0";
|
||||||
|
env(tx, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
if (!asset.raw().native())
|
if (!asset.raw().native())
|
||||||
{
|
{
|
||||||
testcase(
|
testcase(
|
||||||
@@ -1335,6 +1357,7 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
struct CaseArgs
|
struct CaseArgs
|
||||||
{
|
{
|
||||||
bool enableClawback = true;
|
bool enableClawback = true;
|
||||||
|
bool requireAuth = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto testCase = [this](
|
auto testCase = [this](
|
||||||
@@ -1356,16 +1379,20 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
Vault vault{env};
|
Vault vault{env};
|
||||||
|
|
||||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||||
|
auto const none = LedgerSpecificFlags(0);
|
||||||
mptt.create(
|
mptt.create(
|
||||||
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
||||||
(args.enableClawback ? lsfMPTCanClawback
|
(args.enableClawback ? tfMPTCanClawback : none) |
|
||||||
: LedgerSpecificFlags(0)) |
|
(args.requireAuth ? tfMPTRequireAuth : none)});
|
||||||
tfMPTRequireAuth});
|
|
||||||
PrettyAsset asset = mptt.issuanceID();
|
PrettyAsset asset = mptt.issuanceID();
|
||||||
mptt.authorize({.account = owner});
|
mptt.authorize({.account = owner});
|
||||||
mptt.authorize({.account = issuer, .holder = owner});
|
|
||||||
mptt.authorize({.account = depositor});
|
mptt.authorize({.account = depositor});
|
||||||
mptt.authorize({.account = issuer, .holder = depositor});
|
if (args.requireAuth)
|
||||||
|
{
|
||||||
|
mptt.authorize({.account = issuer, .holder = owner});
|
||||||
|
mptt.authorize({.account = issuer, .holder = depositor});
|
||||||
|
}
|
||||||
|
|
||||||
env(pay(issuer, depositor, asset(1000)));
|
env(pay(issuer, depositor, asset(1000)));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
@@ -1514,6 +1541,100 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testCase(
|
||||||
|
[this](
|
||||||
|
Env& env,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& depositor,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
Vault& vault,
|
||||||
|
MPTTester& mptt) {
|
||||||
|
testcase(
|
||||||
|
"MPT 3rd party without MPToken cannot be withdrawal "
|
||||||
|
"destination");
|
||||||
|
|
||||||
|
auto [tx, keylet] =
|
||||||
|
vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.deposit(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Set destination to 3rd party without MPToken
|
||||||
|
Account charlie{"charlie"};
|
||||||
|
env.fund(XRP(1000), charlie);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
tx[sfDestination] = charlie.human();
|
||||||
|
env(tx, ter(tecNO_AUTH));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{.requireAuth = false});
|
||||||
|
|
||||||
|
testCase(
|
||||||
|
[this](
|
||||||
|
Env& env,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& depositor,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
Vault& vault,
|
||||||
|
MPTTester& mptt) {
|
||||||
|
testcase("MPT depositor without MPToken cannot withdraw");
|
||||||
|
|
||||||
|
auto [tx, keylet] =
|
||||||
|
vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.deposit(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Remove depositor's MPToken and withdraw will fail
|
||||||
|
mptt.authorize(
|
||||||
|
{.account = depositor, .flags = tfMPTUnauthorize});
|
||||||
|
env.close();
|
||||||
|
auto const mptoken =
|
||||||
|
env.le(keylet::mptoken(mptt.issuanceID(), depositor));
|
||||||
|
BEAST_EXPECT(mptoken == nullptr);
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx, ter(tecNO_AUTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Restore depositor's MPToken and withdraw will succeed
|
||||||
|
mptt.authorize({.account = depositor});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{.requireAuth = false});
|
||||||
|
|
||||||
testCase([this](
|
testCase([this](
|
||||||
Env& env,
|
Env& env,
|
||||||
Account const& issuer,
|
Account const& issuer,
|
||||||
@@ -1803,6 +1924,7 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
PrettyAsset const asset = issuer["IOU"];
|
PrettyAsset const asset = issuer["IOU"];
|
||||||
env.trust(asset(1000), owner);
|
env.trust(asset(1000), owner);
|
||||||
|
env.trust(asset(1000), charlie);
|
||||||
env(pay(issuer, owner, asset(200)));
|
env(pay(issuer, owner, asset(200)));
|
||||||
env(rate(issuer, 1.25));
|
env(rate(issuer, 1.25));
|
||||||
env.close();
|
env.close();
|
||||||
@@ -2118,6 +2240,79 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
env.close();
|
env.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testCase([&, this](
|
||||||
|
Env& env,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& charlie,
|
||||||
|
auto,
|
||||||
|
Vault& vault,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
auto&&...) {
|
||||||
|
testcase("IOU no trust line to 3rd party");
|
||||||
|
|
||||||
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(vault.deposit(
|
||||||
|
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
Account const erin{"erin"};
|
||||||
|
env.fund(XRP(1000), erin);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Withdraw to 3rd party without trust line
|
||||||
|
auto const tx1 = [&](ripple::Keylet keylet) {
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = owner,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(10)});
|
||||||
|
tx[sfDestination] = erin.human();
|
||||||
|
return tx;
|
||||||
|
}(keylet);
|
||||||
|
env(tx1, ter{tecNO_LINE});
|
||||||
|
});
|
||||||
|
|
||||||
|
testCase([&, this](
|
||||||
|
Env& env,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& charlie,
|
||||||
|
auto,
|
||||||
|
Vault& vault,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
auto&&...) {
|
||||||
|
testcase("IOU no trust line to depositor");
|
||||||
|
|
||||||
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// reset limit, so deposit of all funds will delete the trust line
|
||||||
|
env.trust(asset(0), owner);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(vault.deposit(
|
||||||
|
{.depositor = owner, .id = keylet.key, .amount = asset(200)}));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto trustline =
|
||||||
|
env.le(keylet::line(owner, asset.raw().get<Issue>()));
|
||||||
|
BEAST_EXPECT(trustline == nullptr);
|
||||||
|
|
||||||
|
// Withdraw without trust line, will succeed
|
||||||
|
auto const tx1 = [&](ripple::Keylet keylet) {
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = owner,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(10)});
|
||||||
|
return tx;
|
||||||
|
}(keylet);
|
||||||
|
env(tx1);
|
||||||
|
});
|
||||||
|
|
||||||
testCase([&, this](
|
testCase([&, this](
|
||||||
Env& env,
|
Env& env,
|
||||||
Account const& owner,
|
Account const& owner,
|
||||||
|
|||||||
@@ -315,14 +315,14 @@ escrowCreatePreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
// If the issuer has requireAuth set, check if the destination is
|
// If the issuer has requireAuth set, check if the destination is
|
||||||
// authorized
|
// authorized
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
@@ -746,7 +746,7 @@ escrowFinishPreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
@@ -1259,7 +1259,7 @@ escrowCancelPreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,19 @@ VaultWithdraw::preflight(PreflightContext const& ctx)
|
|||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
if (auto const destination = ctx.tx[~sfDestination];
|
if (auto const destination = ctx.tx[~sfDestination];
|
||||||
destination && *destination == beast::zero)
|
destination.has_value())
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty destination account.";
|
if (*destination == beast::zero)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug())
|
||||||
|
<< "VaultWithdraw: zero/empty destination account.";
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.tx.isFieldPresent(sfDestinationTag))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug()) << "VaultWithdraw: sfDestinationTag is set but "
|
||||||
|
"sfDestination is not";
|
||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,33 +133,39 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
|||||||
|
|
||||||
// Withdrawal to a 3rd party destination account is essentially a transfer,
|
// Withdrawal to a 3rd party destination account is essentially a transfer,
|
||||||
// via shares in the vault. Enforce all the usual asset transfer checks.
|
// via shares in the vault. Enforce all the usual asset transfer checks.
|
||||||
|
AuthType authType = AuthType::Legacy;
|
||||||
if (account != dstAcct)
|
if (account != dstAcct)
|
||||||
{
|
{
|
||||||
auto const sleDst = ctx.view.read(keylet::account(dstAcct));
|
auto const sleDst = ctx.view.read(keylet::account(dstAcct));
|
||||||
if (sleDst == nullptr)
|
if (sleDst == nullptr)
|
||||||
return tecNO_DST;
|
return tecNO_DST;
|
||||||
|
|
||||||
if (sleDst->getFlags() & lsfRequireDestTag)
|
if (sleDst->isFlag(lsfRequireDestTag) &&
|
||||||
|
!ctx.tx.isFieldPresent(sfDestinationTag))
|
||||||
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
||||||
|
|
||||||
if (sleDst->getFlags() & lsfDepositAuth)
|
if (sleDst->isFlag(lsfDepositAuth))
|
||||||
{
|
{
|
||||||
if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
|
if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
}
|
}
|
||||||
|
// The destination account must have consented to receive the asset by
|
||||||
|
// creating a RippleState or MPToken
|
||||||
|
authType = AuthType::StrongAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destination MPToken must exist (if asset is an MPT)
|
// Destination MPToken (for an MPT) or trust line (for an IOU) must exist
|
||||||
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct);
|
// if not sending to Account.
|
||||||
|
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType);
|
||||||
!isTesSuccess(ter))
|
!isTesSuccess(ter))
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
// Cannot withdraw from a Vault an Asset frozen for the destination account
|
// Cannot withdraw from a Vault an Asset frozen for the destination account
|
||||||
if (isFrozen(ctx.view, dstAcct, vaultAsset))
|
if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
|
||||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
return ret;
|
||||||
|
|
||||||
if (isFrozen(ctx.view, account, vaultShare))
|
if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
|
||||||
return tecLOCKED;
|
return ret;
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,29 @@ isFrozen(
|
|||||||
asset.value());
|
asset.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||||
|
{
|
||||||
|
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(
|
||||||
|
ReadView const& view,
|
||||||
|
AccountID const& account,
|
||||||
|
MPTIssue const& mptIssue)
|
||||||
|
{
|
||||||
|
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||||
|
{
|
||||||
|
return std::visit(
|
||||||
|
[&](auto const& issue) { return checkFrozen(view, account, issue); },
|
||||||
|
asset.value());
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
isAnyFrozen(
|
isAnyFrozen(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
@@ -725,19 +748,40 @@ transferXRP(
|
|||||||
STAmount const& amount,
|
STAmount const& amount,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
/* Check if MPToken exists:
|
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
|
||||||
* - StrongAuth - before checking lsfMPTRequireAuth is set
|
* - StrongAuth - before checking if authorization is required
|
||||||
* - WeakAuth - after checking if lsfMPTRequireAuth is set
|
* - WeakAuth
|
||||||
|
* for MPT - after checking lsfMPTRequireAuth flag
|
||||||
|
* for IOU - do not check if trust line exists
|
||||||
|
* - Legacy
|
||||||
|
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
|
||||||
|
* for IOU - do not check if trust line exists i.e. same as WeakAuth
|
||||||
*/
|
*/
|
||||||
enum class MPTAuthType : bool { StrongAuth = true, WeakAuth = false };
|
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||||
|
|
||||||
/** Check if the account lacks required authorization.
|
/** Check if the account lacks required authorization.
|
||||||
*
|
*
|
||||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||||
* and tesSUCCESS otherwise.
|
* and tesSUCCESS otherwise.
|
||||||
|
*
|
||||||
|
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||||
|
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||||
|
* RippleState does exist, and the RippleState is not authorized.
|
||||||
|
*
|
||||||
|
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||||
|
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||||
|
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||||
|
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||||
|
* tesSUCCESS even if RippleState does *not* exist.
|
||||||
|
*
|
||||||
|
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] TER
|
[[nodiscard]] TER
|
||||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
|
requireAuth(
|
||||||
|
ReadView const& view,
|
||||||
|
Issue const& issue,
|
||||||
|
AccountID const& account,
|
||||||
|
AuthType authType = AuthType::Legacy);
|
||||||
|
|
||||||
/** Check if the account lacks required authorization.
|
/** Check if the account lacks required authorization.
|
||||||
*
|
*
|
||||||
@@ -751,32 +795,33 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
|
|||||||
* purely defensive, as we currently do not allow such vaults to be created.
|
* purely defensive, as we currently do not allow such vaults to be created.
|
||||||
*
|
*
|
||||||
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
|
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
|
||||||
* lsfMPTRequireAuth is set and MPToken is not authorized. If WeakAuth then
|
* lsfMPTRequireAuth is set and MPToken is not authorized.
|
||||||
* return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken doesn't exist or is
|
*
|
||||||
* not authorized (explicitly or via credentials, if DomainID is set in
|
* If WeakAuth then return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken
|
||||||
* MPTokenIssuance). Consequently, if WeakAuth and lsfMPTRequireAuth is *not*
|
* doesn't exist or is not authorized (explicitly or via credentials, if
|
||||||
* set, this function will return true even if MPToken does *not* exist.
|
* DomainID is set in MPTokenIssuance). Consequently, if WeakAuth and
|
||||||
|
* lsfMPTRequireAuth is *not* set, this function will return true even if
|
||||||
|
* MPToken does *not* exist.
|
||||||
|
*
|
||||||
|
* The default "Legacy" auth type is equivalent to StrongAuth.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] TER
|
[[nodiscard]] TER
|
||||||
requireAuth(
|
requireAuth(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
MPTIssue const& mptIssue,
|
MPTIssue const& mptIssue,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType = MPTAuthType::StrongAuth,
|
AuthType authType = AuthType::Legacy,
|
||||||
int depth = 0);
|
int depth = 0);
|
||||||
|
|
||||||
[[nodiscard]] TER inline requireAuth(
|
[[nodiscard]] TER inline requireAuth(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
Asset const& asset,
|
Asset const& asset,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType = MPTAuthType::StrongAuth)
|
AuthType authType = AuthType::Legacy)
|
||||||
{
|
{
|
||||||
return std::visit(
|
return std::visit(
|
||||||
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
||||||
if constexpr (std::is_same_v<TIss, Issue>)
|
return requireAuth(view, issue_, account, authType);
|
||||||
return requireAuth(view, issue_, account);
|
|
||||||
else
|
|
||||||
return requireAuth(view, issue_, account, authType);
|
|
||||||
},
|
},
|
||||||
asset.value());
|
asset.value());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,8 +505,8 @@ accountHolds(
|
|||||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||||
view.rules().enabled(featureSingleAssetVault))
|
view.rules().enabled(featureSingleAssetVault))
|
||||||
{
|
{
|
||||||
if (auto const err = requireAuth(
|
if (auto const err =
|
||||||
view, mptIssue, account, MPTAuthType::StrongAuth);
|
requireAuth(view, mptIssue, account, AuthType::StrongAuth);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
amount.clear(mptIssue);
|
amount.clear(mptIssue);
|
||||||
}
|
}
|
||||||
@@ -2298,15 +2298,27 @@ transferXRP(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account)
|
requireAuth(
|
||||||
|
ReadView const& view,
|
||||||
|
Issue const& issue,
|
||||||
|
AccountID const& account,
|
||||||
|
AuthType authType)
|
||||||
{
|
{
|
||||||
if (isXRP(issue) || issue.account == account)
|
if (isXRP(issue) || issue.account == account)
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
|
|
||||||
|
auto const trustLine =
|
||||||
|
view.read(keylet::line(account, issue.account, issue.currency));
|
||||||
|
// If account has no line, and this is a strong check, fail
|
||||||
|
if (!trustLine && authType == AuthType::StrongAuth)
|
||||||
|
return tecNO_LINE;
|
||||||
|
|
||||||
|
// If this is a weak or legacy check, or if the account has a line, fail if
|
||||||
|
// auth is required and not set on the line
|
||||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||||
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||||
{
|
{
|
||||||
if (auto const trustLine =
|
if (trustLine)
|
||||||
view.read(keylet::line(account, issue.account, issue.currency)))
|
|
||||||
return ((*trustLine)[sfFlags] &
|
return ((*trustLine)[sfFlags] &
|
||||||
((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
||||||
? tesSUCCESS
|
? tesSUCCESS
|
||||||
@@ -2322,7 +2334,7 @@ requireAuth(
|
|||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
MPTIssue const& mptIssue,
|
MPTIssue const& mptIssue,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType,
|
AuthType authType,
|
||||||
int depth)
|
int depth)
|
||||||
{
|
{
|
||||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||||
@@ -2357,7 +2369,7 @@ requireAuth(
|
|||||||
if (auto const err = std::visit(
|
if (auto const err = std::visit(
|
||||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||||
if constexpr (std::is_same_v<TIss, Issue>)
|
if constexpr (std::is_same_v<TIss, Issue>)
|
||||||
return requireAuth(view, issue, account);
|
return requireAuth(view, issue, account, authType);
|
||||||
else
|
else
|
||||||
return requireAuth(
|
return requireAuth(
|
||||||
view, issue, account, authType, depth + 1);
|
view, issue, account, authType, depth + 1);
|
||||||
@@ -2372,7 +2384,8 @@ requireAuth(
|
|||||||
auto const sleToken = view.read(mptokenID);
|
auto const sleToken = view.read(mptokenID);
|
||||||
|
|
||||||
// if account has no MPToken, fail
|
// if account has no MPToken, fail
|
||||||
if (!sleToken && authType == MPTAuthType::StrongAuth)
|
if (!sleToken &&
|
||||||
|
(authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||||
return tecNO_AUTH;
|
return tecNO_AUTH;
|
||||||
|
|
||||||
// Note, this check is not amendment-gated because DomainID will be always
|
// Note, this check is not amendment-gated because DomainID will be always
|
||||||
|
|||||||
Reference in New Issue
Block a user