Merge branch 'ripple/se/fees' of https://github.com/XRPLF/rippled into ripple/smart-escrow

This commit is contained in:
Mayukha Vadari
2026-04-08 13:52:24 -04:00
223 changed files with 34897 additions and 4228 deletions

View File

@@ -213,11 +213,60 @@ public:
// Called when a credit is made to an account
// This is required to support PaymentSandbox
virtual void
creditHook(
creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance)
{
XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
}
virtual void
creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer)
{
XRPL_ASSERT(amount.holds<MPTIssue>(), "creditHookMPT: amount is for MPTIssue");
}
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* Unlike IOU, MPT doesn't have bi-directional relationship with an issuer,
* where a trustline limits an amount that can be issued to a holder.
* Consequently, the credit step (last MPTEndpointStep or
* BookStep buying MPT) might temporarily overflow OutstandingAmount.
* Limiting of a step's output amount in this case is delegated to
* the next step (in rev order). The next step always redeems when a holder
* account sells MPT (first MPTEndpointStep or BookStep selling MPT).
* In this case the holder account is only limited by the step's output
* and it's available funds since it's transferring the funds from one
* account to another account and doesn't change OutstandingAmount.
* This doesn't apply to an offer owned by an issuer.
* In this case the issuer sells or self debits and is increasing
* OutstandingAmount. Ability to issue is limited by the issuer
* originally available funds less already self sold MPT amounts (MPT sell
* offer).
* Consider an example:
* - GW creates MPT(USD) with 1,000USD MaximumAmount.
* - GW pays 950USD to A1.
* - A1 creates an offer 100XRP(buy)/100USD(sell).
* - GW creates an offer 100XRP(buy)/100USD(sell).
* - A2 pays 200USD to A3 with sendMax of 200XRP.
* Since the payment engine executes payments in reverse,
* OutstandingAmount overflows in MPTEndpointStep: 950 + 200 = 1,150USD.
* BookStep first consumes A1 offer. This reduces OutstandingAmount
* by 100USD: 1,150 - 100 = 1,050USD. GW offer can only be partially
* consumed because the initial available amount is 50USD = 1,000 - 950.
* BookStep limits it's output to 150USD. This in turn limits A3's send
* amount to 150XRP: A1 buys 100XRP and sells 100USD to A3. This doesn't
* change OutstandingAmount. GW buys 50XRP and sells 50USD to A3. This
* changes OutstandingAmount to 1,000USD.
*/
virtual void
issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance)
{
}

View File

@@ -3,8 +3,8 @@
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/BookListeners.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/UintTypes.h>
@@ -53,30 +53,30 @@ public:
issue. This is useful for pathfinding to find all possible next hops
from a given currency.
@param issue The issue to search for
@param asset The asset to search for
@param domain Optional domain restriction for the order book
@return Vector of books that want this issue
*/
virtual std::vector<Book>
getBooksByTakerPays(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
getBooksByTakerPays(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Get the count of order books that want a specific issue.
@param issue The issue to search for
@param asset The asset to search for
@param domain Optional domain restriction for the order book
@return Number of books that want this issue
*/
virtual int
getBookSize(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
getBookSize(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/** Check if an order book to XRP exists for the given issue.
@param issue The issue to check
@param asset The asset to check
@param domain Optional domain restriction for the order book
@return true if a book from this issue to XRP exists
*/
virtual bool
isBookToXRP(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
/**
* Process a transaction for order book tracking.

View File

@@ -15,10 +15,55 @@ namespace detail {
// into the PaymentSandbox class itself
class DeferredCredits
{
public:
struct Adjustment
private:
using KeyIOU = std::tuple<AccountID, AccountID, Currency>;
struct ValueIOU
{
Adjustment(STAmount const& d, STAmount const& c, STAmount const& b)
explicit ValueIOU() = default;
STAmount lowAcctCredits;
STAmount highAcctCredits;
STAmount lowAcctOrigBalance;
};
struct HolderValueMPT
{
HolderValueMPT() = default;
// Debit to issuer
std::uint64_t debit = 0;
std::uint64_t origBalance = 0;
};
struct IssuerValueMPT
{
IssuerValueMPT() = default;
std::map<AccountID, HolderValueMPT> holders;
// Credit to holder
std::uint64_t credit = 0;
// OutstandingAmount might overflow when MPTs are credited to a holder.
// Consider A1 paying 100MPT to A2 and A1 already having maximum MPTs.
// Since the payment engine executes a payment in revers, A2 is
// credited first and OutstandingAmount is going to be equal
// to MaximumAmount + 100MPT. In the next step A1 redeems 100MPT
// to the issuer and OutstandingAmount balances out.
std::int64_t origBalance = 0;
// Self debit on offer selling MPT. Since the payment engine executes
// a payment in reverse, a crediting/buying step may overflow
// OutstandingAmount. A sell MPT offer owned by a holder can redeem any
// amount up to the offer's amount and holder's available funds,
// balancing out OutstandingAmount. But if the offer's owner is issuer
// then it issues more MPT. In this case the available amount to issue
// is the initial issuer's available amount less all offer sell amounts
// by the issuer. This is self-debit, where the offer's owner,
// issuer in this case, debits to self.
std::uint64_t selfDebit = 0;
};
using AdjustmentMPT = IssuerValueMPT;
public:
struct AdjustmentIOU
{
AdjustmentIOU(STAmount const& d, STAmount const& c, STAmount const& b)
: debits(d), credits(c), origBalance(b)
{
}
@@ -29,16 +74,30 @@ public:
// Get the adjustments for the balance between main and other.
// Returns the debits, credits and the original balance
std::optional<Adjustment>
adjustments(AccountID const& main, AccountID const& other, Currency const& currency) const;
std::optional<AdjustmentIOU>
adjustmentsIOU(AccountID const& main, AccountID const& other, Currency const& currency) const;
std::optional<AdjustmentMPT>
adjustmentsMPT(MPTID const& mptID) const;
void
credit(
creditIOU(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
STAmount const& preCreditSenderBalance);
void
creditMPT(
AccountID const& sender,
AccountID const& receiver,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer);
void
issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance);
void
ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next);
@@ -52,21 +111,11 @@ public:
apply(DeferredCredits& to);
private:
// lowAccount, highAccount
using Key = std::tuple<AccountID, AccountID, Currency>;
struct Value
{
explicit Value() = default;
static KeyIOU
makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& currency);
STAmount lowAcctCredits;
STAmount highAcctCredits;
STAmount lowAcctOrigBalance;
};
static Key
makeKey(AccountID const& a1, AccountID const& a2, Currency const& c);
std::map<Key, Value> credits_;
std::map<KeyIOU, ValueIOU> creditsIOU_;
std::map<MPTID, IssuerValueMPT> creditsMPT_;
std::map<AccountID, std::uint32_t> ownerCounts_;
};
@@ -131,16 +180,35 @@ public:
/** @} */
STAmount
balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount)
balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount)
const override;
STAmount
balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount)
const override;
STAmount
balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const override;
void
creditHook(
creditHookIOU(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
STAmount const& preCreditBalance) override;
void
creditHookMPT(
AccountID const& from,
AccountID const& to,
STAmount const& amount,
std::uint64_t preCreditBalanceHolder,
std::int64_t preCreditBalanceIssuer) override;
void
issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance)
override;
void
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override;

View File

@@ -148,15 +148,35 @@ public:
// Accounts in a payment are not allowed to use assets acquired during that
// payment. The PaymentSandbox tracks the debits, credits, and owner count
// changes that accounts make during a payment. `balanceHook` adjusts
// changes that accounts make during a payment. `balanceHookIOU` adjusts
// balances so newly acquired assets are not counted toward the balance.
// This is required to support PaymentSandbox.
virtual STAmount
balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount) const
balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount) const
{
XRPL_ASSERT(amount.holds<Issue>(), "balanceHookIOU: amount is for Issue");
return amount;
}
// balanceHookMPT adjusts balances so newly acquired assets are not counted
// toward the balance.
virtual STAmount
balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount) const
{
return STAmount{issue, amount};
}
// An offer owned by an issuer and selling MPT is limited by the issuer's
// funds available to issue, which are originally available funds less
// already self sold MPT amounts (MPT sell offer). This hook is used
// by issuerFundsToSelfIssue() function.
virtual STAmount
balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const
{
return STAmount{issue, amount};
}
// Accounts in a payment are not allowed to use assets acquired during that
// payment. The PaymentSandbox tracks the debits, credits, and owner count
// changes that accounts make during a payment. `ownerCountHook` adjusts the

View File

@@ -63,8 +63,8 @@ isVaultPseudoAccountFrozen(
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Issue const& asset,
Issue const& asset2);
Asset const& asset,
Asset const& asset2);
// Return the list of enabled amendments
[[nodiscard]] std::set<uint256>

View File

@@ -36,7 +36,7 @@ enum class IsDeposit : bool { No = false, Yes = true };
* @return LP Tokens as IOU
*/
STAmount
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue);
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue);
/** Calculate LP Tokens given asset's deposit amount.
* @param asset1Balance current AMM asset1 balance
@@ -124,7 +124,8 @@ withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Nu
template <typename Amt>
requires(
std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, MPTAmount> ||
std::is_same_v<Amt, Number>)
bool
withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
{
@@ -195,7 +196,7 @@ getAMMOfferStartWithTakerGets(
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerGets is XRP.
auto const takerGets =
toAmount<TOut>(getIssue(pool.out), nTakerGetsProposed, Number::downward);
toAmount<TOut>(getAsset(pool.out), nTakerGetsProposed, Number::downward);
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
};
@@ -262,7 +263,7 @@ getAMMOfferStartWithTakerPays(
// Round downward to minimize the offer and to maximize the quality.
// This has the most impact when takerPays is XRP.
auto const takerPays =
toAmount<TIn>(getIssue(pool.in), nTakerPaysProposed, Number::downward);
toAmount<TIn>(getAsset(pool.in), nTakerPaysProposed, Number::downward);
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
};
@@ -331,7 +332,7 @@ changeSpotPriceQuality(
<< " " << to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
auto const takerPays = toAmount<TIn>(getAsset(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality &&
@@ -360,7 +361,7 @@ changeSpotPriceQuality(
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto amounts = [&]() {
if (isXRP(getIssue(pool.out)))
if (isXRP(getAsset(pool.out)))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
}();
@@ -445,7 +446,7 @@ swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t t
auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getAsset(pool.out), 0);
Number::setround(Number::upward);
auto const ratio = numerator / denom;
@@ -454,14 +455,14 @@ swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t t
auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getAsset(pool.out), 0);
return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
return toAmount<TOut>(getAsset(pool.out), swapOut, Number::downward);
}
else
{
return toAmount<TOut>(
getIssue(pool.out),
getAsset(pool.out),
pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::downward);
}
@@ -508,7 +509,7 @@ swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getIssue(pool.in));
return toMaxAmount<TIn>(getAsset(pool.in));
}
Number::setround(Number::upward);
@@ -522,14 +523,14 @@ swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getAsset(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
return toAmount<TIn>(getAsset(pool.in), swapIn, Number::upward);
}
else
{
return toAmount<TIn>(
getIssue(pool.in),
getAsset(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
Number::upward);
}
@@ -616,9 +617,9 @@ getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDe
if (!rules.enabled(fixAMMv1_3))
{
if constexpr (std::is_same_v<A, STAmount>)
return multiply(balance, frac, balance.issue());
return multiply(balance, frac, balance.asset());
else
return toSTAmount(balance.issue(), balance * frac);
return toSTAmount(balance.asset(), balance * frac);
}
auto const rm = detail::getAssetRounding(isDeposit);
return multiply(balance, frac, rm);

View File

@@ -21,9 +21,10 @@ std::pair<STAmount, STAmount>
ammPoolHolds(
ReadView const& view,
AccountID const& ammAccountID,
Issue const& issue1,
Issue const& issue2,
Asset const& asset1,
Asset const& asset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j);
/** Get AMM pool and LP token balances. If both optIssue are
@@ -34,9 +35,10 @@ Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
ammHolds(
ReadView const& view,
SLE const& ammSle,
std::optional<Issue> const& optIssue1,
std::optional<Issue> const& optIssue2,
std::optional<Asset> const& optAsset1,
std::optional<Asset> const& optAsset2,
FreezeHandling freezeHandling,
AuthHandling authHandling,
beast::Journal const j);
/** Get the balance of LP tokens.
@@ -44,8 +46,8 @@ ammHolds(
STAmount
ammLPHolds(
ReadView const& view,
Currency const& cur1,
Currency const& cur2,
Asset const& asset1,
Asset const& asset2,
AccountID const& ammAccount,
AccountID const& lpAccount,
beast::Journal const j);
@@ -67,13 +69,13 @@ getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
/** Returns total amount held by AMM for the given token.
*/
STAmount
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue);
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset);
/** Delete trustlines to AMM. If all trustlines are deleted then
* AMM object and account are deleted. Otherwise tecIMPCOMPLETE is returned.
*/
TER
deleteAMMAccount(Sandbox& view, Issue const& asset, Issue const& asset2, beast::Journal j);
deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j);
/** Initialize Auction and Voting slots and set the trading/discounted fee.
*/
@@ -82,7 +84,7 @@ initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
AccountID const& account,
Issue const& lptIssue,
Asset const& lptAsset,
std::uint16_t tfee);
/** Return true if the Liquidity Provider is the only AMM provider, false

View File

@@ -41,7 +41,8 @@ escrowUnlockApplyHelper<Issue>(
bool createAsset,
beast::Journal journal)
{
Keylet const trustLineKey = keylet::line(receiver, amount.issue());
Issue const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::line(receiver, issue);
bool const recvLow = issuer > receiver;
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
@@ -64,9 +65,9 @@ escrowUnlockApplyHelper<Issue>(
return tecNO_LINE_INSUF_RESERVE;
}
Currency const currency = amount.getCurrency();
STAmount initialBalance(amount.issue());
initialBalance.setIssuer(noAccount());
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
view, // payment sandbox
@@ -113,7 +114,8 @@ escrowUnlockApplyHelper<Issue>(
if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate)
{
// compute transfer fee, if any
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.issue(), true);
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}

View File

@@ -80,6 +80,7 @@ authorizeMPToken(
* 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.
* WeakAuth intentionally allows missing MPTokens under MPToken V2.
*/
[[nodiscard]] TER
requireAuth(
@@ -114,6 +115,12 @@ canTransfer(
AccountID const& from,
AccountID const& to);
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION
* if it doesn't and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTrade(ReadView const& view, Asset const& asset);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
@@ -164,4 +171,73 @@ createMPToken(
AccountID const& account,
std::uint32_t const flags);
TER
checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
beast::Journal j);
//------------------------------------------------------------------------------
//
// MPT Overflow related
//
//------------------------------------------------------------------------------
// MaximumAmount doesn't exceed 2**63-1
std::int64_t
maxMPTAmount(SLE const& sleIssuance);
// OutstandingAmount may overflow and available amount might be negative.
// But available amount is always <= |MaximumAmount - OutstandingAmount|.
std::int64_t
availableMPTAmount(SLE const& sleIssuance);
std::int64_t
availableMPTAmount(ReadView const& view, MPTID const& mptID);
/** Checks for two types of OutstandingAmount overflow during a send operation.
* 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when
* `OutstandingAmount > MaximumAmount`. This threshold is used for direct
* directSendNoFee transactions that bypass the payment engine.
* 2. **accountSend & Payment Engine (Overflow: Yes):** A temporary overflow
* check when `OutstandingAmount > UINT64_MAX`. This higher threshold is used
* for `accountSend` and payments processed via the payment engine.
*/
bool
isMPTOverflow(
std::int64_t sendAmount,
std::uint64_t outstandingAmount,
std::int64_t maximumAmount,
AllowMPTOverflow allowOverflow);
/**
* Determine funds available for an issuer to sell in an issuer owned offer.
* Issuing step, which could be either MPTEndPointStep last step or BookStep's
* TakerPays may overflow OutstandingAmount. Redeeming step, in BookStep's
* TakerGets redeems the offer's owner funds, essentially balancing out
* the overflow, unless the offer's owner is the issuer.
*/
[[nodiscard]] STAmount
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* See ApplyView::issuerSelfDebitHookMPT().
*/
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
//------------------------------------------------------------------------------
//
// MPT DEX
//
//------------------------------------------------------------------------------
/* Return true if a transaction is allowed for the specified MPT/account. The
* function checks MPTokenIssuance and MPToken objects flags to determine if the
* transaction is allowed.
*/
TER
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
} // namespace xrpl

View File

@@ -252,4 +252,14 @@ deleteAMMTrustLine(
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
* call to view.peek().
*/
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMPT,
AccountID const& ammAccountID,
beast::Journal j);
} // namespace xrpl

View File

@@ -31,6 +31,9 @@ enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
enum class WaiveTransferFee : bool { No = false, Yes };
/** Controls whether accountSend is allowed to overflow OutstandingAmount **/
enum class AllowMPTOverflow : bool { No = false, Yes };
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
* - StrongAuth - before checking if authorization is required
* - WeakAuth
@@ -176,6 +179,16 @@ accountFunds(
FreezeHandling freezeHandling,
beast::Journal j);
// Overload with AuthHandling to support IOU and MPT.
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
AuthHandling authHandling,
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
@@ -257,7 +270,8 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
/** Like accountSend, except one account is sending multiple payments (with the

View File

@@ -2,7 +2,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
@@ -31,12 +31,12 @@ class Rules;
/** Calculate Liquidity Provider Token (LPT) Currency.
*/
Currency
ammLPTCurrency(Currency const& cur1, Currency const& cur2);
ammLPTCurrency(Asset const& asset1, Asset const& asset2);
/** Calculate LPT Issue from AMM asset pair.
*/
Issue
ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID);
ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID);
/** Validate the amount.
* If validZero is false and amount is beast::zero then invalid amount.
@@ -46,19 +46,19 @@ ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAcco
NotTEC
invalidAMMAmount(
STAmount const& amount,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt,
bool validZero = false);
NotTEC
invalidAMMAsset(
Issue const& issue,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt);
Asset const& asset,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt);
NotTEC
invalidAMMAssetPair(
Issue const& issue1,
Issue const& issue2,
std::optional<std::pair<Issue, Issue>> const& pair = std::nullopt);
Asset const& asset1,
Asset const& asset2,
std::optional<std::pair<Asset, Asset>> const& pair = std::nullopt);
/** Get time slot of the auction slot.
*/

View File

@@ -1,6 +1,7 @@
#pragma once
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -9,11 +10,12 @@
namespace xrpl {
inline STAmount
toSTAmount(IOUAmount const& iou, Issue const& iss)
toSTAmount(IOUAmount const& iou, Asset const& asset)
{
XRPL_ASSERT(asset.holds<Issue>(), "xrpl::toSTAmount : is Issue");
bool const isNeg = iou.signum() < 0;
std::uint64_t const umant = isNeg ? -iou.mantissa() : iou.mantissa();
return STAmount(iss, umant, iou.exponent(), isNeg, STAmount::unchecked());
return STAmount(asset, umant, iou.exponent(), isNeg, STAmount::unchecked());
}
inline STAmount
@@ -31,12 +33,25 @@ toSTAmount(XRPAmount const& xrp)
}
inline STAmount
toSTAmount(XRPAmount const& xrp, Issue const& iss)
toSTAmount(XRPAmount const& xrp, Asset const& asset)
{
XRPL_ASSERT(isXRP(iss.account) && isXRP(iss.currency), "xrpl::toSTAmount : is XRP");
XRPL_ASSERT(isXRP(asset), "xrpl::toSTAmount : is XRP");
return toSTAmount(xrp);
}
inline STAmount
toSTAmount(MPTAmount const& mpt)
{
return STAmount(mpt, noMPT());
}
inline STAmount
toSTAmount(MPTAmount const& mpt, Asset const& asset)
{
XRPL_ASSERT(asset.holds<MPTIssue>(), "xrpl::toSTAmount : is MPT");
return STAmount(mpt, asset.get<MPTIssue>());
}
template <class T>
T
toAmount(STAmount const& amt) = delete;
@@ -76,6 +91,21 @@ toAmount<XRPAmount>(STAmount const& amt)
return XRPAmount(sMant);
}
template <>
inline MPTAmount
toAmount<MPTAmount>(STAmount const& amt)
{
XRPL_ASSERT(
amt.holds<MPTIssue>() && amt.mantissa() <= maxMPTokenAmount && amt.exponent() == 0,
"xrpl::toAmount<MPTAmount> : maximum mantissa");
if (amt.mantissa() > maxMPTokenAmount || amt.exponent() != 0)
Throw<std::runtime_error>("toAmount<MPTAmount>: invalid mantissa or exponent");
bool const isNeg = amt.negative();
std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa();
return MPTAmount(sMant);
}
template <class T>
T
toAmount(IOUAmount const& amt) = delete;
@@ -98,23 +128,36 @@ toAmount<XRPAmount>(XRPAmount const& amt)
return amt;
}
template <class T>
T
toAmount(MPTAmount const& amt) = delete;
template <>
inline MPTAmount
toAmount<MPTAmount>(MPTAmount const& amt)
{
return amt;
}
template <typename T>
T
toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround())
toAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround())
{
saveNumberRoundMode const rm(Number::getround());
if (isXRP(issue))
if (isXRP(asset))
Number::setround(mode);
if constexpr (std::is_same_v<IOUAmount, T>)
return IOUAmount(n);
else if constexpr (std::is_same_v<XRPAmount, T>)
return XRPAmount(static_cast<std::int64_t>(n));
else if constexpr (std::is_same_v<MPTAmount, T>)
return MPTAmount(static_cast<std::int64_t>(n));
else if constexpr (std::is_same_v<STAmount, T>)
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(n));
return STAmount(issue, n);
if (isXRP(asset))
return STAmount(asset, static_cast<std::int64_t>(n));
return STAmount(asset, n);
}
else
{
@@ -125,17 +168,23 @@ toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Numbe
template <typename T>
T
toMaxAmount(Issue const& issue)
toMaxAmount(Asset const& asset)
{
if constexpr (std::is_same_v<IOUAmount, T>)
return IOUAmount(STAmount::cMaxValue, STAmount::cMaxOffset);
else if constexpr (std::is_same_v<XRPAmount, T>)
return XRPAmount(static_cast<std::int64_t>(STAmount::cMaxNativeN));
else if constexpr (std::is_same_v<MPTAmount, T>)
return MPTAmount(maxMPTokenAmount);
else if constexpr (std::is_same_v<STAmount, T>)
{
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(STAmount::cMaxNativeN));
return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset);
return asset.visit(
[](Issue const& issue) {
if (isXRP(issue))
return STAmount(issue, static_cast<std::int64_t>(STAmount::cMaxNativeN));
return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset);
},
[](MPTIssue const& issue) { return STAmount(issue, maxMPTokenAmount); });
}
else
{
@@ -145,21 +194,23 @@ toMaxAmount(Issue const& issue)
}
inline STAmount
toSTAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround())
toSTAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround())
{
return toAmount<STAmount>(issue, n, mode);
return toAmount<STAmount>(asset, n, mode);
}
template <typename T>
Issue
getIssue(T const& amt)
Asset
getAsset(T const& amt)
{
if constexpr (std::is_same_v<IOUAmount, T>)
return noIssue();
else if constexpr (std::is_same_v<XRPAmount, T>)
return xrpIssue();
else if constexpr (std::is_same_v<MPTAmount, T>)
return noMPT();
else if constexpr (std::is_same_v<STAmount, T>)
return amt.issue();
return amt.asset();
else
{
constexpr bool alwaysFalse = !std::is_same_v<T, T>;
@@ -175,6 +226,8 @@ get(STAmount const& a)
return a.iou();
else if constexpr (std::is_same_v<XRPAmount, T>)
return a.xrp();
else if constexpr (std::is_same_v<MPTAmount, T>)
return a.mpt();
else if constexpr (std::is_same_v<STAmount, T>)
return a;
else

View File

@@ -2,20 +2,37 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rules.h>
namespace xrpl {
class Asset;
class STAmount;
template <typename TIss>
concept ValidIssueType = std::is_same_v<TIss, Issue> || std::is_same_v<TIss, MPTIssue>;
template <typename T>
requires(
std::is_same_v<T, XRPAmount> || std::is_same_v<T, IOUAmount> ||
std::is_same_v<T, MPTAmount>)
struct AmountType
{
using amount_type = T;
};
template <typename A>
concept AssetType = std::is_convertible_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
/* Used to check for an asset with either badCurrency()
* or MPT with 0 account.
*/
struct BadAsset
{
};
inline BadAsset const&
badAsset()
{
static BadAsset const a;
return a;
}
/* Asset is an abstraction of three different issue types: XRP, IOU, MPT.
* For historical reasons, two issue types XRP and IOU are wrapped in Issue
@@ -26,6 +43,9 @@ class Asset
{
public:
using value_type = std::variant<Issue, MPTIssue>;
using token_type = std::variant<Currency, MPTID>;
using AmtType =
std::variant<AmountType<XRPAmount>, AmountType<IOUAmount>, AmountType<MPTAmount>>;
private:
value_type issue_;
@@ -69,36 +89,42 @@ public:
constexpr value_type const&
value() const;
constexpr token_type
token() const;
void
setJson(Json::Value& jv) const;
STAmount
operator()(Number const&) const;
bool
constexpr AmtType
getAmountType() const;
// Custom, generic visit implementation
template <typename... Visitors>
constexpr auto
visit(Visitors&&... visitors) const -> decltype(auto)
{
// Simple delegation to the reusable utility, passing the internal
// variant data.
return detail::visit(issue_, std::forward<Visitors>(visitors)...);
}
constexpr bool
native() const
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return issue.native();
if constexpr (std::is_same_v<TIss, MPTIssue>)
return false;
},
issue_);
return visit(
[&](Issue const& issue) { return issue.native(); },
[&](MPTIssue const&) { return false; });
}
bool
integral() const
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return issue.native();
if constexpr (std::is_same_v<TIss, MPTIssue>)
return true;
},
issue_);
return visit(
[&](Issue const& issue) { return issue.native(); },
[&](MPTIssue const&) { return true; });
}
friend constexpr bool
@@ -110,6 +136,10 @@ public:
friend constexpr bool
operator==(Currency const& lhs, Asset const& rhs);
// rhs is either badCurrency() or MPT issuer is 0
friend constexpr bool
operator==(BadAsset const& lhs, Asset const& rhs);
/** Return true if both assets refer to the same currency (regardless of
* issuer) or MPT issuance. Otherwise return false.
*/
@@ -117,6 +147,12 @@ public:
equalTokens(Asset const& lhs, Asset const& rhs);
};
template <ValidIssueType TIss>
constexpr bool is_issue_v = std::is_same_v<TIss, Issue>;
template <ValidIssueType TIss>
constexpr bool is_mptissue_v = std::is_same_v<TIss, MPTIssue>;
inline Json::Value
to_json(Asset const& asset)
{
@@ -156,6 +192,29 @@ Asset::value() const
return issue_;
}
constexpr Asset::token_type
Asset::token() const
{
return visit(
[&](Issue const& issue) -> Asset::token_type { return issue.currency; },
[&](MPTIssue const& issue) -> Asset::token_type { return issue.getMptID(); });
}
constexpr Asset::AmtType
Asset::getAmountType() const
{
return visit(
[&](Issue const& issue) -> Asset::AmtType {
constexpr AmountType<XRPAmount> xrp;
constexpr AmountType<IOUAmount> iou;
return native() ? AmtType(xrp) : AmtType(iou);
},
[&](MPTIssue const& issue) -> Asset::AmtType {
constexpr AmountType<MPTAmount> mpt;
return AmtType(mpt);
});
}
constexpr bool
operator==(Asset const& lhs, Asset const& rhs)
{
@@ -177,7 +236,7 @@ operator<=>(Asset const& lhs, Asset const& rhs)
[]<ValidIssueType TLhs, ValidIssueType TRhs>(TLhs const& lhs_, TRhs const& rhs_) {
if constexpr (std::is_same_v<TLhs, TRhs>)
return std::weak_ordering(lhs_ <=> rhs_);
else if constexpr (std::is_same_v<TLhs, Issue> && std::is_same_v<TRhs, MPTIssue>)
else if constexpr (is_issue_v<TLhs> && is_mptissue_v<TRhs>)
return std::weak_ordering::greater;
else
return std::weak_ordering::less;
@@ -189,7 +248,17 @@ operator<=>(Asset const& lhs, Asset const& rhs)
constexpr bool
operator==(Currency const& lhs, Asset const& rhs)
{
return rhs.holds<Issue>() && rhs.get<Issue>().currency == lhs;
return rhs.visit(
[&](Issue const& issue) { return issue.currency == lhs; },
[](MPTIssue const& issue) { return false; });
}
constexpr bool
operator==(BadAsset const&, Asset const& rhs)
{
return rhs.visit(
[](Issue const& issue) -> bool { return badCurrency() == issue.currency; },
[](MPTIssue const& issue) -> bool { return issue.getIssuer() == xrpAccount(); });
}
constexpr bool
@@ -223,4 +292,36 @@ validJSONAsset(Json::Value const& jv);
Asset
assetFromJson(Json::Value const& jv);
Json::Value
to_json(Asset const& asset);
inline bool
isConsistent(Asset const& asset)
{
return asset.visit(
[](Issue const& issue) { return isConsistent(issue); },
[](MPTIssue const&) { return true; });
}
inline bool
validAsset(Asset const& asset)
{
return asset.visit(
[](Issue const& issue) { return isConsistent(issue) && issue.currency != badCurrency(); },
[](MPTIssue const& issue) { return issue.getIssuer() != xrpAccount(); });
}
template <class Hasher>
void
hash_append(Hasher& h, Asset const& r)
{
using beast::hash_append;
r.visit(
[&](Issue const& issue) { hash_append(h, issue); },
[&](MPTIssue const& issue) { hash_append(h, issue); });
}
std::ostream&
operator<<(std::ostream& os, Asset const& x);
} // namespace xrpl

View File

@@ -2,7 +2,7 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Asset.h>
#include <boost/utility/base_from_member.hpp>
@@ -15,15 +15,15 @@ namespace xrpl {
class Book final : public CountedObject<Book>
{
public:
Issue in;
Issue out;
Asset in;
Asset out;
std::optional<uint256> domain;
Book()
{
}
Book(Issue const& in_, Issue const& out_, std::optional<uint256> const& domain_)
Book(Asset const& in_, Asset const& out_, std::optional<uint256> const& domain_)
: in(in_), out(out_), domain(domain_)
{
}
@@ -112,16 +112,67 @@ public:
}
};
template <>
struct hash<xrpl::MPTIssue> : private boost::base_from_member<std::hash<xrpl::MPTID>, 0>
{
private:
using id_hash_type = boost::base_from_member<std::hash<xrpl::MPTID>, 0>;
public:
explicit hash() = default;
using value_type = std::size_t;
using argument_type = xrpl::MPTIssue;
value_type
operator()(argument_type const& value) const
{
value_type const result(id_hash_type::member(value.getMptID()));
return result;
}
};
template <>
struct hash<xrpl::Asset>
{
private:
using value_type = std::size_t;
using argument_type = xrpl::Asset;
using issue_hasher = std::hash<xrpl::Issue>;
using mptissue_hasher = std::hash<xrpl::MPTIssue>;
issue_hasher m_issue_hasher;
mptissue_hasher m_mptissue_hasher;
public:
explicit hash() = default;
value_type
operator()(argument_type const& asset) const
{
return asset.visit(
[&](xrpl::Issue const& issue) {
value_type const result(m_issue_hasher(issue));
return result;
},
[&](xrpl::MPTIssue const& issue) {
value_type const result(m_mptissue_hasher(issue));
return result;
});
}
};
//------------------------------------------------------------------------------
template <>
struct hash<xrpl::Book>
{
private:
using issue_hasher = std::hash<xrpl::Issue>;
using asset_hasher = std::hash<xrpl::Asset>;
using uint256_hasher = xrpl::uint256::hasher;
issue_hasher m_issue_hasher;
asset_hasher m_asset_hasher;
uint256_hasher m_uint256_hasher;
public:
@@ -133,8 +184,8 @@ public:
value_type
operator()(argument_type const& value) const
{
value_type result(m_issue_hasher(value.in));
boost::hash_combine(result, m_issue_hasher(value.out));
value_type result(m_asset_hasher(value.in));
boost::hash_combine(result, m_asset_hasher(value.out));
if (value.domain)
boost::hash_combine(result, m_uint256_hasher(*value.domain));
@@ -159,6 +210,22 @@ struct hash<xrpl::Issue> : std::hash<xrpl::Issue>
// using Base::Base; // inherit ctors
};
template <>
struct hash<xrpl::MPTIssue> : std::hash<xrpl::MPTIssue>
{
explicit hash() = default;
using Base = std::hash<xrpl::MPTIssue>;
};
template <>
struct hash<xrpl::Asset> : std::hash<xrpl::Asset>
{
explicit hash() = default;
using Base = std::hash<xrpl::Asset>;
};
template <>
struct hash<xrpl::Book> : std::hash<xrpl::Book>
{

View File

@@ -0,0 +1,86 @@
#pragma once
#include <xrpl/protocol/UintTypes.h>
#include <type_traits>
namespace xrpl {
class STAmount;
class Asset;
class Issue;
class MPTIssue;
class IOUAmount;
class XRPAmount;
class MPTAmount;
template <typename A>
concept StepAmount =
std::is_same_v<A, XRPAmount> || std::is_same_v<A, IOUAmount> || std::is_same_v<A, MPTAmount>;
template <typename TIss>
concept ValidIssueType = std::is_same_v<TIss, Issue> || std::is_same_v<TIss, MPTIssue>;
template <typename A>
concept AssetType = std::is_convertible_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
template <typename T>
concept ValidPathAsset = (std::is_same_v<T, Currency> || std::is_same_v<T, MPTID>);
template <class TTakerPays, class TTakerGets>
concept ValidTaker =
((std::is_same_v<TTakerPays, IOUAmount> || std::is_same_v<TTakerPays, XRPAmount> ||
std::is_same_v<TTakerPays, MPTAmount>) &&
(std::is_same_v<TTakerGets, IOUAmount> || std::is_same_v<TTakerGets, XRPAmount> ||
std::is_same_v<TTakerGets, MPTAmount>) &&
(!std::is_same_v<TTakerPays, XRPAmount> || !std::is_same_v<TTakerGets, XRPAmount>));
namespace detail {
// This template combines multiple callable objects (lambdas) into a single
// object that std::visit can use for overload resolution.
template <typename... Ts>
struct CombineVisitors : Ts...
{
// Bring all operator() overloads from base classes into this scope.
// It's the mechanism that makes the CombineVisitors struct function
// as a single callable object with multiple overloads.
using Ts::operator()...;
// Perfect forwarding constructor to correctly initialize the base class
// lambdas
constexpr CombineVisitors(Ts&&... ts) : Ts(std::forward<Ts>(ts))...
{
}
};
// This function forces function template argument deduction, which is more
// robust than class template argument deduction (CTAD) via the deduction guide.
template <typename... Ts>
constexpr CombineVisitors<std::decay_t<Ts>...>
make_combine_visitors(Ts&&... ts)
{
// std::decay_t<Ts> is used to remove references/constness from the lambda
// types before they are passed as template arguments to the CombineVisitors
// struct.
return CombineVisitors<std::decay_t<Ts>...>{std::forward<Ts>(ts)...};
}
// This function takes ANY variant and ANY number of visitors, and performs the
// visit. It is the reusable core logic.
template <typename Variant, typename... Visitors>
constexpr auto
visit(Variant&& v, Visitors&&... visitors) -> decltype(auto)
{
// Use the function template helper instead of raw CTAD.
auto visitor_set = make_combine_visitors(std::forward<Visitors>(visitors)...);
// Delegate to std::visit, perfectly forwarding the variant and the visitor
// set.
return std::visit(visitor_set, std::forward<Variant>(v));
}
} // namespace detail
} // namespace xrpl

View File

@@ -187,7 +187,8 @@ enum LedgerEntryType : std::uint16_t {
\
LEDGER_OBJECT(MPToken, \
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002)) \
LSF_FLAG(lsfMPTAuthorized, 0x00000002) \
LSF_FLAG(lsfMPTAMM, 0x00000004)) \
\
LEDGER_OBJECT(Credential, \
LSF_FLAG(lsfAccepted, 0x00010000)) \

View File

@@ -22,11 +22,12 @@ public:
using value_type = std::int64_t;
protected:
value_type value_;
value_type value_{};
public:
MPTAmount() = default;
constexpr MPTAmount(MPTAmount const& other) = default;
constexpr MPTAmount(beast::Zero);
constexpr MPTAmount&
operator=(MPTAmount const& other) = default;
@@ -85,6 +86,11 @@ constexpr MPTAmount::MPTAmount(value_type value) : value_(value)
{
}
constexpr MPTAmount::MPTAmount(beast::Zero)
{
*this = beast::zero;
}
constexpr MPTAmount&
MPTAmount::operator=(beast::Zero)
{
@@ -116,6 +122,14 @@ MPTAmount::value() const
return value_;
}
// Output MPTAmount as just the value.
template <class Char, class Traits>
std::basic_ostream<Char, Traits>&
operator<<(std::basic_ostream<Char, Traits>& os, MPTAmount const& q)
{
return os << q.value();
}
inline std::string
to_string(MPTAmount const& amount)
{

View File

@@ -17,7 +17,14 @@ private:
public:
MPTIssue() = default;
explicit MPTIssue(MPTID const& issuanceID);
MPTIssue(MPTID const& issuanceID);
MPTIssue(std::uint32_t sequence, AccountID const& account);
operator MPTID const&() const
{
return mptID_;
}
AccountID const&
getIssuer() const;
@@ -73,6 +80,47 @@ isXRP(MPTID const&)
return false;
}
inline AccountID
getMPTIssuer(MPTID const& mptid)
{
static_assert(sizeof(MPTID) == (sizeof(std::uint32_t) + sizeof(AccountID)));
// Extract the 20 bytes for the AccountID
std::array<std::uint8_t, sizeof(AccountID)> bytes{};
std::copy_n(mptid.data() + sizeof(std::uint32_t), sizeof(AccountID), bytes.begin());
// bit_cast is a "magic" compiler intrinsic that is
// usually optimized away to nothing in the final assembly.
return std::bit_cast<AccountID>(bytes);
}
// Disallow temporary
inline AccountID const&
getMPTIssuer(MPTID const&&) = delete;
inline AccountID const&
getMPTIssuer(MPTID&&) = delete;
inline MPTID
noMPT()
{
static MPTIssue const mpt{0, noAccount()};
return mpt.getMptID();
}
inline MPTID
badMPT()
{
static MPTIssue const mpt{0, xrpAccount()};
return mpt.getMptID();
}
template <class Hasher>
void
hash_append(Hasher& h, MPTIssue const& r)
{
using beast::hash_append;
hash_append(h, r.getMptID());
}
Json::Value
to_json(MPTIssue const& mptIssue);
@@ -82,4 +130,17 @@ to_string(MPTIssue const& mptIssue);
MPTIssue
mptIssueFromJson(Json::Value const& jv);
std::ostream&
operator<<(std::ostream& os, MPTIssue const& x);
} // namespace xrpl
namespace std {
template <>
struct hash<xrpl::MPTID> : xrpl::MPTID::hasher
{
explicit hash() = default;
};
} // namespace std

View File

@@ -0,0 +1,130 @@
#pragma once
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Concepts.h>
namespace xrpl {
/* Represent STPathElement's asset, which can be Currency or MPTID.
*/
class PathAsset
{
private:
std::variant<Currency, MPTID> easset_;
public:
PathAsset() = default;
// Enables comparing Asset and PathAsset
PathAsset(Asset const& asset);
PathAsset(Currency const& currency) : easset_(currency)
{
}
PathAsset(MPTID const& mpt) : easset_(mpt)
{
}
template <ValidPathAsset T>
constexpr bool
holds() const;
constexpr bool
isXRP() const;
template <ValidPathAsset T>
T const&
get() const;
constexpr std::variant<Currency, MPTID> const&
value() const;
// Custom, generic visit implementation
template <typename... Visitors>
constexpr auto
visit(Visitors&&... visitors) const -> decltype(auto)
{
// Simple delegation to the reusable utility, passing the internal
// variant data.
return detail::visit(easset_, std::forward<Visitors>(visitors)...);
}
friend constexpr bool
operator==(PathAsset const& lhs, PathAsset const& rhs);
};
template <ValidPathAsset PA>
constexpr bool is_currency_v = std::is_same_v<PA, Currency>;
template <ValidPathAsset PA>
constexpr bool is_mptid_v = std::is_same_v<PA, MPTID>;
inline PathAsset::PathAsset(Asset const& asset)
{
asset.visit(
[&](Issue const& issue) { easset_ = issue.currency; },
[&](MPTIssue const& issue) { easset_ = issue.getMptID(); });
}
template <ValidPathAsset T>
constexpr bool
PathAsset::holds() const
{
return std::holds_alternative<T>(easset_);
}
template <ValidPathAsset T>
T const&
PathAsset::get() const
{
if (!holds<T>())
Throw<std::runtime_error>("PathAsset doesn't hold requested asset.");
return std::get<T>(easset_);
}
constexpr std::variant<Currency, MPTID> const&
PathAsset::value() const
{
return easset_;
}
constexpr bool
PathAsset::isXRP() const
{
return visit(
[&](Currency const& currency) { return xrpl::isXRP(currency); },
[](MPTID const&) { return false; });
}
constexpr bool
operator==(PathAsset const& lhs, PathAsset const& rhs)
{
return std::visit(
[]<ValidPathAsset TLhs, ValidPathAsset TRhs>(TLhs const& lhs_, TRhs const& rhs_) {
if constexpr (std::is_same_v<TLhs, TRhs>)
return lhs_ == rhs_;
else
return false;
},
lhs.value(),
rhs.value());
}
template <typename Hasher>
void
hash_append(Hasher& h, PathAsset const& pathAsset)
{
std::visit([&]<ValidPathAsset T>(T const& e) { hash_append(h, e); }, pathAsset.value());
}
inline bool
isXRP(PathAsset const& asset)
{
return asset.isXRP();
}
std::string
to_string(PathAsset const& asset);
std::ostream&
operator<<(std::ostream& os, PathAsset const& x);
} // namespace xrpl

View File

@@ -164,12 +164,9 @@ public:
constexpr TIss const&
get() const;
Issue const&
issue() const;
// These three are deprecated
Currency const&
getCurrency() const;
template <ValidIssueType TIss>
TIss&
get();
AccountID const&
getIssuer() const;
@@ -225,9 +222,6 @@ public:
void
clear(Asset const& asset);
void
setIssuer(AccountID const& uIssuer);
/** Set the Issue for this amount. */
void
setIssue(Asset const& asset);
@@ -466,16 +460,11 @@ STAmount::get() const
return mAsset.get<TIss>();
}
inline Issue const&
STAmount::issue() const
template <ValidIssueType TIss>
TIss&
STAmount::get()
{
return get<Issue>();
}
inline Currency const&
STAmount::getCurrency() const
{
return mAsset.get<Issue>().currency;
return mAsset.get<TIss>();
}
inline AccountID const&
@@ -505,11 +494,13 @@ operator bool() const noexcept
inline STAmount::
operator Number() const
{
if (native())
return xrp();
if (mAsset.holds<MPTIssue>())
return mpt();
return iou();
return asset().visit(
[&](Issue const& issue) -> Number {
if (issue.native())
return xrp();
return iou();
},
[&](MPTIssue const&) -> Number { return mpt(); });
}
inline STAmount&
@@ -568,12 +559,6 @@ STAmount::clear(Asset const& asset)
clear();
}
inline void
STAmount::setIssuer(AccountID const& uIssuer)
{
mAsset.get<Issue>().account = uIssuer;
}
inline STAmount const&
STAmount::value() const noexcept
{

View File

@@ -349,6 +349,8 @@ public:
void
setFieldH128(SField const& field, uint128 const&);
void
setFieldH192(SField const& field, uint192 const&);
void
setFieldH256(SField const& field, uint256 const&);
void
setFieldI32(SField const& field, std::int32_t);

View File

@@ -3,6 +3,8 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/PathAsset.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/UintTypes.h>
@@ -16,7 +18,7 @@ class STPathElement final : public CountedObject<STPathElement>
{
unsigned int mType;
AccountID mAccountID;
Currency mCurrencyID;
PathAsset mAssetID;
AccountID mIssuerID;
bool is_offer_;
@@ -28,8 +30,10 @@ public:
typeAccount = 0x01, // Rippling through an account (vs taking an offer).
typeCurrency = 0x10, // Currency follows.
typeIssuer = 0x20, // Issuer follows.
typeMPT = 0x40, // MPT follows.
typeBoundary = 0xFF, // Boundary between alternate paths.
typeAll = typeAccount | typeCurrency | typeIssuer,
typeAsset = typeCurrency | typeMPT,
typeAll = typeAccount | typeCurrency | typeIssuer | typeMPT,
// Combination of all types.
};
@@ -40,19 +44,19 @@ public:
STPathElement(
std::optional<AccountID> const& account,
std::optional<Currency> const& currency,
std::optional<PathAsset> const& asset,
std::optional<AccountID> const& issuer);
STPathElement(
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer,
bool forceCurrency = false);
bool forceAsset = false);
STPathElement(
unsigned int uType,
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer);
auto
@@ -70,6 +74,12 @@ public:
bool
hasCurrency() const;
bool
hasMPT() const;
bool
hasAsset() const;
bool
isNone() const;
@@ -78,12 +88,21 @@ public:
AccountID const&
getAccountID() const;
PathAsset const&
getPathAsset() const;
Currency const&
getCurrency() const;
MPTID const&
getMPTID() const;
AccountID const&
getIssuerID() const;
bool
isType(Type const& pe) const;
bool
operator==(STPathElement const& t) const;
@@ -118,7 +137,7 @@ public:
emplace_back(Args&&... args);
bool
hasSeen(AccountID const& account, Currency const& currency, AccountID const& issuer) const;
hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const;
Json::Value getJson(JsonOptions) const;
@@ -221,7 +240,7 @@ inline STPathElement::STPathElement() : mType(typeNone), is_offer_(true)
inline STPathElement::STPathElement(
std::optional<AccountID> const& account,
std::optional<Currency> const& currency,
std::optional<PathAsset> const& asset,
std::optional<AccountID> const& issuer)
: mType(typeNone)
{
@@ -238,10 +257,10 @@ inline STPathElement::STPathElement(
mAccountID != noAccount(), "xrpl::STPathElement::STPathElement : account is set");
}
if (currency)
if (asset)
{
mCurrencyID = *currency;
mType |= typeCurrency;
mAssetID = *asset;
mType |= mAssetID.holds<Currency>() ? typeCurrency : typeMPT;
}
if (issuer)
@@ -256,20 +275,20 @@ inline STPathElement::STPathElement(
inline STPathElement::STPathElement(
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer,
bool forceCurrency)
bool forceAsset)
: mType(typeNone)
, mAccountID(account)
, mCurrencyID(currency)
, mAssetID(asset)
, mIssuerID(issuer)
, is_offer_(isXRP(mAccountID))
{
if (!is_offer_)
mType |= typeAccount;
if (forceCurrency || !isXRP(currency))
mType |= typeCurrency;
if (forceAsset || !isXRP(mAssetID))
mType |= asset.holds<Currency>() ? typeCurrency : typeMPT;
if (!isXRP(issuer))
mType |= typeIssuer;
@@ -280,14 +299,19 @@ inline STPathElement::STPathElement(
inline STPathElement::STPathElement(
unsigned int uType,
AccountID const& account,
Currency const& currency,
PathAsset const& asset,
AccountID const& issuer)
: mType(uType)
, mAccountID(account)
, mCurrencyID(currency)
, mAssetID(asset)
, mIssuerID(issuer)
, is_offer_(isXRP(mAccountID))
{
// uType could be assetType; i.e. either Currency or MPTID.
// Get the actual type.
mAssetID.visit(
[&](Currency const&) { mType = mType & (~Type::typeMPT); },
[&](MPTID const&) { mType = mType & (~Type::typeCurrency); });
hash_value_ = get_hash(*this);
}
@@ -309,16 +333,34 @@ STPathElement::isAccount() const
return !isOffer();
}
inline bool
STPathElement::isType(Type const& pe) const
{
return (mType & pe) != 0u;
}
inline bool
STPathElement::hasIssuer() const
{
return getNodeType() & STPathElement::typeIssuer;
return isType(STPathElement::typeIssuer);
}
inline bool
STPathElement::hasCurrency() const
{
return getNodeType() & STPathElement::typeCurrency;
return isType(STPathElement::typeCurrency);
}
inline bool
STPathElement::hasMPT() const
{
return isType(STPathElement::typeMPT);
}
inline bool
STPathElement::hasAsset() const
{
return isType(STPathElement::typeAsset);
}
inline bool
@@ -335,10 +377,22 @@ STPathElement::getAccountID() const
return mAccountID;
}
inline PathAsset const&
STPathElement::getPathAsset() const
{
return mAssetID;
}
inline Currency const&
STPathElement::getCurrency() const
{
return mCurrencyID;
return mAssetID.get<Currency>();
}
inline MPTID const&
STPathElement::getMPTID() const
{
return mAssetID.get<MPTID>();
}
inline AccountID const&
@@ -351,7 +405,7 @@ inline bool
STPathElement::operator==(STPathElement const& t) const
{
return (mType & typeAccount) == (t.mType & typeAccount) && hash_value_ == t.hash_value_ &&
mAccountID == t.mAccountID && mCurrencyID == t.mCurrencyID && mIssuerID == t.mIssuerID;
mAccountID == t.mAccountID && mAssetID == t.mAssetID && mIssuerID == t.mIssuerID;
}
inline bool

View File

@@ -122,6 +122,7 @@ enum TEMcodes : TERUnderlyingType {
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_MPT,
temBAD_WASM,
temTEMP_DISABLED,
};
@@ -213,6 +214,7 @@ enum TERcodes : TERUnderlyingType {
terADDRESS_COLLISION, // Failed to allocate AccountID when trying to
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
};
//------------------------------------------------------------------------------

View File

@@ -16,6 +16,7 @@
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -161,8 +161,10 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
{sfOwner, soeOPTIONAL}, // for owner directories
{sfTakerPaysCurrency, soeOPTIONAL}, // order book directories
{sfTakerPaysIssuer, soeOPTIONAL}, // order book directories
{sfTakerPaysMPT, soeOPTIONAL}, // order book directories
{sfTakerGetsCurrency, soeOPTIONAL}, // order book directories
{sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
{sfTakerGetsMPT, soeOPTIONAL}, // order book directories
{sfExchangeRate, soeOPTIONAL}, // order book directories
{sfIndexes, soeREQUIRED},
{sfRootIndex, soeREQUIRED},

View File

@@ -164,6 +164,8 @@ TYPED_SFIELD(sfTakerGetsIssuer, UINT160, 4)
// 192-bit (common)
TYPED_SFIELD(sfMPTokenIssuanceID, UINT192, 1)
TYPED_SFIELD(sfShareMPTID, UINT192, 2)
TYPED_SFIELD(sfTakerPaysMPT, UINT192, 3)
TYPED_SFIELD(sfTakerGetsMPT, UINT192, 4)
// 256-bit (common)
TYPED_SFIELD(sfLedgerHash, UINT256, 1)

View File

@@ -27,7 +27,7 @@
TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegable,
uint256{},
createAcct,
createAcct | mayCreateMPT,
({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
@@ -132,10 +132,10 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegable,
uint256{},
noPriv,
mayCreateMPT,
({
{sfTakerPays, soeREQUIRED},
{sfTakerGets, soeREQUIRED},
{sfTakerPays, soeREQUIRED, soeMPTSupported},
{sfTakerGets, soeREQUIRED, soeMPTSupported},
{sfExpiration, soeOPTIONAL},
{sfOfferSequence, soeOPTIONAL},
{sfDomainID, soeOPTIONAL},
@@ -242,7 +242,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
noPriv,
({
{sfDestination, soeREQUIRED},
{sfSendMax, soeREQUIRED},
{sfSendMax, soeREQUIRED, soeMPTSupported},
{sfExpiration, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
@@ -255,11 +255,11 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegable,
uint256{},
noPriv,
mayCreateMPT,
({
{sfCheckID, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfDeliverMin, soeOPTIONAL},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfDeliverMin, soeOPTIONAL, soeMPTSupported},
}))
/** This transaction type cancels an existing check. */
@@ -412,12 +412,12 @@ TRANSACTION(ttCLAWBACK, 30, Clawback,
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegable,
featureAMMClawback,
mayDeleteAcct | overrideFreeze,
mayDeleteAcct | overrideFreeze | mayAuthorizeMPT,
({
{sfHolder, soeREQUIRED},
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
}))
/** This transaction type creates an AMM instance */
@@ -427,10 +427,10 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegable,
featureAMM,
createPseudoAcct,
createPseudoAcct | mayCreateMPT,
({
{sfAmount, soeREQUIRED},
{sfAmount2, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
{sfAmount2, soeREQUIRED, soeMPTSupported},
{sfTradingFee, soeREQUIRED},
}))
@@ -443,10 +443,10 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAmount2, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfAmount2, soeOPTIONAL, soeMPTSupported},
{sfEPrice, soeOPTIONAL},
{sfLPTokenOut, soeOPTIONAL},
{sfTradingFee, soeOPTIONAL},
@@ -459,12 +459,12 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegable,
featureAMM,
mayDeleteAcct,
mayDeleteAcct | mayAuthorizeMPT,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAmount, soeOPTIONAL},
{sfAmount2, soeOPTIONAL},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfAmount, soeOPTIONAL, soeMPTSupported},
{sfAmount2, soeOPTIONAL, soeMPTSupported},
{sfEPrice, soeOPTIONAL},
{sfLPTokenIn, soeOPTIONAL},
}))
@@ -478,8 +478,8 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfTradingFee, soeREQUIRED},
}))
@@ -492,8 +492,8 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
featureAMM,
noPriv,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
{sfBidMin, soeOPTIONAL},
{sfBidMax, soeOPTIONAL},
{sfAuthAccounts, soeOPTIONAL},
@@ -506,10 +506,10 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegable,
featureAMM,
mustDeleteAcct,
mustDeleteAcct | mayDeleteMPT,
({
{sfAsset, soeREQUIRED},
{sfAsset2, soeREQUIRED},
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAsset2, soeREQUIRED, soeMPTSupported},
}))
/** This transactions creates a crosschain sequence number */

View File

@@ -404,6 +404,8 @@ JSS(min_ledger); // in: LedgerCleaner
JSS(minimum_fee); // out: TxQ
JSS(minimum_level); // out: TxQ
JSS(missingCommand); // error
JSS(mpt_issuance_id_a); // out: BookChanges
JSS(mpt_issuance_id_b); // out: BookChanges
JSS(name); // out: AmendmentTableImpl, PeerImp
JSS(needed_state_hashes); // out: InboundLedger
JSS(needed_transaction_hashes); // out: InboundLedger

View File

@@ -117,6 +117,30 @@ public:
return this->sle_->isFieldPresent(sfTakerPaysIssuer);
}
/**
* @brief Get sfTakerPaysMPT (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT192::type::value_type>
getTakerPaysMPT() const
{
if (hasTakerPaysMPT())
return this->sle_->at(sfTakerPaysMPT);
return std::nullopt;
}
/**
* @brief Check if sfTakerPaysMPT is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTakerPaysMPT() const
{
return this->sle_->isFieldPresent(sfTakerPaysMPT);
}
/**
* @brief Get sfTakerGetsCurrency (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
@@ -165,6 +189,30 @@ public:
return this->sle_->isFieldPresent(sfTakerGetsIssuer);
}
/**
* @brief Get sfTakerGetsMPT (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT192::type::value_type>
getTakerGetsMPT() const
{
if (hasTakerGetsMPT())
return this->sle_->at(sfTakerGetsMPT);
return std::nullopt;
}
/**
* @brief Check if sfTakerGetsMPT is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTakerGetsMPT() const
{
return this->sle_->isFieldPresent(sfTakerGetsMPT);
}
/**
* @brief Get sfExchangeRate (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
@@ -427,6 +475,17 @@ public:
return *this;
}
/**
* @brief Set sfTakerPaysMPT (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
DirectoryNodeBuilder&
setTakerPaysMPT(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfTakerPaysMPT] = value;
return *this;
}
/**
* @brief Set sfTakerGetsCurrency (soeOPTIONAL)
* @return Reference to this builder for method chaining.
@@ -449,6 +508,17 @@ public:
return *this;
}
/**
* @brief Set sfTakerGetsMPT (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
DirectoryNodeBuilder&
setTakerGetsMPT(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfTakerGetsMPT] = value;
return *this;
}
/**
* @brief Set sfExchangeRate (soeOPTIONAL)
* @return Reference to this builder for method chaining.

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -192,6 +194,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMBidBuilder&
@@ -203,6 +206,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMBidBuilder&

View File

@@ -21,7 +21,7 @@ class AMMClawbackBuilder;
* Type: ttAMM_CLAWBACK (31)
* Delegable: Delegation::delegable
* Amendment: featureAMMClawback
* Privileges: mayDeleteAcct | overrideFreeze
* Privileges: mayDeleteAcct | overrideFreeze | mayAuthorizeMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMClawbackBuilder to construct new transactions.
@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +72,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -82,6 +84,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -166,6 +169,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&
@@ -177,6 +181,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&
@@ -188,6 +193,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMClawbackBuilder&

View File

@@ -21,7 +21,7 @@ class AMMCreateBuilder;
* Type: ttAMM_CREATE (35)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: createPseudoAcct
* Privileges: createPseudoAcct | mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMCreateBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAmount2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -129,6 +131,7 @@ public:
/**
* @brief Set sfAmount (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMCreateBuilder&
@@ -140,6 +143,7 @@ public:
/**
* @brief Set sfAmount2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMCreateBuilder&

View File

@@ -21,7 +21,7 @@ class AMMDeleteBuilder;
* Type: ttAMM_DELETE (40)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: mustDeleteAcct
* Privileges: mustDeleteAcct | mayDeleteMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMDeleteBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -116,6 +118,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDeleteBuilder&
@@ -127,6 +130,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDeleteBuilder&

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +73,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -97,6 +100,7 @@ public:
/**
* @brief Get sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -246,6 +250,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -257,6 +262,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -268,6 +274,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&
@@ -279,6 +286,7 @@ public:
/**
* @brief Set sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMDepositBuilder&

View File

@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -129,6 +131,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMVoteBuilder&
@@ -140,6 +143,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMVoteBuilder&

View File

@@ -21,7 +21,7 @@ class AMMWithdrawBuilder;
* Type: ttAMM_WITHDRAW (37)
* Delegable: Delegation::delegable
* Amendment: featureAMM
* Privileges: mayDeleteAcct
* Privileges: mayDeleteAcct | mayAuthorizeMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use AMMWithdrawBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -71,6 +73,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -97,6 +100,7 @@ public:
/**
* @brief Get sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -220,6 +224,7 @@ public:
/**
* @brief Set sfAsset (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -231,6 +236,7 @@ public:
/**
* @brief Set sfAsset2 (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -242,6 +248,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&
@@ -253,6 +260,7 @@ public:
/**
* @brief Set sfAmount2 (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
AMMWithdrawBuilder&

View File

@@ -21,7 +21,7 @@ class CheckCashBuilder;
* Type: ttCHECK_CASH (17)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: noPriv
* Privileges: mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use CheckCashBuilder to construct new transactions.
@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -86,6 +87,7 @@ public:
/**
* @brief Get sfDeliverMin (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -166,6 +168,7 @@ public:
/**
* @brief Set sfAmount (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCashBuilder&
@@ -177,6 +180,7 @@ public:
/**
* @brief Set sfDeliverMin (soeOPTIONAL)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCashBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfSendMax (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -205,6 +206,7 @@ public:
/**
* @brief Set sfSendMax (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
CheckCreateBuilder&

View File

@@ -21,7 +21,7 @@ class OfferCreateBuilder;
* Type: ttOFFER_CREATE (7)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: noPriv
* Privileges: mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use OfferCreateBuilder to construct new transactions.
@@ -49,6 +49,7 @@ public:
/**
* @brief Get sfTakerPays (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -60,6 +61,7 @@ public:
/**
* @brief Get sfTakerGets (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -194,6 +196,7 @@ public:
/**
* @brief Set sfTakerPays (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
OfferCreateBuilder&
@@ -205,6 +208,7 @@ public:
/**
* @brief Set sfTakerGets (soeREQUIRED)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
OfferCreateBuilder&

View File

@@ -21,7 +21,7 @@ class PaymentBuilder;
* Type: ttPAYMENT (0)
* Delegable: Delegation::delegable
* Amendment: uint256{}
* Privileges: createAcct
* Privileges: createAcct | mayCreateMPT
*
* Immutable wrapper around STTx providing type-safe field access.
* Use PaymentBuilder to construct new transactions.

View File

@@ -398,7 +398,8 @@ using InvariantChecks = std::tuple<
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,
ValidVault>;
ValidVault,
ValidMPTPayment>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -44,6 +44,7 @@ enum Privilege {
mayDeleteMPT = 0x0400, // The transaction MAY delete an MPT object. May not create.
mustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault
mayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault
mayCreateMPT = 0x2000, // The transaction MAY create an MPT object, except for issuer.
};
constexpr Privilege

View File

@@ -28,4 +28,32 @@ public:
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
/** Verify:
* - OutstandingAmount <= MaximumAmount for any MPT
* - OutstandingAmount after = OutstandingAmount before +
* sum (MPT after - MPT before) - this is total MPT credit/debit
*/
class ValidMPTPayment
{
enum Order { Before = 0, After = 1 };
struct MPTData
{
std::array<std::int64_t, After + 1> outstanding{};
// sum (MPT after - MPT before)
std::int64_t mptAmount{0};
};
// true if OutstandingAmount > MaximumAmount in after for any MPT
bool overflow_{false};
// mptid:MPTData
hash_map<uint192, MPTData> data_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -5,12 +5,13 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AMMUtils.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/tx/transactors/dex/AMMContext.h>
namespace xrpl {
template <typename TIn, typename TOut>
template <StepAmount TIn, StepAmount TOut>
class AMMOffer;
/** AMMLiquidity class provides AMM offers to BookStep class.
@@ -35,8 +36,8 @@ private:
AMMContext& ammContext_;
AccountID const ammAccountID_;
std::uint32_t const tradingFee_;
Issue const issueIn_;
Issue const issueOut_;
Asset const assetIn_;
Asset const assetOut_;
// Initial AMM pool balances
TAmounts<TIn, TOut> const initialBalances_;
beast::Journal const j_;
@@ -46,8 +47,8 @@ public:
ReadView const& view,
AccountID const& ammAccountID,
std::uint32_t tradingFee,
Issue const& in,
Issue const& out,
Asset const& in,
Asset const& out,
AMMContext& ammContext,
beast::Journal j);
~AMMLiquidity() = default;
@@ -87,16 +88,16 @@ public:
return ammContext_;
}
Issue const&
issueIn() const
Asset const&
assetIn() const
{
return issueIn_;
return assetIn_;
}
Issue const&
issueOut() const
Asset const&
assetOut() const
{
return issueOut_;
return assetOut_;
}
private:

View File

@@ -3,6 +3,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/TER.h>
@@ -16,7 +17,7 @@ class QualityFunction;
* methods for use in generic BookStep methods. AMMOffer amounts
* are changed indirectly in BookStep limiting steps.
*/
template <typename TIn, typename TOut>
template <StepAmount TIn, StepAmount TOut>
class AMMOffer
{
private:
@@ -52,8 +53,11 @@ public:
return quality_;
}
Issue const&
issueIn() const;
Asset const&
assetIn() const;
Asset const&
assetOut() const;
AccountID const&
owner() const;
@@ -99,7 +103,8 @@ public:
static TER
send(Args&&... args)
{
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::Yes);
return accountSend(
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
}
bool

View File

@@ -3,6 +3,8 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
@@ -12,28 +14,15 @@
namespace xrpl {
template <class TIn, class TOut>
class TOfferBase
{
protected:
Issue issIn_;
Issue issOut_;
};
template <>
class TOfferBase<STAmount, STAmount>
{
public:
explicit TOfferBase() = default;
};
template <class TIn = STAmount, class TOut = STAmount>
class TOffer : private TOfferBase<TIn, TOut>
template <StepAmount TIn, StepAmount TOut>
class TOffer
{
private:
SLE::pointer m_entry;
Quality m_quality{};
AccountID m_account;
Asset assetIn_;
Asset assetOut_;
TAmounts<TIn, TOut> m_amounts{};
void
@@ -113,10 +102,10 @@ public:
return m_entry->key();
}
Issue const&
issueIn() const;
Issue const&
issueOut() const;
Asset const&
assetIn() const;
Asset const&
assetOut() const;
TAmounts<TIn, TOut>
limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const;
@@ -131,8 +120,8 @@ public:
bool
isFunded() const
{
// Offer owner is issuer; they have unlimited funds
return m_account == issueOut().account;
// Offer owner is issuer; they have unlimited funds if IOU
return m_account == assetOut_.getIssuer() && assetOut_.holds<Issue>();
}
static std::pair<std::uint32_t, std::uint32_t>
@@ -167,9 +156,7 @@ public:
}
};
using Offer = TOffer<>;
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount))
{
@@ -177,33 +164,26 @@ TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
auto const tg = m_entry->getFieldAmount(sfTakerGets);
m_amounts.in = toAmount<TIn>(tp);
m_amounts.out = toAmount<TOut>(tg);
this->issIn_ = tp.issue();
this->issOut_ = tg.issue();
assetIn_ = tp.asset();
assetOut_ = tg.asset();
}
template <>
inline TOffer<STAmount, STAmount>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry)
, m_quality(quality)
, m_account(m_entry->getAccountID(sfAccount))
, m_amounts(m_entry->getFieldAmount(sfTakerPays), m_entry->getFieldAmount(sfTakerGets))
{
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
void
TOffer<TIn, TOut>::setFieldAmounts()
{
// LCOV_EXCL_START
#ifdef _MSC_VER
UNREACHABLE("xrpl::TOffer::setFieldAmounts : must be specialized");
#else
static_assert(sizeof(TOut) == -1, "Must be specialized");
#endif
// LCOV_EXCL_STOP
if constexpr (std::is_same_v<TIn, XRPAmount>)
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
else
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, assetIn_));
if constexpr (std::is_same_v<TOut, XRPAmount>)
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
else
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, assetOut_));
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp)
const
@@ -213,7 +193,7 @@ TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const&
return quality().ceil_out_strict(offerAmount, limit, roundUp);
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp)
const
@@ -228,75 +208,29 @@ TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& li
return m_quality.ceil_in(offerAmount, limit);
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)...);
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <>
inline void
TOffer<STAmount, STAmount>::setFieldAmounts()
template <StepAmount TIn, StepAmount TOut>
Asset const&
TOffer<TIn, TOut>::assetIn() const
{
m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
return assetIn_;
}
template <>
inline void
TOffer<IOUAmount, IOUAmount>::setFieldAmounts()
template <StepAmount TIn, StepAmount TOut>
Asset const&
TOffer<TIn, TOut>::assetOut() const
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
return assetOut_;
}
template <>
inline void
TOffer<IOUAmount, XRPAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
}
template <>
inline void
TOffer<XRPAmount, IOUAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueIn() const
{
return this->issIn_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueIn() const
{
return m_amounts.in.issue();
}
template <class TIn, class TOut>
Issue const&
TOffer<TIn, TOut>::issueOut() const
{
return this->issOut_;
}
template <>
inline Issue const&
TOffer<STAmount, STAmount>::issueOut() const
{
return m_amounts.out.issue();
}
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
inline std::ostream&
operator<<(std::ostream& os, TOffer<TIn, TOut> const& offer)
{

View File

@@ -4,6 +4,7 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/tx/paths/BookTip.h>
#include <xrpl/tx/paths/Offer.h>
@@ -11,7 +12,7 @@
namespace xrpl {
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
class TOfferStreamBase
{
public:
@@ -64,6 +65,7 @@ protected:
permRmOffer(uint256 const& offerIndex) = 0;
template <class TTakerPays, class TTakerGets>
requires ValidTaker<TTakerPays, TTakerGets>
bool
shouldRmSmallIncreasedQOffer() const;
@@ -105,33 +107,6 @@ public:
}
};
/** Presents and consumes the offers in an order book.
Two `ApplyView` objects accumulate changes to the ledger. `view`
is applied when the calling transaction succeeds. If the calling
transaction fails, then `view_cancel` is applied.
Certain invalid offers are automatically removed:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful.
*/
class OfferStream : public TOfferStreamBase<STAmount, STAmount>
{
protected:
void
permRmOffer(uint256 const& offerIndex) override;
public:
using TOfferStreamBase<STAmount, STAmount>::TOfferStreamBase;
};
/** Presents and consumes the offers in an order book.
The `view_' ` `ApplyView` accumulates changes to the ledger.
@@ -149,7 +124,7 @@ public:
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
*/
template <class TIn, class TOut>
template <StepAmount TIn, StepAmount TOut>
class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
{
private:

View File

@@ -1,198 +0,0 @@
#pragma once
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
namespace xrpl {
struct AmountSpec
{
explicit AmountSpec() = default;
bool native{};
union
{
XRPAmount xrp;
IOUAmount iou = {};
};
std::optional<AccountID> issuer;
std::optional<Currency> currency;
friend std::ostream&
operator<<(std::ostream& stream, AmountSpec const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
if (amt.currency)
stream << "/(" << *amt.currency << ")";
if (amt.issuer)
stream << "/" << *amt.issuer << "";
return stream;
}
};
struct EitherAmount
{
#ifndef NDEBUG
bool native = false;
#endif
union
{
IOUAmount iou = {};
XRPAmount xrp;
};
EitherAmount() = default;
explicit EitherAmount(IOUAmount const& a) : iou(a)
{
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
// ignore warning about half of iou amount being uninitialized
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
explicit EitherAmount(XRPAmount const& a) : xrp(a)
{
#ifndef NDEBUG
native = true;
#endif
}
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
explicit EitherAmount(AmountSpec const& a)
{
#ifndef NDEBUG
native = a.native;
#endif
if (a.native)
xrp = a.xrp;
else
iou = a.iou;
}
#ifndef NDEBUG
friend std::ostream&
operator<<(std::ostream& stream, EitherAmount const& amt)
{
if (amt.native)
stream << to_string(amt.xrp);
else
stream << to_string(amt.iou);
return stream;
}
#endif
};
template <class T>
T&
get(EitherAmount& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount&
get<IOUAmount>(EitherAmount& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount&
get<XRPAmount>(EitherAmount& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount&) : is XRP");
return amt.xrp;
}
template <class T>
T const&
get(EitherAmount const& amt)
{
static_assert(sizeof(T) == -1, "Must used specialized function");
return T(0);
}
template <>
inline IOUAmount const&
get<IOUAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(!amt.native, "xrpl::get<IOUAmount>(EitherAmount const&) : is not XRP");
return amt.iou;
}
template <>
inline XRPAmount const&
get<XRPAmount>(EitherAmount const& amt)
{
XRPL_ASSERT(amt.native, "xrpl::get<XRPAmount>(EitherAmount const&) : is XRP");
return amt.xrp;
}
inline AmountSpec
toAmountSpec(STAmount const& amt)
{
XRPL_ASSERT(
amt.mantissa() < std::numeric_limits<std::int64_t>::max(),
"xrpl::toAmountSpec(STAmount const&) : maximum mantissa");
bool const isNeg = amt.negative();
std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa();
AmountSpec result;
result.native = isXRP(amt);
if (result.native)
{
result.xrp = XRPAmount(sMant);
}
else
{
result.iou = IOUAmount(sMant, amt.exponent());
result.issuer = amt.issue().account;
result.currency = amt.issue().currency;
}
return result;
}
inline EitherAmount
toEitherAmount(STAmount const& amt)
{
if (isXRP(amt))
return EitherAmount{amt.xrp()};
return EitherAmount{amt.iou()};
}
inline AmountSpec
toAmountSpec(EitherAmount const& ea, std::optional<Currency> const& c)
{
AmountSpec r;
r.native = (!c || isXRP(*c));
r.currency = c;
XRPL_ASSERT(
ea.native == r.native,
"xrpl::toAmountSpec(EitherAmount const&&, std::optional<Currency>) : "
"matching native");
if (r.native)
{
r.xrp = ea.xrp;
}
else
{
r.iou = ea.iou;
}
return r;
}
} // namespace xrpl

View File

@@ -0,0 +1,54 @@
#pragma once
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
struct EitherAmount
{
std::variant<XRPAmount, IOUAmount, MPTAmount> amount;
explicit EitherAmount() = default;
template <StepAmount T>
explicit EitherAmount(T const& a) : amount(a)
{
}
template <StepAmount T>
[[nodiscard]] bool
holds() const
{
return std::holds_alternative<T>(amount);
}
template <StepAmount T>
[[nodiscard]] T const&
get() const
{
if (!holds<T>())
Throw<std::logic_error>("EitherAmount doesn't hold requested amount");
return std::get<T>(amount);
}
#ifndef NDEBUG
friend std::ostream&
operator<<(std::ostream& stream, EitherAmount const& amt)
{
std::visit([&]<StepAmount T>(T const& a) { stream << to_string(a); }, amt.amount);
return stream;
}
#endif
};
template <StepAmount T>
T const&
get(EitherAmount const& amt)
{
return amt.get<T>();
}
} // namespace xrpl

View File

@@ -208,14 +208,14 @@ struct FlowDebugInfo
auto writeXrpAmtList = [&write_list](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.xrp);
return xrpl::to_string(a.get<XRPAmount>());
};
write_list(amts, get_val, delim);
};
auto writeIouAmtList = [&write_list](
std::vector<EitherAmount> const& amts, char delim = ';') {
auto get_val = [](EitherAmount const& a) -> std::string {
return xrpl::to_string(a.iou);
return xrpl::to_string(a.get<IOUAmount>());
};
write_list(amts, get_val, delim);
};

View File

@@ -50,8 +50,7 @@ checkFreeze(
if (!sleAmm)
return tecINTERNAL; // LCOV_EXCL_LINE
if (isLPTokenFrozen(
view, src, (*sleAmm)[sfAsset].get<Issue>(), (*sleAmm)[sfAsset2].get<Issue>()))
if (isLPTokenFrozen(view, src, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
{
return terNO_LINE;
}

View File

@@ -2,11 +2,11 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/QualityFunction.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <xrpl/tx/paths/detail/EitherAmount.h>
#include <boost/container/flat_set.hpp>
@@ -44,6 +44,7 @@ issues(DebtDirection dir)
BookStepIX is an IOU/XRP offer book
BookStepXI is an XRP/IOU offer book
XRPEndpointStep is the source or destination account for XRP
MPTEndpointStep is the source or destination account for MPT
Amounts may be transformed through a step in either the forward or the
reverse direction. In the forward direction, the function `fwd` is used to
@@ -339,8 +340,8 @@ std::pair<TER, STPath>
normalizePath(
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Issue> const& sendMaxIssue,
Asset const& deliver,
std::optional<Asset> const& sendMaxAsset,
STPath const& path);
/**
@@ -355,7 +356,7 @@ normalizePath(
optimization. If, during direct offer crossing, the
quality of the tip of the book drops below this value,
then evaluating the strand can stop.
@param sendMaxIssue Optional asset to send.
@param sendMaxAsset Optional asset to send.
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
@@ -372,9 +373,9 @@ toStrand(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
Asset const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMaxIssue,
std::optional<Asset> const& sendMaxAsset,
STPath const& path,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
@@ -413,9 +414,9 @@ toStrands(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
Asset const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMax,
std::optional<Asset> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
bool ownerPaysTransferFee,
@@ -425,7 +426,7 @@ toStrands(
beast::Journal j);
/// @cond INTERNAL
template <class TIn, class TOut, class TDerived>
template <StepAmount TIn, StepAmount TOut, class TDerived>
struct StepImp : public Step
{
explicit StepImp() = default;
@@ -493,8 +494,16 @@ public:
// Check equal with tolerance
bool
checkNear(IOUAmount const& expected, IOUAmount const& actual);
bool
checkNear(XRPAmount const& expected, XRPAmount const& actual);
inline bool
checkNear(MPTAmount const& expected, MPTAmount const& actual)
{
return expected == actual;
}
inline bool
checkNear(XRPAmount const& expected, XRPAmount const& actual)
{
return expected == actual;
}
/// @endcond
/**
@@ -505,7 +514,7 @@ struct StrandContext
ReadView const& view; ///< Current ReadView
AccountID const strandSrc; ///< Strand source account
AccountID const strandDst; ///< Strand destination account
Issue const strandDeliver; ///< Issue strand delivers
Asset const strandDeliver; ///< Asset strand delivers
std::optional<Quality> const limitQuality; ///< Worst accepted quality
bool const isFirst; ///< true if Step is first in Strand
bool const isLast = false; ///< true if Step is last in Strand
@@ -522,11 +531,11 @@ struct StrandContext
at most twice: once as a src and once as a dst (hence the two element
array). The strandSrc and strandDst will only show up once each.
*/
std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues;
std::array<boost::container::flat_set<Asset>, 2>& seenDirectAssets;
/** A strand may not include an offer that output the same issue more
than once
*/
boost::container::flat_set<Issue>& seenBookOuts;
boost::container::flat_set<Asset>& seenBookOuts;
AMMContext& ammContext;
std::optional<uint256> domainID; // the domain the order book will use
beast::Journal const j;
@@ -539,15 +548,15 @@ struct StrandContext
// replicates the source or destination.
AccountID const& strandSrc_,
AccountID const& strandDst_,
Issue const& strandDeliver_,
Asset const& strandDeliver_,
std::optional<Quality> const& limitQuality_,
bool isLast_,
bool ownerPaysTransferFee_,
OfferCrossing offerCrossing_,
bool isDefaultPath_,
std::array<boost::container::flat_set<Issue>, 2>&
seenDirectIssues_, ///< For detecting currency loops
boost::container::flat_set<Issue>& seenBookOuts_, ///< For detecting book loops
std::array<boost::container::flat_set<Asset>, 2>&
seenDirectAssets_, ///< For detecting currency loops
boost::container::flat_set<Asset>& seenBookOuts_, ///< For detecting book loops
AMMContext& ammContext_,
std::optional<uint256> const& domainID,
beast::Journal j_); ///< Journal for logging
@@ -563,6 +572,13 @@ directStepEqual(
AccountID const& dst,
Currency const& currency);
bool
mptEndpointStepEqual(
Step const& step,
AccountID const& src,
AccountID const& dst,
MPTID const& mptid);
bool
xrpEndpointStepEqual(Step const& step, AccountID const& acc);
@@ -577,6 +593,13 @@ make_DirectStepI(
AccountID const& dst,
Currency const& c);
std::pair<TER, std::unique_ptr<Step>>
make_MPTEndpointStep(
StrandContext const& ctx,
AccountID const& src,
AccountID const& dst,
MPTID const& a);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out);
@@ -589,9 +612,30 @@ make_BookStepXI(StrandContext const& ctx, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_XRPEndpointStep(StrandContext const& ctx, AccountID const& acc);
template <class InAmt, class OutAmt>
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMM(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMX(StrandContext const& ctx, MPTIssue const& in);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepXM(StrandContext const& ctx, MPTIssue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepMI(StrandContext const& ctx, MPTIssue const& in, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIM(StrandContext const& ctx, Issue const& in, MPTIssue const& out);
template <StepAmount InAmt, StepAmount OutAmt>
bool
isDirectXrpToXrp(Strand const& strand);
isDirectXrpToXrp(Strand const& strand)
{
if constexpr (std::is_same_v<InAmt, XRPAmount> && std::is_same_v<OutAmt, XRPAmount>)
return strand.size() == 2;
else
return false;
}
/// @endcond
} // namespace xrpl

View File

@@ -246,7 +246,7 @@ flow(
EitherAmount stepIn(*strand[0]->cachedIn());
for (auto i = 0; i < s; ++i)
{
bool valid;
bool valid = false;
std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
if (!valid)
{
@@ -379,8 +379,10 @@ limitOut(
return XRPAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
return IOUAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, MPTAmount>)
return MPTAmount{*out};
else
return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()};
return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
}();
// A tiny difference could be due to the round off
if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
@@ -534,7 +536,7 @@ public:
@return Actual amount in and out from the strands, errors, and payment
sandbox
*/
template <class TInAmt, class TOutAmt>
template <StepAmount TInAmt, StepAmount TOutAmt>
FlowResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(xrpl::PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,6 +13,9 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);

View File

@@ -224,7 +224,7 @@ private:
AccountID const& ammAccount,
STAmount const& amount,
STAmount const& amount2,
Issue const& lptIssue,
Asset const& lptIssue,
std::uint16_t tfee);
};

View File

@@ -98,7 +98,8 @@ public:
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHanding,
FreezeHandling freezeHandling,
AuthHandling authHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
@@ -132,6 +133,7 @@ public:
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHandling,
AuthHandling authHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
@@ -141,8 +143,8 @@ public:
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
Asset const& asset1,
Asset const& asset2,
beast::Journal const& journal);
private:

View File

@@ -51,7 +51,7 @@ private:
ApplyFlags const flags,
AccountID const id,
beast::Journal const j,
Issue const& issue);
Asset const& asset);
// Use the payment flow code to perform offer crossing.
std::pair<TER, Amounts>