diff --git a/src/libxrpl/ledger/entries/MPTokenHelpers.cpp b/src/libxrpl/ledger/entries/MPTokenHelpers.cpp deleted file mode 100644 index b8aea3154e..0000000000 --- a/src/libxrpl/ledger/entries/MPTokenHelpers.cpp +++ /dev/null @@ -1,811 +0,0 @@ -#include -// -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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& 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 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 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(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(); - 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(); - 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 diff --git a/src/libxrpl/ledger/entries/RippleStateHelpers.cpp b/src/libxrpl/ledger/entries/RippleStateHelpers.cpp deleted file mode 100644 index 705f41680a..0000000000 --- a/src/libxrpl/ledger/entries/RippleStateHelpers.cpp +++ /dev/null @@ -1,925 +0,0 @@ -#include -// -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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(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 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(), - (*sleAmm)[sfAsset2].get())) - { - 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(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 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(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) != - static_cast(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 sleState, - std::optional 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 diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index cfb7dd4d2d..b8aea3154e 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -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 const& accounts, int depth) const +MPTokenIssuance::isAnyFrozen(std::initializer_list const& accounts, int depth) const { if (isGlobalFrozen()) return true; @@ -72,26 +72,25 @@ MPToken::isAnyFrozen(std::initializer_list 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(); - 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(); - 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, diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index a5ca4151e9..705f41680a 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -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 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 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(), (*sleAmm)[sfAsset2].get())) { - 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 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)