mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 23:30:54 +00:00
fix rebase issues
This commit is contained in:
@@ -1,811 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Forward declarations for functions that remain in View.h/cpp
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
int depth);
|
||||
|
||||
[[nodiscard]] TER
|
||||
dirLink(
|
||||
ApplyView& view,
|
||||
AccountID const& owner,
|
||||
std::shared_ptr<SLE>& object,
|
||||
SF_UINT64 const& node = sfOwnerNode);
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isGlobalFrozen() const
|
||||
{
|
||||
if (sle_)
|
||||
return sle_->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (auto const sle = readView_.read(keylet::mptoken(mptID_, account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
return isGlobalFrozen() || isIndividualFrozen(account) ||
|
||||
isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
MPTokenIssuance::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isIndividualFrozen(account))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuance::checkFrozen(AccountID const& account) const
|
||||
{
|
||||
return isFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
return isFrozen(account, depth);
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuance::checkDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
return isDeepFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
Rate
|
||||
MPTokenIssuance::transferRate() const
|
||||
{
|
||||
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
|
||||
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
|
||||
// which represents 50% of 1,000,000,000
|
||||
if (sle_ && sle_->isFieldPresent(sfTransferFee))
|
||||
return Rate{1'000'000'000u + 10'000 * sle_->getFieldU16(sfTransferFee)};
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
MPTokenIssuance::canAddHolding() const
|
||||
{
|
||||
if (!sle_)
|
||||
{
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
if (!sle_->isFlag(lsfMPTCanTransfer))
|
||||
{
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPTokenIssuance::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (!mutableSle_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (mutableSle_->isFlag(lsfMPTLocked))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (applyView_.peek(keylet::mptoken(mptID_, accountID)))
|
||||
return tecDUPLICATE;
|
||||
if (accountID == mptIssue_.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(priorBalance, accountID, journal);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPTokenIssuance::authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags,
|
||||
std::optional<AccountID> holderID)
|
||||
{
|
||||
WritableAccountRoot wrappedAcct(account, applyView_);
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted the tx is a holder
|
||||
// Note: `account_` is holder's account
|
||||
// `holderID` is NOT used
|
||||
if (!holderID)
|
||||
{
|
||||
// When a holder wants to unauthorize/delete a MPT, the ledger must
|
||||
// - delete mptokenKey from owner directory
|
||||
// - delete the MPToken
|
||||
if (flags & tfMPTUnauthorize)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
auto const sleMpt = applyView_.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!applyView_.dirRemove(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
wrappedAcct.adjustOwnerCount(-1, journal);
|
||||
|
||||
applyView_.erase(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// A potential holder wants to authorize/hold a mpt, the ledger must:
|
||||
// - add the new mptokenKey to the owner directory
|
||||
// - create the MPToken object for the holder
|
||||
|
||||
// The reserve that is required to create the MPToken. Note
|
||||
// that although the reserve increases with every item
|
||||
// an account owns, in the case of MPTokens we only
|
||||
// *enforce* a reserve if the user owns more than two
|
||||
// items. This is similar to the reserve requirements of trust lines.
|
||||
std::uint32_t const uOwnerCount = wrappedAcct->getFieldU32(sfOwnerCount);
|
||||
XRPAmount const reserveCreate(
|
||||
(uOwnerCount < 2) ? XRPAmount(beast::zero)
|
||||
: applyView_.fees().accountReserve(uOwnerCount + 1));
|
||||
|
||||
if (priorBalance < reserveCreate)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Defensive check before we attempt to create MPToken for the issuer
|
||||
if (!mutableSle_ || mutableSle_->getAccountID(sfIssuer) == account)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
|
||||
if (applyView_.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
if (auto ter = dirLink(applyView_, account, mptoken))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
applyView_.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
wrappedAcct.adjustOwnerCount(1, journal);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (!mutableSle_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted this tx is the issuer of the MPT
|
||||
// Note: `account_` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (account != (*mutableSle_)[sfIssuer])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMpt = applyView_.peek(keylet::mptoken(mptID_, *holderID));
|
||||
if (!sleMpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
|
||||
std::uint32_t flagsOut = flagsIn;
|
||||
|
||||
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
|
||||
// their MPToken
|
||||
if (flags & tfMPTUnauthorize)
|
||||
{
|
||||
flagsOut &= ~lsfMPTAuthorized;
|
||||
}
|
||||
// Issuer wants to authorize a holder, set lsfMPTAuthorized on their
|
||||
// MPToken
|
||||
else
|
||||
{
|
||||
flagsOut |= lsfMPTAuthorized;
|
||||
}
|
||||
|
||||
if (flagsIn != flagsOut)
|
||||
sleMpt->setFieldU32(sfFlags, flagsOut);
|
||||
|
||||
applyView_.update(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPTokenIssuance::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
{
|
||||
// If the account is the issuer, then no token should exist. MPTs do not
|
||||
// have the legacy ability to create such a situation, but check anyway. If
|
||||
// a token does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == mptIssue_.getIssuer();
|
||||
auto const mptoken = applyView_.peek(keylet::mptoken(mptID_, accountID));
|
||||
if (!mptoken)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
// Unlike a trust line, if the account is the issuer, and the token has a
|
||||
// balance, it can not just be deleted, because that will throw the issuance
|
||||
// accounting out of balance, so fail. Since this should be impossible
|
||||
// anyway, I'm not going to put any effort into it.
|
||||
if (mptoken->at(sfMPTAmount) != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return authorizeMPToken(
|
||||
{}, // priorBalance
|
||||
accountID,
|
||||
journal,
|
||||
tfMPTUnauthorize // flags
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
MPTokenIssuance::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const mptIssuer = AccountRoot(sle_->getAccountID(sfIssuer), readView_);
|
||||
|
||||
// issuer is always "authorized"
|
||||
if (mptIssuer == account) // Issuer won't have MPToken
|
||||
return tesSUCCESS;
|
||||
|
||||
bool const featureSAVEnabled = readView_.rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
if (depth >= maxAssetCheckDepth)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// requireAuth is recursive if the issuer is a vault pseudo-account
|
||||
if (!mptIssuer.exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (mptIssuer->isFieldPresent(sfVaultID))
|
||||
{
|
||||
auto const sleVault = readView_.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
|
||||
if (!sleVault)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const asset = sleVault->at(sfAsset);
|
||||
if (auto const err =
|
||||
makeTokenBase(readView_, asset)->requireAuth(account, authType, depth + 1);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
auto const sleToken = readView_.read(keylet::mptoken(mptID_, account));
|
||||
|
||||
// if account has no MPToken, fail
|
||||
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||
return tecNO_AUTH;
|
||||
|
||||
// Note, this check is not amendment-gated because DomainID will be always
|
||||
// empty **unless** writing to it has been enabled by an amendment
|
||||
auto const maybeDomainID = sle_->at(~sfDomainID);
|
||||
if (maybeDomainID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sle_->getFieldU32(sfFlags) & lsfMPTRequireAuth,
|
||||
"xrpl::requireAuth : issuance requires authorization");
|
||||
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
|
||||
auto const ter = credentials::validDomain(readView_, *maybeDomainID, account);
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
return ter; // Note: sleToken might be null
|
||||
}
|
||||
if (!sleToken)
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
// We ignore error from validDomain if we found sleToken, as it could
|
||||
// belong to someone who is explicitly authorized e.g. a vault owner.
|
||||
}
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
// Implicitly authorize Vault and LoanBroker pseudo-accounts
|
||||
if (isPseudoAccount(readView_, account, {&sfVaultID, &sfLoanBrokerID}))
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// mptoken must be authorized if issuance enabled requireAuth
|
||||
if (sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
return tecNO_AUTH;
|
||||
|
||||
return tesSUCCESS; // Note: sleToken might be null
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPTokenIssuance::enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!mutableSle_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
mutableSle_->isFlag(lsfMPTRequireAuth),
|
||||
"xrpl::enforceMPTokenAuthorization : authorization required");
|
||||
|
||||
if (account == mutableSle_->at(sfIssuer))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const keylet = keylet::mptoken(mptID_, account);
|
||||
auto const sleToken = readView_.read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = mutableSle_->at(~sfDomainID);
|
||||
bool expired = false;
|
||||
bool const authorizedByDomain = [&]() -> bool {
|
||||
// NOTE: defensive here, should be checked in preclaim
|
||||
if (!maybeDomainID)
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = verifyValidDomain(applyView(), account, *maybeDomainID, j);
|
||||
if (isTesSuccess(ter))
|
||||
return true;
|
||||
if (ter == tecEXPIRED)
|
||||
expired = true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!authorizedByDomain && sleToken == nullptr)
|
||||
{
|
||||
// Could not find MPToken and won't create one, could be either of:
|
||||
//
|
||||
// 1. Field sfDomainID not set in MPTokenIssuance or
|
||||
// 2. Account has no matching and accepted credentials or
|
||||
// 3. Account has all expired credentials (deleted in verifyValidDomain)
|
||||
//
|
||||
// Either way, return tecNO_AUTH and there is nothing else to do
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain && maybeDomainID)
|
||||
{
|
||||
// Found an MPToken but the account is not authorized and we expect
|
||||
// it to have been authorized by the domain. This could be because the
|
||||
// credentials used to create the MPToken have expired or been deleted.
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain)
|
||||
{
|
||||
// We found an MPToken, but sfDomainID is not set, so this is a classic
|
||||
// MPToken which requires authorization by the token issuer.
|
||||
XRPL_ASSERT(
|
||||
sleToken != nullptr && !maybeDomainID,
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken");
|
||||
if (sleToken->isFlag(lsfMPTAuthorized))
|
||||
return tesSUCCESS;
|
||||
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
if (authorizedByDomain && sleToken != nullptr)
|
||||
{
|
||||
// Found an MPToken, authorized by the domain. Ignore authorization flag
|
||||
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
|
||||
XRPL_ASSERT(maybeDomainID, "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
|
||||
return tesSUCCESS;
|
||||
}
|
||||
if (authorizedByDomain)
|
||||
{
|
||||
// Could not find MPToken but there should be one because we are
|
||||
// authorized by domain. Proceed to create it, then return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID && sleToken == nullptr,
|
||||
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
|
||||
if (auto const err = authorizeMPToken(
|
||||
priorBalance, // priorBalance
|
||||
account, // account
|
||||
j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::enforceMPTokenAuthorization : condition list is incomplete");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuance::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!(sle_->getFieldU32(sfFlags) & lsfMPTCanTransfer))
|
||||
{
|
||||
if (from != (*sle_)[sfIssuer] && to != (*sle_)[sfIssuer])
|
||||
return TER{tecNO_AUTH};
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
MPTokenIssuance::canClawback() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTCanClawback);
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::requiresAuth() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTRequireAuth);
|
||||
}
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance.exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (mptIssuance.getIssuer() == sender)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// 1. Decrease the MPT Holder MPTAmount
|
||||
// 2. Increase the MPT Holder EscrowedAmount
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const amt = sle->getFieldU64(sfMPTAmount);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: insufficient MPTAmount for "
|
||||
<< to_string(sender) << ": " << amt << " < " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] = amt - pay;
|
||||
|
||||
// Overflow check for addition
|
||||
uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
|
||||
|
||||
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on locked amount for "
|
||||
<< to_string(sender) << ": " << locked << " + " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sle->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sle)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sle);
|
||||
}
|
||||
|
||||
// 1. Increase the Issuance EscrowedAmount
|
||||
// 2. DO NOT change the Issuance OutstandingAmount
|
||||
{
|
||||
uint64_t const issuanceEscrowed = (*mptIssuance)[~sfLockedAmount].value_or(0);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
|
||||
"locked amount for "
|
||||
<< mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (mptIssuance->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*mptIssuance)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
mptIssuance->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
mptIssuance.update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
rippleUnlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(fixTokenEscrowV1))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
netAmount == grossAmount, "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
|
||||
}
|
||||
|
||||
auto const& issuer = netAmount.getIssuer();
|
||||
auto const& mptIssue = netAmount.get<MPTIssue>();
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// Decrease the Issuance EscrowedAmount
|
||||
{
|
||||
if (!mptIssuance->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = mptIssuance->getFieldU64(sfLockedAmount);
|
||||
auto const redeem = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
|
||||
<< mptIssue.getMptID() << ": " << locked << " < " << redeem;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const newLocked = locked - redeem;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
mptIssuance->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
mptIssuance->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
mptIssuance.update();
|
||||
}
|
||||
|
||||
if (issuer != receiver)
|
||||
{
|
||||
// Increase the MPT Holder MPTAmount
|
||||
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), receiver);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto current = sle->getFieldU64(sfMPTAmount);
|
||||
auto delta = netAmount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
|
||||
<< to_string(receiver) << ": " << current << " + " << delta;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
(*sle)[sfMPTAmount] += delta;
|
||||
view.update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decrease the Issuance OutstandingAmount
|
||||
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = netAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
|
||||
<< mptIssue.getMptID() << ": " << outstanding << " < " << redeem;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
mptIssuance.update();
|
||||
}
|
||||
|
||||
if (issuer == sender)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
|
||||
"cannot unlock MPTs.";
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
// Decrease the MPT Holder EscrowedAmount
|
||||
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (!sle->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
|
||||
<< to_string(sender);
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sle->getFieldU64(sfLockedAmount);
|
||||
auto const delta = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
|
||||
<< to_string(sender) << ": " << locked << " < " << delta;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const newLocked = locked - delta;
|
||||
if (newLocked == 0)
|
||||
sle->makeFieldAbsent(sfLockedAmount);
|
||||
else
|
||||
sle->setFieldU64(sfLockedAmount, newLocked);
|
||||
view.update(sle);
|
||||
|
||||
// Note: The gross amount is the amount that was locked, the net
|
||||
// amount is the amount that is being unlocked. The difference is the fee
|
||||
// that was charged for the transfer. If this difference is greater than
|
||||
// zero, we need to update the outstanding amount.
|
||||
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
|
||||
if (diff != 0)
|
||||
{
|
||||
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
|
||||
<< mptIssue.getMptID() << ": " << outstanding << " < " << diff;
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
mptIssuance.update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
|
||||
if (returnSpendable && account == mptIssue_.getIssuer())
|
||||
{
|
||||
// if the account is the issuer, and the issuance exists, their limit is
|
||||
// the issuance limit minus the outstanding value
|
||||
|
||||
if (!sle_)
|
||||
{
|
||||
return STAmount{mptIssue_};
|
||||
}
|
||||
return STAmount{
|
||||
mptIssue_,
|
||||
sle_->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - sle_->at(sfOutstandingAmount)};
|
||||
}
|
||||
|
||||
STAmount amount;
|
||||
|
||||
auto const sleMpt = readView_.read(keylet::mptoken(mptID_, account));
|
||||
|
||||
if (!sleMpt)
|
||||
amount.clear(mptIssue_);
|
||||
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(account))
|
||||
amount.clear(mptIssue_);
|
||||
else
|
||||
{
|
||||
amount = STAmount{mptIssue_, sleMpt->getFieldU64(sfMPTAmount)};
|
||||
|
||||
// Only if auth check is needed, as it needs to do an additional read
|
||||
// operation. Note featureSingleAssetVault will affect error codes.
|
||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||
readView_.rules().enabled(featureSingleAssetVault))
|
||||
{
|
||||
if (auto const err = requireAuth(account, AuthType::StrongAuth); !isTesSuccess(err))
|
||||
amount.clear(mptIssue_);
|
||||
}
|
||||
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
|
||||
{
|
||||
// if auth is enabled on the issuance and mpt is not authorized,
|
||||
// clear amount
|
||||
if (sle_ && sle_->isFlag(lsfMPTRequireAuth) && !sleMpt->isFlag(lsfMPTAuthorized))
|
||||
amount.clear(mptIssue_);
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,925 +0,0 @@
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Credit functions (from Credit.cpp)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& readView_,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
|
||||
{
|
||||
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
|
||||
}
|
||||
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& readView_,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
|
||||
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(sfBalance);
|
||||
if (account < issuer)
|
||||
result.negate();
|
||||
result.setIssuer(account);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
IOUToken::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
if (issuer_ != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (sle && sle->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can the specified account spend the specified currency issued by
|
||||
// the specified issuer or does the freeze flag prohibit it?
|
||||
bool
|
||||
IOUToken::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
if (issuerAccount_.exists() && issuerAccount_->isFlag(lsfGlobalFreeze))
|
||||
return true;
|
||||
if (issuer_ != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sleLine = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (sleLine && sleLine->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (issuer_ == account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::checkFrozen(AccountID const& account) const
|
||||
{
|
||||
return isFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::checkDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
return isDeepFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isFrozen(account, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountFunds(
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j) const
|
||||
{
|
||||
if (!saDefault.native() && saDefault.getIssuer() == id)
|
||||
return saDefault;
|
||||
|
||||
return accountHolds(id, freezeHandling, j);
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance) const
|
||||
{
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
AccountRoot accountRoot(account, readView_);
|
||||
return {accountRoot.xrpLiquid(0, j)};
|
||||
}
|
||||
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
if (returnSpendable && account == issuer_)
|
||||
// If the account is the issuer, then their limit is effectively
|
||||
// infinite
|
||||
return STAmount{issue_, STAmount::cMaxValue, STAmount::cMaxOffset};
|
||||
|
||||
// IOU: Return balance on trust line modulo freeze
|
||||
// Check if line exists and is usable (mirrors old getLineIfUsable)
|
||||
SLE::const_pointer sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
|
||||
if (sle && zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
{
|
||||
if (isFrozen(account) || isDeepFrozen(account))
|
||||
{
|
||||
sle = nullptr;
|
||||
}
|
||||
|
||||
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
|
||||
// we need to check if the associated assets have been frozen
|
||||
if (sle && readView_.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
auto const sleIssuer = readView_.read(keylet::account(issuer_));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
sle = nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
else if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
{
|
||||
auto const sleAmm = readView_.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
||||
|
||||
if (!sleAmm ||
|
||||
isLPTokenFrozen(
|
||||
readView_,
|
||||
account,
|
||||
(*sleAmm)[sfAsset].get<Issue>(),
|
||||
(*sleAmm)[sfAsset2].get<Issue>()))
|
||||
{
|
||||
sle = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract balance (mirrors old getTrustLineBalance)
|
||||
STAmount amount;
|
||||
if (sle)
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
bool const accountHigh = account > issuer_;
|
||||
auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
|
||||
if (accountHigh)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
if (returnSpendable)
|
||||
{
|
||||
amount += sle->getFieldAmount(oppositeField);
|
||||
}
|
||||
amount.setIssuer(issuer_);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount.clear(Issue{currency_, issuer_});
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "IOUToken::accountHolds:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
return readView_.balanceHook(account, issuer_, amount);
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::canAddHolding() const
|
||||
{
|
||||
if (isXRP(issue_))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!issuerAccount_.exists())
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
if (!issuerAccount_->isFlag(lsfDefaultRipple))
|
||||
return terNO_RIPPLE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
Rate
|
||||
IOUToken::transferRate() const
|
||||
{
|
||||
return issuerAccount_.transferRate();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // ripple state entry
|
||||
WritableAccountRoot& wrappedAcct, // the account being set.
|
||||
bool const bAuth, // authorize account.
|
||||
bool const bNoRipple, // others cannot ripple through
|
||||
bool const bFreeze, // funds cannot leave
|
||||
bool bDeepFreeze, // can neither receive nor send funds
|
||||
STAmount const& saBalance, // balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j)
|
||||
{
|
||||
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
|
||||
<< to_string(uDstAccountID) << ", " << saBalance.getFullText();
|
||||
|
||||
auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
|
||||
auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
|
||||
if (uLowAccountID == uHighAccountID)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::trustCreate : trust line to self");
|
||||
if (view.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
|
||||
view.insert(sleRippleState);
|
||||
|
||||
auto lowNode = view.dirInsert(
|
||||
keylet::ownerDir(uLowAccountID), sleRippleState->key(), describeOwnerDir(uLowAccountID));
|
||||
|
||||
if (!lowNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
auto highNode = view.dirInsert(
|
||||
keylet::ownerDir(uHighAccountID), sleRippleState->key(), describeOwnerDir(uHighAccountID));
|
||||
|
||||
if (!highNode)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
|
||||
bool const bSetHigh = bSrcHigh ^ bSetDst;
|
||||
|
||||
XRPL_ASSERT(wrappedAcct, "xrpl::trustCreate : non-null SLE");
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
wrappedAcct->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
"xrpl::trustCreate : matching account ID");
|
||||
auto const peer = AccountRoot(bSetHigh ? uLowAccountID : uHighAccountID, view);
|
||||
if (!peer.exists())
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Remember deletion hints.
|
||||
sleRippleState->setFieldU64(sfLowNode, *lowNode);
|
||||
sleRippleState->setFieldU64(sfHighNode, *highNode);
|
||||
|
||||
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
|
||||
sleRippleState->setFieldAmount(
|
||||
bSetHigh ? sfLowLimit : sfHighLimit,
|
||||
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
|
||||
if (uQualityIn)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
|
||||
|
||||
if (uQualityOut)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
|
||||
|
||||
std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
|
||||
|
||||
if (bAuth)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
|
||||
}
|
||||
if (bNoRipple)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
if (bFreeze)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
}
|
||||
if (bDeepFreeze)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
if ((peer->getFlags() & lsfDefaultRipple) == 0)
|
||||
{
|
||||
// The other side's default is no rippling
|
||||
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
}
|
||||
|
||||
sleRippleState->setFieldU32(sfFlags, uFlags);
|
||||
wrappedAcct.adjustOwnerCount(1, j);
|
||||
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
|
||||
view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Detect legacy dirs.
|
||||
std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
|
||||
std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
|
||||
|
||||
if (!view.dirRemove(keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
|
||||
|
||||
if (!view.dirRemove(keylet::ownerDir(uHighAccountID), uHighNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
|
||||
view.erase(sleRippleState);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// IOU issuance/redemption
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static bool
|
||||
updateTrustLine(
|
||||
ApplyView& view,
|
||||
SLE::pointer state,
|
||||
bool bSenderHigh,
|
||||
AccountID const& sender,
|
||||
STAmount const& before,
|
||||
STAmount const& after,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!state)
|
||||
return false;
|
||||
std::uint32_t const flags(state->getFieldU32(sfFlags));
|
||||
|
||||
WritableAccountRoot wrappedAcct(sender, view);
|
||||
if (!wrappedAcct)
|
||||
return false;
|
||||
|
||||
// YYY Could skip this if rippling in reverse.
|
||||
if (before > beast::zero
|
||||
// Sender balance was positive.
|
||||
&& after <= beast::zero
|
||||
// Sender is zero or negative.
|
||||
&& (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
|
||||
// Sender reserve is set.
|
||||
&& static_cast<bool>(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
|
||||
static_cast<bool>(wrappedAcct->getFlags() & lsfDefaultRipple) &&
|
||||
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
|
||||
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
|
||||
// Sender quality in is 0.
|
||||
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
|
||||
// Sender quality out is 0.
|
||||
{
|
||||
// VFALCO Where is the line being deleted?
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
wrappedAcct.adjustOwnerCount(-1, j);
|
||||
|
||||
// Clear reserve flag.
|
||||
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
|
||||
|
||||
// Balance is zero, receiver reserve is clear.
|
||||
if (!after // Balance is zero.
|
||||
&& !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::issueIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool bSenderHigh = issue.account > account;
|
||||
|
||||
auto const index = keylet::line(issue.account, account, issue.currency);
|
||||
|
||||
if (auto state = view.peek(index))
|
||||
{
|
||||
STAmount final_balance = state->getFieldAmount(sfBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate(); // Put balance in sender terms.
|
||||
|
||||
STAmount const start_balance = final_balance;
|
||||
|
||||
final_balance -= amount;
|
||||
|
||||
auto const must_delete = updateTrustLine(
|
||||
view, state, bSenderHigh, issue.account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(issue.account, account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
|
||||
// Adjust the balance on the trust line if necessary. We do this even
|
||||
// if we are going to delete the line to reflect the correct balance
|
||||
// at the time of deletion.
|
||||
state->setFieldAmount(sfBalance, final_balance);
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? account : issue.account,
|
||||
bSenderHigh ? issue.account : account,
|
||||
j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// NIKB TODO: The limit uses the receiver's account as the issuer and
|
||||
// this is unnecessarily inefficient as copying which could be avoided
|
||||
// is now required. Consider available options.
|
||||
STAmount const limit(Issue{issue.currency, account});
|
||||
STAmount final_balance = amount;
|
||||
|
||||
final_balance.setIssuer(noAccount());
|
||||
|
||||
WritableAccountRoot receiverAccount(account, view);
|
||||
if (!receiverAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
issue.account,
|
||||
account,
|
||||
index.key,
|
||||
receiverAccount,
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
final_balance,
|
||||
limit,
|
||||
0,
|
||||
0,
|
||||
j);
|
||||
}
|
||||
|
||||
TER
|
||||
redeemIOU(
|
||||
ApplyView& applyView,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::redeemIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool bSenderHigh = account > issue.account;
|
||||
|
||||
if (auto state = applyView.peek(keylet::line(account, issue.account, issue.currency)))
|
||||
{
|
||||
STAmount final_balance = state->getFieldAmount(sfBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate(); // Put balance in sender terms.
|
||||
|
||||
STAmount const start_balance = final_balance;
|
||||
|
||||
final_balance -= amount;
|
||||
|
||||
auto const must_delete = updateTrustLine(
|
||||
applyView, state, bSenderHigh, account, start_balance, final_balance, j);
|
||||
|
||||
applyView.creditHook(account, issue.account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
|
||||
// Adjust the balance on the trust line if necessary. We do this even
|
||||
// if we are going to delete the line to reflect the correct balance
|
||||
// at the time of deletion.
|
||||
state->setFieldAmount(sfBalance, final_balance);
|
||||
|
||||
if (must_delete)
|
||||
{
|
||||
return trustDelete(
|
||||
applyView,
|
||||
state,
|
||||
bSenderHigh ? issue.account : account,
|
||||
bSenderHigh ? account : issue.account,
|
||||
j);
|
||||
}
|
||||
|
||||
applyView.update(state);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// In order to hold an IOU, a trust line *MUST* exist to track the
|
||||
// balance. If it doesn't, then something is very wrong. Don't try
|
||||
// to continue.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "redeemIOU: " << to_string(account) << " attempts to "
|
||||
<< "redeem " << amount.getFullText() << " but no trust line exists!";
|
||||
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
IOUToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(issue_) || issuer_ == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const trustLine = readView_.read(keylet::line(account, issuer_, 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 (issuerAccount_.exists() && (*issuerAccount_)[sfFlags] & lsfRequireAuth)
|
||||
{
|
||||
if (trustLine)
|
||||
{
|
||||
return ((*trustLine)[sfFlags] & ((account > issuer_) ? lsfLowAuth : lsfHighAuth))
|
||||
? tesSUCCESS
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
return TER{tecNO_LINE};
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
if (issue_.native())
|
||||
return tesSUCCESS;
|
||||
|
||||
if (issuer_ == from || issuer_ == to)
|
||||
return tesSUCCESS;
|
||||
if (!issuerAccount_.exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const isRippleDisabled = [&](AccountID account) -> bool {
|
||||
// Line might not exist, but some transfers can create it. If this
|
||||
// is the case, just check the default ripple on the issuer account.
|
||||
auto const line = readView_.read(keylet::line(account, issue_));
|
||||
if (line)
|
||||
{
|
||||
bool const issuerHigh = issuer_ > account;
|
||||
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
return issuerAccount_->isFlag(lsfDefaultRipple) == false;
|
||||
};
|
||||
|
||||
// Fail if rippling disabled on both trust lines
|
||||
if (isRippleDisabled(from) && isRippleDisabled(to))
|
||||
return terNO_RIPPLE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
IOUToken::canClawback() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfAllowTrustLineClawback) &&
|
||||
!issuerAccount_->isFlag(lsfNoFreeze);
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::requiresAuth() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfRequireAuth);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
WritableIOUToken::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Every account can hold XRP. An issuer can issue directly.
|
||||
if (issue_.native() || accountID == issuer_)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (issuerAccount_.isGlobalFrozen())
|
||||
return tecFROZEN; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& srcId = issuer_;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::line(srcId, dstId, currency_);
|
||||
WritableAccountRoot wrappedSrc(srcId, applyView_);
|
||||
WritableAccountRoot wrappedDst(dstId, applyView_);
|
||||
if (!wrappedDst || !wrappedSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (!wrappedSrc->isFlag(lsfDefaultRipple))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// If the line already exists, don't create it again.
|
||||
if (applyView_.read(index))
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Can the account cover the trust line reserve ?
|
||||
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
|
||||
if (priorBalance < readView_.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
return trustCreate(
|
||||
applyView_,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
index.key,
|
||||
wrappedDst,
|
||||
/*bAuth=*/false,
|
||||
/*bNoRipple=*/true,
|
||||
/*bFreeze=*/false,
|
||||
/*deepFreeze*/ false,
|
||||
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency_, dstId}},
|
||||
/*uQualityIn=*/0,
|
||||
/*uQualityOut=*/0,
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
{
|
||||
if (issue_.native())
|
||||
{
|
||||
auto const account = AccountRoot(accountID, applyView_);
|
||||
if (!account.exists())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = account->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// `asset` is an IOU.
|
||||
// If the account is the issuer, then no line should exist. Check anyway.
|
||||
// If a line does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == issue_.account;
|
||||
auto const line = applyView_.peek(keylet::line(accountID, issue_));
|
||||
if (!line)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Adjust the owner count(s)
|
||||
if (line->isFlag(lsfLowReserve))
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
WritableAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), applyView_);
|
||||
if (!wrappedLow)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
wrappedLow.adjustOwnerCount(-1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
line->clearFlag(lsfLowReserve);
|
||||
}
|
||||
|
||||
if (line->isFlag(lsfHighReserve))
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
WritableAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), applyView_);
|
||||
if (!wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
wrappedHigh.adjustOwnerCount(-1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
line->clearFlag(lsfHighReserve);
|
||||
}
|
||||
|
||||
return trustDelete(
|
||||
applyView_,
|
||||
line,
|
||||
line->at(sfLowLimit)->getIssuer(),
|
||||
line->at(sfHighLimit)->getIssuer(),
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMTrustLine(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleState,
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!sleState || sleState->getType() != ltRIPPLE_STATE)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& [low, high] = std::minmax(
|
||||
sleState->getFieldAmount(sfLowLimit).getIssuer(),
|
||||
sleState->getFieldAmount(sfHighLimit).getIssuer());
|
||||
WritableAccountRoot wrappedLow(low, view);
|
||||
WritableAccountRoot wrappedHigh(high, view);
|
||||
if (!wrappedLow || !wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const ammLow = wrappedLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = wrappedHigh->isFieldPresent(sfAMMID);
|
||||
|
||||
// can't both be AMM
|
||||
if (ammLow && ammHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// at least one must be
|
||||
if (!ammLow && !ammHigh)
|
||||
return terNO_AMM;
|
||||
|
||||
// one must be the target amm
|
||||
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
|
||||
return terNO_AMM;
|
||||
|
||||
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
|
||||
if (!(sleState->getFlags() & uFlags))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
WritableAccountRoot wrappedHolder = !ammLow ? wrappedLow : wrappedHigh;
|
||||
wrappedHolder.adjustOwnerCount(-1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -28,7 +28,7 @@ dirLink(
|
||||
SF_UINT64 const& node = sfOwnerNode);
|
||||
|
||||
bool
|
||||
MPToken::isGlobalFrozen() const
|
||||
MPTokenIssuance::isGlobalFrozen() const
|
||||
{
|
||||
if (sle_)
|
||||
return sle_->isFlag(lsfMPTLocked);
|
||||
@@ -36,7 +36,7 @@ MPToken::isGlobalFrozen() const
|
||||
}
|
||||
|
||||
bool
|
||||
MPToken::isIndividualFrozen(AccountID const& account) const
|
||||
MPTokenIssuance::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (auto const sle = readView_.read(keylet::mptoken(mptID_, account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
@@ -44,14 +44,14 @@ MPToken::isIndividualFrozen(AccountID const& account) const
|
||||
}
|
||||
|
||||
bool
|
||||
MPToken::isFrozen(AccountID const& account, int depth) const
|
||||
MPTokenIssuance::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
return isGlobalFrozen() || isIndividualFrozen(account) ||
|
||||
isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
MPToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
MPTokenIssuance::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
@@ -72,26 +72,25 @@ MPToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth
|
||||
}
|
||||
|
||||
TER
|
||||
MPToken::checkFrozen(AccountID const& account) const
|
||||
MPTokenIssuance::checkFrozen(AccountID const& account) const
|
||||
{
|
||||
return isFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
|
||||
return isFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
bool
|
||||
MPToken::isDeepFrozen(AccountID const& account, int depth) const
|
||||
MPTokenIssuance::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
// MPTs don't have deep freeze, so this always returns false
|
||||
return false;
|
||||
return isFrozen(account, depth);
|
||||
}
|
||||
|
||||
TER
|
||||
MPToken::checkDeepFrozen(AccountID const& account) const
|
||||
MPTokenIssuance::checkDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
return isDeepFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
|
||||
}
|
||||
|
||||
Rate
|
||||
MPToken::transferRate() const
|
||||
MPTokenIssuance::transferRate() const
|
||||
{
|
||||
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
|
||||
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
|
||||
@@ -103,7 +102,7 @@ MPToken::transferRate() const
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
MPToken::canAddHolding() const
|
||||
MPTokenIssuance::canAddHolding() const
|
||||
{
|
||||
if (!sle_)
|
||||
{
|
||||
@@ -118,7 +117,7 @@ MPToken::canAddHolding() const
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPToken::addEmptyHolding(
|
||||
WritableMPTokenIssuance::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
beast::Journal journal)
|
||||
@@ -136,7 +135,7 @@ WritableMPToken::addEmptyHolding(
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPToken::authorizeMPToken(
|
||||
WritableMPTokenIssuance::authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
@@ -252,7 +251,7 @@ WritableMPToken::authorizeMPToken(
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPToken::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
WritableMPTokenIssuance::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
{
|
||||
// If the account is the issuer, then no token should exist. MPTs do not
|
||||
// have the legacy ability to create such a situation, but check anyway. If
|
||||
@@ -277,7 +276,7 @@ WritableMPToken::removeEmptyHolding(AccountID const& accountID, beast::Journal j
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
MPToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
MPTokenIssuance::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
@@ -356,7 +355,7 @@ MPToken::requireAuth(AccountID const& account, AuthType authType, int depth) con
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
WritableMPToken::enforceMPTokenAuthorization(
|
||||
WritableMPTokenIssuance::enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
@@ -449,7 +448,7 @@ WritableMPToken::enforceMPTokenAuthorization(
|
||||
}
|
||||
|
||||
TER
|
||||
MPToken::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
MPTokenIssuance::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
if (!sle_)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
@@ -462,6 +461,28 @@ MPToken::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
MPTokenIssuance::canClawback() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTCanClawback);
|
||||
}
|
||||
|
||||
bool
|
||||
MPTokenIssuance::requiresAuth() const
|
||||
{
|
||||
if (!sle_)
|
||||
return false;
|
||||
return sle_->isFlag(lsfMPTRequireAuth);
|
||||
}
|
||||
|
||||
TER
|
||||
rippleLockEscrowMPT(
|
||||
ApplyView& view,
|
||||
@@ -470,7 +491,7 @@ rippleLockEscrowMPT(
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
auto mptIssuance = WritableMPToken(view, mptIssue);
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance.exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
|
||||
@@ -576,7 +597,7 @@ rippleUnlockEscrowMPT(
|
||||
|
||||
auto const& issuer = netAmount.getIssuer();
|
||||
auto const& mptIssue = netAmount.get<MPTIssue>();
|
||||
auto mptIssuance = WritableMPToken(view, mptIssue);
|
||||
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
|
||||
if (!mptIssuance)
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
|
||||
@@ -722,7 +743,7 @@ rippleUnlockEscrowMPT(
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPToken::accountHolds(
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
beast::Journal j,
|
||||
@@ -732,7 +753,7 @@ MPToken::accountHolds(
|
||||
}
|
||||
|
||||
STAmount
|
||||
MPToken::accountHolds(
|
||||
MPTokenIssuance::accountHolds(
|
||||
AccountID const& account,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
|
||||
@@ -98,7 +98,7 @@ IOUToken::isIndividualFrozen(AccountID const& account) const
|
||||
bool
|
||||
IOUToken::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
XRPL_ASSERT(depth == 0, "IOUToken::isFrozen : depth is 0");
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
if (issuerAccount_.exists() && issuerAccount_->isFlag(lsfGlobalFreeze))
|
||||
@@ -116,7 +116,7 @@ IOUToken::isFrozen(AccountID const& account, int depth) const
|
||||
bool
|
||||
IOUToken::isDeepFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
XRPL_ASSERT(depth == 0, "IOUToken::isDeepFrozen : depth is 0");
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
return false;
|
||||
@@ -151,7 +151,7 @@ IOUToken::checkDeepFrozen(AccountID const& account) const
|
||||
bool
|
||||
IOUToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
|
||||
{
|
||||
XRPL_ASSERT(depth == 0, "IOUToken::isAnyFrozen : depth is 0");
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
@@ -164,6 +164,19 @@ IOUToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int dept
|
||||
return false;
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountFunds(
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j) const
|
||||
{
|
||||
if (!saDefault.native() && saDefault.getIssuer() == id)
|
||||
return saDefault;
|
||||
|
||||
return accountHolds(id, freezeHandling, j);
|
||||
}
|
||||
|
||||
STAmount
|
||||
IOUToken::accountHolds(
|
||||
AccountID const& account,
|
||||
@@ -184,7 +197,8 @@ IOUToken::accountHolds(
|
||||
{
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
return {issuerAccount_.xrpLiquid(0, j)};
|
||||
AccountRoot accountRoot(account, readView_);
|
||||
return {accountRoot.xrpLiquid(0, j)};
|
||||
}
|
||||
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
@@ -194,35 +208,24 @@ IOUToken::accountHolds(
|
||||
return STAmount{issue_, STAmount::cMaxValue, STAmount::cMaxOffset};
|
||||
|
||||
// IOU: Return balance on trust line modulo freeze
|
||||
// Check if line exists and is usable
|
||||
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
if (!sle)
|
||||
{
|
||||
STAmount result;
|
||||
result.clear(Issue{currency_, issuer_});
|
||||
return result;
|
||||
}
|
||||
// Check if line exists and is usable (mirrors old getLineIfUsable)
|
||||
SLE::const_pointer sle = readView_.read(keylet::line(account, issuer_, currency_));
|
||||
|
||||
// Check freeze status
|
||||
if (zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
if (sle && zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
{
|
||||
if (isFrozen(account) || isDeepFrozen(account))
|
||||
{
|
||||
STAmount result;
|
||||
result.clear(Issue{currency_, issuer_});
|
||||
return result;
|
||||
sle = nullptr;
|
||||
}
|
||||
|
||||
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
|
||||
// we need to check if the associated assets have been frozen
|
||||
if (readView_.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
if (sle && readView_.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
auto const sleIssuer = readView_.read(keylet::account(issuer_));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
STAmount result;
|
||||
result.clear(Issue{currency_, issuer_});
|
||||
return result;
|
||||
sle = nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
else if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
{
|
||||
@@ -235,39 +238,54 @@ IOUToken::accountHolds(
|
||||
(*sleAmm)[sfAsset].get<Issue>(),
|
||||
(*sleAmm)[sfAsset2].get<Issue>()))
|
||||
{
|
||||
STAmount result;
|
||||
result.clear(Issue{currency_, issuer_});
|
||||
return result;
|
||||
sle = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract balance from SLE
|
||||
STAmount amount = sle->getFieldAmount(sfBalance);
|
||||
bool const accountHigh = account > issuer_;
|
||||
auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
|
||||
if (accountHigh)
|
||||
// Extract balance (mirrors old getTrustLineBalance)
|
||||
STAmount amount;
|
||||
if (sle)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
bool const accountHigh = account > issuer_;
|
||||
auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
|
||||
if (accountHigh)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
if (returnSpendable)
|
||||
{
|
||||
amount += sle->getFieldAmount(oppositeField);
|
||||
}
|
||||
amount.setIssuer(issuer_);
|
||||
}
|
||||
if (returnSpendable)
|
||||
else
|
||||
{
|
||||
amount += sle->getFieldAmount(oppositeField);
|
||||
amount.clear(Issue{currency_, issuer_});
|
||||
}
|
||||
amount.setIssuer(issuer_);
|
||||
|
||||
JLOG(j.trace()) << "IOUToken::accountHolds:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
return amount;
|
||||
return readView_.balanceHook(account, issuer_, amount);
|
||||
}
|
||||
|
||||
TER
|
||||
IOUToken::canAddHolding() const
|
||||
{
|
||||
return tesSUCCESS; // IOUs don't have restrictions on adding holdings
|
||||
if (isXRP(issue_))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!issuerAccount_.exists())
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
if (!issuerAccount_->isFlag(lsfDefaultRipple))
|
||||
return terNO_RIPPLE;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
Rate
|
||||
@@ -398,7 +416,7 @@ trustCreate(
|
||||
|
||||
TER
|
||||
trustDelete(
|
||||
ApplyView& readView_,
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleRippleState,
|
||||
AccountID const& uLowAccountID,
|
||||
AccountID const& uHighAccountID,
|
||||
@@ -410,22 +428,20 @@ trustDelete(
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
|
||||
|
||||
if (!readView_.dirRemove(
|
||||
keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
|
||||
if (!view.dirRemove(keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
|
||||
|
||||
if (!readView_.dirRemove(
|
||||
keylet::ownerDir(uHighAccountID), uHighNode, sleRippleState->key(), false))
|
||||
if (!view.dirRemove(keylet::ownerDir(uHighAccountID), uHighNode, sleRippleState->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
|
||||
readView_.erase(sleRippleState);
|
||||
view.erase(sleRippleState);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -660,7 +676,7 @@ redeemIOU(
|
||||
TER
|
||||
IOUToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
XRPL_ASSERT(depth == 0, "IOUToken::requireAuth : depth is 0");
|
||||
// NOTE: depth is ignored here because it's only relevant for MPTs
|
||||
if (isXRP(issue_) || issuer_ == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -715,6 +731,29 @@ IOUToken::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Token capability checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
IOUToken::canClawback() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfAllowTrustLineClawback) &&
|
||||
!issuerAccount_->isFlag(lsfNoFreeze);
|
||||
}
|
||||
|
||||
bool
|
||||
IOUToken::requiresAuth() const
|
||||
{
|
||||
if (!issuerAccount_.exists())
|
||||
return false;
|
||||
return issuerAccount_->isFlag(lsfRequireAuth);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
|
||||
Reference in New Issue
Block a user