mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 23:30:54 +00:00
implement most of the token stuff
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "
|
||||
|
||||
811
src/libxrpl/ledger/entries/MPTokenHelpers.cpp
Normal file
811
src/libxrpl/ledger/entries/MPTokenHelpers.cpp
Normal 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
|
||||
925
src/libxrpl/ledger/entries/RippleStateHelpers.cpp
Normal file
925
src/libxrpl/ledger/entries/RippleStateHelpers.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) //
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) //
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user