mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-17 20:32:42 +00:00
Compare commits
15 Commits
ximinez/de
...
tapanito/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71b9f982bb | ||
|
|
74c968d4e3 | ||
|
|
f57b715936 | ||
|
|
82b0d57aac | ||
|
|
087a9c1cf3 | ||
|
|
1010866ba0 | ||
|
|
a2198146a8 | ||
|
|
c808c46049 | ||
|
|
167147281c | ||
|
|
ba60306610 | ||
|
|
6674500896 | ||
|
|
c5d7ebe93d | ||
|
|
d0b5ca9dab | ||
|
|
5e51893e9b | ||
|
|
3422c11d02 |
@@ -985,6 +985,11 @@ sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
|
||||
// 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.
|
||||
[[nodiscard]] bool
|
||||
isVaultInsolvent(std::shared_ptr<SLE const> const& vault, std::shared_ptr<SLE const> const& shareIssuance);
|
||||
|
||||
/** Has the specified time passed?
|
||||
|
||||
@param now the current time
|
||||
|
||||
@@ -185,6 +185,7 @@ enum LedgerSpecificFlags {
|
||||
|
||||
// ltVAULT
|
||||
lsfVaultPrivate = 0x00010000,
|
||||
lsfVaultDepositBlocked = 0x00020000, // True, vault deposit is blocked
|
||||
|
||||
// ltLOAN
|
||||
lsfLoanDefault = 0x00010000,
|
||||
|
||||
@@ -291,6 +291,11 @@ constexpr std::uint32_t const tfLoanImpair = 0x00020000;
|
||||
constexpr std::uint32_t const tfLoanUnimpair = 0x00040000;
|
||||
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
|
||||
|
||||
// VaultSet flags:
|
||||
constexpr std::uint32_t const tfVaultDepositBlock = 0x00010000;
|
||||
constexpr std::uint32_t const tfVaultDepositUnblock = 0x00020000;
|
||||
constexpr std::uint32_t const tfVaultSetMask = ~(tfUniversal | tfVaultDepositBlock | tfVaultDepositUnblock);
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FIX (LendingProtocolV1_1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -3438,4 +3438,13 @@ after(NetClock::time_point now, std::uint32_t mark)
|
||||
return now.time_since_epoch().count() > mark;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isVaultInsolvent(std::shared_ptr<SLE const> const& vault, std::shared_ptr<SLE const> const& shareIssuance)
|
||||
{
|
||||
auto const assetsTotal = vault->at(sfAssetsTotal);
|
||||
auto const sharesOutstanding = shareIssuance->at(sfOutstandingAmount);
|
||||
|
||||
return assetsTotal == 0 && sharesOutstanding > 0;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1701,10 +1701,21 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
testRIPD4274MPT();
|
||||
}
|
||||
|
||||
void
|
||||
testFixAmendmentEnabled()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("testFixAmendmentEnabled");
|
||||
Env env{*this};
|
||||
|
||||
BEAST_EXPECT(env.enabled(fixLendingProtocolV1_1));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testFixAmendmentEnabled();
|
||||
testLoanBrokerSetDebtMaximum();
|
||||
testLoanBrokerCoverDepositNullVault();
|
||||
|
||||
|
||||
@@ -178,6 +178,34 @@ class Vault_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + " fail to unblock a non-blocked vault");
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(tx, ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + " block a vault");
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + " fail to block an already blocked vault");
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + " unblock a blocked vault");
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(tx, ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase(prefix + " fail to withdraw more than assets held");
|
||||
auto tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(1000)});
|
||||
@@ -885,13 +913,26 @@ class Vault_test : public beast::unit_test::suite
|
||||
});
|
||||
|
||||
testCase([&](Env& env, Account const&, Account const& owner, Asset const& asset, Vault& vault) {
|
||||
testcase("invalid set immutable flag");
|
||||
testcase("set flags fail without fixLendingProtocolV1_1");
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
|
||||
{
|
||||
env.disableFeature(fixLendingProtocolV1_1);
|
||||
auto tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, ter(temINVALID_FLAG));
|
||||
env.enableFeature(fixLendingProtocolV1_1);
|
||||
}
|
||||
});
|
||||
|
||||
testCase([&](Env& env, Account const&, Account const& owner, Asset const& asset, Vault& vault) {
|
||||
testcase("invalid set flag combination");
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
|
||||
{
|
||||
auto tx = vault.set({.owner = owner, .id = keylet.key});
|
||||
tx[sfFlags] = tfVaultPrivate;
|
||||
tx[sfFlags] = tfVaultDepositBlock | tfVaultDepositUnblock;
|
||||
env(tx, ter(temINVALID_FLAG));
|
||||
}
|
||||
});
|
||||
@@ -2092,6 +2133,167 @@ class Vault_test : public beast::unit_test::suite
|
||||
// Delete vault with zero balance
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
});
|
||||
|
||||
testCase([&, this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester const& mptt) {
|
||||
testcase("lsfVaultDepositBlocked prevents deposits");
|
||||
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// First deposit assets to later show that withdrawals are not blocked
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Block Vault deposits
|
||||
{
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, ter(tesSUCCESS), THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tecNO_PERMISSION}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
auto tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Unblock Vault Deposits
|
||||
{
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(tx, ter(tesSUCCESS), THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Deposits now succeed
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Withdraw assets from the vault to delete it
|
||||
{
|
||||
auto const tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
env.close();
|
||||
});
|
||||
|
||||
testCase([&, this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester const& mptt) {
|
||||
testcase("insolvent vault blocks deposits");
|
||||
|
||||
auto const depositAmount = asset(20);
|
||||
|
||||
auto const [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// First deposit assets to later show that withdrawals are not blocked
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = depositor, .id = vaultKeylet.key, .amount = depositAmount});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const& brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner));
|
||||
auto const& loanKeylet = keylet::loan(brokerKeylet.key, 1);
|
||||
|
||||
// Create a LoanBroker and a Loan, to drain the vault
|
||||
{
|
||||
using namespace loanBroker;
|
||||
using namespace loan;
|
||||
|
||||
env(set(owner, vaultKeylet.key), THISLINE);
|
||||
env.close();
|
||||
|
||||
// Create a simple Loan for the full amount of Vault assets
|
||||
env(set(depositor, brokerKeylet.key, depositAmount.value()),
|
||||
loan::interestRate(TenthBips32(0)),
|
||||
paymentInterval(120),
|
||||
paymentTotal(1),
|
||||
sig(sfCounterpartySignature, owner),
|
||||
fee(env.current()->fees().base * 2),
|
||||
ter(tesSUCCESS),
|
||||
THISLINE);
|
||||
env.close();
|
||||
|
||||
env.close(std::chrono::seconds{120 + 60});
|
||||
|
||||
env(manage(owner, loanKeylet.key, tfLoanDefault), ter(tesSUCCESS), THISLINE);
|
||||
|
||||
auto const sleVault = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
|
||||
auto const sleIssuance = env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID)));
|
||||
if (!BEAST_EXPECT(sleIssuance))
|
||||
return;
|
||||
|
||||
auto const shareBalance = sleIssuance->at(sfOutstandingAmount);
|
||||
auto const expectedShares = Number{
|
||||
depositAmount.number().mantissa(), depositAmount.number().exponent() + sleVault->at(sfScale)};
|
||||
|
||||
// verify that the vault is insolvent
|
||||
if (!BEAST_EXPECT(
|
||||
sleVault->at(sfAssetsTotal) == 0 && sleVault->at(sfAssetsAvailable) == 0 &&
|
||||
shareBalance == expectedShares))
|
||||
return;
|
||||
}
|
||||
|
||||
// The vault is insolvent, deposit must fail
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = depositor, .id = vaultKeylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tecNO_PERMISSION}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Clean up the vault to delete it
|
||||
{
|
||||
auto const sleVault = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
|
||||
Asset share = sleVault->at(sfShareMPTID);
|
||||
env(vault.clawback(
|
||||
{.issuer = owner, .id = vaultKeylet.key, .holder = depositor, .amount = share(0).value()}),
|
||||
ter(tesSUCCESS),
|
||||
THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
env(loan::del(owner, loanKeylet.key), ter(tesSUCCESS), THISLINE);
|
||||
env(loanBroker::del(owner, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
|
||||
env(vault.del({.owner = owner, .id = vaultKeylet.key}));
|
||||
env.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2761,10 +2963,173 @@ class Vault_test : public beast::unit_test::suite
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
env.close();
|
||||
});
|
||||
|
||||
testCase([&, this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const& issuer,
|
||||
Account const&,
|
||||
auto vaultAccount,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("lsfVaultDepositBlocked prevents deposits");
|
||||
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// First deposit assets to later show that withdrawals are not blocked
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = issuer, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Block Vault deposits
|
||||
{
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, ter(tesSUCCESS), THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = issuer, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tecNO_PERMISSION}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
auto tx = vault.withdraw({.depositor = issuer, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Unblock Vault Deposits
|
||||
{
|
||||
auto const tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(tx, ter(tesSUCCESS), THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Deposits now succeed
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = issuer, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Withdraw assets from the vault to delete it
|
||||
{
|
||||
auto const tx = vault.withdraw({.depositor = issuer, .id = keylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
env.close();
|
||||
});
|
||||
|
||||
testCase([&, this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const& issuer,
|
||||
Account const&,
|
||||
auto vaultAccount,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("insolvent vault blocks deposits");
|
||||
|
||||
auto const depositAmount = asset(20);
|
||||
|
||||
auto const [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// First deposit assets to later show that withdrawals are not blocked
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = depositAmount});
|
||||
env(tx, ter{tesSUCCESS}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const& brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner));
|
||||
auto const& loanKeylet = keylet::loan(brokerKeylet.key, 1);
|
||||
|
||||
// Create a LoanBroker and a Loan, to drain the vault
|
||||
{
|
||||
using namespace loanBroker;
|
||||
using namespace loan;
|
||||
|
||||
env(set(owner, vaultKeylet.key), THISLINE);
|
||||
env.close();
|
||||
|
||||
// Create a simple Loan for the full amount of Vault assets
|
||||
env(set(issuer, brokerKeylet.key, depositAmount.value()),
|
||||
loan::interestRate(TenthBips32(0)),
|
||||
paymentInterval(120),
|
||||
paymentTotal(1),
|
||||
sig(sfCounterpartySignature, owner),
|
||||
fee(env.current()->fees().base * 2),
|
||||
ter(tesSUCCESS),
|
||||
THISLINE);
|
||||
env.close();
|
||||
|
||||
env.close(std::chrono::seconds{120 + 60});
|
||||
|
||||
env(manage(owner, loanKeylet.key, tfLoanDefault), ter(tesSUCCESS), THISLINE);
|
||||
|
||||
auto const sleVault = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
|
||||
auto const sleIssuance = env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID)));
|
||||
if (!BEAST_EXPECT(sleIssuance))
|
||||
return;
|
||||
|
||||
auto const shareBalance = sleIssuance->at(sfOutstandingAmount);
|
||||
auto const expectedShares = Number{
|
||||
depositAmount.number().mantissa(), depositAmount.number().exponent() + sleVault->at(sfScale)};
|
||||
|
||||
// verify that the vault is insolvent
|
||||
if (!BEAST_EXPECT(
|
||||
sleVault->at(sfAssetsTotal) == 0 && sleVault->at(sfAssetsAvailable) == 0 &&
|
||||
shareBalance == expectedShares))
|
||||
return;
|
||||
}
|
||||
|
||||
// The vault is insolvent, deposit must fail
|
||||
{
|
||||
auto const tx = vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = asset(20)});
|
||||
env(tx, ter{tecNO_PERMISSION}, THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Clean up the vault to delete it
|
||||
{
|
||||
auto const sleVault = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
|
||||
Asset share = sleVault->at(sfShareMPTID);
|
||||
env(vault.clawback(
|
||||
{.issuer = owner, .id = vaultKeylet.key, .holder = issuer, .amount = share(0).value()}),
|
||||
ter(tesSUCCESS),
|
||||
THISLINE);
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
env(loan::del(owner, loanKeylet.key), ter(tesSUCCESS), THISLINE);
|
||||
env(loanBroker::del(owner, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
|
||||
env(vault.del({.owner = owner, .id = vaultKeylet.key}));
|
||||
env.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testWithDomainCheck()
|
||||
testPrivateVault()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
@@ -2819,6 +3184,28 @@ class Vault_test : public beast::unit_test::suite
|
||||
env(tx, ter{tecOBJECT_NOT_FOUND});
|
||||
}
|
||||
|
||||
{
|
||||
testcase("blocking a private vault does not change lsfVaultPrivate flag");
|
||||
auto tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock});
|
||||
env(tx, 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 tx = vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock});
|
||||
env(tx, ter{tesSUCCESS});
|
||||
auto const sleVault = env.le(keylet);
|
||||
if (!BEAST_EXPECT(sleVault))
|
||||
return;
|
||||
BEAST_EXPECT(!sleVault->isFlag(lsfVaultDepositBlocked));
|
||||
BEAST_EXPECT(sleVault->isFlag(lsfVaultPrivate));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("private vault set domainId");
|
||||
|
||||
@@ -5003,7 +5390,7 @@ public:
|
||||
testCreateFailMPT();
|
||||
testWithMPT();
|
||||
testWithIOU();
|
||||
testWithDomainCheck();
|
||||
testPrivateVault();
|
||||
testWithDomainCheckXRP();
|
||||
testNonTransferableShares();
|
||||
testFailedPseudoAccount();
|
||||
|
||||
@@ -31,6 +31,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ struct Vault
|
||||
{
|
||||
Account owner;
|
||||
uint256 id;
|
||||
std::optional<std::uint32_t> flags{};
|
||||
};
|
||||
|
||||
Json::Value
|
||||
|
||||
@@ -60,8 +60,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
auto const sleShareIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleShareIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
|
||||
@@ -69,7 +69,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (sleIssuance->isFlag(lsfMPTLocked))
|
||||
if (sleShareIssuance->isFlag(lsfMPTLocked))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked.";
|
||||
@@ -77,6 +77,24 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(fixLendingProtocolV1_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 tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (vault->isFlag(lsfVaultDepositBlocked))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "VaultDeposit: Vault deposits are blocked";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot deposit inside Vault an Asset frozen for the depositor
|
||||
if (isFrozen(ctx.view, account, vaultAsset))
|
||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
@@ -87,7 +105,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
|
||||
{
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
auto const maybeDomainID = sleShareIssuance->at(~sfDomainID);
|
||||
// Since this is a private vault and the account is not its owner, we
|
||||
// perform authorization check based on DomainID read from sleIssuance.
|
||||
// Had the vault shares been a regular MPToken, we would allow
|
||||
|
||||
@@ -21,6 +21,29 @@ VaultSet::checkExtraFeatures(PreflightContext const& ctx)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
VaultSet::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// VaultSet mask is built assuming fixLendingProtocolV1_1 is enabled
|
||||
if (ctx.rules.enabled(fixLendingProtocolV1_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 shouldCheckFlags = ctx.rules.enabled(fixLendingProtocolV1_1);
|
||||
|
||||
auto const atLeastOneFieldPresent =
|
||||
ctx.tx.isFieldPresent(sfDomainID) || ctx.tx.isFieldPresent(sfAssetsMaximum) || ctx.tx.isFieldPresent(sfData);
|
||||
|
||||
return atLeastOneFieldPresent ||
|
||||
(shouldCheckFlags && (ctx.tx.isFlag(tfVaultDepositBlock) || ctx.tx.isFlag(tfVaultDepositUnblock)));
|
||||
}
|
||||
|
||||
NotTEC
|
||||
VaultSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -48,12 +71,18 @@ 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;
|
||||
}
|
||||
|
||||
@@ -107,6 +136,21 @@ VaultSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(fixLendingProtocolV1_1))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -164,6 +208,15 @@ VaultSet::doApply()
|
||||
view().update(sleIssuance);
|
||||
}
|
||||
|
||||
if (view().rules().enabled(fixLendingProtocolV1_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.
|
||||
|
||||
@@ -13,6 +13,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user