mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 16:56:48 +00:00
adjustOwnerCountObj, more checks, some fixes
This commit is contained in:
@@ -105,6 +105,26 @@ adjustOwnerCount(
|
||||
j);
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCountObj(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref objectSle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
inline void
|
||||
adjustOwnerCountObj(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
SLE::ref objectSle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
SLE::ref accountSle = view.peek(keylet::account(account));
|
||||
return adjustOwnerCountObj(view, accountSle, objectSle, amount, j);
|
||||
}
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
|
||||
@@ -248,13 +248,19 @@ adjustOwnerCount(
|
||||
std::int32_t adjustment,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!accountSle)
|
||||
return;
|
||||
XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCount : valid account sle");
|
||||
auto const sleType = accountSle->getType();
|
||||
bool const validType = sponsorSle ? sleType == ltACCOUNT_ROOT
|
||||
: sleType == ltLOAN_BROKER || sleType == ltACCOUNT_ROOT;
|
||||
XRPL_ASSERT(validType, "xrpl::adjustOwnerCount : valid account sle type");
|
||||
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input");
|
||||
|
||||
auto const accountID = accountSle->getAccountID(sfAccount);
|
||||
if (sponsorSle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sponsorSle->getType() == ltACCOUNT_ROOT,
|
||||
"xrpl::adjustOwnerCount : valid sponsor sle type");
|
||||
auto const sponsorID = sponsorSle->getAccountID(sfAccount);
|
||||
|
||||
adjustSponsorOwnerCountHlp(
|
||||
@@ -274,6 +280,21 @@ adjustOwnerCount(
|
||||
adjustSponsorOwnerCountHlp(view, accountSle, sfOwnerCount, accountID, adjustment, j);
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCountObj(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref objectSle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(objectSle, "xrpl::adjustOwnerCount : valid object sle");
|
||||
XRPL_ASSERT(
|
||||
objectSle->getType() != ltACCOUNT_ROOT, "xrpl::adjustOwnerCount : valid object sle type");
|
||||
SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
|
||||
adjustOwnerCount(view, accountSle, sponsorSle, amount, j);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
accountReserve(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -94,10 +94,7 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
|
||||
}
|
||||
|
||||
if (isOwner)
|
||||
{
|
||||
auto const sponsorSle = getLedgerEntryReserveSponsor(view, sleCredential);
|
||||
adjustOwnerCount(view, sleAccount, sponsorSle, -1, j);
|
||||
}
|
||||
adjustOwnerCountObj(view, sleAccount, sleCredential, -1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
@@ -180,8 +180,7 @@ authorizeMPToken(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sleMpt);
|
||||
adjustOwnerCount(view, sleAcct, sponsor, -1, journal);
|
||||
adjustOwnerCountObj(view, sleAcct, sleMpt, -1, journal);
|
||||
|
||||
view.erase(sleMpt);
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -451,6 +451,7 @@ removeToken(
|
||||
auto const prev = loadPage(curr, sfPreviousPageMin);
|
||||
auto const next = loadPage(curr, sfNextPageMin);
|
||||
|
||||
auto nullJ = beast::Journal{beast::Journal::getNullSink()};
|
||||
if (!arr.empty())
|
||||
{
|
||||
// The current page isn't empty. Update it and then try to consolidate
|
||||
@@ -460,26 +461,10 @@ removeToken(
|
||||
view.update(curr);
|
||||
|
||||
if (prev && mergePages(view, prev, curr))
|
||||
{
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
sponsor,
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
}
|
||||
adjustOwnerCountObj(view, owner, prev, -1, nullJ);
|
||||
|
||||
if (next && mergePages(view, curr, next))
|
||||
{
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
sponsor,
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
}
|
||||
adjustOwnerCountObj(view, owner, curr, -1, nullJ);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -513,13 +498,7 @@ removeToken(
|
||||
curr->makeFieldAbsent(sfPreviousPageMin);
|
||||
}
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
sponsor,
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
adjustOwnerCountObj(view, owner, prev, -1, nullJ);
|
||||
|
||||
view.update(curr);
|
||||
view.erase(prev);
|
||||
@@ -555,13 +534,7 @@ removeToken(
|
||||
view.update(next);
|
||||
}
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
sponsor,
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
adjustOwnerCountObj(view, owner, curr, -1, nullJ);
|
||||
|
||||
view.erase(curr);
|
||||
|
||||
@@ -579,12 +552,7 @@ removeToken(
|
||||
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
|
||||
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
|
||||
{
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
getLedgerEntryReserveSponsor(view, prev),
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
adjustOwnerCountObj(view, owner, prev, -1, nullJ);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -701,13 +669,7 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
|
||||
false))
|
||||
return false;
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, offer);
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
sponsor,
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
adjustOwnerCountObj(view, owner, offer, -1, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
view.erase(offer);
|
||||
return true;
|
||||
|
||||
@@ -57,8 +57,7 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
}
|
||||
}
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sle);
|
||||
adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsor, -1, j);
|
||||
adjustOwnerCountObj(view, owner, sle, -1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -58,8 +58,7 @@ closeChannel(
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, slep);
|
||||
adjustOwnerCount(view, sle, sponsor, -1, j);
|
||||
adjustOwnerCountObj(view, sle, slep, -1, j);
|
||||
view.update(sle);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
|
||||
@@ -757,8 +757,7 @@ Transactor::ticketDelete(
|
||||
}
|
||||
|
||||
// Update the Ticket owner's reserve.
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sleTicket);
|
||||
adjustOwnerCount(view, sleAccount, sponsor, -1, j);
|
||||
adjustOwnerCountObj(view, sleAccount, sleTicket, -1, j);
|
||||
|
||||
// Remove Ticket from ledger.
|
||||
view.erase(sleTicket);
|
||||
|
||||
@@ -202,8 +202,7 @@ SponsorshipSet::doApply()
|
||||
if (!sponsorObjSle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), sponsorObjSle);
|
||||
adjustOwnerCount(ctx_.view(), sponsorAccSle, sponsor, -1, ctx_.journal);
|
||||
adjustOwnerCountObj(ctx_.view(), sponsorAccSle, sponsorObjSle, -1, ctx_.journal);
|
||||
|
||||
ctx_.view().dirRemove(
|
||||
keylet::ownerDir(sponsorAccountID),
|
||||
|
||||
@@ -216,9 +216,8 @@ removeSignersFromLedger(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, signers);
|
||||
adjustOwnerCount(
|
||||
view, view.peek(accountKeylet), sponsor, removeFromOwnerCount, registry.getJournal("View"));
|
||||
adjustOwnerCountObj(
|
||||
view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View"));
|
||||
|
||||
view.erase(signers);
|
||||
|
||||
|
||||
@@ -726,11 +726,10 @@ finalizeClaimHelper(
|
||||
return result;
|
||||
}
|
||||
|
||||
adjustOwnerCountObj(outerSb, sleOwner, sleClaimID, -1, j);
|
||||
|
||||
// Remove the claim id from the ledger
|
||||
outerSb.erase(sleClaimID);
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(outerSb, sleClaimID);
|
||||
adjustOwnerCount(outerSb, sleOwner, sponsor, -1, j);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,9 +94,7 @@ CheckCancel::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
auto const sleSrc = view().peek(keylet::account(srcId));
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view(), sleCheck);
|
||||
adjustOwnerCount(view(), sleSrc, sponsor, -1, viewJ);
|
||||
adjustOwnerCountObj(view(), srcId, sleCheck, -1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
view().erase(sleCheck);
|
||||
|
||||
@@ -303,7 +303,7 @@ CheckCash::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sponsorSle = getLedgerEntryReserveSponsor(psb, sleCheck);
|
||||
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
|
||||
|
||||
// Preclaim already checked that source has at least the requested
|
||||
// funds.
|
||||
@@ -333,7 +333,7 @@ CheckCash::doApply()
|
||||
// from src's directory, we allow them to send that additional
|
||||
// incremental reserve amount in the transfer. Hence the -1
|
||||
// argument.
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorSle ? 0 : -1, viewJ)};
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)};
|
||||
|
||||
// Now, how much do they need in order to be successful?
|
||||
STAmount const xrpDeliver{
|
||||
@@ -588,7 +588,7 @@ CheckCash::doApply()
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
|
||||
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), sponsorSle, -1, viewJ);
|
||||
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), sponsorCheckSle, -1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
psb.erase(sleCheck);
|
||||
|
||||
@@ -104,8 +104,7 @@ CredentialAccept::doApply()
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(account_, issuer, credType);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
auto const currentSponsor = getLedgerEntryReserveSponsor(view(), sleCred);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
|
||||
if (checkExpired(sleCred, view().header().parentCloseTime))
|
||||
{
|
||||
@@ -118,7 +117,7 @@ CredentialAccept::doApply()
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, currentSponsor, -1, j_);
|
||||
adjustOwnerCountObj(view(), sleIssuer, sleCred, -1, j_);
|
||||
removeSponsorFromLedgerEntry(sleCred);
|
||||
adjustOwnerCount(view(), sleSubject, newSponsor, 1, j_);
|
||||
addSponsorToLedgerEntry(sleCred, newSponsor);
|
||||
|
||||
@@ -168,8 +168,7 @@ DelegateSet::deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, be
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sle);
|
||||
adjustOwnerCount(view, sleOwner, sponsor, -1, j);
|
||||
adjustOwnerCountObj(view, sleOwner, sle, -1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ DIDDelete::deleteSLE(
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sle);
|
||||
adjustOwnerCount(view, sleOwner, sponsor, -1, j);
|
||||
view.update(sleOwner);
|
||||
adjustOwnerCountObj(view, sleOwner, sle, -1, j);
|
||||
|
||||
// Remove object from ledger
|
||||
view.erase(sle);
|
||||
|
||||
@@ -212,9 +212,7 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), slep);
|
||||
adjustOwnerCount(ctx_.view(), sle, sponsor, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
adjustOwnerCountObj(ctx_.view(), sle, slep, -1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -392,10 +392,7 @@ EscrowFinish::doApply()
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), slep);
|
||||
adjustOwnerCount(ctx_.view(), sle, sponsor, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
adjustOwnerCountObj(ctx_.view(), account, slep, -1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -181,17 +181,14 @@ LoanBrokerDelete::doApply()
|
||||
// Decreases the owner count by two: one for the LoanBroker object, and
|
||||
// one for the pseudo-account.
|
||||
// LoanBroker object can be sponsored
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view(), broker);
|
||||
adjustOwnerCount(view(), owner, sponsor, -1, j_);
|
||||
adjustOwnerCountObj(view(), owner, broker, -1, j_);
|
||||
|
||||
// pseudo-account cannot be sponsored
|
||||
adjustOwnerCount(view(), owner, {}, -1, j_);
|
||||
}
|
||||
|
||||
view().erase(brokerPseudoSLE);
|
||||
|
||||
view().erase(broker);
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -130,8 +130,7 @@ LoanDelete::doApply()
|
||||
}
|
||||
}
|
||||
// Decrement the borrower's owner count
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, loanSle);
|
||||
adjustOwnerCount(view, borrowerSle, sponsor, -1, j_);
|
||||
adjustOwnerCountObj(view, borrowerSle, loanSle, -1, j_);
|
||||
|
||||
// Delete the Loan object
|
||||
view.erase(loanSle);
|
||||
|
||||
@@ -72,10 +72,7 @@ OracleDelete::deleteOracle(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1;
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, sle);
|
||||
adjustOwnerCount(view, sleOwner, sponsor, count, j);
|
||||
|
||||
adjustOwnerCountObj(view, sleOwner, sle, count, j);
|
||||
view.erase(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -281,11 +281,10 @@ OracleSet::doApply()
|
||||
// the sponsor decrease current sponsored owner count.
|
||||
// Otherwise, the sponsorship will be deleted.
|
||||
|
||||
auto const currentSponsorSle = getLedgerEntryReserveSponsor(ctx_.view(), sle);
|
||||
auto const newSponsorSle = getTxReserveSponsor(ctx_.view(), ctx_.tx);
|
||||
|
||||
// decrease current sponsored owner count
|
||||
adjustOwnerCount(ctx_.view(), accountSle, currentSponsorSle, -oldCount, ctx_.journal);
|
||||
adjustOwnerCountObj(ctx_.view(), accountSle, sle, -oldCount, ctx_.journal);
|
||||
removeSponsorFromLedgerEntry(sle);
|
||||
// increase new owner count
|
||||
adjustOwnerCount(ctx_.view(), accountSle, newSponsorSle, newCount, ctx_.journal);
|
||||
@@ -294,8 +293,7 @@ OracleSet::doApply()
|
||||
else if (adjust < 0)
|
||||
{
|
||||
// decrease owner count
|
||||
auto const sponsorSle = getLedgerEntryReserveSponsor(ctx_.view(), sle);
|
||||
adjustOwnerCount(ctx_.view(), accountSle, sponsorSle, adjust, ctx_.journal);
|
||||
adjustOwnerCountObj(ctx_.view(), accountSle, sle, adjust, ctx_.journal);
|
||||
}
|
||||
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -288,9 +288,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
|
||||
if (!sleOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view, slePreauth);
|
||||
adjustOwnerCount(view, sleOwner, sponsor, -1, j);
|
||||
|
||||
adjustOwnerCountObj(view, sleOwner, slePreauth, -1, j);
|
||||
// Remove DepositPreauth from ledger.
|
||||
view.erase(slePreauth);
|
||||
|
||||
|
||||
@@ -68,8 +68,7 @@ PermissionedDomainDelete::doApply()
|
||||
XRPL_ASSERT(
|
||||
ownerSle && ownerSle->getFieldU32(sfOwnerCount) > 0,
|
||||
"xrpl::PermissionedDomainDelete::doApply : nonzero owner count");
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view(), slePd);
|
||||
adjustOwnerCount(view(), ownerSle, sponsor, -1, ctx_.journal);
|
||||
adjustOwnerCountObj(view(), ownerSle, slePd, -1, ctx_.journal);
|
||||
view().erase(slePd);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -52,10 +52,9 @@ MPTokenIssuanceDestroy::doApply()
|
||||
if (!view().dirRemove(keylet::ownerDir(account_), (*mpt)[sfOwnerNode], mpt->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsor = getLedgerEntryReserveSponsor(view(), mpt);
|
||||
adjustOwnerCount(view(), view().peek(keylet::account(account_)), sponsor, -1, j_);
|
||||
|
||||
adjustOwnerCountObj(view(), account_, mpt, -1, j_);
|
||||
view().erase(mpt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -207,8 +207,7 @@ VaultDelete::doApply()
|
||||
}
|
||||
|
||||
// We are destroying Vault and PseudoAccount, hence decrease by 2
|
||||
auto const vaultSponsor = getLedgerEntryReserveSponsor(view(), vault);
|
||||
adjustOwnerCount(view(), owner, vaultSponsor, -2, j_);
|
||||
adjustOwnerCountObj(view(), owner, vault, -2, j_);
|
||||
|
||||
// Destroy the vault.
|
||||
view().erase(vault);
|
||||
|
||||
Reference in New Issue
Block a user