implement most of the token stuff

This commit is contained in:
Mayukha Vadari
2026-03-17 09:37:29 -04:00
parent 8a1adc4ec9
commit 89e9caed17
43 changed files with 2927 additions and 1107 deletions

View File

@@ -14,126 +14,196 @@
namespace xrpl {
//------------------------------------------------------------------------------
//
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
class MPToken : public virtual TokenBase
{
public:
MPToken(ReadView const& view, MPTIssue const& mptIssue)
: ReadOnlySLE(view.read(keylet::mptIssuance(mptIssue.getMptID())), view)
, TokenBase(view, view.read(keylet::mptIssuance(mptIssue.getMptID())))
, mptID_(mptIssue.getMptID())
, mptIssue_(mptIssue)
{
}
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
MPToken(ReadView const& view, MPTID const& mptID)
: ReadOnlySLE(view.read(keylet::mptIssuance(mptID)), view)
, TokenBase(view, view.read(keylet::mptIssuance(mptID)))
, mptID_(mptID)
, mptIssue_(MPTIssue(mptID_))
{
}
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
MPTID const&
getMptID() const
{
return mptID_;
}
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
MPTIssue const&
getMptIssue() const
{
return mptIssue_;
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth = 0);
AccountID const&
getIssuer() const
{
return mptIssue_.getIssuer();
}
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, MPTID const& issuanceID);
[[nodiscard]] bool
isGlobalFrozen() const override;
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
checkFrozen(AccountID const& account) const override;
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
[[nodiscard]] bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType = AuthType::Legacy,
int depth = 0);
[[nodiscard]] bool
isDeepFrozen(AccountID const& account, int depth = 0) const override;
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
[[nodiscard]] TER
checkDeepFrozen(AccountID const& account) const override;
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to);
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate() const override;
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] TER
canAddHolding() const override;
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
[[nodiscard]] STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
protected:
MPTID const mptID_;
MPTIssue const mptIssue_;
};
class WritableMPToken : public virtual WritableTokenBase, public virtual MPToken
{
public:
WritableMPToken(ApplyView& view, MPTIssue const& mptIssue)
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
, TokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
, WritableSLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
, MPToken(view, mptIssue)
{
}
WritableMPToken(ApplyView& view, MPTID const& mptID)
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptID)), view)
, TokenBase(view, view.peek(keylet::mptIssuance(mptID)))
, WritableSLE(view.peek(keylet::mptIssuance(mptID)), view)
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptID)))
, MPToken(view, mptID)
{
}
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
using WritableSLE::operator->;
using MPToken::operator->;
using WritableSLE::operator*;
using MPToken::operator*;
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
authorizeMPToken(
XRPAmount const& priorBalance,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
override;
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
};
//------------------------------------------------------------------------------
//

View File

@@ -19,6 +19,126 @@
namespace xrpl {
class IOUToken : public virtual TokenBase
{
public:
IOUToken(ReadView const& view, Issue const& issue)
: ReadOnlySLE(view.read(keylet::account(issue.getIssuer())), view)
, TokenBase(view, view.read(keylet::account(issue.getIssuer())))
, issue_(issue)
, issuer_(issue.getIssuer())
, issuerAccount_(issuer_, view)
, currency_(issue.currency)
{
}
IOUToken(ReadView const& view, AccountID const& issuer, Currency const& currency)
: IOUToken(view, Issue{currency, issuer})
{
}
[[nodiscard]] bool
isGlobalFrozen() const override
{
return issuerAccount_.isGlobalFrozen();
}
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
[[nodiscard]] TER
checkFrozen(AccountID const& account) const override;
[[nodiscard]] bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
[[nodiscard]] bool
isDeepFrozen(AccountID const& account, int depth = 0) const override;
[[nodiscard]] TER
checkDeepFrozen(AccountID const& account) const override;
[[nodiscard]] Rate
transferRate() const override;
STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
[[nodiscard]] STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
[[nodiscard]] TER
canAddHolding() const override;
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
protected:
Issue const issue_;
AccountID const issuer_;
AccountRoot const issuerAccount_;
Currency const currency_;
};
class WritableIOUToken : public virtual WritableTokenBase, public virtual IOUToken
{
public:
WritableIOUToken(ApplyView& view, Issue const& issue)
: ReadOnlySLE(view.peek(keylet::account(issue.getIssuer())), view)
, TokenBase(view, view.peek(keylet::account(issue.getIssuer())))
, WritableSLE(view.peek(keylet::account(issue.getIssuer())), view)
, WritableTokenBase(view, view.peek(keylet::account(issue.getIssuer())))
, IOUToken(view, issue)
{
}
WritableIOUToken(ApplyView& view, AccountID const& issuer, Currency const& currency)
: WritableIOUToken(view, Issue{currency, issuer})
{
}
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
using WritableSLE::operator->;
using IOUToken::operator->;
using WritableSLE::operator*;
using IOUToken::operator*;
//------------------------------------------------------------------------------
//
// Holding management (WritableTokenBase interface)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
override;
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
};
//------------------------------------------------------------------------------
//
// Credit functions (from Credit.h)
@@ -59,69 +179,6 @@ creditBalance(
Currency const& currency);
/** @} */
//------------------------------------------------------------------------------
//
// Freeze checking (IOU-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isIndividualFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue.currency, issue.account);
}
// Overload with depth parameter for uniformity with MPTIssue version.
// The depth parameter is ignored for IOUs since they don't have vault recursion.
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
{
return isFrozen(view, account, issue);
}
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Issue const& issue,
int = 0 /*ignored*/)
{
return isDeepFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Trust line operations
@@ -182,43 +239,6 @@ redeemIOU(
Issue const& issue,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (IOU-specific)
//
//------------------------------------------------------------------------------
/** Check if the account lacks required authorization.
*
* Return tecNO_AUTH or tecNO_LINE if it does
* and tesSUCCESS otherwise.
*
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
* RippleState does exist, and the RippleState is not authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
* RippleState exists, and is not authorized. Return tecNO_LINE if
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
* WeakAuth and lsfRequireAuth is *not* set, this function will return
* tesSUCCESS even if RippleState does *not* exist.
*
* The default "Legacy" auth type is equivalent to WeakAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Issue const& issue,
AccountID const& account,
AuthType authType = AuthType::Legacy);
/** Check if the destination account is allowed
* to receive IOU. Return terNO_RIPPLE if rippling is
* disabled on both sides and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpersWrappedSLEBase.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
@@ -10,6 +11,7 @@
#include <xrpl/protocol/TER.h>
#include <initializer_list>
#include <memory>
#include <vector>
namespace xrpl {
@@ -48,82 +50,136 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
class TokenBase : public virtual ReadOnlySLE
{
public:
[[nodiscard]] virtual bool
isGlobalFrozen() const = 0;
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] virtual bool
isIndividualFrozen(AccountID const& account) const = 0;
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] virtual bool
isFrozen(AccountID const& account, int depth = 0) const;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
[[nodiscard]] virtual TER
checkFrozen(AccountID const& account) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] virtual bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] virtual bool
isDeepFrozen(AccountID const& account, int depth = 0) const = 0;
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue);
[[nodiscard]] virtual TER
checkDeepFrozen(AccountID const& account) const = 0;
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth = 0);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] virtual Rate
transferRate() const = 0;
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
int depth = 0);
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
virtual STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
[[nodiscard]] virtual STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] virtual TER
canAddHolding() const = 0;
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] virtual TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const = 0;
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] virtual TER
canTransfer(AccountID const& from, AccountID const& to) const = 0;
protected:
TokenBase(ReadView const& view, std::shared_ptr<SLE const> sle) : ReadOnlySLE(sle, view)
{
}
};
class WritableTokenBase : public virtual TokenBase, public virtual WritableSLE
{
public:
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] virtual TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal) = 0;
[[nodiscard]] virtual TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) = 0;
protected:
WritableTokenBase(ApplyView& view, std::shared_ptr<SLE> sle)
: TokenBase(view, sle), WritableSLE(sle, view)
{
}
};
std::unique_ptr<TokenBase>
makeTokenBase(ReadView const& view, Asset const& asset);
std::unique_ptr<WritableTokenBase>
makeWritableTokenBase(ApplyView& view, Asset const& asset);
// Helper function to get transfer rate from an STAmount
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
// Returns the amount the specified account can spend.
// Supports both IOU and MPT via Currency/AccountID parameters.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
@@ -134,25 +190,8 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount the specified account can spend for a given Asset.
// Dispatches to appropriate token wrapper based on Asset type.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
@@ -163,66 +202,6 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount an account can spend of the currency type saDefault, or
// returns saDefault if this account is the issuer of the currency in
// question. Should be used in favor of accountHolds when questioning how much
// an account can spend while also allowing currency issuers to spend
// unlimited amounts of their own currency (since they can always issue more).
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Asset const& asset,
AccountID const& account,
AuthType authType = AuthType::Legacy);
[[nodiscard]] TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Money Transfers (Asset-based dispatchers)

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
@@ -44,12 +45,10 @@ AcceptedLedgerTx::AcceptedLedgerTx(
// If the offer create is not self funded then add the owner balance
if (account != amount.issue().account)
{
auto const ownerFunds = accountFunds(
*ledger,
account,
amount,
fhIGNORE_FREEZE,
beast::Journal{beast::Journal::getNullSink()});
auto const ownerFunds =
makeTokenBase(*ledger, amount.asset())
->accountHolds(
account, fhIGNORE_FREEZE, beast::Journal{beast::Journal::getNullSink()});
mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText();
}
}

View File

@@ -49,8 +49,8 @@ isVaultPseudoAccountFrozen(
if (depth >= maxAssetCheckDepth)
return true; // LCOV_EXCL_LINE
auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID()));
if (mptIssuance == nullptr)
auto mptIssuance = MPToken(view, mptShare);
if (!mptIssuance.exists())
return false; // zero MPToken won't block deletion of MPTokenIssuance
auto const issuer = mptIssuance->getAccountID(sfIssuer);
@@ -74,7 +74,7 @@ isVaultPseudoAccountFrozen(
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
return mptIssuance.isAnyFrozen({issuer, account}, depth + 1);
}
bool
@@ -84,8 +84,9 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2)
{
return isFrozen(view, account, asset.currency, asset.account) ||
isFrozen(view, account, asset2.currency, asset2.account);
auto assetToken = IOUToken(view, asset);
auto asset2Token = IOUToken(view, asset2);
return assetToken.isFrozen(account) || asset2Token.isFrozen(account);
}
bool
@@ -406,10 +407,11 @@ doWithdraw(
STAmount const& amount,
beast::Journal j)
{
auto const asset = makeWritableTokenBase(view, amount.asset());
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = asset->addEmptyHolding(senderAcct, priorBalance, j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
@@ -421,13 +423,8 @@ doWithdraw(
}
// Sanity check
if (accountHolds(
view,
sourceAcct,
amount.asset(),
FreezeHandling::fhIGNORE_FREEZE,
AuthHandling::ahIGNORE_AUTH,
j) < amount)
if (asset->accountHolds(
sourceAcct, FreezeHandling::fhIGNORE_FREEZE, AuthHandling::ahIGNORE_AUTH, j) < amount)
{
// LCOV_EXCL_START
JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "

View File

@@ -0,0 +1,811 @@
#include <xrpl/ledger/helpersMPTokenHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/helpersAccountRootHelpers.h>
#include <xrpl/ledger/helpersCredentialHelpers.h>
#include <xrpl/ledger/helpersDirectoryHelpers.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

View File

@@ -0,0 +1,925 @@
#include <xrpl/ledger/helpersRippleStateHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpersAccountRootHelpers.h>
#include <xrpl/ledger/helpersDirectoryHelpers.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

View File

@@ -28,76 +28,88 @@ dirLink(
SF_UINT64 const& node = sfOwnerNode);
bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
MPToken::isGlobalFrozen() const
{
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
if (sle_)
return sle_->isFlag(lsfMPTLocked);
return false;
}
bool
MPToken::isIndividualFrozen(AccountID const& account) const
{
if (auto const sle = readView_.read(keylet::mptoken(mptID_, account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
MPToken::isFrozen(AccountID const& account, int depth) const
{
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
return isGlobalFrozen() || isIndividualFrozen(account) ||
isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth);
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth)
MPToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
{
if (isGlobalFrozen(view, mptIssue))
if (isGlobalFrozen())
return true;
for (auto const& account : accounts)
{
if (isIndividualFrozen(view, account, mptIssue))
if (isIndividualFrozen(account))
return true;
}
for (auto const& account : accounts)
{
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
if (isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth))
return true;
}
return false;
}
TER
MPToken::checkFrozen(AccountID const& account) const
{
return isFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
}
bool
MPToken::isDeepFrozen(AccountID const& account, int depth) const
{
// MPTs don't have deep freeze, so this always returns false
return false;
}
TER
MPToken::checkDeepFrozen(AccountID const& account) const
{
return isDeepFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
}
Rate
transferRate(ReadView const& view, MPTID const& issuanceID)
MPToken::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 (auto const sle = view.read(keylet::mptIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
if (sle_ && sle_->isFieldPresent(sfTransferFee))
return Rate{1'000'000'000u + 10'000 * sle_->getFieldU16(sfTransferFee)};
return parityRate;
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
MPToken::canAddHolding() const
{
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptIssuance(mptID));
if (!issuance)
if (!sle_)
{
return tecOBJECT_NOT_FOUND;
}
if (!issuance->isFlag(lsfMPTCanTransfer))
if (!sle_->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
@@ -106,38 +118,32 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
}
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
WritableMPToken::addEmptyHolding(
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = view.peek(keylet::mptIssuance(mptID));
if (!mpt)
if (!mutableSle_)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mpt->isFlag(lsfMPTLocked))
if (mutableSle_->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (view.peek(keylet::mptoken(mptID, accountID)))
if (applyView_.peek(keylet::mptoken(mptID_, accountID)))
return tecDUPLICATE;
if (accountID == mptIssue.getIssuer())
if (accountID == mptIssue_.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
return authorizeMPToken(priorBalance, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
WritableMPToken::authorizeMPToken(
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags,
std::optional<AccountID> holderID)
{
WritableAccountRoot wrappedAcct(account, view);
WritableAccountRoot wrappedAcct(account, applyView_);
if (!wrappedAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -151,18 +157,18 @@ authorizeMPToken(
// - delete the MPToken
if (flags & tfMPTUnauthorize)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto const sleMpt = applyView_.peek(mptokenKey);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(
if (!applyView_.dirRemove(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
wrappedAcct.adjustOwnerCount(-1, journal);
view.erase(sleMpt);
applyView_.erase(sleMpt);
return tesSUCCESS;
}
@@ -178,31 +184,30 @@ authorizeMPToken(
std::uint32_t const uOwnerCount = wrappedAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::zero)
: view.fees().accountReserve(uOwnerCount + 1));
: applyView_.fees().accountReserve(uOwnerCount + 1));
if (priorBalance < reserveCreate)
return tecINSUFFICIENT_RESERVE;
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
if (!mpt || mpt->getAccountID(sfIssuer) == account)
if (!mutableSle_ || mutableSle_->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
if (view.rules().enabled(featureLendingProtocol))
if (applyView_.rules().enabled(featureLendingProtocol))
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(view, account, mptoken))
if (auto ter = dirLink(applyView_, account, mptoken))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
(*mptoken)[sfFlags] = 0;
view.insert(mptoken);
applyView_.insert(mptoken);
// Update owner count.
wrappedAcct.adjustOwnerCount(1, journal);
@@ -210,17 +215,16 @@ authorizeMPToken(
return tesSUCCESS;
}
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleMptIssuance)
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 != (*sleMptIssuance)[sfIssuer])
if (account != (*mutableSle_)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
auto const sleMpt = applyView_.peek(keylet::mptoken(mptID_, *holderID));
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -243,23 +247,18 @@ authorizeMPToken(
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
view.update(sleMpt);
applyView_.update(sleMpt);
return tesSUCCESS;
}
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
WritableMPToken::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& mptID = mptIssue.getMptID();
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
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
@@ -270,9 +269,7 @@ removeEmptyHolding(
return tecHAS_OBLIGATIONS;
return authorizeMPToken(
view,
{}, // priorBalance
mptID,
accountID,
journal,
tfMPTUnauthorize // flags
@@ -280,25 +277,18 @@ removeEmptyHolding(
}
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType,
int depth)
MPToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
if (!sle_)
return tecOBJECT_NOT_FOUND;
auto const mptIssuer = AccountRoot(sleIssuance->getAccountID(sfIssuer), view);
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 = view.rules().enabled(featureSingleAssetVault);
bool const featureSAVEnabled = readView_.rules().enabled(featureSingleAssetVault);
if (featureSAVEnabled)
{
@@ -311,30 +301,19 @@ requireAuth(
if (mptIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault = view.read(keylet::vault(mptIssuer->getFieldH256(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 = std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return requireAuth(view, issue, account, authType);
}
else
{
return requireAuth(view, issue, account, authType, depth + 1);
}
},
asset.value());
if (auto const err =
makeTokenBase(readView_, asset)->requireAuth(account, authType, depth + 1);
!isTesSuccess(err))
return err;
}
}
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
auto const sleToken = readView_.read(keylet::mptoken(mptID_, account));
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
@@ -342,14 +321,14 @@ requireAuth(
// 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 = sleIssuance->at(~sfDomainID);
auto const maybeDomainID = sle_->at(~sfDomainID);
if (maybeDomainID)
{
XRPL_ASSERT(
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
sle_->getFieldU32(sfFlags) & lsfMPTRequireAuth,
"xrpl::requireAuth : issuance requires authorization");
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
auto const ter = credentials::validDomain(readView_, *maybeDomainID, account);
if (isTesSuccess(ter))
{
return ter; // Note: sleToken might be null
@@ -365,47 +344,43 @@ requireAuth(
if (featureSAVEnabled)
{
// Implicitly authorize Vault and LoanBroker pseudo-accounts
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
if (isPseudoAccount(readView_, account, {&sfVaultID, &sfLoanBrokerID}))
return tesSUCCESS;
}
// mptoken must be authorized if issuance enabled requireAuth
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
if (sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
return tecNO_AUTH;
return tesSUCCESS; // Note: sleToken might be null
}
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
WritableMPToken::enforceMPTokenAuthorization(
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
if (!mutableSle_)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
sleIssuance->isFlag(lsfMPTRequireAuth),
mutableSle_->isFlag(lsfMPTRequireAuth),
"xrpl::enforceMPTokenAuthorization : authorization required");
if (account == sleIssuance->at(sfIssuer))
if (account == mutableSle_->at(sfIssuer))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = view.read(keylet); // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
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.has_value())
if (!maybeDomainID)
return false; // LCOV_EXCL_LINE
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
auto const ter = verifyValidDomain(applyView(), account, *maybeDomainID, j);
if (isTesSuccess(ter))
return true;
if (ter == tecEXPIRED)
@@ -424,7 +399,7 @@ enforceMPTokenAuthorization(
// Either way, return tecNO_AUTH and there is nothing else to do
return expired ? tecEXPIRED : tecNO_AUTH;
}
if (!authorizedByDomain && maybeDomainID.has_value())
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
@@ -436,7 +411,7 @@ enforceMPTokenAuthorization(
// 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.has_value(),
sleToken != nullptr && !maybeDomainID,
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
@@ -447,9 +422,7 @@ enforceMPTokenAuthorization(
{
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
XRPL_ASSERT(maybeDomainID, "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
return tesSUCCESS;
}
if (authorizedByDomain)
@@ -457,13 +430,11 @@ enforceMPTokenAuthorization(
// 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.has_value() && sleToken == nullptr,
maybeDomainID && sleToken == nullptr,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
priorBalance, // priorBalance
account, // account
j);
!isTesSuccess(err))
return err;
@@ -478,20 +449,14 @@ enforceMPTokenAuthorization(
}
TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to)
MPToken::canTransfer(AccountID const& from, AccountID const& to) const
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
if (!sle_)
return tecOBJECT_NOT_FOUND;
if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
if (!(sle_->getFieldU32(sfFlags) & lsfMPTCanTransfer))
{
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
if (from != (*sle_)[sfIssuer] && to != (*sle_)[sfIssuer])
return TER{tecNO_AUTH};
}
return tesSUCCESS;
@@ -505,16 +470,15 @@ rippleLockEscrowMPT(
beast::Journal j)
{
auto const mptIssue = amount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
auto mptIssuance = WritableMPToken(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 (amount.getIssuer() == sender)
if (mptIssuance.getIssuer() == sender)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
return tecINTERNAL;
@@ -523,7 +487,7 @@ rippleLockEscrowMPT(
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -569,7 +533,7 @@ rippleLockEscrowMPT(
// 1. Increase the Issuance EscrowedAmount
// 2. DO NOT change the Issuance OutstandingAmount
{
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
uint64_t const issuanceEscrowed = (*mptIssuance)[~sfLockedAmount].value_or(0);
auto const pay = amount.mpt().value();
// Overflow check for addition
@@ -581,16 +545,16 @@ rippleLockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sleIssuance->isFieldPresent(sfLockedAmount))
if (mptIssuance->isFieldPresent(sfLockedAmount))
{
(*sleIssuance)[sfLockedAmount] += pay;
(*mptIssuance)[sfLockedAmount] += pay;
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, pay);
mptIssuance->setFieldU64(sfLockedAmount, pay);
}
view.update(sleIssuance);
mptIssuance.update();
}
return tesSUCCESS;
}
@@ -612,9 +576,8 @@ rippleUnlockEscrowMPT(
auto const& issuer = netAmount.getIssuer();
auto const& mptIssue = netAmount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
auto mptIssuance = WritableMPToken(view, mptIssue);
if (!mptIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
@@ -623,14 +586,14 @@ rippleUnlockEscrowMPT(
// Decrease the Issuance EscrowedAmount
{
if (!sleIssuance->isFieldPresent(sfLockedAmount))
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 = sleIssuance->getFieldU64(sfLockedAmount);
auto const locked = mptIssuance->getFieldU64(sfLockedAmount);
auto const redeem = grossAmount.mpt().value();
// Underflow check for subtraction
@@ -644,19 +607,19 @@ rippleUnlockEscrowMPT(
auto const newLocked = locked - redeem;
if (newLocked == 0)
{
sleIssuance->makeFieldAbsent(sfLockedAmount);
mptIssuance->makeFieldAbsent(sfLockedAmount);
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
mptIssuance->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sleIssuance);
mptIssuance.update();
}
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), receiver);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -681,7 +644,7 @@ rippleUnlockEscrowMPT(
else
{
// Decrease the Issuance OutstandingAmount
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = netAmount.mpt().value();
// Underflow check for subtraction
@@ -692,8 +655,8 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
mptIssuance.update();
}
if (issuer == sender)
@@ -703,7 +666,7 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -731,13 +694,9 @@ rippleUnlockEscrowMPT(
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
@@ -747,7 +706,7 @@ rippleUnlockEscrowMPT(
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
if (diff != 0)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
{ // LCOV_EXCL_START
@@ -756,10 +715,76 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
mptIssuance.update();
}
return tesSUCCESS;
}
STAmount
MPToken::accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance) const
{
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
}
STAmount
MPToken::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

View File

@@ -3,6 +3,7 @@
#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>
@@ -21,14 +22,14 @@ namespace xrpl {
STAmount
creditLimit(
ReadView const& view,
ReadView const& readView_,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -49,14 +50,14 @@ creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Curr
STAmount
creditBalance(
ReadView const& view,
ReadView const& readView_,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -78,19 +79,15 @@ creditBalance(
//------------------------------------------------------------------------------
bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isIndividualFrozen(AccountID const& account) const
{
if (isXRP(currency))
if (isXRP(currency_))
return false;
if (issuer != account)
if (issuer_ != account)
{
// Check if the issuer froze the line
auto const sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
if (sle && sle->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
@@ -99,45 +96,38 @@ isIndividualFrozen(
// Can the specified account spend the specified currency issued by
// the specified issuer or does the freeze flag prohibit it?
bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isFrozen(AccountID const& account, int depth) const
{
if (isXRP(currency))
XRPL_ASSERT(depth == 0, "IOUToken::isFrozen : depth is 0");
if (isXRP(currency_))
return false;
auto const issuerRoot = AccountRoot(issuer, view);
if (issuerRoot.exists() && issuerRoot->isFlag(lsfGlobalFreeze))
if (issuerAccount_.exists() && issuerAccount_->isFlag(lsfGlobalFreeze))
return true;
if (issuer != account)
if (issuer_ != account)
{
// Check if the issuer froze the line
auto const sleLine = view.read(keylet::line(account, issuer, currency));
if (sleLine && sleLine->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
auto const sleLine = readView_.read(keylet::line(account, issuer_, currency_));
if (sleLine && sleLine->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
}
bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isDeepFrozen(AccountID const& account, int depth) const
{
if (isXRP(currency))
XRPL_ASSERT(depth == 0, "IOUToken::isDeepFrozen : depth is 0");
if (isXRP(currency_))
{
return false;
}
if (issuer == account)
if (issuer_ == account)
{
return false;
}
auto const sle = view.read(keylet::line(account, issuer, currency));
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
if (!sle)
{
return false;
@@ -146,6 +136,146 @@ isDeepFrozen(
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
{
XRPL_ASSERT(depth == 0, "IOUToken::isAnyFrozen : depth is 0");
if (isGlobalFrozen())
return true;
for (auto const& account : accounts)
{
if (isFrozen(account, depth))
return true;
}
return false;
}
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_))
{
return {issuerAccount_.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
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
if (!sle)
{
STAmount result;
result.clear(Issue{currency_, issuer_});
return result;
}
// Check freeze status
if (zeroIfFrozen == fhZERO_IF_FROZEN)
{
if (isFrozen(account) || isDeepFrozen(account))
{
STAmount result;
result.clear(Issue{currency_, issuer_});
return result;
}
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
// we need to check if the associated assets have been frozen
if (readView_.rules().enabled(fixFrozenLPTokenTransfer))
{
auto const sleIssuer = readView_.read(keylet::account(issuer_));
if (!sleIssuer)
{
STAmount result;
result.clear(Issue{currency_, issuer_});
return result;
}
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>()))
{
STAmount result;
result.clear(Issue{currency_, issuer_});
return result;
}
}
}
}
// Extract balance from SLE
STAmount 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_);
JLOG(j.trace()) << "IOUToken::accountHolds:" << " account=" << to_string(account)
<< " amount=" << amount.getFullText();
return amount;
}
TER
IOUToken::canAddHolding() const
{
return tesSUCCESS; // IOUs don't have restrictions on adding holdings
}
Rate
IOUToken::transferRate() const
{
return issuerAccount_.transferRate();
}
//------------------------------------------------------------------------------
//
// Trust line operations
@@ -268,7 +398,7 @@ trustCreate(
TER
trustDelete(
ApplyView& view,
ApplyView& readView_,
std::shared_ptr<SLE> const& sleRippleState,
AccountID const& uLowAccountID,
AccountID const& uHighAccountID,
@@ -280,20 +410,22 @@ trustDelete(
JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
if (!view.dirRemove(keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
if (!readView_.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))
if (!readView_.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);
readView_.erase(sleRippleState);
return tesSUCCESS;
}
@@ -450,7 +582,7 @@ issueIOU(
TER
redeemIOU(
ApplyView& view,
ApplyView& applyView,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
@@ -470,7 +602,7 @@ redeemIOU(
bool bSenderHigh = account > issue.account;
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
if (auto state = applyView.peek(keylet::line(account, issue.account, issue.currency)))
{
STAmount final_balance = state->getFieldAmount(sfBalance);
@@ -481,10 +613,10 @@ redeemIOU(
final_balance -= amount;
auto const must_delete =
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
auto const must_delete = updateTrustLine(
applyView, state, bSenderHigh, account, start_balance, final_balance, j);
view.creditHook(account, issue.account, amount, start_balance);
applyView.creditHook(account, issue.account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
@@ -497,14 +629,14 @@ redeemIOU(
if (must_delete)
{
return trustDelete(
view,
applyView,
state,
bSenderHigh ? issue.account : account,
bSenderHigh ? account : issue.account,
j);
}
view.update(state);
applyView.update(state);
return tesSUCCESS;
}
@@ -526,24 +658,24 @@ redeemIOU(
//------------------------------------------------------------------------------
TER
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
IOUToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
{
if (isXRP(issue) || issue.account == account)
XRPL_ASSERT(depth == 0, "IOUToken::requireAuth : depth is 0");
if (isXRP(issue_) || issuer_ == account)
return tesSUCCESS;
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
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
auto const issuerAccount = AccountRoot(issue.account, view);
if (issuerAccount.exists() && (*issuerAccount)[sfFlags] & lsfRequireAuth)
if (issuerAccount_.exists() && (*issuerAccount_)[sfFlags] & lsfRequireAuth)
{
if (trustLine)
{
return ((*trustLine)[sfFlags] & ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
return ((*trustLine)[sfFlags] & ((account > issuer_) ? lsfLowAuth : lsfHighAuth))
? tesSUCCESS
: TER{tecNO_AUTH};
}
@@ -554,28 +686,26 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account,
}
TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
IOUToken::canTransfer(AccountID const& from, AccountID const& to) const
{
if (issue.native())
if (issue_.native())
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
if (issuerId == from || issuerId == to)
if (issuer_ == from || issuer_ == to)
return tesSUCCESS;
auto const issuer = AccountRoot(issuerId, view);
if (!issuer.exists())
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 = view.read(keylet::line(account, issue));
auto const line = readView_.read(keylet::line(account, issue_));
if (line)
{
bool const issuerHigh = issuerId > account;
bool const issuerHigh = issuer_ > account;
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
}
return issuer->isFlag(lsfDefaultRipple) == false;
return issuerAccount_->isFlag(lsfDefaultRipple) == false;
};
// Fail if rippling disabled on both trust lines
@@ -592,44 +722,39 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
//------------------------------------------------------------------------------
TER
addEmptyHolding(
ApplyView& view,
WritableIOUToken::addEmptyHolding(
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal)
{
// Every account can hold XRP. An issuer can issue directly.
if (issue.native() || accountID == issue.getIssuer())
if (issue_.native() || accountID == issuer_)
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
WritableAccountRoot wrappedIssuer(issuerId, view);
if (wrappedIssuer.isGlobalFrozen())
if (issuerAccount_.isGlobalFrozen())
return tecFROZEN; // LCOV_EXCL_LINE
auto const& srcId = issuerId;
auto const& srcId = issuer_;
auto const& dstId = accountID;
auto const high = srcId > dstId;
auto const index = keylet::line(srcId, dstId, currency);
WritableAccountRoot wrappedSrc(srcId, view);
WritableAccountRoot wrappedDst(dstId, view);
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 (view.read(index))
if (applyView_.read(index))
return tecDUPLICATE;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
if (priorBalance < readView_.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
view,
applyView_,
high,
srcId,
dstId,
@@ -639,23 +764,19 @@ addEmptyHolding(
/*bNoRipple=*/true,
/*bFreeze=*/false,
/*deepFreeze*/ false,
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
/*saLimit=*/STAmount{Issue{currency_, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
journal);
}
TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal)
WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
{
if (issue.native())
if (issue_.native())
{
auto const account = AccountRoot(accountID, view);
auto const account = AccountRoot(accountID, applyView_);
if (!account.exists())
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -669,8 +790,8 @@ removeEmptyHolding(
// `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 = view.peek(keylet::line(accountID, issue));
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)
@@ -680,7 +801,7 @@ removeEmptyHolding(
if (line->isFlag(lsfLowReserve))
{
// Clear reserve for low account.
WritableAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), view);
WritableAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), applyView_);
if (!wrappedLow)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -694,7 +815,7 @@ removeEmptyHolding(
if (line->isFlag(lsfHighReserve))
{
// Clear reserve for high account.
WritableAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), view);
WritableAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), applyView_);
if (!wrappedHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -706,7 +827,11 @@ removeEmptyHolding(
}
return trustDelete(
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
applyView_,
line,
line->at(sfLowLimit)->getIssuer(),
line->at(sfHighLimit)->getIssuer(),
journal);
}
TER

View File

@@ -26,125 +26,62 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2);
//------------------------------------------------------------------------------
//
// Freeze checking (Asset-based)
//
//------------------------------------------------------------------------------
class IOUToken;
class MPToken;
class WritableIOUToken;
class WritableMPToken;
bool
isGlobalFrozen(ReadView const& view, Asset const& asset)
std::unique_ptr<TokenBase>
makeTokenBase(ReadView const& view, Asset const& asset)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
[&]<typename T>(T const& issue) -> std::unique_ptr<TokenBase> {
if constexpr (std::is_same_v<T, Issue>)
{
AccountRoot issuer(issue.getIssuer(), view);
return issuer.isGlobalFrozen();
return std::make_unique<IOUToken>(view, issue);
}
else
{
return isGlobalFrozen(view, issue);
return std::make_unique<MPToken>(view, issue);
}
},
asset.value());
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
std::unique_ptr<WritableTokenBase>
makeWritableTokenBase(ApplyView& view, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
}
bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
{
return std::visit(
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
}
TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
}
bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue)
{
for (auto const& account : accounts)
{
if (isFrozen(view, account, issue.currency, issue.account))
return true;
}
return false;
}
bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
[&]<typename T>(T const& issue) -> std::unique_ptr<WritableTokenBase> {
if constexpr (std::is_same_v<T, Issue>)
{
return isAnyFrozen(view, accounts, issue);
return std::make_unique<WritableIOUToken>(view, issue);
}
else
{
return isAnyFrozen(view, accounts, issue, depth);
return std::make_unique<WritableMPToken>(view, issue);
}
},
asset.value());
}
bool
isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
// funds, so checking "deep frozen" is the same as checking "frozen".
return isFrozen(view, account, mptIssue, depth);
}
//------------------------------------------------------------------------------
//
// TokenBase implementation
//
//------------------------------------------------------------------------------
bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
TokenBase::isFrozen(AccountID const& account, int depth) const
{
return std::visit(
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
asset.value());
// Default implementation - subclasses should override
return isGlobalFrozen() || isIndividualFrozen(account);
}
TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
return makeTokenBase(view, amount.asset())->transferRate();
}
//------------------------------------------------------------------------------
@@ -162,6 +99,7 @@ getLineIfUsable(
FreezeHandling zeroIfFrozen,
beast::Journal j)
{
IOUToken token(view, issuer, currency);
auto const sle = view.read(keylet::line(account, issuer, currency));
if (!sle)
@@ -171,8 +109,7 @@ getLineIfUsable(
if (zeroIfFrozen == fhZERO_IF_FROZEN)
{
if (isFrozen(view, account, currency, issuer) ||
isDeepFrozen(view, account, currency, issuer))
if (token.isFrozen(account) || token.isDeepFrozen(account))
{
return nullptr;
}
@@ -277,87 +214,6 @@ accountHolds(
}
STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance)
{
return accountHolds(
view, account, issue.currency, issue.account, zeroIfFrozen, j, includeFullBalance);
}
STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance)
{
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
auto const issuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!issuance)
{
return STAmount{mptIssue};
}
return STAmount{
mptIssue,
issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
issuance->at(sfOutstandingAmount)};
}
STAmount amount;
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
if (!sleMpt)
{
amount.clear(mptIssue);
}
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
{
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 &&
view.rules().enabled(featureSingleAssetVault))
{
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
!isTesSuccess(err))
amount.clear(mptIssue);
}
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
{
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
// if auth is enabled on the issuance and mpt is not authorized,
// clear amount
if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
!sleMpt->isFlag(lsfMPTAuthorized))
amount.clear(mptIssue);
}
}
return amount;
}
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
@@ -367,52 +223,8 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& value) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance);
}
else if constexpr (std::is_same_v<TIss, MPTIssue>)
{
return accountHolds(
view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
}
},
asset.value());
}
STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j)
{
if (!saDefault.native() && saDefault.getIssuer() == id)
return saDefault;
return accountHolds(
view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j);
}
Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
AccountRoot issuer(issue.getIssuer(), view);
return issuer.transferRate();
}
else
{
return transferRate(view, issue.getMptID());
}
},
amount.asset().value());
auto token = makeTokenBase(view, asset);
return token->accountHolds(account, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
}
//------------------------------------------------------------------------------
@@ -446,9 +258,7 @@ canAddHolding(ReadView const& view, Issue const& issue)
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
asset.value());
return makeTokenBase(view, asset)->canAddHolding();
}
TER
@@ -459,11 +269,7 @@ addEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
},
asset.value());
return makeWritableTokenBase(view, asset)->addEmptyHolding(accountID, priorBalance, journal);
}
TER
@@ -473,37 +279,7 @@ removeEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
},
asset.value());
}
//------------------------------------------------------------------------------
//
// Authorization and transfer checks
//
//------------------------------------------------------------------------------
TER
requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue_) {
return requireAuth(view, issue_, account, authType);
},
asset.value());
}
TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return canTransfer(view, issue, from, to);
},
asset.value());
return makeWritableTokenBase(view, asset)->removeEmptyHolding(accountID, journal);
}
//------------------------------------------------------------------------------
@@ -1036,19 +812,18 @@ rippleCreditMPT(
beast::Journal j)
{
// Do not check MPT authorization here - it must have been checked earlier
auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
WritableMPToken mptIssuance(view, saAmount.get<MPTIssue>().getMptID());
auto const& issuer = saAmount.getIssuer();
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer)
{
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
view.update(sleIssuance);
(*mptIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
mptIssuance.update();
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uSenderID);
if (auto sle = view.peek(mptokenID))
{
auto const amt = sle->getFieldU64(sfMPTAmount);
@@ -1066,12 +841,12 @@ rippleCreditMPT(
if (uReceiverID == issuer)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = saAmount.mpt().value();
if (outstanding >= redeem)
{
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
mptIssuance.update();
}
else
{
@@ -1080,7 +855,7 @@ rippleCreditMPT(
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uReceiverID);
if (auto sle = view.peek(mptokenID))
{
(*sle)[sfMPTAmount] += saAmount.mpt().value();
@@ -1109,9 +884,8 @@ rippleSendMPT(
// Safe to get MPT since rippleSendMPT is only called by accountSendMPT
auto const& issuer = saAmount.getIssuer();
auto const sle = view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
if (!sle)
WritableMPToken mptIssuance(view, saAmount.get<MPTIssue>().getMptID());
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer || uReceiverID == issuer)
@@ -1121,9 +895,9 @@ rippleSendMPT(
if (uSenderID == issuer)
{
auto const sendAmount = saAmount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
auto const maximumAmount = mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
@@ -1136,9 +910,8 @@ rippleSendMPT(
}
// Sending 3rd party MPTs: transit.
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
: multiply(saAmount, mptIssuance.transferRate());
JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -1164,9 +937,8 @@ rippleSendMultiMPT(
// Safe to get MPT since rippleSendMultiMPT is only called by
// accountSendMultiMPT
auto const& issuer = mptIssue.getIssuer();
auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!sle)
auto mptIssuance = MPToken(view, mptIssue);
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
// These may diverge
@@ -1200,9 +972,10 @@ rippleSendMultiMPT(
"xrpl::rippleSendMultiMPT",
"sender == issuer, takeFromSender == zero");
auto const sendAmount = amount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
auto const maximumAmount =
mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
@@ -1219,7 +992,7 @@ rippleSendMultiMPT(
// Sending 3rd party MPTs: transit.
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
: multiply(amount, mptIssuance.transferRate());
actual += actualSend;
takeFromSender += actualSend;

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>

View File

@@ -4,6 +4,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STNumber.h>

View File

@@ -1,5 +1,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpersRippleStateHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/tx/paths/OfferStream.h>
@@ -78,11 +79,13 @@ accountFundsHelper(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
Issue const&,
Issue const& issue,
FreezeHandling freezeHandling,
beast::Journal j)
{
return accountFunds(view, id, saDefault, freezeHandling, j);
if (!saDefault.native() && saDefault.getIssuer() == id)
return saDefault;
return IOUToken(view, issue).accountHolds(id, freezeHandling, j);
}
static IOUAmount
@@ -99,9 +102,7 @@ accountFundsHelper(
// self funded
return amtDefault;
}
return toAmount<IOUAmount>(
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
return toAmount<IOUAmount>(IOUToken(view, issue).accountHolds(id, freezeHandling, j));
}
static XRPAmount
@@ -113,8 +114,7 @@ accountFundsHelper(
FreezeHandling freezeHandling,
beast::Journal j)
{
return toAmount<XRPAmount>(
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
return toAmount<XRPAmount>(IOUToken(view, issue).accountHolds(id, freezeHandling, j));
}
template <class TIn, class TOut>
@@ -238,8 +238,8 @@ TOfferStreamBase<TIn, TOut>::step()
continue;
}
bool const deepFrozen = isDeepFrozen(
view_, offer_.owner(), offer_.issueIn().currency, offer_.issueIn().account);
IOUToken wrapped(view_, offer_.issueIn());
bool const deepFrozen = wrapped.isDeepFrozen(offer_.owner());
if (deepFrozen)
{
JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key();

View File

@@ -2,6 +2,8 @@
#include <xrpl/basics/scope.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TER.h>
@@ -127,8 +129,13 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// Make sure the check owner holds at least value. If they have
// less than value the check cannot be cashed.
{
STAmount availableFunds{
accountFunds(ctx.view, sleCheck->at(sfAccount), value, fhZERO_IF_FROZEN, ctx.j)};
auto const checkAccount = sleCheck->at(sfAccount);
STAmount availableFunds = [&]() -> STAmount {
if (!value.native() && value.getIssuer() == checkAccount)
return value;
return makeTokenBase(ctx.view, value.asset())
->accountHolds(checkAccount, fhZERO_IF_FROZEN, ctx.j);
}();
// Note that src will have one reserve's worth of additional XRP
// once the check is cashed, since the check's reserve will no
@@ -187,7 +194,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// However, the trustline from destination to issuer may not
// be frozen.
if (isFrozen(ctx.view, dstId, currency, issuerId))
IOUToken wrapped(ctx.view, Issue{currency, issuerId});
if (wrapped.isFrozen(dstId))
{
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
return tecFROZEN;

View File

@@ -73,21 +73,22 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
return tecDUPLICATE;
}
if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID); !isTesSuccess(ter))
// Globally or individually frozen
auto const token1 = IOUToken(ctx.view, amount.issue());
auto const token2 = IOUToken(ctx.view, amount2.issue());
if (auto const ter = token1.requireAuth(accountID); !isTesSuccess(ter))
{
JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount.issue();
return ter;
}
if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID); !isTesSuccess(ter))
if (auto const ter = token2.requireAuth(accountID); !isTesSuccess(ter))
{
JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount2.issue();
return ter;
}
// Globally or individually frozen
if (isFrozen(ctx.view, accountID, amount.issue()) ||
isFrozen(ctx.view, accountID, amount2.issue()))
if (token1.isFrozen(accountID) || token2.isFrozen(accountID))
{
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
return tecFROZEN;
@@ -124,9 +125,9 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
if (isXRP(asset))
return xrpBalance < asset;
return accountID != asset.issue().account &&
accountHolds(
ctx.view, accountID, asset.issue(), FreezeHandling::fhZERO_IF_FROZEN, ctx.j) <
asset;
IOUToken(ctx.view, asset.issue())
.accountHolds(
accountID, FreezeHandling::fhZERO_IF_FROZEN, ctx.j, shSIMPLE_BALANCE) < asset;
};
if (insufficientBalance(amount) || insufficientBalance(amount2))

View File

@@ -201,8 +201,9 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
return tecINSUF_RESERVE_LINE;
}
return (accountID == deposit.issue().account ||
accountHolds(
ctx.view, accountID, deposit.issue(), FreezeHandling::fhIGNORE_FREEZE, ctx.j) >=
IOUToken(ctx.view, deposit.issue())
.accountHolds(
accountID, FreezeHandling::fhIGNORE_FREEZE, ctx.j, shSIMPLE_BALANCE) >=
deposit)
? TER(tesSUCCESS)
: tecUNFUNDED_AMM;
@@ -213,13 +214,14 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
// Check if either of the assets is frozen, AMMDeposit is not allowed
// if either asset is frozen
auto checkAsset = [&](Issue const& asset) -> TER {
if (auto const ter = requireAuth(ctx.view, asset, accountID))
auto const token = IOUToken(ctx.view, asset);
if (auto const ter = token.requireAuth(accountID))
{
JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
return ter;
}
if (isFrozen(ctx.view, accountID, asset))
if (token.isFrozen(accountID))
{
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, "
<< to_string(accountID) << " " << to_string(asset.currency);
@@ -244,10 +246,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
auto checkAmount = [&](std::optional<STAmount> const& amount, bool checkBalance) -> TER {
if (amount)
{
// AMM account or currency frozen
auto const token = IOUToken(ctx.view, amount->issue());
// This normally should not happen.
// Account is not authorized to hold the assets it's depositing,
// or it doesn't even have a trust line for them
if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
if (auto const ter = token.requireAuth(accountID))
{
// LCOV_EXCL_START
JLOG(ctx.j.debug())
@@ -255,15 +259,14 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
return ter;
// LCOV_EXCL_STOP
}
// AMM account or currency frozen
if (isFrozen(ctx.view, ammAccountID, amount->issue()))
if (token.isFrozen(ammAccountID))
{
JLOG(ctx.j.debug())
<< "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
return tecFROZEN;
}
// Account frozen
if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
if (token.isIndividualFrozen(accountID))
{
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID)
<< " " << to_string(amount->issue().currency);
@@ -469,13 +472,13 @@ AMMDeposit::deposit(
return tesSUCCESS;
}
else if (
accountID_ == depositAmount.issue().account ||
accountHolds(
view,
accountID_,
depositAmount.issue(),
FreezeHandling::fhIGNORE_FREEZE,
ctx_.journal) >= depositAmount)
account_ == depositAmount.issue().account ||
IOUToken(view, depositAmount.issue())
.accountHolds(
accountID_,
FreezeHandling::fhIGNORE_FREEZE,
ctx_.journal,
shSIMPLE_BALANCE) >= depositAmount)
{
return tesSUCCESS;
}

View File

@@ -2,6 +2,7 @@
#include <xrpl/basics/safe_cast.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/tx/transactors/dex/AMMHelpers.h>
@@ -18,8 +19,10 @@ ammPoolHolds(
FreezeHandling freezeHandling,
beast::Journal const j)
{
auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j);
auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j);
auto const assetInBalance =
IOUToken(view, issue1).accountHolds(ammAccountID, freezeHandling, j, shSIMPLE_BALANCE);
auto const assetOutBalance =
IOUToken(view, issue2).accountHolds(ammAccountID, freezeHandling, j, shSIMPLE_BALANCE);
return std::make_pair(assetInBalance, assetOutBalance);
}
@@ -109,7 +112,7 @@ ammLPHolds(
<< " lpAccount=" << to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
else if (isFrozen(view, lpAccount, currency, ammAccount))
else if (IOUToken wrapped(view, Issue{currency, ammAccount}); wrapped.isFrozen(lpAccount))
{
amount.clear(Issue{currency, ammAccount});
JLOG(j.trace()) << "ammLPHolds: frozen currency "
@@ -190,7 +193,7 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const
}
else if (
auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency));
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
sle && !IOUToken(view, issue).isFrozen(ammAccountID))
{
auto amount = (*sle)[sfBalance];
if (ammAccountID > issue.account)

View File

@@ -192,21 +192,22 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx)
<< "AMM Withdraw: withdrawing more than the balance, " << *amount;
return tecAMM_BALANCE;
}
if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
// AMM account or currency frozen
auto const token = IOUToken(ctx.view, amount->issue());
if (auto const ter = token.requireAuth(accountID))
{
JLOG(ctx.j.debug())
<< "AMM Withdraw: account is not authorized, " << amount->issue();
return ter;
}
// AMM account or currency frozen
if (isFrozen(ctx.view, ammAccountID, amount->issue()))
if (token.isFrozen(ammAccountID))
{
JLOG(ctx.j.debug())
<< "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
return tecFROZEN;
}
// Account frozen
if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
if (token.isIndividualFrozen(accountID))
{
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
<< " " << to_string(amount->issue().currency);

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
@@ -153,7 +154,14 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
return tecFROZEN;
}
if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <= beast::zero)
// Check account funds: issuer can always afford their own currency
auto const funds = [&]() -> STAmount {
if (!saTakerGets.native() && saTakerGets.getIssuer() == id)
return saTakerGets;
return makeTokenBase(ctx.view, saTakerGets.asset())
->accountHolds(id, fhZERO_IF_FROZEN, viewJ);
}();
if (funds <= beast::zero)
{
JLOG(ctx.j.debug()) << "delay: Offers must be at least partially funded.";
return tecUNFUNDED_OFFER;
@@ -280,8 +288,12 @@ OfferCreate::flowCross(
// We check this in preclaim, but when selling XRP charged fees can
// cause a user's available balance to go to 0 (by causing it to dip
// below the reserve) so we check this case again.
STAmount const inStartBalance =
accountFunds(psb, accountID_, takerAmount.in, fhZERO_IF_FROZEN, j_);
STAmount const inStartBalance = [&]() -> STAmount {
if (!takerAmount.in.native() && takerAmount.in.getIssuer() == accountID_)
return takerAmount.in;
return makeTokenBase(psb, takerAmount.in.asset())
->accountHolds(accountID_, fhZERO_IF_FROZEN, j_);
}();
if (inStartBalance <= beast::zero)
{
// The account balance can't cover even part of the offer.
@@ -383,8 +395,12 @@ OfferCreate::flowCross(
auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged
if (isTesSuccess(result.result()))
{
STAmount const takerInBalance =
accountFunds(psb, accountID_, takerAmount.in, fhZERO_IF_FROZEN, j_);
STAmount const takerInBalance = [&]() -> STAmount {
if (!takerAmount.in.native() && takerAmount.in.getIssuer() == accountID_)
return takerAmount.in;
return makeTokenBase(psb, takerAmount.in.asset())
->accountHolds(accountID_, fhZERO_IF_FROZEN, j_);
}();
if (takerInBalance <= beast::zero)
{

View File

@@ -35,7 +35,7 @@ escrowCancelPreclaimHelper<Issue>(
return tecINTERNAL; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter))
if (auto const ter = IOUToken(ctx.view, amount.issue()).requireAuth(account); !isTesSuccess(ter))
return ter;
return tesSUCCESS;
@@ -54,16 +54,13 @@ escrowCancelPreclaimHelper<MPTIssue>(
return tecINTERNAL; // LCOV_EXCL_LINE
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
auto const mptIssuance = MPToken(ctx.view, amount.get<MPTIssue>());
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
if (auto const ter = mptIssuance.requireAuth(account, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
return tesSUCCESS;

View File

@@ -187,19 +187,20 @@ escrowCreatePreclaimHelper<Issue>(
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter))
auto const token = IOUToken(ctx.view, amount.issue());
if (auto const ter = token.requireAuth(account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter))
if (auto const ter = token.requireAuth(dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(ctx.view, account, amount.issue()))
if (token.isFrozen(account))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(ctx.view, dest, amount.issue()))
if (token.isFrozen(dest))
return tecFROZEN;
STAmount const spendableAmount =
@@ -235,47 +236,43 @@ escrowCreatePreclaimHelper<MPTIssue>(
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
auto const mptIssuance = MPToken(ctx.view, amount.get<MPTIssue>());
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
if (!mptIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
if (mptIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
if (!ctx.view.exists(keylet::mptoken(mptIssuance.getMptID(), account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
if (auto const ter = mptIssuance.requireAuth(account, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
!isTesSuccess(ter))
if (auto const ter = mptIssuance.requireAuth(dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(ctx.view, account, mptIssue))
if (mptIssuance.isFrozen(account))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(ctx.view, dest, mptIssue))
if (mptIssuance.isFrozen(dest))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter))
if (auto const ter = mptIssuance.canTransfer(account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(

View File

@@ -6,6 +6,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h>
@@ -130,11 +131,12 @@ escrowFinishPreclaimHelper<Issue>(
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter))
IOUToken token(ctx.view, amount.issue());
if (auto const ter = token.requireAuth(dest); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer()))
if (token.isDeepFrozen(dest))
return tecFROZEN;
return tesSUCCESS;
@@ -147,26 +149,22 @@ escrowFinishPreclaimHelper<MPTIssue>(
AccountID const& dest,
STAmount const& amount)
{
AccountID issuer = amount.getIssuer();
auto const mptIssuance = MPToken(ctx.view, amount.get<MPTIssue>());
// If the issuer is the same as the dest, return tesSUCCESS
if (issuer == dest)
if (mptIssuance.getIssuer() == dest)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the destination is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
!isTesSuccess(ter))
if (auto const ter = mptIssuance.requireAuth(dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(ctx.view, dest, mptIssue))
if (mptIssuance.isFrozen(dest))
return tecLOCKED;
return tesSUCCESS;

View File

@@ -180,9 +180,9 @@ escrowUnlockApplyHelper<MPTIssue>(
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
auto const mptIssuance = MPToken(view, amount.get<MPTIssue>());
auto const mptID = mptIssuance.getMptID();
if (!view.exists(keylet::mptoken(mptID, receiver)) && createAsset && !receiverIssuer)
{
// For backwards compatibility: if dest is not WritableAccountRoot, return error
if (!std::holds_alternative<WritableAccountRoot>(dest))
@@ -206,10 +206,10 @@ escrowUnlockApplyHelper<MPTIssue>(
wrappedDest.adjustOwnerCount(1, journal);
}
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
if (!view.exists(keylet::mptoken(mptID, receiver)) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(view, amount);
auto const xferRate = mptIssuance.transferRate();
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;

View File

@@ -61,17 +61,18 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx)
return tecWRONG_ASSET;
auto const pseudoAccountID = sleBroker->at(sfAccount);
auto token = makeTokenBase(ctx.view, vaultAsset);
// Cannot transfer a non-transferable Asset
if (auto const ret = canTransfer(ctx.view, vaultAsset, account, pseudoAccountID))
if (auto const ret = token->canTransfer(account, pseudoAccountID))
return ret;
// Cannot transfer a frozen Asset
if (auto const ret = checkFrozen(ctx.view, account, vaultAsset))
if (auto const ret = token->checkFrozen(account))
return ret;
// Pseudo-account cannot receive if asset is deep frozen
if (auto const ret = checkDeepFrozen(ctx.view, pseudoAccountID, vaultAsset))
if (auto const ret = token->checkDeepFrozen(pseudoAccountID))
return ret;
// Cannot transfer unauthorized asset
if (auto const ret = requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth))
if (auto const ret = token->requireAuth(account, AuthType::StrongAuth))
return ret;
if (accountHolds(

View File

@@ -79,8 +79,9 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
// The broker's pseudo-account is the source of funds.
auto const pseudoAccountID = sleBroker->at(sfAccount);
auto token = makeTokenBase(ctx.view, vaultAsset);
// Cannot transfer a non-transferable Asset
if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct))
if (auto const ret = token->canTransfer(pseudoAccountID, dstAcct))
return ret;
// Withdrawal to a 3rd party destination account is essentially a transfer.
@@ -97,17 +98,17 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
}
// Destination MPToken must exist (if asset is an MPT)
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType))
if (auto const ter = token->requireAuth(dstAcct, authType))
return ter;
// Check for freezes, unless sending directly to the issuer
if (dstAcct != vaultAsset.getIssuer())
{
// Cannot send a frozen Asset
if (auto const ret = checkFrozen(ctx.view, pseudoAccountID, vaultAsset))
if (auto const ret = token->checkFrozen(pseudoAccountID))
return ret;
// Destination account cannot receive if asset is deep frozen
if (auto const ret = checkDeepFrozen(ctx.view, dstAcct, vaultAsset))
if (auto const ret = token->checkDeepFrozen(dstAcct))
return ret;
}

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/lending/LoanBrokerDelete.h>
//
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
@@ -83,7 +84,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx)
// So we need to check if the broker owner is deep frozen for that asset.
if (coverAvailable > beast::zero)
{
if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
if (auto const ret = makeTokenBase(ctx.view, asset)->checkDeepFrozen(brokerOwner))
{
JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
return ret;
@@ -131,7 +132,8 @@ LoanBrokerDelete::doApply()
return ter;
}
if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
if (auto ter =
makeWritableTokenBase(view(), vaultAsset)->removeEmptyHolding(brokerPseudoID, j_))
return ter;
WritableAccountRoot brokerPseudo(brokerPseudoID, view());

View File

@@ -1,5 +1,6 @@
#include <xrpl/tx/transactors/lending/LoanBrokerSet.h>
//
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
@@ -124,10 +125,11 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
}
else
{
if (auto const ter = canAddHolding(ctx.view, asset))
if (auto const ter = makeTokenBase(ctx.view, asset)->canAddHolding())
return ter;
if (auto const ter = checkFrozen(ctx.view, sleVault->at(sfAccount), sleVault->at(sfAsset)))
if (auto const ter = makeTokenBase(ctx.view, sleVault->at(sfAsset))
->checkFrozen(sleVault->at(sfAccount)))
{
JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
return ter;
@@ -229,7 +231,8 @@ LoanBrokerSet::doApply()
auto& pseudo = *maybePseudo;
auto pseudoId = pseudo->at(sfAccount);
if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
if (auto ter = makeWritableTokenBase(view, sleVault->at(sfAsset))
->addEmptyHolding(pseudoId, preFeeBalance_, j_))
return ter;
// Initialize data fields:

View File

@@ -2,6 +2,7 @@
//
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/protocol/TxFlags.h>
@@ -196,17 +197,18 @@ LoanPay::preclaim(PreclaimContext const& ctx)
return tecWRONG_ASSET;
}
if (auto const ret = checkFrozen(ctx.view, account, asset))
auto token = makeTokenBase(ctx.view, asset);
if (auto const ret = token->checkFrozen(account))
{
JLOG(ctx.j.warn()) << "Borrower account is frozen.";
return ret;
}
if (auto const ret = checkDeepFrozen(ctx.view, vaultPseudoAccount, asset))
if (auto const ret = token->checkDeepFrozen(vaultPseudoAccount))
{
JLOG(ctx.j.warn()) << "Vault pseudo-account can not receive funds (deep frozen).";
return ret;
}
if (auto const ret = requireAuth(ctx.view, asset, account))
if (auto const ret = token->requireAuth(account))
{
JLOG(ctx.j.warn()) << "Borrower account is not authorized.";
return ret;
@@ -273,6 +275,7 @@ LoanPay::doApply()
//
// Normally freeze status is checked in preclaim, but we do it here to
// avoid duplicating the check. It'll claim a fee either way.
auto token = makeTokenBase(view, asset);
bool const sendBrokerFeeToOwner = [&]() {
// Round the minimum required cover up to be conservative. This ensures
// CoverAvailable never drops below the theoretical minimum, protecting
@@ -281,8 +284,8 @@ LoanPay::doApply()
return coverAvailableProxy >=
roundToAsset(
asset, tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale) &&
!isDeepFrozen(view, brokerOwner, asset) &&
!requireAuth(view, asset, brokerOwner, AuthType::StrongAuth);
!token->isDeepFrozen(brokerOwner) &&
!token->requireAuth(brokerOwner, AuthType::StrongAuth);
}();
auto const brokerPayee = sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
@@ -291,7 +294,7 @@ LoanPay::doApply()
{
// If we can't send the fee to the owner, and the pseudo-account is
// frozen, then we have to fail the payment.
if (auto const ret = checkDeepFrozen(view, brokerPayee, asset))
if (auto const ret = token->checkDeepFrozen(brokerPayee))
{
JLOG(j_.warn()) << "Both Loan Broker and Loan Broker pseudo-account "
"can not receive funds (deep frozen).";
@@ -518,7 +521,7 @@ LoanPay::doApply()
if (totalPaidToVaultRounded != beast::zero)
{
if (auto const ter = requireAuth(view, asset, vaultPseudoAccount, AuthType::StrongAuth))
if (auto const ter = token->requireAuth(vaultPseudoAccount, AuthType::StrongAuth))
return ter;
}
@@ -527,8 +530,10 @@ LoanPay::doApply()
if (brokerPayee == accountID_)
{
// The broker may have deleted their holding. Recreate it if needed
if (auto const ter = addEmptyHolding(
view, brokerPayee, brokerPayeeAcct->at(sfBalance).value().xrp(), asset, j_);
if (auto const ter =
makeWritableTokenBase(view, asset)
->addEmptyHolding(
brokerPayee, brokerPayeeAcct->at(sfBalance).value().xrp(), j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists,
@@ -536,7 +541,7 @@ LoanPay::doApply()
return ter;
}
}
if (auto const ter = requireAuth(view, asset, brokerPayee, AuthType::StrongAuth))
if (auto const ter = token->requireAuth(brokerPayee, AuthType::StrongAuth))
return ter;
}

View File

@@ -1,6 +1,7 @@
#include <xrpl/tx/transactors/lending/LoanSet.h>
//
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/lending/LendingHelpers.h>
@@ -296,11 +297,12 @@ LoanSet::preclaim(PreclaimContext const& ctx)
}
}
if (auto const ter = canAddHolding(ctx.view, asset))
if (auto const ter = makeTokenBase(ctx.view, asset)->canAddHolding())
return ter;
auto token = makeTokenBase(ctx.view, asset);
// vaultPseudo is going to send funds, so it can't be frozen.
if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
if (auto const ret = token->checkFrozen(vaultPseudo))
{
JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
return ret;
@@ -309,7 +311,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
// brokerPseudo is the fallback account to receive LoanPay fees, even if the
// broker owner is unable to accept them. Don't create the loan if it is
// deep frozen.
if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
if (auto const ret = token->checkDeepFrozen(brokerPseudo))
{
JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
return ret;
@@ -319,14 +321,14 @@ LoanSet::preclaim(PreclaimContext const& ctx)
// frozen now. It is also going to receive funds, so it can't be deep
// frozen, but being frozen is a prerequisite for being deep frozen, so
// checking the one is sufficient.
if (auto const ret = checkFrozen(ctx.view, borrower, asset))
if (auto const ret = token->checkFrozen(borrower))
{
JLOG(ctx.j.warn()) << "Borrower account is frozen.";
return ret;
}
// brokerOwner is going to receive funds if there's an origination fee, so
// it can't be deep frozen
if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
if (auto const ret = token->checkDeepFrozen(brokerOwner))
{
JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
return ret;
@@ -492,8 +494,9 @@ LoanSet::doApply()
borrower == accountID_ || borrower == counterparty,
"xrpl::LoanSet::doApply",
"borrower signed transaction");
if (auto const ter = addEmptyHolding(
view, borrower, wrappedBorrower->at(sfBalance).value().xrp(), vaultAsset, j_);
if (auto const ter =
makeWritableTokenBase(view, vaultAsset)
->addEmptyHolding(borrower, wrappedBorrower->at(sfBalance).value().xrp(), j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists, and
@@ -501,7 +504,8 @@ LoanSet::doApply()
return ter;
}
if (auto const ter = requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
auto const token = makeTokenBase(view, vaultAsset);
if (auto const ter = token->requireAuth(borrower, AuthType::StrongAuth))
return ter;
// 2. Transfer originationFee, if any, from vault pseudo-account to
@@ -515,8 +519,9 @@ LoanSet::doApply()
"xrpl::LoanSet::doApply",
"broker owner signed transaction");
if (auto const ter = addEmptyHolding(
view, brokerOwner, brokerOwnerAcct->at(sfBalance).value().xrp(), vaultAsset, j_);
if (auto const ter =
makeWritableTokenBase(view, vaultAsset)
->addEmptyHolding(brokerOwner, brokerOwnerAcct->at(sfBalance).value().xrp(), j_);
ter && ter != tecDUPLICATE)
{
// ignore tecDUPLICATE. That means the holding already exists,
@@ -525,7 +530,7 @@ LoanSet::doApply()
}
}
if (auto const ter = requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
if (auto const ter = token->requireAuth(brokerOwner, AuthType::StrongAuth))
return ter;
if (auto const ter = accountSendMulti(

View File

@@ -1,5 +1,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/TxFlags.h>
@@ -170,7 +171,14 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
// own currency
auto const needed = bo->at(sfAmount);
if (accountFunds(ctx.view, (*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
auto const funds = [&]() -> STAmount {
auto const owner = (*bo)[sfOwner];
if (!needed.native() && needed.getIssuer() == owner)
return needed;
return makeTokenBase(ctx.view, needed.asset())
->accountHolds(owner, fhZERO_IF_FROZEN, ctx.j);
}();
if (funds < needed)
return tecINSUFFICIENT_FUNDS;
// Check that the account accepting the buy offer (he's selling the NFT)
@@ -238,7 +246,14 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
// mode, because then we are confirming that the broker can
// cover what the buyer will pay, which doesn't make sense, causes
// an unnecessary tec, and is also resolved with this amendment.
if (accountFunds(ctx.view, ctx.tx[sfAccount], needed, fhZERO_IF_FROZEN, ctx.j) < needed)
auto const funds = [&]() -> STAmount {
auto const account = ctx.tx[sfAccount];
if (!needed.native() && needed.getIssuer() == account)
return needed;
return makeTokenBase(ctx.view, needed.asset())
->accountHolds(account, fhZERO_IF_FROZEN, ctx.j);
}();
if (funds < needed)
return tecINSUFFICIENT_FUNDS;
}
@@ -326,9 +341,14 @@ NFTokenAcceptOffer::pay(AccountID const& from, AccountID const& to, STAmount con
// just confirm that the end state is OK.
if (!isTesSuccess(result))
return result;
if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
auto const checkFunds = [&](AccountID const& account) -> STAmount {
if (!amount.native() && amount.getIssuer() == account)
return amount;
return makeTokenBase(view(), amount.asset())->accountHolds(account, fhZERO_IF_FROZEN, j_);
};
if (checkFunds(from).signum() < 0)
return tecINSUFFICIENT_FUNDS;
if (accountFunds(view(), to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
if (checkFunds(to).signum() < 0)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}

View File

@@ -2,6 +2,8 @@
#include <xrpl/ledger/Dir.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFlags.h>
@@ -847,7 +849,8 @@ tokenOfferCreatePreclaim(
return tecNO_LINE;
}
if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
IOUToken wrapped(view, amount.issue());
if (wrapped.isFrozen(nftIssuer))
return tecFROZEN;
}
@@ -860,7 +863,7 @@ tokenOfferCreatePreclaim(
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
}
if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
if (IOUToken(view, amount.issue()).isFrozen(acctID))
return tecFROZEN;
// If this is an offer to buy the token, the account must have the
@@ -870,7 +873,13 @@ tokenOfferCreatePreclaim(
{
// We allow an IOU issuer to make a buy offer
// using their own currency.
if (accountFunds(view, acctID, amount, FreezeHandling::fhZERO_IF_FROZEN, j).signum() <= 0)
auto const funds = [&]() -> STAmount {
if (!amount.native() && amount.getIssuer() == acctID)
return amount;
return makeTokenBase(view, amount.asset())
->accountHolds(acctID, FreezeHandling::fhZERO_IF_FROZEN, j);
}();
if (funds.signum() <= 0)
return tecUNFUNDED_OFFER;
}

View File

@@ -463,15 +463,15 @@ Payment::doApply()
{
JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
auto const& mptIssue = dstAmount.get<MPTIssue>();
MPToken mptToken(view(), mptIssue);
if (auto const ter = requireAuth(view(), mptIssue, accountID_); !isTesSuccess(ter))
if (auto const ter = mptToken.requireAuth(accountID_); !isTesSuccess(ter))
return ter;
if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); !isTesSuccess(ter))
if (auto const ter = mptToken.requireAuth(dstAccountID); !isTesSuccess(ter))
return ter;
if (auto const ter = canTransfer(view(), mptIssue, accountID_, dstAccountID);
!isTesSuccess(ter))
if (auto const ter = mptToken.canTransfer(accountID_, dstAccountID); !isTesSuccess(ter))
return ter;
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
@@ -489,11 +489,11 @@ Payment::doApply()
// - can't send between holders
// - holder can send back to issuer
// - issuer can send to holder
if (isAnyFrozen(view(), {accountID_, dstAccountID}, mptIssue))
if (mptToken.isAnyFrozen({accountID_, dstAccountID}))
return tecLOCKED;
// Get the rate for a payment between the holders.
rate = transferRate(view(), mptIssue.getMptID());
rate = mptToken.transferRate();
}
// Amount to deliver.

View File

@@ -1,5 +1,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/st.h>
@@ -159,14 +160,9 @@ TER
MPTokenAuthorize::doApply()
{
auto const& tx = ctx_.tx;
return authorizeMPToken(
ctx_.view(),
preFeeBalance_,
tx[sfMPTokenIssuanceID],
accountID_,
ctx_.journal,
tx.getFlags(),
tx[~sfHolder]);
WritableMPToken mptToken(ctx_.view(), tx[sfMPTokenIssuanceID]);
return mptToken.authorizeMPToken(
preFeeBalance_, accountID_, ctx_.journal, tx.getFlags(), tx[~sfHolder]);
}
} // namespace xrpl

View File

@@ -1,6 +1,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
@@ -379,7 +380,8 @@ VaultClawback::doApply()
// Keep MPToken if holder is the vault owner.
if (holder != vault->at(sfOwner))
{
if (auto const ter = removeEmptyHolding(view(), holder, sharesDestroyed.asset(), j_);
if (auto const ter = makeWritableTokenBase(view(), sharesDestroyed.asset())
->removeEmptyHolding(holder, j_);
isTesSuccess(ter))
{
JLOG(j_.debug()) //

View File

@@ -1,4 +1,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpersMPTokenHelpers.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -90,7 +92,7 @@ VaultCreate::preclaim(PreclaimContext const& ctx)
auto const vaultAsset = ctx.tx[sfAsset];
auto const account = ctx.tx[sfAccount];
if (auto const ter = canAddHolding(ctx.view, vaultAsset))
if (auto const ter = makeTokenBase(ctx.view, vaultAsset)->canAddHolding())
return ter;
// Check for pseudo-account issuers - we do not want a vault to hold such
@@ -103,8 +105,11 @@ VaultCreate::preclaim(PreclaimContext const& ctx)
}
// Cannot create Vault for an Asset frozen for the vault owner
if (isFrozen(ctx.view, account, vaultAsset))
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
if (auto token = makeTokenBase(ctx.view, vaultAsset))
{
if (token->isFrozen(account))
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
}
if (auto const domain = ctx.tx[~sfDomainID])
{
@@ -151,7 +156,9 @@ VaultCreate::doApply()
auto pseudoId = pseudo->at(sfAccount);
auto asset = tx[sfAsset];
if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter))
if (auto ter =
makeWritableTokenBase(view(), asset)->addEmptyHolding(pseudoId, preFeeBalance_, j_);
!isTesSuccess(ter))
return ter;
std::uint8_t const scale = (asset.holds<MPTIssue>() || asset.native())
@@ -213,16 +220,16 @@ VaultCreate::doApply()
view().insert(vault);
// Explicitly create MPToken for the vault owner
if (auto const err =
authorizeMPToken(view(), preFeeBalance_, mptIssuanceID, accountID_, ctx_.journal);
WritableMPToken mptToken(view(), mptIssuanceID);
if (auto const err = mptToken.authorizeMPToken(preFeeBalance_, accountID_, ctx_.journal);
!isTesSuccess(err))
return err;
// If the vault is private, set the authorized flag for the vault owner
if (txFlags & tfVaultPrivate)
{
if (auto const err = authorizeMPToken(
view(), preFeeBalance_, mptIssuanceID, pseudoId, ctx_.journal, {}, accountID_);
if (auto const err =
mptToken.authorizeMPToken(preFeeBalance_, pseudoId, ctx_.journal, {}, accountID_);
!isTesSuccess(err))
return err;
}

View File

@@ -1,5 +1,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STNumber.h>
@@ -85,7 +86,9 @@ VaultDelete::doApply()
// Destroy the asset holding.
auto asset = vault->at(sfAsset);
if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); !isTesSuccess(ter))
if (auto ter =
makeWritableTokenBase(view(), asset)->removeEmptyHolding(vault->at(sfAccount), j_);
!isTesSuccess(ter))
return ter;
auto const& pseudoID = vault->at(sfAccount);
@@ -113,7 +116,8 @@ VaultDelete::doApply()
// Try to remove MPToken for vault shares for the vault owner if it exists.
if (auto const mptoken = view().peek(keylet::mptoken(shareMPTID, accountID_)))
{
if (auto const ter = removeEmptyHolding(view(), accountID_, MPTIssue(shareMPTID), j_);
if (auto const ter = makeWritableTokenBase(view(), MPTIssue(shareMPTID))
->removeEmptyHolding(accountID_, j_);
!isTesSuccess(ter))
{
// LCOV_EXCL_START

View File

@@ -1,5 +1,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -43,7 +45,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
return tecWRONG_ASSET;
auto const& vaultAccount = vault->at(sfAccount);
if (auto ter = canTransfer(ctx.view, vaultAsset, account, vaultAccount); !isTesSuccess(ter))
auto const token = makeTokenBase(ctx.view, vaultAsset);
if (auto ter = token->canTransfer(account, vaultAccount); !isTesSuccess(ter))
{
JLOG(ctx.j.debug()) << "VaultDeposit: vault assets are non-transferable.";
return ter;
@@ -59,8 +62,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
auto const mptIssuance = MPToken(ctx.view, vaultShare);
if (!mptIssuance)
{
// LCOV_EXCL_START
JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
@@ -68,7 +71,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
if (sleIssuance->isFlag(lsfMPTLocked))
if (mptIssuance->isFlag(lsfMPTLocked))
{
// LCOV_EXCL_START
JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked.";
@@ -77,18 +80,18 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
}
// Cannot deposit inside Vault an Asset frozen for the depositor
if (isFrozen(ctx.view, account, vaultAsset))
if (token->isFrozen(account))
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
// Cannot deposit if the shares of the vault are frozen
if (isFrozen(ctx.view, account, vaultShare))
if (MPToken(ctx.view, vaultShare).isFrozen(account))
return tecLOCKED;
if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
{
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
auto const maybeDomainID = mptIssuance->at(~sfDomainID);
// Since this is a private vault and the account is not its owner, we
// perform authorization check based on DomainID read from sleIssuance.
// perform authorization check based on DomainID read from mptIssuance.
// Had the vault shares been a regular MPToken, we would allow
// authorization granted by the Issuer explicitly, but Vault uses Issuer
// pseudo-account, which cannot grant an authorization.
@@ -107,8 +110,11 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
}
// Source MPToken must exist (if asset is an MPT)
if (auto const ter = requireAuth(ctx.view, vaultAsset, account); !isTesSuccess(ter))
return ter;
if (auto token = makeTokenBase(ctx.view, vaultAsset))
{
if (auto const ter = token->requireAuth(account); !isTesSuccess(ter))
return ter;
}
if (accountHolds(
ctx.view,
@@ -133,9 +139,8 @@ VaultDeposit::doApply()
auto const amount = ctx_.tx[sfAmount];
// Make sure the depositor can hold shares.
auto const mptIssuanceID = (*vault)[sfShareMPTID];
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
WritableMPToken mptoken(view(), (*vault)[sfShareMPTID]);
if (!mptoken.exists())
{
// LCOV_EXCL_START
JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
@@ -147,18 +152,16 @@ VaultDeposit::doApply()
// Note, vault owner is always authorized
if (vault->isFlag(lsfVaultPrivate) && accountID_ != vault->at(sfOwner))
{
if (auto const err = enforceMPTokenAuthorization(
ctx_.view(), mptIssuanceID, accountID_, preFeeBalance_, j_);
if (auto const err = mptoken.enforceMPTokenAuthorization(accountID_, preFeeBalance_, j_);
!isTesSuccess(err))
return err;
}
else // !vault->isFlag(lsfVaultPrivate) || accountID_ == vault->at(sfOwner)
{
// No authorization needed, but must ensure there is MPToken
if (!view().exists(keylet::mptoken(mptIssuanceID, accountID_)))
if (!view().exists(keylet::mptoken(mptoken.getMptID(), accountID_)))
{
if (auto const err = authorizeMPToken(
view(), preFeeBalance_, mptIssuanceID->value(), accountID_, ctx_.journal);
if (auto const err = mptoken.authorizeMPToken(preFeeBalance_, accountID_, ctx_.journal);
!isTesSuccess(err))
return err;
}
@@ -169,11 +172,9 @@ VaultDeposit::doApply()
// This follows from the reverse of the outer enclosing if condition
XRPL_ASSERT(
accountID_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner");
if (auto const err = authorizeMPToken(
view(),
preFeeBalance_, // priorBalance
mptIssuanceID->value(), // mptIssuanceID
sleIssuance->at(sfIssuer), // account
if (auto const err = mptoken.authorizeMPToken(
preFeeBalance_, // priorBalance
mptoken->at(sfIssuer), // account
ctx_.journal,
{}, // flags
accountID_ // holderID
@@ -188,7 +189,7 @@ VaultDeposit::doApply()
{
// Compute exchange before transferring any amounts.
{
auto const maybeShares = assetsToSharesDeposit(vault, sleIssuance, amount);
auto const maybeShares = assetsToSharesDeposit(vault, mptoken.sle(), amount);
if (!maybeShares)
return tecINTERNAL; // LCOV_EXCL_LINE
sharesCreated = *maybeShares;
@@ -196,7 +197,7 @@ VaultDeposit::doApply()
if (sharesCreated == beast::zero)
return tecPRECISION_LOSS;
auto const maybeAssets = sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
auto const maybeAssets = sharesToAssetsDeposit(vault, mptoken.sle(), sharesCreated);
if (!maybeAssets)
{
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -218,7 +219,7 @@ VaultDeposit::doApply()
<< "VaultDeposit: overflow error with"
<< " scale=" << (int)vault->at(sfScale).value() //
<< ", assetsTotal=" << vault->at(sfAssetsTotal).value()
<< ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount) << ", amount=" << amount;
<< ", sharesTotal=" << mptoken->at(sfOutstandingAmount) << ", amount=" << amount;
return tecPATH_DRY;
}

View File

@@ -1,5 +1,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/SField.h>
@@ -50,7 +52,8 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
auto const& vaultAccount = vault->at(sfAccount);
auto const& account = ctx.tx[sfAccount];
auto const& dstAcct = ctx.tx[~sfDestination].value_or(account);
if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct); !isTesSuccess(ter))
auto const vaultAssetToken = makeTokenBase(ctx.view, vaultAsset);
if (auto ter = vaultAssetToken->canTransfer(vaultAccount, dstAcct); !isTesSuccess(ter))
{
JLOG(ctx.j.debug()) << "VaultWithdraw: vault assets are non-transferable.";
return ter;
@@ -72,16 +75,16 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
// if authorized) a trust line or MPToken as needed, in doApply().
// Destination MPToken or trust line must exist if _not_ sending to Account.
AuthType const authType = account == dstAcct ? AuthType::WeakAuth : AuthType::StrongAuth;
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType); !isTesSuccess(ter))
if (auto const ter = vaultAssetToken->requireAuth(dstAcct, authType); !isTesSuccess(ter))
return ter;
// Cannot withdraw from a Vault an Asset frozen for the destination account
if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
if (auto const ret = vaultAssetToken->checkFrozen(dstAcct))
return ret;
// Cannot return shares to the vault, if the underlying asset was frozen for
// the submitter
if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
if (auto const ret = MPToken(ctx.view, vaultShare).checkFrozen(account))
return ret;
return tesSUCCESS;
@@ -205,7 +208,8 @@ VaultWithdraw::doApply()
// Keep MPToken if holder is the vault owner.
if (accountID_ != vault->at(sfOwner))
{
if (auto const ter = removeEmptyHolding(view(), accountID_, sharesRedeemed.asset(), j_);
if (auto const ter = makeWritableTokenBase(view(), sharesRedeemed.asset())
->removeEmptyHolding(accountID_, j_);
isTesSuccess(ter))
{
JLOG(j_.debug()) //

View File

@@ -224,7 +224,8 @@ AMM::getLPTokensBalance(std::optional<AccountID> const& account) const
return accountHolds(
*env_.current(),
*account,
lptIssue_,
lptIssue_.currency,
lptIssue_.account,
FreezeHandling::fhZERO_IF_FROZEN,
env_.journal)
.iou();

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <type_traits>
@@ -802,25 +803,33 @@ class View_test : public beast::unit_test::suite
*env.closed(), carol, xrpCurrency(), gw, fhZERO_IF_FROZEN, env.journal));
}
{
// accountFunds().
// Gateways have whatever funds they claim to have.
auto const gwUSD =
accountFunds(*env.closed(), gw, USD(314159), fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(gwUSD == USD(314159));
// accountHolds() via IOUToken.
// Gateways have whatever funds they claim to have (tested by
// checking issuer == account, so we use USD(314159) for that).
// Note: When account == issuer, the old accountFunds() returned the
// passed amount. Now we test that the issuer check should be done
// at the call site.
auto const gwUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(gw, fhZERO_IF_FROZEN, env.journal);
// gwUSD would be 0 since gw is the issuer and has no trustline to
// itself. The old accountFunds returned the passed amount if
// account == issuer. We test carol's funds instead.
// carol has funds from the gateway.
auto carolsUSD =
accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
auto carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(carolsUSD == USD(50));
// If carol's funds are frozen she has no funds...
env(fset(gw, asfGlobalFreeze));
env.close();
carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhZERO_IF_FROZEN, env.journal);
BEAST_EXPECT(carolsUSD == USD(0));
// ... unless the query ignores the FROZEN state.
carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhIGNORE_FREEZE, env.journal);
carolsUSD = IOUToken(*env.closed(), USD.issue())
.accountHolds(carol, fhIGNORE_FREEZE, env.journal);
BEAST_EXPECT(carolsUSD == USD(50));
// Just to be tidy, thaw gw.

View File

@@ -7,6 +7,7 @@
#include <xrpld/rpc/MPTokenIssuanceID.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/jss.h>
@@ -181,12 +182,10 @@ fillJsonTx(
// owner balance
if (account != amount.getIssuer())
{
auto const ownerFunds = accountFunds(
fill.ledger,
account,
amount,
fhIGNORE_FREEZE,
beast::Journal{beast::Journal::getNullSink()});
auto const ownerFunds =
makeTokenBase(fill.ledger, amount.asset())
->accountHolds(
account, fhIGNORE_FREEZE, beast::Journal{beast::Journal::getNullSink()});
txJson[jss::owner_funds] = ownerFunds.getText();
}
}

View File

@@ -43,6 +43,7 @@
#include <xrpl/git/Git.h>
#include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/ledger/OrderBookDB.h>
#include <xrpl/ledger/helpersTokenHelpers.h>
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/MultiApiJson.h>
@@ -3165,7 +3166,8 @@ NetworkOPsImp::transJson(
if (account != amount.issue().account)
{
auto const ownerFunds =
accountFunds(*ledger, account, amount, fhIGNORE_FREEZE, registry_.journal("View"));
makeTokenBase(*ledger, amount.asset())
->accountHolds(account, fhIGNORE_FREEZE, registry_.journal("View"));
jvObj[jss::transaction][jss::owner_funds] = ownerFunds.getText();
}
}

View File

@@ -6,6 +6,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/tx/transactors/dex/AMMUtils.h>
@@ -214,13 +215,11 @@ doAMMInfo(RPC::JsonContext& context)
if (!isXRP(asset1Balance))
{
ammResult[jss::asset_frozen] =
isFrozen(*ledger, ammAccountID, issue1.currency, issue1.account);
ammResult[jss::asset_frozen] = IOUToken(*ledger, issue1).isFrozen(ammAccountID);
}
if (!isXRP(asset2Balance))
{
ammResult[jss::asset2_frozen] =
isFrozen(*ledger, ammAccountID, issue2.currency, issue2.account);
ammResult[jss::asset2_frozen] = IOUToken(*ledger, issue2).isFrozen(ammAccountID);
}
result[jss::amm] = std::move(ammResult);