mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 07:26:51 +00:00
Merge remote-tracking branch 'origin/tapanito/vault-block-deposit' into ripple/lending-protocol-fv
This commit is contained in:
@@ -171,6 +171,18 @@ sharesToAssetsWithdraw(
|
||||
[[nodiscard]] bool
|
||||
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance);
|
||||
|
||||
/**
|
||||
* Determine if a vault is insolvent. A vault is considered insolvent when
|
||||
* the total assets in the vault are zero, and outstanding shares are non-zero.
|
||||
*
|
||||
* @param vault The vault SLE.
|
||||
* @param shareIssuance The MPTokenIssuance SLE for the vault's shares.
|
||||
*
|
||||
* @return True if the vault is insolvent, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isVaultInsolvent(SLE::const_ref vault, SLE::const_ref shareIssuance);
|
||||
|
||||
/**
|
||||
* Resolves a Vault's LEVersion, the single point every accounting touch
|
||||
* point should call to determine which recognition model (accrual vs.
|
||||
|
||||
@@ -199,7 +199,9 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsfAccepted, 0x00010000)) \
|
||||
\
|
||||
LEDGER_OBJECT(Vault, \
|
||||
LSF_FLAG(lsfVaultPrivate, 0x00010000)) \
|
||||
LSF_FLAG(lsfVaultPrivate, 0x00010000) \
|
||||
LSF_FLAG(lsfVaultDepositBlocked, 0x00020000) /* True, vault deposit is blocked */ \
|
||||
LSF_FLAG(lsfVaultOwnerCanBlockDeposit, 0x00040000)) /* True, vault owner can block deposit */ \
|
||||
\
|
||||
LEDGER_OBJECT(Loan, \
|
||||
LSF_FLAG(lsfLoanDefault, 0x00010000) \
|
||||
|
||||
@@ -195,7 +195,13 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
\
|
||||
TRANSACTION(VaultCreate, \
|
||||
TF_FLAG(tfVaultPrivate, lsfVaultPrivate) \
|
||||
TF_FLAG(tfVaultShareNonTransferable, 0x00020000), \
|
||||
TF_FLAG(tfVaultShareNonTransferable, 0x00020000) \
|
||||
TF_FLAG(tfVaultOwnerCanBlockDeposit, lsfVaultOwnerCanBlockDeposit), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(VaultSet, \
|
||||
TF_FLAG(tfVaultDepositBlock, 0x00010000) \
|
||||
TF_FLAG(tfVaultDepositUnblock, 0x00020000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(VaultDeposit, \
|
||||
@@ -243,7 +249,6 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
|
||||
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
|
||||
MASK_ADJ(0))
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Create all the flag values.
|
||||
|
||||
@@ -20,6 +20,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
PRs merged into the `ripple/lending-protocol-fv` branch.
|
||||
|
||||
| PR | Title | Author | Branch | Merged |
|
||||
| --------------------------------------------------- | ---------------------------------- | ---------- | --------------------------------------------------------- | ---------- |
|
||||
| [#6383](https://github.com/XRPLF/rippled/pull/6383) | feat: Add tfVaultDonate feature | @Tapanito | `tapanito/vault-donation` | 2026-09-02 |
|
||||
| [#7820](https://github.com/XRPLF/rippled/pull/7820) | feat: Split LoanSet and LoanAccept | @a1q123456 | `a1q123456/split-loan-set-and-loan-accept-implementation` | 2026-09-02 |
|
||||
| PR | Title | Author | Branch | Merged |
|
||||
| --------------------------------------------------- | ----------------------------------------------- | ---------- | --------------------------------------------------------- | ---------- |
|
||||
| [#6383](https://github.com/XRPLF/rippled/pull/6383) | feat: Add tfVaultDonate feature | @Tapanito | `tapanito/vault-donation` | 2026-09-02 |
|
||||
| [#7820](https://github.com/XRPLF/rippled/pull/7820) | feat: Split LoanSet and LoanAccept | @a1q123456 | `a1q123456/split-loan-set-and-loan-accept-implementation` | 2026-09-02 |
|
||||
| [#6528](https://github.com/XRPLF/rippled/pull/6528) | feat: Make VaultID conditional on LoanBrokerSet | @Tapanito | `tapanito/loan-broker-set` | 2026-09-02 |
|
||||
| [#6361](https://github.com/XRPLF/rippled/pull/6361) | Adds functionality to block vault deposits | @Tapanito | `tapanito/vault-block-deposit` | 2026-09-02 |
|
||||
|
||||
@@ -225,6 +225,20 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
|
||||
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isVaultInsolvent(SLE::const_ref vault, SLE::const_ref shareIssuance)
|
||||
{
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::isVaultInsolvent : Vault sle");
|
||||
XRPL_ASSERT(
|
||||
shareIssuance && shareIssuance->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isVaultInsolvent : MPTokenIssuance sle");
|
||||
|
||||
auto const assetsTotal = vault->at(sfAssetsTotal);
|
||||
auto const sharesOutstanding = shareIssuance->at(sfOutstandingAmount);
|
||||
|
||||
return assetsTotal == 0 && sharesOutstanding > 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] VaultVersion
|
||||
getVaultVersion(SLE::const_ref vault)
|
||||
{
|
||||
|
||||
@@ -55,7 +55,10 @@ VaultCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
std::uint32_t
|
||||
VaultCreate::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
return tfVaultCreateMask;
|
||||
if (ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||
return tfVaultCreateMask;
|
||||
|
||||
return tfVaultCreateMask | tfVaultOwnerCanBlockDeposit;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -212,6 +215,7 @@ VaultCreate::doApply()
|
||||
std::uint32_t mptFlags = 0;
|
||||
if (!tx.isFlag(tfVaultShareNonTransferable))
|
||||
mptFlags |= (lsfMPTCanEscrow | lsfMPTCanTrade | lsfMPTCanTransfer);
|
||||
|
||||
if (tx.isFlag(tfVaultPrivate))
|
||||
mptFlags |= lsfMPTRequireAuth;
|
||||
|
||||
@@ -249,7 +253,13 @@ VaultCreate::doApply()
|
||||
auto const& mptIssuanceID = *maybeShare;
|
||||
|
||||
vault->setFieldIssue(sfAsset, STIssue{sfAsset, asset});
|
||||
vault->at(sfFlags) = tx.getFlags() & tfVaultPrivate;
|
||||
if (tx.isFlag(tfVaultPrivate))
|
||||
vault->setFlag(lsfVaultPrivate);
|
||||
|
||||
if (view().rules().enabled(featureLendingProtocolV1_1) &&
|
||||
tx.isFlag(tfVaultOwnerCanBlockDeposit))
|
||||
vault->setFlag(lsfVaultOwnerCanBlockDeposit);
|
||||
|
||||
vault->at(sfSequence) = sequence.value();
|
||||
vault->at(sfOwner) = accountID_;
|
||||
vault->at(sfAccount) = pseudoId;
|
||||
@@ -294,7 +304,7 @@ VaultCreate::doApply()
|
||||
return err;
|
||||
|
||||
// If the vault is private, set the authorized flag for the vault owner
|
||||
if (tx.isFlag(tfVaultPrivate))
|
||||
if (vault->isFlag(lsfVaultPrivate))
|
||||
{
|
||||
if (auto const err = authorizeMPToken(
|
||||
applyViewContext,
|
||||
|
||||
@@ -152,8 +152,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
auto const sleShareIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleShareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
|
||||
@@ -170,14 +170,14 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Cannot donate to a vault with no shares
|
||||
if (sleIssuance->at(sfOutstandingAmount) == 0)
|
||||
if (sleShareIssuance->at(sfOutstandingAmount) == 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDeposit: empty vault cannot receive donations.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
if (sleIssuance->isFlag(lsfMPTLocked))
|
||||
if (sleShareIssuance->isFlag(lsfMPTLocked))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked.";
|
||||
@@ -185,6 +185,24 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
// Perform these checks early to avoid unnecessary processing
|
||||
|
||||
// The Vault is insolvent, deposits are not allowed
|
||||
if (isVaultInsolvent(vault, sleShareIssuance))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDeposit: Vault is insolvent, deposits are not allowed";
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
if (vault->isFlag(lsfVaultDepositBlocked))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDeposit: Vault deposits are blocked";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
if (fix330Enabled)
|
||||
{
|
||||
if (auto const ret = checkDepositFreeze(ctx.view, account, vaultAccount, vaultAsset))
|
||||
@@ -205,7 +223,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// credential is tolerated here because doApply deletes it.
|
||||
if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
|
||||
{
|
||||
if (auto const err = checkVaultDomain(ctx.view, sleIssuance, account, SuppressExpired::Yes);
|
||||
if (auto const err =
|
||||
checkVaultDomain(ctx.view, sleShareIssuance, account, SuppressExpired::Yes);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,12 @@
|
||||
#include <xrpl/protocol/STTakesAsset.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
@@ -23,6 +26,29 @@ VaultSet::checkExtraFeatures(PreflightContext const& ctx)
|
||||
return !ctx.tx.isFieldPresent(sfDomainID) || ctx.rules.enabled(featurePermissionedDomains);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
VaultSet::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(featureLendingProtocolV1_1))
|
||||
return tfVaultSetMask;
|
||||
|
||||
// Add tfVaultDepositBlock and tfVaultDepositUnblock flags to indicate they are disabled
|
||||
return tfVaultSetMask | tfVaultDepositBlock | tfVaultDepositUnblock;
|
||||
}
|
||||
|
||||
static bool
|
||||
isValidVaultUpdate(PreflightContext const& ctx)
|
||||
{
|
||||
auto const atLeastOneFieldPresent = ctx.tx.isFieldPresent(sfDomainID) ||
|
||||
ctx.tx.isFieldPresent(sfAssetsMaximum) || ctx.tx.isFieldPresent(sfData);
|
||||
|
||||
// Mask of valid, non-universal flags: any bit set here means the
|
||||
// transaction is requesting a meaningful flag change.
|
||||
auto const expectedFlags = ~(VaultSet::getFlagsMask(ctx) | tfUniversal);
|
||||
|
||||
return atLeastOneFieldPresent || ((ctx.tx.getFlags() & expectedFlags) != 0u);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
VaultSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -50,13 +76,19 @@ VaultSet::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.tx.isFieldPresent(sfDomainID) && !ctx.tx.isFieldPresent(sfAssetsMaximum) &&
|
||||
!ctx.tx.isFieldPresent(sfData))
|
||||
if (!isValidVaultUpdate(ctx))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultSet: nothing is being updated.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFlag(tfVaultDepositBlock) && ctx.tx.isFlag(tfVaultDepositUnblock))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "VaultSet: cannot set tfVaultDepositBlock and tfVaultDepositUnblock simultaneously.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -110,6 +142,29 @@ VaultSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
// The Vault is not configured to support deposit blocking
|
||||
if (!vault->isFlag(lsfVaultOwnerCanBlockDeposit) &&
|
||||
(ctx.tx.isFlag(tfVaultDepositBlock) || ctx.tx.isFlag(tfVaultDepositUnblock)))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultSet: vault does not support blocking deposits";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (vault->isFlag(lsfVaultDepositBlocked) && ctx.tx.isFlag(tfVaultDepositBlock))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultSet: vault deposit is already blocked";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (!vault->isFlag(lsfVaultDepositBlocked) && ctx.tx.isFlag(tfVaultDepositUnblock))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultSet: vault deposit is already unblocked";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -167,6 +222,15 @@ VaultSet::doApply()
|
||||
view().update(sleIssuance);
|
||||
}
|
||||
|
||||
if (view().rules().enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
if (tx.isFlag(tfVaultDepositBlock))
|
||||
vault->setFlag(lsfVaultDepositBlocked);
|
||||
|
||||
if (tx.isFlag(tfVaultDepositUnblock))
|
||||
vault->clearFlag(lsfVaultDepositBlocked);
|
||||
}
|
||||
|
||||
// Note, we must update Vault object even if only DomainID is being updated
|
||||
// in Issuance object. Otherwise it's really difficult for Vault invariants
|
||||
// to verify the operation.
|
||||
|
||||
253
src/test/app/vault/VaultBlockDeposit_test.cpp
Normal file
253
src/test/app/vault/VaultBlockDeposit_test.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include <test/app/vault/VaultTestBase.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class VaultBlockDeposit_test : public VaultTestBase
|
||||
{
|
||||
void
|
||||
testVaultDepositBlockGeneral()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const owner{"owner"};
|
||||
Account const other{"other"};
|
||||
|
||||
env.fund(XRP(100'000'000), owner, other);
|
||||
Vault vault{env};
|
||||
PrettyAsset const asset = xrpIssue();
|
||||
std::string const prefix = "VaultDepositBlock: ";
|
||||
|
||||
auto const blockVault = [&](TER expectedTer, Keylet const& keylet) {
|
||||
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock}),
|
||||
Ter(expectedTer));
|
||||
};
|
||||
|
||||
auto const unblockVault = [&](TER expectedTer, Keylet const& keylet) {
|
||||
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock}),
|
||||
Ter(expectedTer));
|
||||
};
|
||||
|
||||
// Blocking Vault with the amendment disabled fails
|
||||
{
|
||||
testcase(prefix + "block/unblock fails when amendment is disabled");
|
||||
|
||||
env.disableFeature(featureLendingProtocolV1_1);
|
||||
auto const [tx, keylet] = vault.create(
|
||||
{.owner = owner, .asset = asset, .flags = tfVaultOwnerCanBlockDeposit});
|
||||
env(tx, Ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
|
||||
blockVault(temINVALID_FLAG, keylet);
|
||||
unblockVault(temINVALID_FLAG, keylet);
|
||||
|
||||
env.enableFeature(featureLendingProtocolV1_1);
|
||||
}
|
||||
|
||||
// Block Vault deposits fails if the vault is not configured to allow blocking deposits
|
||||
{
|
||||
testcase(prefix + "block/unblock fails when vault is not configured");
|
||||
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
blockVault(tecNO_PERMISSION, keylet);
|
||||
unblockVault(tecNO_PERMISSION, keylet);
|
||||
|
||||
env(vault.del({.owner = owner, .id = keylet.key}), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const [tx, keylet] =
|
||||
vault.create({.owner = owner, .asset = asset, .flags = tfVaultOwnerCanBlockDeposit});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
{
|
||||
testcase(prefix + "block/unblock succeeds");
|
||||
// deposit assets to show that blocking deposit does not block withdrawals
|
||||
env(vault.deposit({
|
||||
.depositor = owner,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env(vault.deposit({
|
||||
.depositor = other,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
blockVault(tesSUCCESS, keylet);
|
||||
|
||||
// Owner is blocked from depositing to the vault
|
||||
env(vault.deposit({
|
||||
.depositor = owner,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tecNO_PERMISSION));
|
||||
|
||||
// Other accounts are also blocked from depositing to the vault
|
||||
env(vault.deposit({
|
||||
.depositor = other,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tecNO_PERMISSION));
|
||||
|
||||
// Block vault withdrawal works as normal
|
||||
env(vault.withdraw({
|
||||
.depositor = owner,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
env(vault.withdraw({
|
||||
.depositor = other,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
unblockVault(tesSUCCESS, keylet);
|
||||
|
||||
env(vault.deposit({
|
||||
.depositor = owner,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
env(vault.deposit({
|
||||
.depositor = other,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
// Withdraw to keep the vault empty
|
||||
env(vault.withdraw({
|
||||
.depositor = owner,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
|
||||
env(vault.withdraw({
|
||||
.depositor = other,
|
||||
.id = keylet.key,
|
||||
.amount = XRP(10'000),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + "block/unblock fails when caller is not owner");
|
||||
|
||||
env(vault.set({.owner = other, .id = keylet.key, .flags = tfVaultDepositBlock}),
|
||||
Ter(tecNO_PERMISSION));
|
||||
|
||||
blockVault(tesSUCCESS, keylet);
|
||||
|
||||
env(vault.set({.owner = other, .id = keylet.key, .flags = tfVaultDepositUnblock}),
|
||||
Ter(tecNO_PERMISSION));
|
||||
|
||||
unblockVault(tesSUCCESS, keylet);
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + "unblock fails when vault is already unblocked");
|
||||
unblockVault(tecNO_PERMISSION, keylet);
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + "block fails when vault is already blocked");
|
||||
blockVault(tesSUCCESS, keylet);
|
||||
blockVault(tecNO_PERMISSION, keylet);
|
||||
unblockVault(tesSUCCESS, keylet);
|
||||
}
|
||||
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
}
|
||||
|
||||
void
|
||||
testPrivateVaultBlockDoesNotClearPrivate()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(1000), issuer, owner);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset = issuer["IOU"];
|
||||
env.trust(asset(1000), owner);
|
||||
env(pay(issuer, owner, asset(500)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [tx, keylet] = vault.create(
|
||||
{.owner = owner,
|
||||
.asset = asset,
|
||||
.flags = tfVaultPrivate | tfVaultOwnerCanBlockDeposit});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
{
|
||||
testcase("blocking a private vault does not change lsfVaultPrivate flag");
|
||||
auto setTx =
|
||||
vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(setTx, Ter(tesSUCCESS));
|
||||
auto const sleVault = env.le(keylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
BEAST_EXPECT(sleVault->isFlag(lsfVaultDepositBlocked));
|
||||
BEAST_EXPECT(sleVault->isFlag(lsfVaultPrivate));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("unblocking a private vault does not change lsfVaultPrivate flag");
|
||||
auto setTx =
|
||||
vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(setTx, Ter(tesSUCCESS));
|
||||
auto const sleVault = env.le(keylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
BEAST_EXPECT(!sleVault->isFlag(lsfVaultDepositBlocked));
|
||||
BEAST_EXPECT(sleVault->isFlag(lsfVaultPrivate));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testVaultDepositBlockGeneral();
|
||||
testPrivateVaultBlockDoesNotClearPrivate();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(VaultBlockDeposit, app, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -169,6 +169,14 @@ private:
|
||||
tx[sfFlags] = tfClearDeepFreeze;
|
||||
env(tx, Ter{temINVALID_FLAG});
|
||||
|
||||
{
|
||||
env.disableFeature(featureLendingProtocolV1_1);
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
tx[sfFlags] = tfVaultOwnerCanBlockDeposit;
|
||||
env(tx, Ter(temINVALID_FLAG));
|
||||
env.enableFeature(featureLendingProtocolV1_1);
|
||||
}
|
||||
|
||||
{
|
||||
auto tx = vault.set({.owner = owner, .id = keylet.key});
|
||||
tx[sfFlags] = tfClearDeepFreeze;
|
||||
|
||||
@@ -72,6 +72,8 @@ Vault::set(SetArgs const& args)
|
||||
jv[jss::TransactionType] = jss::VaultSet;
|
||||
jv[jss::Account] = args.owner.human();
|
||||
jv[sfVaultID] = to_string(args.id);
|
||||
if (args.flags)
|
||||
jv[jss::Flags] = *args.flags;
|
||||
return jv;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ struct Vault
|
||||
{
|
||||
Account owner;
|
||||
uint256 id;
|
||||
std::optional<std::uint32_t> flags = std::nullopt;
|
||||
};
|
||||
|
||||
static json::Value
|
||||
|
||||
Reference in New Issue
Block a user