mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-10 14:02:28 +00:00
Compare commits
12 Commits
bthomee/pi
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a2c0e7d32 | ||
|
|
77df21b485 | ||
|
|
63f270f00e | ||
|
|
0a896b7957 | ||
|
|
12eb050bf8 | ||
|
|
d26d93130e | ||
|
|
a91e8ddad7 | ||
|
|
56c9d1d497 | ||
|
|
d52dd29d20 | ||
|
|
7793b5f10b | ||
|
|
dfcad69155 | ||
|
|
6d1a5be8d2 |
@@ -44,6 +44,10 @@ suggestWords:
|
||||
words:
|
||||
- abempty
|
||||
- AMMID
|
||||
- AMMMPT
|
||||
- AMMMPToken
|
||||
- AMMMPTokens
|
||||
- AMMXRP
|
||||
- amt
|
||||
- amts
|
||||
- asnode
|
||||
@@ -96,6 +100,7 @@ words:
|
||||
- distro
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- enabled
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- Falco
|
||||
@@ -148,6 +153,8 @@ words:
|
||||
- ltype
|
||||
- mcmodel
|
||||
- MEMORYSTATUSEX
|
||||
- MPTAMM
|
||||
- MPTDEX
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
@@ -157,6 +164,7 @@ words:
|
||||
- mptid
|
||||
- mptissuance
|
||||
- mptissuanceid
|
||||
- mptissue
|
||||
- mptoken
|
||||
- mptokenid
|
||||
- mptokenissuance
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -11,6 +16,7 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -36,7 +42,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 +130,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 +202,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 +269,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 +338,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 +367,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 +452,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 +461,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 +515,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 +529,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 +623,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);
|
||||
@@ -712,4 +719,94 @@ adjustFracByTokens(
|
||||
STAmount const& tokens,
|
||||
Number const& frac);
|
||||
|
||||
/** Get AMM pool balances.
|
||||
*/
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
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
|
||||
* provided then they are used as the AMM token pair issues.
|
||||
* Otherwise the missing issues are fetched from ammSle.
|
||||
*/
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get the balance of LP tokens.
|
||||
*/
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM trading fee for the given account. The fee is discounted
|
||||
* if the account is the auction slot owner or one of the slot's authorized
|
||||
* accounts.
|
||||
*/
|
||||
std::uint16_t
|
||||
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, Asset const& asset);
|
||||
|
||||
/** Delete trustlines to AMM. If all trustlines are deleted then
|
||||
* AMM object and account are deleted. Otherwise tecINCOMPLETE is returned.
|
||||
*/
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j);
|
||||
|
||||
/** Initialize Auction and Voting slots and set the trading/discounted fee.
|
||||
*/
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Return true if the Liquidity Provider is the only AMM provider, false
|
||||
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
|
||||
* for instance Liquidity Provider has more than one LPToken trustline.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
|
||||
|
||||
/** Due to rounding, the LPTokenBalance of the last LP might
|
||||
* not match the LP's trustline balance. If it's within the tolerance,
|
||||
* update LPTokenBalance to match the LP's trustline balance.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ReadView;
|
||||
class ApplyView;
|
||||
class Sandbox;
|
||||
class NetClock;
|
||||
|
||||
/** Get AMM pool balances.
|
||||
*/
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Issue const& issue1,
|
||||
Issue const& issue2,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM pool and LP token balances. If both optIssue are
|
||||
* provided then they are used as the AMM token pair issues.
|
||||
* Otherwise the missing issues are fetched from ammSle.
|
||||
*/
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Issue> const& optIssue1,
|
||||
std::optional<Issue> const& optIssue2,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get the balance of LP tokens.
|
||||
*/
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Currency const& cur1,
|
||||
Currency const& cur2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j);
|
||||
|
||||
/** Get AMM trading fee for the given account. The fee is discounted
|
||||
* if the account is the auction slot owner or one of the slot's authorized
|
||||
* accounts.
|
||||
*/
|
||||
std::uint16_t
|
||||
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);
|
||||
|
||||
/** 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);
|
||||
|
||||
/** Initialize Auction and Voting slots and set the trading/discounted fee.
|
||||
*/
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Issue const& lptIssue,
|
||||
std::uint16_t tfee);
|
||||
|
||||
/** Return true if the Liquidity Provider is the only AMM provider, false
|
||||
* otherwise. Return tecINTERNAL if encountered an unexpected condition,
|
||||
* for instance Liquidity Provider has more than one LPToken trustline.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount);
|
||||
|
||||
/** Due to rounding, the LPTokenBalance of the last LP might
|
||||
* not match the LP's trustline balance. If it's within the tolerance,
|
||||
* update LPTokenBalance to match the LP's trustline balance.
|
||||
*/
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,21 @@ 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);
|
||||
|
||||
/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& v,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
@@ -164,4 +180,60 @@ 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);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -51,9 +54,15 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
@@ -176,6 +185,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 +276,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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
86
include/xrpl/protocol/Concepts.h
Normal file
86
include/xrpl/protocol/Concepts.h
Normal 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
|
||||
@@ -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)) \
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
130
include/xrpl/protocol/PathAsset.h
Normal file
130
include/xrpl/protocol/PathAsset.h
Normal 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
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -121,6 +121,7 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
temBAD_MPT,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -208,6 +209,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
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -342,10 +344,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecLIMIT_EXCEEDED = 195,
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
// DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for
|
||||
// backward compatibility with historical data on non-prod networks, can be
|
||||
// reclaimed after those networks reset.
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
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)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -159,6 +159,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)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
Delegation::delegable,
|
||||
uint256{},
|
||||
createAcct,
|
||||
createAcct | mayCreateMPT,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
@@ -129,10 +129,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},
|
||||
@@ -239,7 +239,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
|
||||
noPriv,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED, soeMPTSupported},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfInvoiceID, soeOPTIONAL},
|
||||
@@ -252,11 +252,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. */
|
||||
@@ -409,12 +409,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 */
|
||||
@@ -424,10 +424,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},
|
||||
}))
|
||||
|
||||
@@ -440,10 +440,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},
|
||||
@@ -456,12 +456,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},
|
||||
}))
|
||||
@@ -475,8 +475,8 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
|
||||
featureAMM,
|
||||
noPriv,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED, soeMPTSupported},
|
||||
{sfAsset2, soeREQUIRED, soeMPTSupported},
|
||||
{sfTradingFee, soeREQUIRED},
|
||||
}))
|
||||
|
||||
@@ -489,8 +489,8 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
|
||||
featureAMM,
|
||||
noPriv,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED, soeMPTSupported},
|
||||
{sfAsset2, soeREQUIRED, soeMPTSupported},
|
||||
{sfBidMin, soeOPTIONAL},
|
||||
{sfBidMax, soeOPTIONAL},
|
||||
{sfAuthAccounts, soeOPTIONAL},
|
||||
@@ -503,10 +503,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 */
|
||||
|
||||
@@ -401,6 +401,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -398,7 +398,9 @@ using InvariantChecks = std::tuple<
|
||||
ValidPseudoAccounts,
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault>;
|
||||
ValidVault,
|
||||
ValidMPTPayment,
|
||||
ValidMPTTransfer>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,4 +28,50 @@ 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&);
|
||||
};
|
||||
|
||||
class ValidMPTTransfer
|
||||
{
|
||||
struct Value
|
||||
{
|
||||
std::optional<std::uint64_t> amtBefore;
|
||||
std::optional<std::uint64_t> amtAfter;
|
||||
};
|
||||
// MPTID: {holder: Value}
|
||||
hash_map<uint192, hash_map<AccountID, Value>> amount_;
|
||||
|
||||
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
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#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 +35,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 +46,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 +87,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
54
include/xrpl/tx/paths/detail/EitherAmount.h
Normal file
54
include/xrpl/tx/paths/detail/EitherAmount.h
Normal 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
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,6 +13,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(xrpl::PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
AccountID const& ammAccount,
|
||||
STAmount const& amount,
|
||||
STAmount const& amount2,
|
||||
Issue const& lptIssue,
|
||||
Asset const& lptIssue,
|
||||
std::uint16_t tfee);
|
||||
};
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -43,13 +43,14 @@ AcceptedLedgerTx::AcceptedLedgerTx(
|
||||
auto const amount = mTxn->getFieldAmount(sfTakerGets);
|
||||
|
||||
// If the offer create is not self funded then add the owner balance
|
||||
if (account != amount.issue().account)
|
||||
if (account != amount.getIssuer())
|
||||
{
|
||||
auto const ownerFunds = accountFunds(
|
||||
*ledger,
|
||||
account,
|
||||
amount,
|
||||
fhIGNORE_FREEZE,
|
||||
ahIGNORE_AUTH,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText();
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
|
||||
auto
|
||||
DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency const& c) -> Key
|
||||
DeferredCredits::makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& c) -> KeyIOU
|
||||
{
|
||||
if (a1 < a2)
|
||||
{
|
||||
@@ -19,21 +21,23 @@ DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency cons
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::credit(
|
||||
DeferredCredits::creditIOU(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& amount,
|
||||
STAmount const& preCreditSenderBalance)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sender != receiver, "xrpl::detail::DeferredCredits::credit : sender is not receiver");
|
||||
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::credit : positive amount");
|
||||
sender != receiver, "xrpl::detail::DeferredCredits::creditIOU : sender is not receiver");
|
||||
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditIOU : positive amount");
|
||||
XRPL_ASSERT(
|
||||
amount.holds<Issue>(), "xrpl::detail::DeferredCredits::creditIOU : amount is for Issue");
|
||||
|
||||
auto const k = makeKey(sender, receiver, amount.getCurrency());
|
||||
auto i = credits_.find(k);
|
||||
if (i == credits_.end())
|
||||
auto const k = makeKeyIOU(sender, receiver, amount.get<Issue>().currency);
|
||||
auto i = creditsIOU_.find(k);
|
||||
if (i == creditsIOU_.end())
|
||||
{
|
||||
Value v;
|
||||
ValueIOU v;
|
||||
|
||||
if (sender < receiver)
|
||||
{
|
||||
@@ -48,7 +52,7 @@ DeferredCredits::credit(
|
||||
v.lowAcctOrigBalance = -preCreditSenderBalance;
|
||||
}
|
||||
|
||||
credits_[k] = v;
|
||||
creditsIOU_[k] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,6 +69,93 @@ DeferredCredits::credit(
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::creditMPT(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& amount,
|
||||
std::uint64_t preCreditBalanceHolder,
|
||||
std::int64_t preCreditBalanceIssuer)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
amount.holds<MPTIssue>(),
|
||||
"xrpl::detail::DeferredCredits::creditMPT : amount is for MPTIssue");
|
||||
XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditMPT : positive amount");
|
||||
XRPL_ASSERT(
|
||||
sender != receiver, "xrpl::detail::DeferredCredits::creditMPT : sender is not receiver");
|
||||
|
||||
auto const mptAmtVal = amount.mpt().value();
|
||||
auto const& issuer = amount.getIssuer();
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
bool const isSenderIssuer = sender == issuer;
|
||||
|
||||
auto i = creditsMPT_.find(mptID);
|
||||
if (i == creditsMPT_.end())
|
||||
{
|
||||
IssuerValueMPT v;
|
||||
if (isSenderIssuer)
|
||||
{
|
||||
v.credit = mptAmtVal;
|
||||
v.holders[receiver].origBalance = preCreditBalanceHolder;
|
||||
}
|
||||
else
|
||||
{
|
||||
v.holders[sender].debit = mptAmtVal;
|
||||
v.holders[sender].origBalance = preCreditBalanceHolder;
|
||||
}
|
||||
v.origBalance = preCreditBalanceIssuer;
|
||||
creditsMPT_.emplace(mptID, std::move(v));
|
||||
}
|
||||
else
|
||||
{
|
||||
// only record the balance the first time, do not record it here
|
||||
auto& v = i->second;
|
||||
if (isSenderIssuer)
|
||||
{
|
||||
v.credit += mptAmtVal;
|
||||
if (!v.holders.contains(receiver))
|
||||
{
|
||||
v.holders[receiver].origBalance = preCreditBalanceHolder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!v.holders.contains(sender))
|
||||
{
|
||||
v.holders[sender].debit = mptAmtVal;
|
||||
v.holders[sender].origBalance = preCreditBalanceHolder;
|
||||
}
|
||||
else
|
||||
{
|
||||
v.holders[sender].debit += mptAmtVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::issuerSelfDebitMPT(
|
||||
MPTIssue const& issue,
|
||||
std::uint64_t amount,
|
||||
std::int64_t origBalance)
|
||||
{
|
||||
auto const& mptID = issue.getMptID();
|
||||
auto i = creditsMPT_.find(mptID);
|
||||
|
||||
if (i == creditsMPT_.end())
|
||||
{
|
||||
IssuerValueMPT v;
|
||||
v.origBalance = origBalance;
|
||||
v.selfDebit = amount;
|
||||
creditsMPT_.emplace(mptID, std::move(v));
|
||||
}
|
||||
else
|
||||
{
|
||||
i->second.selfDebit += amount;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next)
|
||||
{
|
||||
@@ -88,16 +179,16 @@ DeferredCredits::ownerCount(AccountID const& id) const
|
||||
|
||||
// Get the adjustments for the balance between main and other.
|
||||
auto
|
||||
DeferredCredits::adjustments(
|
||||
DeferredCredits::adjustmentsIOU(
|
||||
AccountID const& main,
|
||||
AccountID const& other,
|
||||
Currency const& currency) const -> std::optional<Adjustment>
|
||||
Currency const& currency) const -> std::optional<AdjustmentIOU>
|
||||
{
|
||||
std::optional<Adjustment> result;
|
||||
std::optional<AdjustmentIOU> result;
|
||||
|
||||
Key const k = makeKey(main, other, currency);
|
||||
auto i = credits_.find(k);
|
||||
if (i == credits_.end())
|
||||
KeyIOU const k = makeKeyIOU(main, other, currency);
|
||||
auto i = creditsIOU_.find(k);
|
||||
if (i == creditsIOU_.end())
|
||||
return result;
|
||||
|
||||
auto const& v = i->second;
|
||||
@@ -112,12 +203,21 @@ DeferredCredits::adjustments(
|
||||
return result;
|
||||
}
|
||||
|
||||
auto
|
||||
DeferredCredits::adjustmentsMPT(xrpl::MPTID const& mptID) const -> std::optional<AdjustmentMPT>
|
||||
{
|
||||
auto i = creditsMPT_.find(mptID);
|
||||
if (i == creditsMPT_.end())
|
||||
return std::nullopt;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::apply(DeferredCredits& to)
|
||||
{
|
||||
for (auto const& i : credits_)
|
||||
for (auto const& i : creditsIOU_)
|
||||
{
|
||||
auto r = to.credits_.emplace(i);
|
||||
auto r = to.creditsIOU_.emplace(i);
|
||||
if (!r.second)
|
||||
{
|
||||
auto& toVal = r.first->second;
|
||||
@@ -128,6 +228,30 @@ DeferredCredits::apply(DeferredCredits& to)
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& i : creditsMPT_)
|
||||
{
|
||||
auto r = to.creditsMPT_.emplace(i);
|
||||
if (!r.second)
|
||||
{
|
||||
auto& toVal = r.first->second;
|
||||
auto const& fromVal = i.second;
|
||||
toVal.credit += fromVal.credit;
|
||||
toVal.selfDebit += fromVal.selfDebit;
|
||||
for (auto& [k, v] : fromVal.holders)
|
||||
{
|
||||
if (toVal.holders.find(k) == toVal.holders.end())
|
||||
{
|
||||
toVal.holders[k] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
toVal.holders[k].debit += v.debit;
|
||||
}
|
||||
}
|
||||
// Do not update the orig balance, it's already correct
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& i : ownerCounts_)
|
||||
{
|
||||
auto r = to.ownerCounts_.emplace(i);
|
||||
@@ -143,11 +267,13 @@ DeferredCredits::apply(DeferredCredits& to)
|
||||
} // namespace detail
|
||||
|
||||
STAmount
|
||||
PaymentSandbox::balanceHook(
|
||||
PaymentSandbox::balanceHookIOU(
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
STAmount const& amount) const
|
||||
{
|
||||
XRPL_ASSERT(amount.holds<Issue>(), "balanceHookIOU: amount is for Issue");
|
||||
|
||||
/*
|
||||
There are two algorithms here. The pre-switchover algorithm takes the
|
||||
current amount and subtracts the recorded credits. The post-switchover
|
||||
@@ -159,14 +285,14 @@ PaymentSandbox::balanceHook(
|
||||
magnitudes, (B+C)-C may not equal B.
|
||||
*/
|
||||
|
||||
auto const currency = amount.getCurrency();
|
||||
auto const& currency = amount.get<Issue>().currency;
|
||||
|
||||
auto delta = amount.zeroed();
|
||||
auto lastBal = amount;
|
||||
auto minBal = amount;
|
||||
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
|
||||
{
|
||||
if (auto adj = curSB->tab_.adjustments(account, issuer, currency))
|
||||
if (auto adj = curSB->tab_.adjustmentsIOU(account, issuer, currency))
|
||||
{
|
||||
delta += adj->debits;
|
||||
lastBal = adj->origBalance;
|
||||
@@ -180,13 +306,13 @@ PaymentSandbox::balanceHook(
|
||||
// to compute usable balance just slightly above what the ledger
|
||||
// calculates (but always less than the actual balance).
|
||||
auto adjustedAmt = std::min({amount, lastBal - delta, minBal});
|
||||
adjustedAmt.setIssuer(amount.getIssuer());
|
||||
adjustedAmt.get<Issue>().account = amount.getIssuer();
|
||||
|
||||
if (isXRP(issuer) && adjustedAmt < beast::zero)
|
||||
{
|
||||
// A calculated negative XRP balance is not an error case. Consider a
|
||||
// payment snippet that credits a large XRP amount and then debits the
|
||||
// same amount. The credit can't be used but we subtract the debit and
|
||||
// same amount. The credit can't be used, but we subtract the debit and
|
||||
// calculate a negative value. It's not an error case.
|
||||
adjustedAmt.clear();
|
||||
}
|
||||
@@ -194,6 +320,64 @@ PaymentSandbox::balanceHook(
|
||||
return adjustedAmt;
|
||||
}
|
||||
|
||||
STAmount
|
||||
PaymentSandbox::balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount)
|
||||
const
|
||||
{
|
||||
auto const& issuer = issue.getIssuer();
|
||||
bool const accountIsHolder = account != issuer;
|
||||
|
||||
std::int64_t delta = 0;
|
||||
std::int64_t lastBal = amount;
|
||||
std::int64_t minBal = amount;
|
||||
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
|
||||
{
|
||||
if (auto adj = curSB->tab_.adjustmentsMPT(issue))
|
||||
{
|
||||
if (accountIsHolder)
|
||||
{
|
||||
if (auto const i = adj->holders.find(account); i != adj->holders.end())
|
||||
{
|
||||
delta += i->second.debit;
|
||||
lastBal = i->second.origBalance;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
delta += adj->credit;
|
||||
lastBal = adj->origBalance;
|
||||
}
|
||||
minBal = std::min(lastBal, minBal);
|
||||
}
|
||||
}
|
||||
|
||||
// The adjusted amount should never be larger than the balance.
|
||||
|
||||
auto const adjustedAmt = std::min({amount, lastBal - delta, minBal});
|
||||
|
||||
return adjustedAmt > 0 ? STAmount{issue, adjustedAmt} : STAmount{issue};
|
||||
}
|
||||
|
||||
STAmount
|
||||
PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_t amount) const
|
||||
{
|
||||
std::int64_t selfDebited = 0;
|
||||
std::int64_t lastBal = amount;
|
||||
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
|
||||
{
|
||||
if (auto adj = curSB->tab_.adjustmentsMPT(issue))
|
||||
{
|
||||
selfDebited += adj->selfDebit;
|
||||
lastBal = adj->origBalance;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastBal > selfDebited)
|
||||
return STAmount{issue, lastBal - selfDebited};
|
||||
|
||||
return STAmount{issue};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const
|
||||
{
|
||||
@@ -207,13 +391,39 @@ PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) co
|
||||
}
|
||||
|
||||
void
|
||||
PaymentSandbox::creditHook(
|
||||
PaymentSandbox::creditHookIOU(
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
STAmount const& preCreditBalance)
|
||||
{
|
||||
tab_.credit(from, to, amount, preCreditBalance);
|
||||
XRPL_ASSERT(amount.holds<Issue>(), "creditHookIOU: amount is for Issue");
|
||||
|
||||
tab_.creditIOU(from, to, amount, preCreditBalance);
|
||||
}
|
||||
|
||||
void
|
||||
PaymentSandbox::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");
|
||||
|
||||
tab_.creditMPT(from, to, amount, preCreditBalanceHolder, preCreditBalanceIssuer);
|
||||
}
|
||||
|
||||
void
|
||||
PaymentSandbox::issuerSelfDebitHookMPT(
|
||||
MPTIssue const& issue,
|
||||
std::uint64_t amount,
|
||||
std::int64_t origBalance)
|
||||
{
|
||||
XRPL_ASSERT(amount > 0, "PaymentSandbox::issuerSelfDebitHookMPT: amount must be > 0");
|
||||
|
||||
tab_.issuerSelfDebitMPT(issue, amount, origBalance);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -346,7 +556,7 @@ PaymentSandbox::balanceChanges(ReadView const& view) const
|
||||
}
|
||||
// The following are now set, put them in the map
|
||||
auto delta = newBalance - oldBalance;
|
||||
auto const cur = newBalance.getCurrency();
|
||||
auto const cur = newBalance.get<Issue>().currency;
|
||||
result[std::make_tuple(lowID, highID, cur)] = delta;
|
||||
auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
|
||||
if (r.second)
|
||||
|
||||
@@ -84,11 +84,10 @@ bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& asset,
|
||||
Issue const& asset2)
|
||||
Asset const& asset,
|
||||
Asset const& asset2)
|
||||
{
|
||||
return isFrozen(view, account, asset.currency, asset.account) ||
|
||||
isFrozen(view, account, asset2.currency, asset2.account);
|
||||
return isFrozen(view, account, asset) || isFrozen(view, account, asset2);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -334,22 +333,19 @@ withdrawToDestExceedsLimit(
|
||||
if (from == to || to == issuer || isXRP(issuer))
|
||||
return tesSUCCESS;
|
||||
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
return amount.asset().visit(
|
||||
[&](Issue const& issue) -> TER {
|
||||
auto const& currency = issue.currency;
|
||||
auto const owed = creditBalance(view, to, issuer, currency);
|
||||
if (owed <= beast::zero)
|
||||
{
|
||||
auto const& currency = issue.currency;
|
||||
auto const owed = creditBalance(view, to, issuer, currency);
|
||||
if (owed <= beast::zero)
|
||||
{
|
||||
auto const limit = creditLimit(view, to, issuer, currency);
|
||||
if (-owed >= limit || amount > (limit + owed))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
auto const limit = creditLimit(view, to, issuer, currency);
|
||||
if (-owed >= limit || amount > (limit + owed))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
},
|
||||
amount.asset().value());
|
||||
[](MPTIssue const&) -> TER { return tesSUCCESS; });
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
//
|
||||
#include <xrpl/ledger/View.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
STAmount
|
||||
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue)
|
||||
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue)
|
||||
{
|
||||
// AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance
|
||||
auto const rounding = isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround();
|
||||
@@ -32,7 +34,7 @@ lpTokensOut(
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const t = lptAMMBalance * (r - c) / (1 + c);
|
||||
return toSTAmount(lptAMMBalance.issue(), t);
|
||||
return toSTAmount(lptAMMBalance.asset(), t);
|
||||
}
|
||||
|
||||
// minimize tokens out
|
||||
@@ -68,7 +70,7 @@ ammAssetIn(
|
||||
auto const c = d * d - f2 * f2;
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
return toSTAmount(asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
|
||||
return toSTAmount(asset1Balance.asset(), asset1Balance * solveQuadraticEq(a, b, c));
|
||||
}
|
||||
|
||||
// maximize deposit
|
||||
@@ -93,7 +95,7 @@ lpTokensIn(
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
|
||||
return toSTAmount(lptAMMBalance.issue(), t);
|
||||
return toSTAmount(lptAMMBalance.asset(), t);
|
||||
}
|
||||
|
||||
// maximize tokens in
|
||||
@@ -123,7 +125,7 @@ ammAssetOut(
|
||||
if (!isFeatureEnabled(fixAMMv1_3))
|
||||
{
|
||||
auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
|
||||
return toSTAmount(assetBalance.issue(), b);
|
||||
return toSTAmount(assetBalance.asset(), b);
|
||||
}
|
||||
|
||||
// minimize withdraw
|
||||
@@ -183,8 +185,8 @@ adjustAmountsByLPTokens(
|
||||
if (amount2)
|
||||
{
|
||||
Number const fr = lpTokensActual / lpTokens;
|
||||
auto const amountActual = toSTAmount(amount.issue(), fr * amount);
|
||||
auto const amount2Actual = toSTAmount(amount2->issue(), fr * *amount2);
|
||||
auto const amountActual = toSTAmount(amount.asset(), fr * amount);
|
||||
auto const amount2Actual = toSTAmount(amount2->asset(), fr * *amount2);
|
||||
if (!ammRoundingEnabled)
|
||||
{
|
||||
return std::make_tuple(
|
||||
@@ -253,7 +255,7 @@ multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm)
|
||||
{
|
||||
NumberRoundModeGuard const g(rm);
|
||||
auto const t = amount * frac;
|
||||
return toSTAmount(amount.issue(), t, rm);
|
||||
return toSTAmount(amount.asset(), t, rm);
|
||||
}
|
||||
|
||||
STAmount
|
||||
@@ -265,13 +267,13 @@ getRoundedAsset(
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(balance.issue(), noRoundCb());
|
||||
return toSTAmount(balance.asset(), noRoundCb());
|
||||
|
||||
auto const rm = detail::getAssetRounding(isDeposit);
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
return multiply(balance, productCb(), rm);
|
||||
NumberRoundModeGuard const g(rm);
|
||||
return toSTAmount(balance.issue(), productCb(), rm);
|
||||
return toSTAmount(balance.asset(), productCb(), rm);
|
||||
}
|
||||
|
||||
STAmount
|
||||
@@ -282,7 +284,7 @@ getRoundedLPTokens(
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(balance.issue(), balance * frac);
|
||||
return toSTAmount(balance.asset(), balance * frac);
|
||||
|
||||
auto const rm = detail::getLPTokenRounding(isDeposit);
|
||||
auto const tokens = multiply(balance, frac, rm);
|
||||
@@ -298,14 +300,14 @@ getRoundedLPTokens(
|
||||
IsDeposit isDeposit)
|
||||
{
|
||||
if (!rules.enabled(fixAMMv1_3))
|
||||
return toSTAmount(lptAMMBalance.issue(), noRoundCb());
|
||||
return toSTAmount(lptAMMBalance.asset(), noRoundCb());
|
||||
|
||||
auto const tokens = [&] {
|
||||
auto const rm = detail::getLPTokenRounding(isDeposit);
|
||||
if (isDeposit == IsDeposit::Yes)
|
||||
{
|
||||
NumberRoundModeGuard const g(rm);
|
||||
return toSTAmount(lptAMMBalance.issue(), productCb(), rm);
|
||||
return toSTAmount(lptAMMBalance.asset(), productCb(), rm);
|
||||
}
|
||||
return multiply(lptAMMBalance, productCb(), rm);
|
||||
}();
|
||||
@@ -376,4 +378,535 @@ adjustFracByTokens(
|
||||
return tokens / lptAMMBalance;
|
||||
}
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assetInBalance =
|
||||
accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j);
|
||||
auto const assetOutBalance =
|
||||
accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j);
|
||||
return std::make_pair(assetInBalance, assetOutBalance);
|
||||
}
|
||||
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Asset> const& optAsset1,
|
||||
std::optional<Asset> const& optAsset2,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assets = [&]() -> std::optional<std::pair<Asset, Asset>> {
|
||||
auto const asset1 = ammSle[sfAsset];
|
||||
auto const asset2 = ammSle[sfAsset2];
|
||||
if (optAsset1 && optAsset2)
|
||||
{
|
||||
if (invalidAMMAssetPair(
|
||||
*optAsset1, *optAsset2, std::make_optional(std::make_pair(asset1, asset2))))
|
||||
{
|
||||
// This error can only be hit if the AMM is corrupted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 << " "
|
||||
<< *optAsset2;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return std::make_optional(std::make_pair(*optAsset1, *optAsset2));
|
||||
}
|
||||
auto const singleAsset = [&asset1, &asset2, &j](
|
||||
Asset checkIssue,
|
||||
char const* label) -> std::optional<std::pair<Asset, Asset>> {
|
||||
if (checkIssue == asset1)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}
|
||||
if (checkIssue == asset2)
|
||||
{
|
||||
return std::make_optional(std::make_pair(asset2, asset1));
|
||||
}
|
||||
// Unreachable unless AMM corrupted.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
if (optAsset1)
|
||||
{
|
||||
return singleAsset(*optAsset1, "optAsset1");
|
||||
}
|
||||
if (optAsset2)
|
||||
{
|
||||
// Cannot have Amount2 without Amount.
|
||||
return singleAsset(*optAsset2, "optAsset2"); // LCOV_EXCL_LINE
|
||||
}
|
||||
return std::make_optional(std::make_pair(asset1, asset2));
|
||||
}();
|
||||
if (!assets)
|
||||
return Unexpected(tecAMM_INVALID_TOKENS);
|
||||
auto const [amount1, amount2] = ammPoolHolds(
|
||||
view,
|
||||
ammSle.getAccountID(sfAccount),
|
||||
assets->first,
|
||||
assets->second,
|
||||
freezeHandling,
|
||||
authHandling,
|
||||
j);
|
||||
return std::make_tuple(amount1, amount2, ammSle[sfLPTokenBalance]);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
// This function looks similar to `accountHolds`. However, it only checks if
|
||||
// a LPToken holder has enough balance. On the other hand, `accountHolds`
|
||||
// checks if the underlying assets of LPToken are frozen with the
|
||||
// fixFrozenLPTokenTransfer amendment
|
||||
|
||||
auto const currency = ammLPTCurrency(asset1, asset2);
|
||||
STAmount amount;
|
||||
|
||||
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
|
||||
if (!sle)
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: no SLE "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
if (lpAccount > ammAccount)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
amount.get<Issue>().account = ammAccount;
|
||||
|
||||
JLOG(j.trace()) << "ammLPHolds:"
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
|
||||
return view.balanceHookIOU(lpAccount, ammAccount, amount);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
XRPL_ASSERT(
|
||||
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
|
||||
"xrpl::getTradingFee : auction present");
|
||||
if (ammSle.isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
|
||||
// Not expired
|
||||
if (auto const expiration = auctionSlot[~sfExpiration];
|
||||
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
|
||||
expiration)
|
||||
{
|
||||
if (auctionSlot[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
if (auctionSlot.isFieldPresent(sfAuthAccounts))
|
||||
{
|
||||
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (acct[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ammSle[sfTradingFee];
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset)
|
||||
{
|
||||
// Get the actual AMM balance without factoring in the balance hook
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle =
|
||||
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
{
|
||||
STAmount amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
amount.negate();
|
||||
amount.get<Issue>().account = issue.account;
|
||||
return amount;
|
||||
}
|
||||
return STAmount{asset};
|
||||
});
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMTrustLines(
|
||||
Sandbox& sb,
|
||||
AccountID const& ammAccountID,
|
||||
std::uint16_t maxTrustlinesToDelete,
|
||||
beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM and MPToken
|
||||
if (nodeType == ltAMM || nodeType == ltMPTOKEN)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines must have zero balance
|
||||
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting trustline with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
maxTrustlinesToDelete);
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM
|
||||
if (nodeType == ltAMM)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
|
||||
if (nodeType == ltMPTOKEN)
|
||||
{
|
||||
// MPT must have zero balance
|
||||
if (sleItem->getFieldU64(sfMPTAmount) != 0 ||
|
||||
(*sleItem)[~sfLockedAmount].value_or(0) != 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting MPT with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
}
|
||||
if (nodeType == ltRIPPLE_STATE)
|
||||
{
|
||||
// Trustlines should have been deleted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: trustlines should have been deleted";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
},
|
||||
j,
|
||||
3); // At most two MPToken plus AMM object
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j)
|
||||
{
|
||||
auto ammSle = sb.peek(keylet::amm(asset, asset2));
|
||||
if (!ammSle)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
<< to_string(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Delete AMM's MPTokens only if all trustlines are deleted. If trustlines
|
||||
// are not deleted then AMM can be re-created with Deposit and
|
||||
// AMM's MPToken(s) must exist.
|
||||
if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
|
||||
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
|
||||
<< toBase58(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Asset const& lptAsset,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
// AMM creator gets the voting slot.
|
||||
STArray voteSlots;
|
||||
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
if (tfee != 0)
|
||||
voteEntry.setFieldU16(sfTradingFee, tfee);
|
||||
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
|
||||
voteEntry.setAccountID(sfAccount, account);
|
||||
voteSlots.push_back(voteEntry);
|
||||
ammSle->setFieldArray(sfVoteSlots, voteSlots);
|
||||
// AMM creator gets the auction slot for free.
|
||||
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
|
||||
// when AMM is in an empty state
|
||||
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
|
||||
ammSle->set(std::move(auctionSlot));
|
||||
}
|
||||
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
|
||||
auctionSlot.setAccountID(sfAccount, account);
|
||||
// current + sec in 24h
|
||||
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
view.header().parentCloseTime.time_since_epoch())
|
||||
.count() +
|
||||
TOTAL_TIME_SLOT_SECS;
|
||||
auctionSlot.setFieldU32(sfExpiration, expiration);
|
||||
auctionSlot.setFieldAmount(sfPrice, STAmount{lptAsset, 0});
|
||||
// Set the fee
|
||||
if (tfee != 0)
|
||||
{
|
||||
ammSle->setFieldU16(sfTradingFee, tfee);
|
||||
}
|
||||
else if (ammSle->isFieldPresent(sfTradingFee))
|
||||
{
|
||||
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
|
||||
{
|
||||
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
|
||||
}
|
||||
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
|
||||
{
|
||||
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
|
||||
{
|
||||
// Liquidity Provider (LP) must have one LPToken trustline
|
||||
std::uint8_t nLPTokenTrustLines = 0;
|
||||
// AMM account has at most two IOU (pool tokens, not LPToken) trustlines.
|
||||
// One or both trustlines could be to the LP if LP is the issuer,
|
||||
// or a different account if LP is not an issuer. For instance,
|
||||
// if AMM has two tokens USD and EUR and LP is not the issuer of the tokens
|
||||
// then the trustlines are between AMM account and the issuer.
|
||||
// There is one LPToken trustline for each LP. Only remaining LP has
|
||||
// exactly one LPToken trustlines and at most two IOU trustline for each
|
||||
// pool token. One or both tokens could be MPT.
|
||||
std::uint8_t nIOUTrustLines = 0;
|
||||
// There are at most two MPT objects, one for each side of the pool.
|
||||
std::uint8_t nMPT = 0;
|
||||
// There is only one AMM object
|
||||
bool hasAMM = false;
|
||||
// AMM LP has at most three trustlines, at most two MPTs, and only one
|
||||
// AMM object must exist. If there are more than four objects then
|
||||
// it's either an error or there are more than one LP. Ten pages should
|
||||
// be sufficient to include four objects.
|
||||
std::uint8_t limit = 10;
|
||||
auto const root = keylet::ownerDir(ammIssue.account);
|
||||
auto currentIndex = root;
|
||||
|
||||
// Iterate over AMM owner directory objects.
|
||||
while (limit-- >= 1)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
auto const sle = view.read(keylet::child(key));
|
||||
if (!sle)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const entryType = sle->getFieldU16(sfLedgerEntryType);
|
||||
// Only one AMM object
|
||||
if (entryType == ltAMM)
|
||||
{
|
||||
if (hasAMM)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
hasAMM = true;
|
||||
continue;
|
||||
}
|
||||
if (entryType == ltMPTOKEN)
|
||||
{
|
||||
++nMPT;
|
||||
continue;
|
||||
}
|
||||
if (entryType != ltRIPPLE_STATE)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sle->getFieldAmount(sfHighLimit);
|
||||
auto const isLPTrustline =
|
||||
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
|
||||
auto const isLPTokenTrustline =
|
||||
lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue;
|
||||
|
||||
// Liquidity Provider trustline
|
||||
if (isLPTrustline)
|
||||
{
|
||||
// LPToken trustline
|
||||
if (isLPTokenTrustline)
|
||||
{
|
||||
// LP has exactly one LPToken trustline
|
||||
if (++nLPTokenTrustLines > 1)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
// Another Liquidity Provider LPToken trustline
|
||||
else if (isLPTokenTrustline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// AMM account has at most two IOU trustlines
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
{
|
||||
if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) ||
|
||||
(nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return true;
|
||||
}
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account)
|
||||
{
|
||||
auto const res = isOnlyLiquidityProvider(sb, lpTokens.get<Issue>(), account);
|
||||
if (!res.has_value())
|
||||
{
|
||||
return Unexpected<TER>(res.error());
|
||||
}
|
||||
|
||||
if (res.value())
|
||||
{
|
||||
if (withinRelativeDistance(
|
||||
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
|
||||
{
|
||||
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
|
||||
sb.update(ammSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::pair<STAmount, STAmount>
|
||||
ammPoolHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& ammAccountID,
|
||||
Issue const& issue1,
|
||||
Issue const& issue2,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j);
|
||||
auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j);
|
||||
return std::make_pair(assetInBalance, assetOutBalance);
|
||||
}
|
||||
|
||||
Expected<std::tuple<STAmount, STAmount, STAmount>, TER>
|
||||
ammHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
std::optional<Issue> const& optIssue1,
|
||||
std::optional<Issue> const& optIssue2,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal const j)
|
||||
{
|
||||
auto const issues = [&]() -> std::optional<std::pair<Issue, Issue>> {
|
||||
auto const issue1 = ammSle[sfAsset].get<Issue>();
|
||||
auto const issue2 = ammSle[sfAsset2].get<Issue>();
|
||||
if (optIssue1 && optIssue2)
|
||||
{
|
||||
if (invalidAMMAssetPair(
|
||||
*optIssue1, *optIssue2, std::make_optional(std::make_pair(issue1, issue2))))
|
||||
{
|
||||
// This error can only be hit if the AMM is corrupted
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid optIssue1 or optIssue2 " << *optIssue1 << " "
|
||||
<< *optIssue2;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return std::make_optional(std::make_pair(*optIssue1, *optIssue2));
|
||||
}
|
||||
auto const singleIssue = [&issue1, &issue2, &j](
|
||||
Issue checkIssue,
|
||||
char const* label) -> std::optional<std::pair<Issue, Issue>> {
|
||||
if (checkIssue == issue1)
|
||||
{
|
||||
return std::make_optional(std::make_pair(issue1, issue2));
|
||||
}
|
||||
if (checkIssue == issue2)
|
||||
{
|
||||
return std::make_optional(std::make_pair(issue2, issue1));
|
||||
}
|
||||
// Unreachable unless AMM corrupted.
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue;
|
||||
return std::nullopt;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
if (optIssue1)
|
||||
{
|
||||
return singleIssue(*optIssue1, "optIssue1");
|
||||
}
|
||||
if (optIssue2)
|
||||
{
|
||||
// Cannot have Amount2 without Amount.
|
||||
return singleIssue(*optIssue2, "optIssue2"); // LCOV_EXCL_LINE
|
||||
}
|
||||
return std::make_optional(std::make_pair(issue1, issue2));
|
||||
}();
|
||||
if (!issues)
|
||||
return Unexpected(tecAMM_INVALID_TOKENS);
|
||||
auto const [asset1, asset2] = ammPoolHolds(
|
||||
view, ammSle.getAccountID(sfAccount), issues->first, issues->second, freezeHandling, j);
|
||||
return std::make_tuple(asset1, asset2, ammSle[sfLPTokenBalance]);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
Currency const& cur1,
|
||||
Currency const& cur2,
|
||||
AccountID const& ammAccount,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
// This function looks similar to `accountHolds`. However, it only checks if
|
||||
// a LPToken holder has enough balance. On the other hand, `accountHolds`
|
||||
// checks if the underlying assets of LPToken are frozen with the
|
||||
// fixFrozenLPTokenTransfer amendment
|
||||
|
||||
auto const currency = ammLPTCurrency(cur1, cur2);
|
||||
STAmount amount;
|
||||
|
||||
auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency));
|
||||
if (!sle)
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: no SLE "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
if (lpAccount > ammAccount)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
amount.setIssuer(ammAccount);
|
||||
|
||||
JLOG(j.trace()) << "ammLPHolds:"
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
|
||||
return view.balanceHook(lpAccount, ammAccount, amount);
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammLPHolds(
|
||||
ReadView const& view,
|
||||
SLE const& ammSle,
|
||||
AccountID const& lpAccount,
|
||||
beast::Journal const j)
|
||||
{
|
||||
return ammLPHolds(
|
||||
view,
|
||||
ammSle[sfAsset].get<Issue>().currency,
|
||||
ammSle[sfAsset2].get<Issue>().currency,
|
||||
ammSle[sfAccount],
|
||||
lpAccount,
|
||||
j);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
XRPL_ASSERT(
|
||||
!view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot),
|
||||
"xrpl::getTradingFee : auction present");
|
||||
if (ammSle.isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
auto const& auctionSlot = safe_downcast<STObject const&>(ammSle.peekAtField(sfAuctionSlot));
|
||||
// Not expired
|
||||
if (auto const expiration = auctionSlot[~sfExpiration];
|
||||
duration_cast<seconds>(view.header().parentCloseTime.time_since_epoch()).count() <
|
||||
expiration)
|
||||
{
|
||||
if (auctionSlot[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
if (auctionSlot.isFieldPresent(sfAuthAccounts))
|
||||
{
|
||||
for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (acct[~sfAccount] == account)
|
||||
return auctionSlot[sfDiscountedFee];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ammSle[sfTradingFee];
|
||||
}
|
||||
|
||||
STAmount
|
||||
ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue)
|
||||
{
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
{
|
||||
auto amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
amount.negate();
|
||||
amount.setIssuer(issue.account);
|
||||
return amount;
|
||||
}
|
||||
|
||||
return STAmount{issue};
|
||||
}
|
||||
|
||||
static TER
|
||||
deleteAMMTrustLines(
|
||||
Sandbox& sb,
|
||||
AccountID const& ammAccountID,
|
||||
std::uint16_t maxTrustlinesToDelete,
|
||||
beast::Journal j)
|
||||
{
|
||||
return cleanupOnAccountDelete(
|
||||
sb,
|
||||
keylet::ownerDir(ammAccountID),
|
||||
[&](LedgerEntryType nodeType,
|
||||
uint256 const&,
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
// Skip AMM
|
||||
if (nodeType == LedgerEntryType::ltAMM)
|
||||
return {tesSUCCESS, SkipEntry::Yes};
|
||||
// Should only have the trustlines
|
||||
if (nodeType != LedgerEntryType::ltRIPPLE_STATE)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMTrustLines: deleting non-trustline " << nodeType;
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Trustlines must have zero balance
|
||||
if (sleItem->getFieldAmount(sfBalance) != beast::zero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMTrustLines: deleting trustline with "
|
||||
"non-zero balance.";
|
||||
return {tecINTERNAL, SkipEntry::No};
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No};
|
||||
},
|
||||
j,
|
||||
maxTrustlinesToDelete);
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMAccount(Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Journal j)
|
||||
{
|
||||
auto ammSle = sb.peek(keylet::amm(asset, asset2));
|
||||
if (!ammSle)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
<< to_string(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(ammAccountID);
|
||||
if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of "
|
||||
<< toBase58(ammAccountID);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
initializeFeeAuctionVote(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account,
|
||||
Issue const& lptIssue,
|
||||
std::uint16_t tfee)
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
// AMM creator gets the voting slot.
|
||||
STArray voteSlots;
|
||||
STObject voteEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
if (tfee != 0)
|
||||
voteEntry.setFieldU16(sfTradingFee, tfee);
|
||||
voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR);
|
||||
voteEntry.setAccountID(sfAccount, account);
|
||||
voteSlots.push_back(voteEntry);
|
||||
ammSle->setFieldArray(sfVoteSlots, voteSlots);
|
||||
// AMM creator gets the auction slot for free.
|
||||
// AuctionSlot is created on AMMCreate and updated on AMMDeposit
|
||||
// when AMM is in an empty state
|
||||
if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot))
|
||||
{
|
||||
STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot);
|
||||
ammSle->set(std::move(auctionSlot));
|
||||
}
|
||||
STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
|
||||
auctionSlot.setAccountID(sfAccount, account);
|
||||
// current + sec in 24h
|
||||
auto const expiration = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
view.header().parentCloseTime.time_since_epoch())
|
||||
.count() +
|
||||
TOTAL_TIME_SLOT_SECS;
|
||||
auctionSlot.setFieldU32(sfExpiration, expiration);
|
||||
auctionSlot.setFieldAmount(sfPrice, STAmount{lptIssue, 0});
|
||||
// Set the fee
|
||||
if (tfee != 0)
|
||||
{
|
||||
ammSle->setFieldU16(sfTradingFee, tfee);
|
||||
}
|
||||
else if (ammSle->isFieldPresent(sfTradingFee))
|
||||
{
|
||||
ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION)
|
||||
{
|
||||
auctionSlot.setFieldU16(sfDiscountedFee, dfee);
|
||||
}
|
||||
else if (auctionSlot.isFieldPresent(sfDiscountedFee))
|
||||
{
|
||||
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount)
|
||||
{
|
||||
// Liquidity Provider (LP) must have one LPToken trustline
|
||||
std::uint8_t nLPTokenTrustLines = 0;
|
||||
// There are at most two IOU trustlines. One or both could be to the LP
|
||||
// if LP is the issuer, or a different account if LP is not an issuer.
|
||||
// For instance, if AMM has two tokens USD and EUR and LP is not the issuer
|
||||
// of the tokens then the trustlines are between AMM account and the issuer.
|
||||
std::uint8_t nIOUTrustLines = 0;
|
||||
// There is only one AMM object
|
||||
bool hasAMM = false;
|
||||
// AMM LP has at most three trustlines and only one AMM object must exist.
|
||||
// If there are more than five objects then it's either an error or
|
||||
// there are more than one LP. Ten pages should be sufficient to include
|
||||
// five objects.
|
||||
std::uint8_t limit = 10;
|
||||
auto const root = keylet::ownerDir(ammIssue.account);
|
||||
auto currentIndex = root;
|
||||
|
||||
// Iterate over AMM owner directory objects.
|
||||
while (limit-- >= 1)
|
||||
{
|
||||
auto const ownerDir = view.read(currentIndex);
|
||||
if (!ownerDir)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
|
||||
{
|
||||
auto const sle = view.read(keylet::child(key));
|
||||
if (!sle)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
// Only one AMM object
|
||||
if (sle->getFieldU16(sfLedgerEntryType) == ltAMM)
|
||||
{
|
||||
if (hasAMM)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
hasAMM = true;
|
||||
continue;
|
||||
}
|
||||
if (sle->getFieldU16(sfLedgerEntryType) != ltRIPPLE_STATE)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const lowLimit = sle->getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sle->getFieldAmount(sfHighLimit);
|
||||
auto const isLPTrustline =
|
||||
lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount;
|
||||
auto const isLPTokenTrustline =
|
||||
lowLimit.issue() == ammIssue || highLimit.issue() == ammIssue;
|
||||
|
||||
// Liquidity Provider trustline
|
||||
if (isLPTrustline)
|
||||
{
|
||||
// LPToken trustline
|
||||
if (isLPTokenTrustline)
|
||||
{
|
||||
if (++nLPTokenTrustLines > 1)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
// Another Liquidity Provider LPToken trustline
|
||||
else if (isLPTokenTrustline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (++nIOUTrustLines > 2)
|
||||
{
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
|
||||
if (uNodeNext == 0)
|
||||
{
|
||||
if (nLPTokenTrustLines != 1 || nIOUTrustLines == 0 || nIOUTrustLines > 2)
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return true;
|
||||
}
|
||||
currentIndex = keylet::page(root, uNodeNext);
|
||||
}
|
||||
return Unexpected<TER>(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
Expected<bool, TER>
|
||||
verifyAndAdjustLPTokenBalance(
|
||||
Sandbox& sb,
|
||||
STAmount const& lpTokens,
|
||||
std::shared_ptr<SLE>& ammSle,
|
||||
AccountID const& account)
|
||||
{
|
||||
auto const res = isOnlyLiquidityProvider(sb, lpTokens.issue(), account);
|
||||
if (!res.has_value())
|
||||
{
|
||||
return Unexpected<TER>(res.error());
|
||||
}
|
||||
|
||||
if (res.value())
|
||||
{
|
||||
if (withinRelativeDistance(
|
||||
lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3}))
|
||||
{
|
||||
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
|
||||
sb.update(ammSle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unexpected<TER>(tecAMM_INVALID_TOKENS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -80,7 +80,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
|
||||
|
||||
auto const fullBalance = sle->getFieldAmount(sfBalance);
|
||||
|
||||
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
|
||||
auto const balance = view.balanceHookIOU(id, xrpAccount(), fullBalance);
|
||||
|
||||
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
|
||||
|
||||
|
||||
@@ -306,18 +306,11 @@ requireAuth(
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const asset = sleVault->at(sfAsset);
|
||||
if (auto const err = std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return requireAuth(view, issue, account, authType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireAuth(view, issue, account, authType, depth + 1);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
if (auto const err = asset.visit(
|
||||
[&](Issue const& issue) { return requireAuth(view, issue, account, authType); },
|
||||
[&](MPTIssue const& issue) {
|
||||
return requireAuth(view, issue, account, authType, depth + 1);
|
||||
});
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
@@ -487,6 +480,37 @@ canTransfer(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
canTrade(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
return asset.visit(
|
||||
[&](Issue const&) -> TER { return tesSUCCESS; },
|
||||
[&](MPTIssue const& mptIssue) -> TER {
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (!sleIssuance->isFlag(lsfMPTCanTrade))
|
||||
return tecNO_PERMISSION;
|
||||
return tesSUCCESS;
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
|
||||
if (auto const ter = canTrade(view, asset); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return canTransfer(view, asset, from, to);
|
||||
}
|
||||
|
||||
TER
|
||||
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
|
||||
{
|
||||
@@ -770,4 +794,88 @@ createMPToken(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (mptIssue.getIssuer() == holder)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const mptIssuanceID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||
if (!view.exists(mptokenID))
|
||||
{
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
}
|
||||
auto const sleAcct = view.peek(keylet::account(holder));
|
||||
if (!sleAcct)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
adjustOwnerCount(view, sleAcct, 1, j);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
maxMPTAmount(SLE const& sleIssuance)
|
||||
{
|
||||
return sleIssuance[~sfMaximumAmount].value_or(maxMPTokenAmount);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
availableMPTAmount(SLE const& sleIssuance)
|
||||
{
|
||||
auto const max = maxMPTAmount(sleIssuance);
|
||||
auto const outstanding = sleIssuance[sfOutstandingAmount];
|
||||
return max - outstanding;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
availableMPTAmount(ReadView const& view, MPTID const& mptID)
|
||||
{
|
||||
auto const sle = view.read(keylet::mptIssuance(mptID));
|
||||
if (!sle)
|
||||
Throw<std::runtime_error>(transHuman(tecINTERNAL));
|
||||
return availableMPTAmount(*sle);
|
||||
}
|
||||
|
||||
bool
|
||||
isMPTOverflow(
|
||||
std::int64_t sendAmount,
|
||||
std::uint64_t outstandingAmount,
|
||||
std::int64_t maximumAmount,
|
||||
AllowMPTOverflow allowOverflow)
|
||||
{
|
||||
std::uint64_t const limit = (allowOverflow == AllowMPTOverflow::Yes)
|
||||
? std::numeric_limits<std::uint64_t>::max()
|
||||
: maximumAmount;
|
||||
return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
|
||||
}
|
||||
|
||||
STAmount
|
||||
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
|
||||
{
|
||||
STAmount amount{issue};
|
||||
|
||||
auto const sle = view.read(keylet::mptIssuance(issue));
|
||||
if (!sle)
|
||||
return amount;
|
||||
auto const available = availableMPTAmount(*sle);
|
||||
return view.balanceHookSelfIssueMPT(issue, available);
|
||||
}
|
||||
|
||||
void
|
||||
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount)
|
||||
{
|
||||
auto const available = availableMPTAmount(view, issue);
|
||||
view.issuerSelfDebitHookMPT(issue, amount, available);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -855,15 +855,15 @@ tokenOfferCreatePreclaim(
|
||||
if (view.rules().enabled(featureNFTokenMintOffer))
|
||||
{
|
||||
if (nftIssuer != amount.getIssuer() &&
|
||||
!view.read(keylet::line(nftIssuer, amount.issue())))
|
||||
!view.read(keylet::line(nftIssuer, amount.get<Issue>())))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
else if (!view.exists(keylet::line(nftIssuer, amount.issue())))
|
||||
else if (!view.exists(keylet::line(nftIssuer, amount.get<Issue>())))
|
||||
{
|
||||
return tecNO_LINE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
|
||||
if (isFrozen(view, nftIssuer, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
@@ -876,7 +876,7 @@ tokenOfferCreatePreclaim(
|
||||
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
|
||||
if (isFrozen(view, acctID, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
// If this is an offer to buy the token, the account must have the
|
||||
|
||||
@@ -33,11 +33,14 @@ creditLimit(
|
||||
if (sleRippleState)
|
||||
{
|
||||
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
|
||||
result.setIssuer(account);
|
||||
result.get<Issue>().account = account;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
|
||||
XRPL_ASSERT(
|
||||
result.get<Issue>().currency == currency,
|
||||
"xrpl::creditLimit : result currency "
|
||||
"match");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -63,11 +66,14 @@ creditBalance(
|
||||
result = sleRippleState->getFieldAmount(sfBalance);
|
||||
if (account < issuer)
|
||||
result.negate();
|
||||
result.setIssuer(account);
|
||||
result.get<Issue>().account = account;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
|
||||
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
|
||||
XRPL_ASSERT(
|
||||
result.get<Issue>().currency == currency,
|
||||
"xrpl::creditBalance : result currency "
|
||||
"match");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -222,7 +228,7 @@ trustCreate(
|
||||
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
|
||||
sleRippleState->setFieldAmount(
|
||||
bSetHigh ? sfLowLimit : sfHighLimit,
|
||||
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
STAmount(Issue{saBalance.get<Issue>().currency, bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
|
||||
if (uQualityIn != 0u)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
|
||||
@@ -261,7 +267,7 @@ trustCreate(
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
|
||||
view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
|
||||
view.creditHookIOU(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -367,7 +373,7 @@ issueIOU(
|
||||
"xrpl::issueIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
|
||||
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
|
||||
@@ -392,7 +398,7 @@ issueIOU(
|
||||
auto const must_delete = updateTrustLine(
|
||||
view, state, bSenderHigh, issue.account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(issue.account, account, amount, start_balance);
|
||||
view.creditHookIOU(issue.account, account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
@@ -422,7 +428,7 @@ issueIOU(
|
||||
STAmount const limit(Issue{issue.currency, account});
|
||||
STAmount final_balance = amount;
|
||||
|
||||
final_balance.setIssuer(noAccount());
|
||||
final_balance.get<Issue>().account = noAccount();
|
||||
|
||||
auto const receiverAccount = view.peek(keylet::account(account));
|
||||
if (!receiverAccount)
|
||||
@@ -461,7 +467,7 @@ redeemIOU(
|
||||
"xrpl::redeemIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
|
||||
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
|
||||
@@ -484,7 +490,7 @@ redeemIOU(
|
||||
auto const must_delete =
|
||||
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
|
||||
|
||||
view.creditHook(account, issue.account, amount, start_balance);
|
||||
view.creditHookIOU(account, issue.account, amount, start_balance);
|
||||
|
||||
if (bSenderHigh)
|
||||
final_balance.negate();
|
||||
@@ -757,4 +763,20 @@ deleteAMMTrustLine(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMMPToken(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleMpt,
|
||||
AccountID const& ammAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
view.erase(sleMpt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -23,8 +23,8 @@ bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& asset,
|
||||
Issue const& asset2);
|
||||
Asset const& asset,
|
||||
Asset const& asset2);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
@@ -35,18 +35,17 @@ isLPTokenFrozen(
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return isGlobalFrozen(view, issue.getIssuer());
|
||||
}
|
||||
else
|
||||
{
|
||||
return isGlobalFrozen(view, issue);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); },
|
||||
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
|
||||
}
|
||||
|
||||
TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
if (isGlobalFrozen(view, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -56,6 +55,14 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
{
|
||||
@@ -103,18 +110,9 @@ isAnyFrozen(
|
||||
Asset const& asset,
|
||||
int depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return isAnyFrozen(view, accounts, issue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return isAnyFrozen(view, accounts, issue, depth);
|
||||
}
|
||||
},
|
||||
asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
|
||||
[&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); });
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -190,11 +188,7 @@ getLineIfUsable(
|
||||
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
||||
|
||||
if (!sleAmm ||
|
||||
isLPTokenFrozen(
|
||||
view,
|
||||
account,
|
||||
(*sleAmm)[sfAsset].get<Issue>(),
|
||||
(*sleAmm)[sfAsset2].get<Issue>()))
|
||||
isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -230,7 +224,7 @@ getTrustLineBalance(
|
||||
{
|
||||
amount += sle->getFieldAmount(oppositeField);
|
||||
}
|
||||
amount.setIssuer(issuer);
|
||||
amount.get<Issue>().account = issuer;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -240,7 +234,7 @@ getTrustLineBalance(
|
||||
JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
return view.balanceHook(account, issuer, amount);
|
||||
return view.balanceHookIOU(account, issuer, amount);
|
||||
}
|
||||
|
||||
STAmount
|
||||
@@ -298,6 +292,9 @@ accountHolds(
|
||||
SpendableHandling includeFullBalance)
|
||||
{
|
||||
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
|
||||
STAmount amount{mptIssue};
|
||||
auto const& issuer = mptIssue.getIssuer();
|
||||
bool const mptokensV2 = view.rules().enabled(featureMPTokensV2);
|
||||
|
||||
if (returnSpendable && account == mptIssue.getIssuer())
|
||||
{
|
||||
@@ -307,16 +304,14 @@ accountHolds(
|
||||
|
||||
if (!issuance)
|
||||
{
|
||||
return STAmount{mptIssue};
|
||||
return amount;
|
||||
}
|
||||
return STAmount{
|
||||
mptIssue,
|
||||
issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
|
||||
issuance->at(sfOutstandingAmount)};
|
||||
auto const available = availableMPTAmount(*issuance);
|
||||
if (!mptokensV2)
|
||||
return STAmount{mptIssue, available};
|
||||
return view.balanceHookMPT(issuer, mptIssue, available);
|
||||
}
|
||||
|
||||
STAmount amount;
|
||||
|
||||
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
|
||||
|
||||
if (!sleMpt)
|
||||
@@ -352,6 +347,8 @@ accountHolds(
|
||||
}
|
||||
}
|
||||
|
||||
if (view.rules().enabled(featureMPTokensV2))
|
||||
return view.balanceHookMPT(account, mptIssue, amount.mpt().value());
|
||||
return amount;
|
||||
}
|
||||
|
||||
@@ -365,19 +362,14 @@ accountHolds(
|
||||
beast::Journal j,
|
||||
SpendableHandling includeFullBalance)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& value) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance);
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss, MPTIssue>)
|
||||
{
|
||||
return accountHolds(
|
||||
view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
|
||||
}
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) {
|
||||
return accountHolds(view, account, issue, zeroIfFrozen, j, includeFullBalance);
|
||||
},
|
||||
asset.value());
|
||||
[&](MPTIssue const& issue) {
|
||||
return accountHolds(
|
||||
view, account, issue, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
|
||||
});
|
||||
}
|
||||
|
||||
STAmount
|
||||
@@ -388,28 +380,38 @@ accountFunds(
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(saDefault.holds<Issue>(), "xrpl::accountFunds: saDefault holds Issue");
|
||||
|
||||
if (!saDefault.native() && saDefault.getIssuer() == id)
|
||||
return saDefault;
|
||||
|
||||
return accountHolds(
|
||||
view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j);
|
||||
view, id, saDefault.get<Issue>().currency, saDefault.getIssuer(), freezeHandling, j);
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
return saDefault.asset().visit(
|
||||
[&](Issue const&) { return accountFunds(view, id, saDefault, freezeHandling, j); },
|
||||
[&](MPTIssue const&) {
|
||||
return accountHolds(
|
||||
view, id, saDefault.asset(), freezeHandling, authHandling, j, shFULL_BALANCE);
|
||||
});
|
||||
}
|
||||
|
||||
Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return transferRate(view, issue.getIssuer());
|
||||
}
|
||||
else
|
||||
{
|
||||
return transferRate(view, issue.getMptID());
|
||||
}
|
||||
},
|
||||
amount.asset().value());
|
||||
return amount.asset().visit(
|
||||
[&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
|
||||
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -522,7 +524,7 @@ directSendNoFeeIOU(
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const& issuer = saAmount.getIssuer();
|
||||
Currency const& currency = saAmount.getCurrency();
|
||||
Currency const& currency = saAmount.get<Issue>().currency;
|
||||
|
||||
// Make sure issuer is involved.
|
||||
XRPL_ASSERT(
|
||||
@@ -551,7 +553,7 @@ directSendNoFeeIOU(
|
||||
if (bSenderHigh)
|
||||
saBalance.negate(); // Put balance in sender terms.
|
||||
|
||||
view.creditHook(uSenderID, uReceiverID, saAmount, saBalance);
|
||||
view.creditHookIOU(uSenderID, uReceiverID, saAmount, saBalance);
|
||||
|
||||
STAmount const saBefore = saBalance;
|
||||
|
||||
@@ -622,7 +624,7 @@ directSendNoFeeIOU(
|
||||
STAmount const saReceiverLimit(Issue{currency, uReceiverID});
|
||||
STAmount saBalance{saAmount};
|
||||
|
||||
saBalance.setIssuer(noAccount());
|
||||
saBalance.get<Issue>().account = noAccount();
|
||||
|
||||
JLOG(j.debug()) << "directSendNoFeeIOU: "
|
||||
"create line: "
|
||||
@@ -859,7 +861,7 @@ accountSendIOU(
|
||||
else
|
||||
{
|
||||
auto const sndBal = sender->getFieldAmount(sfBalance);
|
||||
view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal);
|
||||
view.creditHookIOU(uSenderID, xrpAccount(), saAmount, sndBal);
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sndBal - saAmount);
|
||||
@@ -872,7 +874,7 @@ accountSendIOU(
|
||||
// Increment XRP balance.
|
||||
auto const rcvBal = receiver->getFieldAmount(sfBalance);
|
||||
receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
|
||||
view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal);
|
||||
view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
|
||||
|
||||
view.update(receiver);
|
||||
}
|
||||
@@ -975,7 +977,7 @@ accountSendMultiIOU(
|
||||
// Increment XRP balance.
|
||||
auto const rcvBal = receiver->getFieldAmount(sfBalance);
|
||||
receiver->setFieldAmount(sfBalance, rcvBal + amount);
|
||||
view.creditHook(xrpAccount(), receiverID, amount, -rcvBal);
|
||||
view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
|
||||
|
||||
view.update(receiver);
|
||||
|
||||
@@ -1003,7 +1005,7 @@ accountSendMultiIOU(
|
||||
return TER{tecFAILED_PROCESSING};
|
||||
}
|
||||
auto const sndBal = sender->getFieldAmount(sfBalance);
|
||||
view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal);
|
||||
view.creditHookIOU(senderID, xrpAccount(), takeFromSender, sndBal);
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
|
||||
@@ -1037,9 +1039,20 @@ directSendNoFeeMPT(
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const maxAmount = maxMPTAmount(*sleIssuance);
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const available = availableMPTAmount(*sleIssuance);
|
||||
auto const amt = saAmount.mpt().value();
|
||||
|
||||
if (uSenderID == issuer)
|
||||
{
|
||||
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
|
||||
if (view.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
if (isMPTOverflow(amt, outstanding, maxAmount, AllowMPTOverflow::Yes))
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
(*sleIssuance)[sfOutstandingAmount] += amt;
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
else
|
||||
@@ -1047,11 +1060,11 @@ directSendNoFeeMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
{
|
||||
auto const amt = sle->getFieldU64(sfMPTAmount);
|
||||
auto const pay = saAmount.mpt().value();
|
||||
if (amt < pay)
|
||||
auto const senderBalance = sle->getFieldU64(sfMPTAmount);
|
||||
if (senderBalance < amt)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
(*sle)[sfMPTAmount] = amt - pay;
|
||||
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
|
||||
(*sle)[sfMPTAmount] = senderBalance - amt;
|
||||
view.update(sle);
|
||||
}
|
||||
else
|
||||
@@ -1062,11 +1075,9 @@ directSendNoFeeMPT(
|
||||
|
||||
if (uReceiverID == issuer)
|
||||
{
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = saAmount.mpt().value();
|
||||
if (outstanding >= redeem)
|
||||
if (outstanding >= amt)
|
||||
{
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt);
|
||||
view.update(sleIssuance);
|
||||
}
|
||||
else
|
||||
@@ -1079,7 +1090,8 @@ directSendNoFeeMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
{
|
||||
(*sle)[sfMPTAmount] += saAmount.mpt().value();
|
||||
view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available);
|
||||
(*sle)[sfMPTAmount] += amt;
|
||||
view.update(sle);
|
||||
}
|
||||
else
|
||||
@@ -1099,7 +1111,8 @@ directSendNoLimitMPT(
|
||||
STAmount const& saAmount,
|
||||
STAmount& saActual,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee)
|
||||
WaiveTransferFee waiveFee,
|
||||
AllowMPTOverflow allowOverflow)
|
||||
{
|
||||
XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitMPT : sender is not receiver");
|
||||
|
||||
@@ -1117,9 +1130,13 @@ directSendNoLimitMPT(
|
||||
if (uSenderID == issuer)
|
||||
{
|
||||
auto const sendAmount = saAmount.mpt().value();
|
||||
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
|
||||
if (sendAmount > maximumAmount ||
|
||||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
|
||||
auto const maxAmount = maxMPTAmount(*sle);
|
||||
auto const outstanding = sle->getFieldU64(sfOutstandingAmount);
|
||||
auto const mptokensV2 = view.rules().enabled(featureMPTokensV2);
|
||||
allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2)
|
||||
? AllowMPTOverflow::Yes
|
||||
: AllowMPTOverflow::No;
|
||||
if (isMPTOverflow(sendAmount, outstanding, maxAmount, allowOverflow))
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
@@ -1233,7 +1250,8 @@ directSendNoLimitMultiMPT(
|
||||
}
|
||||
|
||||
// Direct send: redeeming MPTs and/or sending own MPTs.
|
||||
if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j))
|
||||
if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
actual += amount;
|
||||
// Do not add amount to takeFromSender, because directSendNoFeeMPT took it.
|
||||
@@ -1252,13 +1270,15 @@ directSendNoLimitMultiMPT(
|
||||
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
|
||||
<< " cost=" << actualSend.getFullText();
|
||||
|
||||
if (auto const terResult = directSendNoFeeMPT(view, issuer, receiverID, amount, j))
|
||||
return terResult;
|
||||
if (auto const ter = directSendNoFeeMPT(view, issuer, receiverID, amount, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
if (senderID != issuer && takeFromSender)
|
||||
{
|
||||
if (TER const terResult = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j))
|
||||
return terResult;
|
||||
if (auto const ter = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -1271,7 +1291,8 @@ accountSendMPT(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee)
|
||||
WaiveTransferFee waiveFee,
|
||||
AllowMPTOverflow allowOverflow)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
saAmount >= beast::zero && saAmount.holds<MPTIssue>(),
|
||||
@@ -1285,7 +1306,8 @@ accountSendMPT(
|
||||
|
||||
STAmount saActual{saAmount.asset()};
|
||||
|
||||
return directSendNoLimitMPT(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
|
||||
return directSendNoLimitMPT(
|
||||
view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee, allowOverflow);
|
||||
}
|
||||
|
||||
static TER
|
||||
@@ -1317,19 +1339,14 @@ directSendNoFee(
|
||||
bool bCheckIssuer,
|
||||
beast::Journal j)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
|
||||
return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
|
||||
}
|
||||
return saAmount.asset().visit(
|
||||
[&](Issue const&) {
|
||||
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
|
||||
},
|
||||
saAmount.asset().value());
|
||||
[&](MPTIssue const&) {
|
||||
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
|
||||
return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -1339,20 +1356,17 @@ accountSend(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
WaiveTransferFee waiveFee)
|
||||
WaiveTransferFee waiveFee,
|
||||
AllowMPTOverflow allowOverflow)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
return accountSendMPT(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
||||
}
|
||||
return saAmount.asset().visit(
|
||||
[&](Issue const&) {
|
||||
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
||||
},
|
||||
saAmount.asset().value());
|
||||
[&](MPTIssue const&) {
|
||||
return accountSendMPT(
|
||||
view, uSenderID, uReceiverID, saAmount, j, waiveFee, allowOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -1366,18 +1380,13 @@ accountSendMulti(
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
|
||||
}
|
||||
else
|
||||
{
|
||||
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
|
||||
}
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) {
|
||||
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
|
||||
},
|
||||
asset.value());
|
||||
[&](MPTIssue const& issue) {
|
||||
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -21,12 +21,23 @@
|
||||
namespace xrpl {
|
||||
|
||||
Currency
|
||||
ammLPTCurrency(Currency const& cur1, Currency const& cur2)
|
||||
ammLPTCurrency(Asset const& asset1, Asset const& asset2)
|
||||
{
|
||||
// AMM LPToken is 0x03 plus 19 bytes of the hash
|
||||
std::int32_t constexpr AMMCurrencyCode = 0x03;
|
||||
auto const [minC, maxC] = std::minmax(cur1, cur2);
|
||||
auto const hash = sha512Half(minC, maxC);
|
||||
auto const& [minA, maxA] = std::minmax(asset1, asset2);
|
||||
uint256 const hash = std::visit(
|
||||
[](auto&& issue1, auto&& issue2) {
|
||||
auto fromIss = []<ValidIssueType T>(T const& issue) {
|
||||
if constexpr (std::is_same_v<T, Issue>)
|
||||
return issue.currency;
|
||||
if constexpr (std::is_same_v<T, MPTIssue>)
|
||||
return issue.getMptID();
|
||||
};
|
||||
return sha512Half(fromIss(issue1), fromIss(issue2));
|
||||
},
|
||||
minA.value(),
|
||||
maxA.value());
|
||||
Currency currency;
|
||||
*currency.begin() = AMMCurrencyCode;
|
||||
std::copy(hash.begin(), hash.begin() + currency.size() - 1, currency.begin() + 1);
|
||||
@@ -34,34 +45,45 @@ ammLPTCurrency(Currency const& cur1, Currency const& cur2)
|
||||
}
|
||||
|
||||
Issue
|
||||
ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID)
|
||||
ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID)
|
||||
{
|
||||
return Issue(ammLPTCurrency(cur1, cur2), ammAccountID);
|
||||
return Issue(ammLPTCurrency(asset1, asset2), ammAccountID);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
invalidAMMAsset(Issue const& issue, std::optional<std::pair<Issue, Issue>> const& pair)
|
||||
invalidAMMAsset(Asset const& asset, std::optional<std::pair<Asset, Asset>> const& pair)
|
||||
{
|
||||
if (badCurrency() == issue.currency)
|
||||
return temBAD_CURRENCY;
|
||||
if (isXRP(issue) && issue.account.isNonZero())
|
||||
return temBAD_ISSUER;
|
||||
if (pair && issue != pair->first && issue != pair->second)
|
||||
auto const err = asset.visit(
|
||||
[](MPTIssue const& issue) -> std::optional<NotTEC> {
|
||||
if (issue.getIssuer() == beast::zero)
|
||||
return temBAD_MPT;
|
||||
return std::nullopt;
|
||||
},
|
||||
[](Issue const& issue) -> std::optional<NotTEC> {
|
||||
if (badCurrency() == issue.currency)
|
||||
return temBAD_CURRENCY;
|
||||
if (isXRP(issue) && issue.getIssuer().isNonZero())
|
||||
return temBAD_ISSUER;
|
||||
return std::nullopt;
|
||||
});
|
||||
if (err)
|
||||
return *err;
|
||||
if (pair && asset != pair->first && asset != pair->second)
|
||||
return temBAD_AMM_TOKENS;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
invalidAMMAssetPair(
|
||||
Issue const& issue1,
|
||||
Issue const& issue2,
|
||||
std::optional<std::pair<Issue, Issue>> const& pair)
|
||||
Asset const& asset1,
|
||||
Asset const& asset2,
|
||||
std::optional<std::pair<Asset, Asset>> const& pair)
|
||||
{
|
||||
if (issue1 == issue2)
|
||||
if (asset1 == asset2)
|
||||
return temBAD_AMM_TOKENS;
|
||||
if (auto const res = invalidAMMAsset(issue1, pair))
|
||||
if (auto const res = invalidAMMAsset(asset1, pair))
|
||||
return res;
|
||||
if (auto const res = invalidAMMAsset(issue2, pair))
|
||||
if (auto const res = invalidAMMAsset(asset2, pair))
|
||||
return res;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -69,10 +91,10 @@ invalidAMMAssetPair(
|
||||
NotTEC
|
||||
invalidAMMAmount(
|
||||
STAmount const& amount,
|
||||
std::optional<std::pair<Issue, Issue>> const& pair,
|
||||
std::optional<std::pair<Asset, Asset>> const& pair,
|
||||
bool validZero)
|
||||
{
|
||||
if (auto const res = invalidAMMAsset(amount.issue(), pair))
|
||||
if (auto const res = invalidAMMAsset(amount.asset(), pair))
|
||||
return res;
|
||||
if (amount < beast::zero || (!validZero && amount == beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
@@ -62,4 +62,11 @@ assetFromJson(Json::Value const& v)
|
||||
return mptIssueFromJson(v);
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, Asset const& x)
|
||||
{
|
||||
std::visit([&]<ValidIssueType TIss>(TIss const& issue) { os << issue; }, x.value());
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -99,19 +99,36 @@ getBookBase(Book const& book)
|
||||
{
|
||||
XRPL_ASSERT(isConsistent(book), "xrpl::getBookBase : input is consistent");
|
||||
|
||||
auto const index = book.domain ? indexHash(
|
||||
LedgerNameSpace::BOOK_DIR,
|
||||
book.in.currency,
|
||||
book.out.currency,
|
||||
book.in.account,
|
||||
book.out.account,
|
||||
*(book.domain))
|
||||
: indexHash(
|
||||
LedgerNameSpace::BOOK_DIR,
|
||||
book.in.currency,
|
||||
book.out.currency,
|
||||
book.in.account,
|
||||
book.out.account);
|
||||
auto getIndexHash = [&book]<typename... Args>(Args... args) {
|
||||
if (book.domain)
|
||||
return indexHash(std::forward<Args>(args)..., *book.domain);
|
||||
return indexHash(std::forward<Args>(args)...);
|
||||
};
|
||||
|
||||
auto const index = std::visit(
|
||||
[&]<ValidIssueType TIn, ValidIssueType TOut>(TIn const& in, TOut const& out) {
|
||||
if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, Issue>)
|
||||
{
|
||||
return getIndexHash(
|
||||
LedgerNameSpace::BOOK_DIR, in.currency, out.currency, in.account, out.account);
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIn, Issue> && std::is_same_v<TOut, MPTIssue>)
|
||||
{
|
||||
return getIndexHash(
|
||||
LedgerNameSpace::BOOK_DIR, in.currency, out.getMptID(), in.account);
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIn, MPTIssue> && std::is_same_v<TOut, Issue>)
|
||||
{
|
||||
return getIndexHash(
|
||||
LedgerNameSpace::BOOK_DIR, in.getMptID(), out.currency, out.account);
|
||||
}
|
||||
else
|
||||
{
|
||||
return getIndexHash(LedgerNameSpace::BOOK_DIR, in.getMptID(), out.getMptID());
|
||||
}
|
||||
},
|
||||
book.in.value(),
|
||||
book.out.value());
|
||||
|
||||
// Return with quality 0.
|
||||
auto k = keylet::quality({ltDIR_NODE, index}, 0);
|
||||
@@ -401,11 +418,37 @@ nft_sells(uint256 const& id) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
amm(Asset const& issue1, Asset const& issue2) noexcept
|
||||
amm(Asset const& asset1, Asset const& asset2) noexcept
|
||||
{
|
||||
auto const& [minI, maxI] = std::minmax(issue1.get<Issue>(), issue2.get<Issue>());
|
||||
return amm(
|
||||
indexHash(LedgerNameSpace::AMM, minI.account, minI.currency, maxI.account, maxI.currency));
|
||||
auto const& [minA, maxA] = std::minmax(asset1, asset2);
|
||||
return std::visit(
|
||||
[]<ValidIssueType TIss1, ValidIssueType TIss2>(TIss1 const& issue1, TIss2 const& issue2) {
|
||||
if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, Issue>)
|
||||
{
|
||||
return amm(indexHash(
|
||||
LedgerNameSpace::AMM,
|
||||
issue1.account,
|
||||
issue1.currency,
|
||||
issue2.account,
|
||||
issue2.currency));
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss1, Issue> && std::is_same_v<TIss2, MPTIssue>)
|
||||
{
|
||||
return amm(indexHash(
|
||||
LedgerNameSpace::AMM, issue1.account, issue1.currency, issue2.getMptID()));
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, Issue>)
|
||||
{
|
||||
return amm(indexHash(
|
||||
LedgerNameSpace::AMM, issue1.getMptID(), issue2.account, issue2.currency));
|
||||
}
|
||||
else if constexpr (std::is_same_v<TIss1, MPTIssue> && std::is_same_v<TIss2, MPTIssue>)
|
||||
{
|
||||
return amm(indexHash(LedgerNameSpace::AMM, issue1.getMptID(), issue2.getMptID()));
|
||||
}
|
||||
},
|
||||
minA.value(),
|
||||
maxA.value());
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/json_errors.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -17,6 +16,11 @@ MPTIssue::MPTIssue(MPTID const& issuanceID) : mptID_(issuanceID)
|
||||
{
|
||||
}
|
||||
|
||||
MPTIssue::MPTIssue(std::uint32_t sequence, AccountID const& account)
|
||||
: MPTIssue(xrpl::makeMptID(sequence, account))
|
||||
{
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
MPTIssue::getIssuer() const
|
||||
{
|
||||
@@ -86,4 +90,11 @@ mptIssueFromJson(Json::Value const& v)
|
||||
return MPTIssue{id};
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, MPTIssue const& x)
|
||||
{
|
||||
os << to_string(x);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
19
src/libxrpl/protocol/PathAsset.cpp
Normal file
19
src/libxrpl/protocol/PathAsset.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PathAsset.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::string
|
||||
to_string(PathAsset const& asset)
|
||||
{
|
||||
return std::visit([&](auto const& issue) { return to_string(issue); }, asset.value());
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, PathAsset const& x)
|
||||
{
|
||||
os << to_string(x);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -67,6 +67,11 @@ Permission::Permission()
|
||||
#pragma pop_macro("PERMISSION")
|
||||
};
|
||||
|
||||
XRPL_ASSERT(
|
||||
txFeatureMap_.size() == delegableTx_.size(),
|
||||
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
|
||||
"size");
|
||||
|
||||
for ([[maybe_unused]] auto const& permission : granularPermissionMap_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
|
||||
@@ -90,11 +90,23 @@ getMPTValue(STAmount const& amount)
|
||||
static bool
|
||||
areComparable(STAmount const& v1, STAmount const& v2)
|
||||
{
|
||||
if (v1.holds<Issue>() && v2.holds<Issue>())
|
||||
return v1.native() == v2.native() && v1.get<Issue>().currency == v2.get<Issue>().currency;
|
||||
if (v1.holds<MPTIssue>() && v2.holds<MPTIssue>())
|
||||
return v1.get<MPTIssue>() == v2.get<MPTIssue>();
|
||||
return false;
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss1, ValidIssueType TIss2>(TIss1 const& issue1, TIss2 const& issue2) {
|
||||
if constexpr (is_issue_v<TIss1> && is_issue_v<TIss2>)
|
||||
{
|
||||
return v1.native() == v2.native() && issue1.currency == issue2.currency;
|
||||
}
|
||||
else if constexpr (is_mptissue_v<TIss1> && is_mptissue_v<TIss2>)
|
||||
{
|
||||
return issue1 == issue2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
v1.asset().value(),
|
||||
v2.asset().value());
|
||||
}
|
||||
|
||||
static_assert(INITIAL_XRP.drops() == STAmount::cMaxNativeN);
|
||||
@@ -513,26 +525,35 @@ canAdd(STAmount const& a, STAmount const& b)
|
||||
}
|
||||
|
||||
// IOU case (precision check)
|
||||
if (a.holds<Issue>() && b.holds<Issue>())
|
||||
{
|
||||
static STAmount const one{IOUAmount{1, 0}, noIssue()};
|
||||
static STAmount const maxLoss{IOUAmount{1, -4}, noIssue()};
|
||||
STAmount const lhs = divide((a - b) + b, a, noIssue()) - one;
|
||||
STAmount const rhs = divide((b - a) + a, b, noIssue()) - one;
|
||||
return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <= maxLoss;
|
||||
}
|
||||
auto const ret = std::visit(
|
||||
[&]<ValidIssueType TIss1, ValidIssueType TIss2>(
|
||||
TIss1 const&, TIss2 const&) -> std::optional<bool> {
|
||||
if constexpr (is_issue_v<TIss1> && is_issue_v<TIss2>)
|
||||
{
|
||||
static STAmount const one{IOUAmount{1, 0}, noIssue()};
|
||||
static STAmount const maxLoss{IOUAmount{1, -4}, noIssue()};
|
||||
STAmount const lhs = divide((a - b) + b, a, noIssue()) - one;
|
||||
STAmount const rhs = divide((b - a) + a, b, noIssue()) - one;
|
||||
return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <= maxLoss;
|
||||
}
|
||||
|
||||
// MPT (overflow & underflow check)
|
||||
if (a.holds<MPTIssue>() && b.holds<MPTIssue>())
|
||||
{
|
||||
MPTAmount const A = a.mpt();
|
||||
MPTAmount const B = b.mpt();
|
||||
return !(
|
||||
(B > MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} - B) ||
|
||||
(B < MPTAmount{0} &&
|
||||
A < MPTAmount{std::numeric_limits<MPTAmount::value_type>::min()} - B));
|
||||
}
|
||||
// MPT (overflow & underflow check)
|
||||
if constexpr (is_mptissue_v<TIss1> && is_mptissue_v<TIss2>)
|
||||
{
|
||||
MPTAmount const A = a.mpt();
|
||||
MPTAmount const B = b.mpt();
|
||||
return !(
|
||||
(B > MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} - B) ||
|
||||
(B < MPTAmount{0} &&
|
||||
A < MPTAmount{std::numeric_limits<MPTAmount::value_type>::min()} - B));
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
a.asset().value(),
|
||||
b.asset().value());
|
||||
if (ret)
|
||||
return *ret;
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STAmount::canAdd : unexpected STAmount type");
|
||||
return false;
|
||||
@@ -585,27 +606,36 @@ canSubtract(STAmount const& a, STAmount const& b)
|
||||
}
|
||||
|
||||
// IOU case (no underflow)
|
||||
if (a.holds<Issue>() && b.holds<Issue>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto const ret = std::visit(
|
||||
[&]<ValidIssueType TIss1, ValidIssueType TIss2>(
|
||||
TIss1 const&, TIss2 const&) -> std::optional<bool> {
|
||||
if constexpr (is_issue_v<TIss1> && is_issue_v<TIss2>)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// MPT case (underflow & overflow check)
|
||||
if (a.holds<MPTIssue>() && b.holds<MPTIssue>())
|
||||
{
|
||||
MPTAmount const A = a.mpt();
|
||||
MPTAmount const B = b.mpt();
|
||||
// MPT case (underflow & overflow check)
|
||||
if constexpr (is_mptissue_v<TIss1> && is_mptissue_v<TIss2>)
|
||||
{
|
||||
MPTAmount const A = a.mpt();
|
||||
MPTAmount const B = b.mpt();
|
||||
|
||||
// Underflow check
|
||||
if (B > MPTAmount{0} && A < B)
|
||||
return false;
|
||||
// Underflow check
|
||||
if (B > MPTAmount{0} && A < B)
|
||||
return false;
|
||||
|
||||
// Overflow check
|
||||
if (B < MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} + B)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
// Overflow check
|
||||
if (B < MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} + B)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
a.asset().value(),
|
||||
b.asset().value());
|
||||
if (ret)
|
||||
return *ret;
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STAmount::canSubtract : unexpected STAmount type");
|
||||
return false;
|
||||
@@ -751,45 +781,49 @@ STAmount::getJson(JsonOptions) const
|
||||
void
|
||||
STAmount::add(Serializer& s) const
|
||||
{
|
||||
if (native())
|
||||
{
|
||||
XRPL_ASSERT(mOffset == 0, "xrpl::STAmount::add : zero offset");
|
||||
|
||||
if (!mIsNegative)
|
||||
{
|
||||
s.add64(mValue | cPositive);
|
||||
}
|
||||
else
|
||||
{
|
||||
mAsset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
auto u8 = static_cast<unsigned char>(cMPToken >> 56);
|
||||
if (!mIsNegative)
|
||||
u8 |= static_cast<unsigned char>(cPositive >> 56);
|
||||
s.add8(u8);
|
||||
s.add64(mValue);
|
||||
}
|
||||
}
|
||||
else if (mAsset.holds<MPTIssue>())
|
||||
{
|
||||
auto u8 = static_cast<unsigned char>(cMPToken >> 56);
|
||||
if (!mIsNegative)
|
||||
u8 |= static_cast<unsigned char>(cPositive >> 56);
|
||||
s.add8(u8);
|
||||
s.add64(mValue);
|
||||
s.addBitString(mAsset.get<MPTIssue>().getMptID());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*this == beast::zero)
|
||||
{
|
||||
s.add64(cIssuedCurrency);
|
||||
}
|
||||
else if (mIsNegative)
|
||||
{ // 512 = not native
|
||||
s.add64(mValue | (static_cast<std::uint64_t>(mOffset + 512 + 97) << (64 - 10)));
|
||||
}
|
||||
else
|
||||
{ // 256 = positive
|
||||
s.add64(mValue | (static_cast<std::uint64_t>(mOffset + 512 + 256 + 97) << (64 - 10)));
|
||||
}
|
||||
s.addBitString(mAsset.get<Issue>().currency);
|
||||
s.addBitString(mAsset.get<Issue>().account);
|
||||
}
|
||||
s.addBitString(issue.getMptID());
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
if (native())
|
||||
{
|
||||
XRPL_ASSERT(mOffset == 0, "xrpl::STAmount::add : zero offset");
|
||||
|
||||
if (!mIsNegative)
|
||||
{
|
||||
s.add64(mValue | cPositive);
|
||||
}
|
||||
else
|
||||
{
|
||||
s.add64(mValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*this == beast::zero)
|
||||
{
|
||||
s.add64(cIssuedCurrency);
|
||||
}
|
||||
else if (mIsNegative) // 512 = not native
|
||||
{
|
||||
s.add64(mValue | (static_cast<std::uint64_t>(mOffset + 512 + 97) << (64 - 10)));
|
||||
}
|
||||
else // 256 = positive
|
||||
{
|
||||
s.add64(
|
||||
mValue |
|
||||
(static_cast<std::uint64_t>(mOffset + 512 + 256 + 97) << (64 - 10)));
|
||||
}
|
||||
s.addBitString(issue.currency);
|
||||
s.addBitString(issue.account);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -1065,7 +1099,7 @@ amountFromJson(SField const& name, Json::Value const& v)
|
||||
if (isMPT)
|
||||
{
|
||||
// sequence (32 bits) + account (160 bits)
|
||||
uint192 u;
|
||||
MPTID u;
|
||||
if (!u.parseHex(currencyOrMPTID.asString()))
|
||||
Throw<std::runtime_error>("invalid MPTokenIssuanceID");
|
||||
asset = u;
|
||||
@@ -1255,7 +1289,7 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset)
|
||||
int numOffset = num.exponent();
|
||||
int denOffset = den.exponent();
|
||||
|
||||
if (num.native() || num.holds<MPTIssue>())
|
||||
if (num.integral())
|
||||
{
|
||||
while (numVal < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1265,7 +1299,7 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset)
|
||||
}
|
||||
}
|
||||
|
||||
if (den.native() || den.holds<MPTIssue>())
|
||||
if (den.integral())
|
||||
{
|
||||
while (denVal < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1330,7 +1364,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
|
||||
int offset1 = v1.exponent();
|
||||
int offset2 = v2.exponent();
|
||||
|
||||
if (v1.native() || v1.holds<MPTIssue>())
|
||||
if (v1.integral())
|
||||
{
|
||||
while (value1 < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1339,7 +1373,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
|
||||
}
|
||||
}
|
||||
|
||||
if (v2.native() || v2.holds<MPTIssue>())
|
||||
if (v2.integral())
|
||||
{
|
||||
while (value2 < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1363,7 +1397,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
|
||||
// for years, so it is deeply embedded in the behavior of cross-currency
|
||||
// transactions.
|
||||
//
|
||||
// However in 2022 it was noticed that the rounding characteristics were
|
||||
// However, in 2022 it was noticed that the rounding characteristics were
|
||||
// surprising. When the code converts from IOU-like to XRP-like there may
|
||||
// be a fraction of the IOU-like representation that is too small to be
|
||||
// represented in drops. `canonicalizeRound()` currently does some unusual
|
||||
@@ -1380,9 +1414,9 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset)
|
||||
// So an alternative rounding approach was introduced. You'll see that
|
||||
// alternative below.
|
||||
static void
|
||||
canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool)
|
||||
canonicalizeRound(bool integral, std::uint64_t& value, int& offset, bool)
|
||||
{
|
||||
if (native)
|
||||
if (integral)
|
||||
{
|
||||
if (offset < 0)
|
||||
{
|
||||
@@ -1419,9 +1453,9 @@ canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool)
|
||||
// rounding decisions. canonicalizeRoundStrict() tracks all of the bits in
|
||||
// the value being rounded.
|
||||
static void
|
||||
canonicalizeRoundStrict(bool native, std::uint64_t& value, int& offset, bool roundUp)
|
||||
canonicalizeRoundStrict(bool integral, std::uint64_t& value, int& offset, bool roundUp)
|
||||
{
|
||||
if (native)
|
||||
if (integral)
|
||||
{
|
||||
if (offset < 0)
|
||||
{
|
||||
@@ -1512,9 +1546,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
if (v1 == beast::zero || v2 == beast::zero)
|
||||
return {asset};
|
||||
|
||||
bool const xrp = asset.native();
|
||||
|
||||
if (v1.native() && v2.native() && xrp)
|
||||
if (v1.native() && v2.native() && asset.native())
|
||||
{
|
||||
std::uint64_t const minV = std::min(getSNValue(v1), getSNValue(v2));
|
||||
std::uint64_t const maxV = std::max(getSNValue(v1), getSNValue(v2));
|
||||
@@ -1545,7 +1577,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa();
|
||||
int offset1 = v1.exponent(), offset2 = v2.exponent();
|
||||
|
||||
if (v1.native() || v1.holds<MPTIssue>())
|
||||
if (v1.integral())
|
||||
{
|
||||
while (value1 < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1554,7 +1586,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
}
|
||||
}
|
||||
|
||||
if (v2.native() || v2.holds<MPTIssue>())
|
||||
if (v2.integral())
|
||||
{
|
||||
while (value2 < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1570,7 +1602,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
// range. Dividing their product by 10^14 maintains the
|
||||
// precision, by scaling the result to 10^16 to 10^18.
|
||||
//
|
||||
// If the we're rounding up, we want to round up away
|
||||
// If we're rounding up, we want to round up away
|
||||
// from zero, and if we're rounding down, truncation
|
||||
// is implicit.
|
||||
std::uint64_t amount =
|
||||
@@ -1579,7 +1611,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
int offset = offset1 + offset2 + 14;
|
||||
if (resultNegative != roundUp)
|
||||
{
|
||||
CanonicalizeFunc(xrp, amount, offset, roundUp);
|
||||
CanonicalizeFunc(asset.integral(), amount, offset, roundUp);
|
||||
}
|
||||
STAmount result = [&]() {
|
||||
// If appropriate, tell Number to round down. This gives the desired
|
||||
@@ -1590,7 +1622,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
|
||||
if (roundUp && !resultNegative && !result)
|
||||
{
|
||||
if (xrp)
|
||||
if (asset.integral())
|
||||
{
|
||||
// return the smallest value above zero
|
||||
amount = 1;
|
||||
@@ -1634,7 +1666,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
std::uint64_t numVal = num.mantissa(), denVal = den.mantissa();
|
||||
int numOffset = num.exponent(), denOffset = den.exponent();
|
||||
|
||||
if (num.native() || num.holds<MPTIssue>())
|
||||
if (num.integral())
|
||||
{
|
||||
while (numVal < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1643,7 +1675,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
}
|
||||
}
|
||||
|
||||
if (den.native() || den.holds<MPTIssue>())
|
||||
if (den.integral())
|
||||
{
|
||||
while (denVal < STAmount::cMinValue)
|
||||
{
|
||||
@@ -1668,12 +1700,12 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
int offset = numOffset - denOffset - 17;
|
||||
|
||||
if (resultNegative != roundUp)
|
||||
canonicalizeRound(asset.native() || asset.holds<MPTIssue>(), amount, offset, roundUp);
|
||||
canonicalizeRound(asset.integral(), amount, offset, roundUp);
|
||||
|
||||
STAmount result = [&]() {
|
||||
// If appropriate, tell Number the rounding mode we are using.
|
||||
// Note that "roundUp == true" actually means "round away from zero".
|
||||
// Otherwise round toward zero.
|
||||
// Otherwise, round toward zero.
|
||||
using enum Number::rounding_mode;
|
||||
MightSaveRound const savedRound(roundUp ^ resultNegative ? upward : downward);
|
||||
return STAmount(asset, amount, offset, resultNegative);
|
||||
@@ -1681,7 +1713,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
|
||||
if (roundUp && !resultNegative && !result)
|
||||
{
|
||||
if (asset.native() || asset.holds<MPTIssue>())
|
||||
if (asset.integral())
|
||||
{
|
||||
// return the smallest value above zero
|
||||
amount = 1;
|
||||
|
||||
@@ -88,22 +88,19 @@ STIssue::getJson(JsonOptions) const
|
||||
void
|
||||
STIssue::add(Serializer& s) const
|
||||
{
|
||||
if (holds<Issue>())
|
||||
{
|
||||
auto const& issue = asset_.get<Issue>();
|
||||
s.addBitString(issue.currency);
|
||||
if (!isXRP(issue.currency))
|
||||
s.addBitString(issue.account);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& issue = asset_.get<MPTIssue>();
|
||||
s.addBitString(issue.getIssuer());
|
||||
s.addBitString(noAccount());
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
s.add32(sequence);
|
||||
}
|
||||
asset_.visit(
|
||||
[&](Issue const& issue) {
|
||||
s.addBitString(issue.currency);
|
||||
if (!isXRP(issue.currency))
|
||||
s.addBitString(issue.account);
|
||||
},
|
||||
[&](MPTIssue const& issue) {
|
||||
s.addBitString(issue.getIssuer());
|
||||
s.addBitString(noAccount());
|
||||
std::uint32_t sequence = 0;
|
||||
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
|
||||
s.add32(sequence);
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -116,7 +113,9 @@ STIssue::isEquivalent(STBase const& t) const
|
||||
bool
|
||||
STIssue::isDefault() const
|
||||
{
|
||||
return holds<Issue>() && asset_.get<Issue>() == xrpIssue();
|
||||
return asset_.visit(
|
||||
[](Issue const& issue) { return issue == xrpIssue(); },
|
||||
[](MPTIssue const&) { return false; });
|
||||
}
|
||||
|
||||
STBase*
|
||||
|
||||
@@ -749,6 +749,12 @@ STObject::setFieldH128(SField const& field, uint128 const& v)
|
||||
setFieldUsingSetValue<STUInt128>(field, v);
|
||||
}
|
||||
|
||||
void
|
||||
STObject::setFieldH192(SField const& field, uint192 const& v)
|
||||
{
|
||||
setFieldUsingSetValue<STUInt192>(field, v);
|
||||
}
|
||||
|
||||
void
|
||||
STObject::setFieldH256(SField const& field, uint256 const& v)
|
||||
{
|
||||
|
||||
@@ -736,7 +736,7 @@ parseLeaf(
|
||||
std::string const element_name(json_name + "." + ss.str());
|
||||
|
||||
// each element in this path has some combination of
|
||||
// account, currency, or issuer
|
||||
// account, asset, or issuer
|
||||
|
||||
Json::Value pathEl = value[i][j];
|
||||
|
||||
@@ -746,14 +746,22 @@ parseLeaf(
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value const& account = pathEl["account"];
|
||||
Json::Value const& currency = pathEl["currency"];
|
||||
Json::Value const& issuer = pathEl["issuer"];
|
||||
bool hasCurrency = false;
|
||||
AccountID uAccount, uIssuer;
|
||||
Currency uCurrency;
|
||||
if (pathEl.isMember(jss::currency) && pathEl.isMember(jss::mpt_issuance_id))
|
||||
{
|
||||
error = RPC::make_error(rpcINVALID_PARAMS, "Invalid Asset.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!account && !currency && !issuer)
|
||||
bool const isMPT = pathEl.isMember(jss::mpt_issuance_id);
|
||||
auto const assetName = isMPT ? jss::mpt_issuance_id : jss::currency;
|
||||
Json::Value const& account = pathEl[jss::account];
|
||||
Json::Value const& asset = pathEl[assetName];
|
||||
Json::Value const& issuer = pathEl[jss::issuer];
|
||||
bool hasAsset = false;
|
||||
AccountID uAccount, uIssuer;
|
||||
PathAsset uAsset;
|
||||
|
||||
if (!account && !asset && !issuer)
|
||||
{
|
||||
error = invalid_data(element_name);
|
||||
return ret;
|
||||
@@ -764,7 +772,7 @@ parseLeaf(
|
||||
// human account id
|
||||
if (!account.isString())
|
||||
{
|
||||
error = string_expected(element_name, "account");
|
||||
error = string_expected(element_name, jss::account.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -775,31 +783,51 @@ parseLeaf(
|
||||
auto const a = parseBase58<AccountID>(account.asString());
|
||||
if (!a)
|
||||
{
|
||||
error = invalid_data(element_name, "account");
|
||||
error = invalid_data(element_name, jss::account.c_str());
|
||||
return ret;
|
||||
}
|
||||
uAccount = *a;
|
||||
}
|
||||
}
|
||||
|
||||
if (currency)
|
||||
if (asset)
|
||||
{
|
||||
// human currency
|
||||
if (!currency.isString())
|
||||
// human asset
|
||||
if (!asset.isString())
|
||||
{
|
||||
error = string_expected(element_name, "currency");
|
||||
error = string_expected(element_name, assetName.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
hasCurrency = true;
|
||||
hasAsset = true;
|
||||
|
||||
if (!uCurrency.parseHex(currency.asString()))
|
||||
if (isMPT)
|
||||
{
|
||||
if (!to_currency(uCurrency, currency.asString()))
|
||||
MPTID u;
|
||||
if (!u.parseHex(asset.asString()))
|
||||
{
|
||||
error = invalid_data(element_name, "currency");
|
||||
error = invalid_data(element_name, assetName.c_str());
|
||||
return ret;
|
||||
}
|
||||
if (getMPTIssuer(u) == beast::zero)
|
||||
{
|
||||
error = invalid_data(element_name, jss::account.c_str());
|
||||
return ret;
|
||||
}
|
||||
uAsset = u;
|
||||
}
|
||||
else
|
||||
{
|
||||
Currency currency;
|
||||
if (!currency.parseHex(asset.asString()))
|
||||
{
|
||||
if (!to_currency(currency, asset.asString()))
|
||||
{
|
||||
error = invalid_data(element_name, assetName.c_str());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
uAsset = currency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +836,7 @@ parseLeaf(
|
||||
// human account id
|
||||
if (!issuer.isString())
|
||||
{
|
||||
error = string_expected(element_name, "issuer");
|
||||
error = string_expected(element_name, jss::issuer.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -817,14 +845,20 @@ parseLeaf(
|
||||
auto const a = parseBase58<AccountID>(issuer.asString());
|
||||
if (!a)
|
||||
{
|
||||
error = invalid_data(element_name, "issuer");
|
||||
error = invalid_data(element_name, jss::issuer.c_str());
|
||||
return ret;
|
||||
}
|
||||
uIssuer = *a;
|
||||
}
|
||||
|
||||
if (isMPT && uIssuer != getMPTIssuer(uAsset.get<MPTID>()))
|
||||
{
|
||||
error = invalid_data(element_name, jss::issuer.c_str());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
p.emplace_back(uAccount, uCurrency, uIssuer, hasCurrency);
|
||||
p.emplace_back(uAccount, uAsset, uIssuer, hasAsset);
|
||||
}
|
||||
|
||||
tail.push_back(p);
|
||||
|
||||
@@ -31,8 +31,15 @@ STPathElement::get_hash(STPathElement const& element)
|
||||
for (auto const x : element.getAccountID())
|
||||
hash_account += (hash_account * 257) ^ x;
|
||||
|
||||
for (auto const x : element.getCurrency())
|
||||
hash_currency += (hash_currency * 509) ^ x;
|
||||
// Check pathAsset type instead of element's mType
|
||||
// In some cases mType might be account but the asset
|
||||
// is still set to either MPT or currency (see Pathfinder::addLink())
|
||||
element.getPathAsset().visit(
|
||||
[&](MPTID const& mpt) { hash_currency += beast::uhash<>{}(mpt); },
|
||||
[&](Currency const& currency) {
|
||||
for (auto const x : currency)
|
||||
hash_currency += (hash_currency * 509) ^ x;
|
||||
});
|
||||
|
||||
for (auto const x : element.getIssuerID())
|
||||
hash_issuer += (hash_issuer * 911) ^ x;
|
||||
@@ -68,24 +75,30 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hasAccount = iType & STPathElement::typeAccount;
|
||||
auto hasCurrency = iType & STPathElement::typeCurrency;
|
||||
auto hasIssuer = iType & STPathElement::typeIssuer;
|
||||
auto const hasAccount = (iType & STPathElement::typeAccount) != 0u;
|
||||
auto const hasCurrency = (iType & STPathElement::typeCurrency) != 0u;
|
||||
auto const hasIssuer = (iType & STPathElement::typeIssuer) != 0u;
|
||||
auto const hasMPT = (iType & STPathElement::typeMPT) != 0u;
|
||||
|
||||
AccountID account;
|
||||
Currency currency;
|
||||
PathAsset asset;
|
||||
AccountID issuer;
|
||||
|
||||
if (hasAccount != 0)
|
||||
if (hasAccount)
|
||||
account = sit.get160();
|
||||
|
||||
if (hasCurrency != 0)
|
||||
currency = sit.get160();
|
||||
XRPL_ASSERT(
|
||||
!(hasCurrency && hasMPT), "xrpl::STPathSet::STPathSet : not has Currency and MPT");
|
||||
if (hasCurrency)
|
||||
asset = static_cast<Currency>(sit.get160());
|
||||
|
||||
if (hasIssuer != 0)
|
||||
if (hasMPT)
|
||||
asset = sit.get192();
|
||||
|
||||
if (hasIssuer)
|
||||
issuer = sit.get160();
|
||||
|
||||
path.emplace_back(account, currency, issuer, hasCurrency);
|
||||
path.emplace_back(account, asset, issuer, hasCurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,11 +150,11 @@ STPathSet::isDefault() const
|
||||
}
|
||||
|
||||
bool
|
||||
STPath::hasSeen(AccountID const& account, Currency const& currency, AccountID const& issuer) const
|
||||
STPath::hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const
|
||||
{
|
||||
for (auto& p : mPath)
|
||||
{
|
||||
if (p.getAccountID() == account && p.getCurrency() == currency && p.getIssuerID() == issuer)
|
||||
if (p.getAccountID() == account && p.getPathAsset() == asset && p.getIssuerID() == issuer)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,9 +176,16 @@ STPath::getJson(JsonOptions) const
|
||||
if ((iType & STPathElement::typeAccount) != 0u)
|
||||
elem[jss::account] = to_string(it.getAccountID());
|
||||
|
||||
XRPL_ASSERT(
|
||||
((iType & STPathElement::typeCurrency) == 0u) ||
|
||||
((iType & STPathElement::typeMPT) == 0u),
|
||||
"xrpl::STPath::getJson : not type Currency and MPT");
|
||||
if ((iType & STPathElement::typeCurrency) != 0u)
|
||||
elem[jss::currency] = to_string(it.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::typeMPT) != 0u)
|
||||
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::typeIssuer) != 0u)
|
||||
elem[jss::issuer] = to_string(it.getIssuerID());
|
||||
|
||||
@@ -209,13 +229,16 @@ STPathSet::add(Serializer& s) const
|
||||
|
||||
s.add8(iType);
|
||||
|
||||
if ((iType & STPathElement::typeAccount) != 0)
|
||||
if ((iType & STPathElement::typeAccount) != 0u)
|
||||
s.addBitString(speElement.getAccountID());
|
||||
|
||||
if ((iType & STPathElement::typeCurrency) != 0)
|
||||
if ((iType & STPathElement::typeMPT) != 0u)
|
||||
s.addBitString(speElement.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::typeCurrency) != 0u)
|
||||
s.addBitString(speElement.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::typeIssuer) != 0)
|
||||
if ((iType & STPathElement::typeIssuer) != 0u)
|
||||
s.addBitString(speElement.getIssuerID());
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ transResults()
|
||||
MAKE_ERROR(temBAD_FEE, "Invalid fee, negative or not XRP."),
|
||||
MAKE_ERROR(temBAD_ISSUER, "Malformed: Bad issuer."),
|
||||
MAKE_ERROR(temBAD_LIMIT, "Limits must be non-negative."),
|
||||
MAKE_ERROR(temBAD_MPT, "Malformed: Bad MPT."),
|
||||
MAKE_ERROR(temBAD_OFFER, "Malformed: Bad offer."),
|
||||
MAKE_ERROR(temBAD_PATH, "Malformed: Bad path."),
|
||||
MAKE_ERROR(temBAD_PATH_LOOP, "Malformed: Loop in path."),
|
||||
@@ -214,6 +215,7 @@ transResults()
|
||||
MAKE_ERROR(terNO_AMM, "AMM doesn't exist for the asset pair."),
|
||||
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
|
||||
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(terLOCKED, "Fund is locked."),
|
||||
|
||||
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
|
||||
};
|
||||
|
||||
@@ -1006,6 +1006,26 @@ removeDeletedTrustLines(
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
removeDeletedMPTs(ApplyView& view, std::vector<uint256> const& mpts, beast::Journal viewJ)
|
||||
{
|
||||
// There could be at most two MPTs - one for each side of AMM pool
|
||||
if (mpts.size() > 2)
|
||||
{
|
||||
JLOG(viewJ.error()) << "removeDeletedMPTs: deleted mpts exceed 2 " << mpts.size();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& index : mpts)
|
||||
{
|
||||
if (auto const sleState = view.peek({ltMPTOKEN, index}); sleState &&
|
||||
deleteAMMMPToken(view, sleState, (*sleState)[sfIssuer], viewJ) != tesSUCCESS)
|
||||
{
|
||||
JLOG(viewJ.error()) << "removeDeletedMPTs: failed to delete AMM MPT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset the context, discarding any changes made and adjust the fee.
|
||||
|
||||
@param fee The transaction fee to be charged.
|
||||
@@ -1144,19 +1164,21 @@ Transactor::operator()()
|
||||
// when transactions fail with a `tec` code.
|
||||
std::vector<uint256> removedOffers;
|
||||
std::vector<uint256> removedTrustLines;
|
||||
std::vector<uint256> removedMPTs;
|
||||
std::vector<uint256> expiredNFTokenOffers;
|
||||
std::vector<uint256> expiredCredentials;
|
||||
|
||||
bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED));
|
||||
bool const doLines = (result == tecINCOMPLETE);
|
||||
bool const doLinesOrMPTs = (result == tecINCOMPLETE);
|
||||
bool const doNFTokenOffers = (result == tecEXPIRED);
|
||||
bool const doCredentials = (result == tecEXPIRED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials)
|
||||
if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials)
|
||||
{
|
||||
ctx_.visit([doOffers,
|
||||
&removedOffers,
|
||||
doLines,
|
||||
doLinesOrMPTs,
|
||||
&removedTrustLines,
|
||||
&removedMPTs,
|
||||
doNFTokenOffers,
|
||||
&expiredNFTokenOffers,
|
||||
doCredentials,
|
||||
@@ -1178,10 +1200,17 @@ Transactor::operator()()
|
||||
removedOffers.push_back(index);
|
||||
}
|
||||
|
||||
if (doLines && before && after && (before->getType() == ltRIPPLE_STATE))
|
||||
if (doLinesOrMPTs && before && after)
|
||||
{
|
||||
// Removal of obsolete AMM trust line
|
||||
removedTrustLines.push_back(index);
|
||||
if (before->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
removedTrustLines.push_back(index);
|
||||
}
|
||||
else if (before->getType() == ltMPTOKEN)
|
||||
{
|
||||
removedMPTs.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (doNFTokenOffers && before && after &&
|
||||
@@ -1219,6 +1248,7 @@ Transactor::operator()()
|
||||
{
|
||||
removeDeletedTrustLines(
|
||||
view(), removedTrustLines, ctx_.registry.get().getJournal("View"));
|
||||
removeDeletedMPTs(view(), removedMPTs, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (result == tecEXPIRED)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -128,15 +127,16 @@ ValidAMM::finalizeCreate(
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAmount].get<Issue>(),
|
||||
tx[sfAmount2].get<Issue>(),
|
||||
tx[sfAmount].asset(),
|
||||
tx[sfAmount2].asset(),
|
||||
fhIGNORE_FREEZE,
|
||||
ahIGNORE_AUTH,
|
||||
j);
|
||||
// Create invariant:
|
||||
// sqrt(amount * amount2) == LPTokens
|
||||
// all balances are greater than zero
|
||||
if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
|
||||
ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != *lptAMMBalanceAfter_)
|
||||
ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get<Issue>()) != *lptAMMBalanceAfter_)
|
||||
{
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " "
|
||||
<< *lptAMMBalanceAfter_;
|
||||
@@ -188,12 +188,7 @@ ValidAMM::generalInvariant(
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
auto const [amount, amount2] = ammPoolHolds(
|
||||
view,
|
||||
*ammAccount_,
|
||||
tx[sfAsset].get<Issue>(),
|
||||
tx[sfAsset2].get<Issue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
j);
|
||||
view, *ammAccount_, tx[sfAsset], tx[sfAsset2], fhIGNORE_FREEZE, ahIGNORE_AUTH, j);
|
||||
// Deposit and Withdrawal invariant:
|
||||
// sqrt(amount * amount2) >= LPTokens
|
||||
// all balances are greater than zero
|
||||
|
||||
@@ -172,7 +172,7 @@ TransfersNotFrozen::recordBalanceChanges(
|
||||
STAmount const& balanceChange)
|
||||
{
|
||||
auto const balanceChangeSign = balanceChange.signum();
|
||||
auto const currency = after->at(sfBalance).getCurrency();
|
||||
auto const currency = after->at(sfBalance).get<Issue>().currency;
|
||||
|
||||
// Change from low account's perspective, which is trust line default
|
||||
recordBalance({currency, after->at(sfHighLimit).getIssuer()}, {after, balanceChangeSign});
|
||||
|
||||
@@ -284,25 +284,29 @@ NoZeroEscrow::visitEntry(
|
||||
}
|
||||
else
|
||||
{
|
||||
// IOU case
|
||||
if (amount.holds<Issue>())
|
||||
{
|
||||
if (amount <= beast::zero)
|
||||
return true;
|
||||
return amount.asset().visit(
|
||||
[&](Issue const& issue) {
|
||||
// IOU case
|
||||
if (amount <= beast::zero)
|
||||
return true;
|
||||
|
||||
if (badCurrency() == amount.getCurrency())
|
||||
return true;
|
||||
}
|
||||
if (badCurrency() == issue.currency)
|
||||
return true;
|
||||
|
||||
// MPT case
|
||||
if (amount.holds<MPTIssue>())
|
||||
{
|
||||
if (amount <= beast::zero)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amount.mpt() > MPTAmount{maxMPTokenAmount})
|
||||
return true; // LCOV_EXCL_LINE
|
||||
}
|
||||
// MPT case
|
||||
,
|
||||
[&](MPTIssue const&) {
|
||||
if (amount <= beast::zero)
|
||||
return true;
|
||||
|
||||
if (amount.mpt() > MPTAmount{maxMPTokenAmount})
|
||||
return true; // LCOV_EXCL_LINE
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -600,8 +604,8 @@ NoXRPTrustLines::visitEntry(
|
||||
// checking the issue directly here instead of
|
||||
// relying on .native() just in case native somehow
|
||||
// were systematically incorrect
|
||||
xrpTrustLine_ = after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
|
||||
after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
|
||||
xrpTrustLine_ = after->getFieldAmount(sfLowLimit).asset() == xrpIssue() ||
|
||||
after->getFieldAmount(sfHighLimit).asset() == xrpIssue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,17 +778,23 @@ ValidClawback::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trustlinesChanged == 1)
|
||||
bool const mptV2Enabled = view.rules().enabled(featureMPTokensV2);
|
||||
if (trustlinesChanged == 1 || (mptV2Enabled && mptokensChanged == 1))
|
||||
{
|
||||
AccountID const issuer = tx.getAccountID(sfAccount);
|
||||
STAmount const& amount = tx.getFieldAmount(sfAmount);
|
||||
AccountID const& holder = amount.getIssuer();
|
||||
STAmount const holderBalance =
|
||||
accountHolds(view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
|
||||
STAmount const holderBalance = amount.asset().visit(
|
||||
[&](Issue const& issue) {
|
||||
return accountHolds(view, holder, issue.currency, issuer, fhIGNORE_FREEZE, j);
|
||||
},
|
||||
[&](MPTIssue const& issue) {
|
||||
return accountHolds(view, issuer, issue, fhIGNORE_FREEZE, ahIGNORE_AUTH, j);
|
||||
});
|
||||
|
||||
if (holderBalance.signum() < 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: trustline balance is negative";
|
||||
JLOG(j.fatal()) << "Invariant failed: trustline or MPT balance is negative";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -52,9 +53,10 @@ ValidMPTIssuance::finalize(
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (isTesSuccess(result))
|
||||
auto const& rules = view.rules();
|
||||
bool const mptV2Enabled = rules.enabled(featureMPTokensV2);
|
||||
if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE))
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
[[maybe_unused]]
|
||||
bool const enforceCreatedByIssuer =
|
||||
rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol);
|
||||
@@ -112,12 +114,12 @@ ValidMPTIssuance::finalize(
|
||||
return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
|
||||
}
|
||||
|
||||
bool const lendingProtocolEnabled = view.rules().enabled(featureLendingProtocol);
|
||||
bool const lendingProtocolEnabled = rules.enabled(featureLendingProtocol);
|
||||
// ttESCROW_FINISH may authorize an MPT, but it can't have the
|
||||
// mayAuthorizeMPT privilege, because that may cause
|
||||
// non-amendment-gated side effects.
|
||||
bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) &&
|
||||
(view.rules().enabled(featureSingleAssetVault) || lendingProtocolEnabled);
|
||||
(rules.enabled(featureSingleAssetVault) || lendingProtocolEnabled);
|
||||
if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || enforceEscrowFinish)
|
||||
{
|
||||
bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
|
||||
@@ -134,19 +136,42 @@ ValidMPTIssuance::finalize(
|
||||
"succeeded but deleted issuances";
|
||||
return false;
|
||||
}
|
||||
if (lendingProtocolEnabled && mptokensCreated_ + mptokensDeleted_ > 1)
|
||||
if (mptV2Enabled && hasPrivilege(tx, mayAuthorizeMPT) &&
|
||||
(txnType == ttAMM_WITHDRAW || txnType == ttAMM_CLAWBACK))
|
||||
{
|
||||
if (submittedByIssuer && txnType == ttAMM_WITHDRAW && mptokensCreated_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"submitted by issuer succeeded "
|
||||
"but created bad number of mptokens";
|
||||
return false;
|
||||
}
|
||||
// At most one MPToken may be created on withdraw/clawback since:
|
||||
// - Liquidity Provider must have at least one token in order
|
||||
// participate in AMM pool liquidity.
|
||||
// - At most two MPTokens may be deleted if AMM pool, which has exactly
|
||||
// two tokens, is empty after withdraw/clawback.
|
||||
if (mptokensCreated_ > 1 || mptokensDeleted_ > 2)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
|
||||
"but created/deleted bad number of mptokens";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (lendingProtocolEnabled && (mptokensCreated_ + mptokensDeleted_) > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
|
||||
"but created/deleted bad number mptokens";
|
||||
return false;
|
||||
}
|
||||
if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
|
||||
else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer "
|
||||
"succeeded but created/deleted mptokens";
|
||||
return false;
|
||||
}
|
||||
if (!submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
|
||||
else if (
|
||||
!submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
|
||||
(mptokensCreated_ + mptokensDeleted_ != 1))
|
||||
{
|
||||
// if the holder submitted this tx, then a mptoken must be
|
||||
@@ -158,6 +183,52 @@ ValidMPTIssuance::finalize(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasPrivilege(tx, mayCreateMPT))
|
||||
{
|
||||
bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
|
||||
|
||||
if (mptIssuancesCreated_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but created MPT issuances";
|
||||
return false;
|
||||
}
|
||||
if (mptIssuancesDeleted_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but deleted issuances";
|
||||
return false;
|
||||
}
|
||||
if (mptokensDeleted_ > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but deleted MPTokens";
|
||||
return false;
|
||||
}
|
||||
// AMMCreate may auto-create up to two MPT objects:
|
||||
// - one per asset side in an MPT/MPT AMM, or one in an IOU/MPT AMM.
|
||||
// CheckCash may auto-create at most one MPT object for the receiver.
|
||||
if ((txnType == ttAMM_CREATE && mptokensCreated_ > 2) ||
|
||||
(txnType == ttCHECK_CASH && mptokensCreated_ > 1))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize "
|
||||
"succeeded but created bad number of mptokens";
|
||||
return false;
|
||||
}
|
||||
if (submittedByIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer "
|
||||
"succeeded but created mptokens";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Offer crossing or payment may consume multiple offers
|
||||
// where takerPays is MPT amount. If the offer owner doesn't
|
||||
// own MPT then MPT is created automatically.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (txnType == ttESCROW_FINISH)
|
||||
{
|
||||
// ttESCROW_FINISH may authorize an MPT, but it can't have the
|
||||
@@ -168,8 +239,9 @@ ValidMPTIssuance::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && mptokensCreated_ == 0 &&
|
||||
mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)
|
||||
if (hasPrivilege(tx, mayDeleteMPT) &&
|
||||
((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) &&
|
||||
mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -194,4 +266,223 @@ ValidMPTIssuance::finalize(
|
||||
mptokensDeleted_ == 0;
|
||||
}
|
||||
|
||||
void
|
||||
ValidMPTPayment::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (overflow_)
|
||||
return;
|
||||
|
||||
auto makeKey = [](SLE const& sle) {
|
||||
if (sle.getType() == ltMPTOKEN_ISSUANCE)
|
||||
return makeMptID(sle[sfSequence], sle[sfIssuer]);
|
||||
return sle[sfMPTokenIssuanceID];
|
||||
};
|
||||
|
||||
auto update = [&](SLE const& sle, Order order) -> bool {
|
||||
auto const type = sle.getType();
|
||||
if (type == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
auto const outstanding = sle[sfOutstandingAmount];
|
||||
if (outstanding > maxMPTokenAmount)
|
||||
{
|
||||
overflow_ = true;
|
||||
return false;
|
||||
}
|
||||
data_[makeKey(sle)].outstanding[order] = outstanding;
|
||||
}
|
||||
else if (type == ltMPTOKEN)
|
||||
{
|
||||
auto const mptAmt = sle[sfMPTAmount];
|
||||
auto const lockedAmt = sle[~sfLockedAmount].value_or(0);
|
||||
if (mptAmt > maxMPTokenAmount || lockedAmt > maxMPTokenAmount ||
|
||||
lockedAmt > (maxMPTokenAmount - mptAmt))
|
||||
{
|
||||
overflow_ = true;
|
||||
return false;
|
||||
}
|
||||
auto const res = static_cast<std::int64_t>(mptAmt + lockedAmt);
|
||||
// subtract before from after
|
||||
if (order == Before)
|
||||
{
|
||||
data_[makeKey(sle)].mptAmount -= res;
|
||||
}
|
||||
else
|
||||
{
|
||||
data_[makeKey(sle)].mptAmount += res;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (before && !update(*before, Before))
|
||||
return;
|
||||
|
||||
if (after)
|
||||
{
|
||||
if (after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
overflow_ = (*after)[sfOutstandingAmount] > maxMPTAmount(*after);
|
||||
}
|
||||
if (!update(*after, After))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTPayment::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (isTesSuccess(result))
|
||||
{
|
||||
bool const enforce = view.rules().enabled(featureMPTokensV2);
|
||||
if (overflow_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: OutstandingAmount overflow";
|
||||
return !enforce;
|
||||
}
|
||||
|
||||
auto const signedMax = static_cast<std::int64_t>(maxMPTokenAmount);
|
||||
for (auto const& [id, data] : data_)
|
||||
{
|
||||
(void)id;
|
||||
bool const addOverflows =
|
||||
(data.mptAmount > 0 && data.outstanding[Before] > (signedMax - data.mptAmount)) ||
|
||||
(data.mptAmount < 0 && data.outstanding[Before] < (-signedMax - data.mptAmount));
|
||||
if (addOverflows ||
|
||||
data.outstanding[After] != (data.outstanding[Before] + data.mptAmount))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: invalid OutstandingAmount balance "
|
||||
<< data.outstanding[Before] << " " << data.outstanding[After] << " "
|
||||
<< data.mptAmount;
|
||||
return !enforce;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidMPTTransfer::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// Record the before/after MPTAmount for each (issuanceID, account) pair
|
||||
// so finalize() can determine whether a transfer actually occurred.
|
||||
auto update = [&](SLE const& sle, bool isBefore) {
|
||||
if (sle.getType() == ltMPTOKEN)
|
||||
{
|
||||
auto const issuanceID = sle[sfMPTokenIssuanceID];
|
||||
auto const account = sle[sfAccount];
|
||||
auto const amount = sle[sfMPTAmount];
|
||||
if (isBefore)
|
||||
{
|
||||
amount_[issuanceID][account].amtBefore = amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount_[issuanceID][account].amtAfter = amount;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (before)
|
||||
update(*before, true);
|
||||
|
||||
if (after)
|
||||
update(*after, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTTransfer::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// AMMClawback is called by the issuer, so freeze restrictions do not apply.
|
||||
auto const txnType = tx.getTxnType();
|
||||
if (txnType == ttAMM_CLAWBACK)
|
||||
return true;
|
||||
|
||||
// DEX transactions (AMM[Create,Deposit,Withdraw], cross-currency payments, offer creates) are
|
||||
// subject to the MPTCanTrade flag in addition to the standard transfer rules.
|
||||
// A payment is only DEX if it is a cross-currency payment.
|
||||
auto const isDEX = [&tx, &txnType] {
|
||||
if (txnType == ttPAYMENT)
|
||||
{
|
||||
// A payment is cross-currency (and thus DEX) only if SendMax is present
|
||||
// and its asset differs from the destination asset.
|
||||
auto const amount = tx[sfAmount];
|
||||
return tx[~sfSendMax].value_or(amount).asset() != amount.asset();
|
||||
}
|
||||
return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttAMM_WITHDRAW ||
|
||||
txnType == ttOFFER_CREATE;
|
||||
}();
|
||||
|
||||
// Only enforce once MPTokensV2 is enabled to preserve consensus with non-V2 nodes.
|
||||
// Log invariant failure error even if MPTokensV2 is disabled.
|
||||
auto const enforce = !view.rules().enabled(featureMPTokensV2);
|
||||
|
||||
for (auto const& [mptID, values] : amount_)
|
||||
{
|
||||
std::uint16_t senders = 0;
|
||||
std::uint16_t receivers = 0;
|
||||
bool frozen = false;
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer);
|
||||
auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade);
|
||||
|
||||
for (auto const& [account, value] : values)
|
||||
{
|
||||
// Classify each account as a sender or receiver based on whether their MPTAmount
|
||||
// decreased or increased. Count new MPToken holders (no amtBefore) as receivers.
|
||||
// Skip deleted MPToken holders (amtAfter is nullopt); deletion requires zero balance.
|
||||
if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
|
||||
{
|
||||
if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore)
|
||||
{
|
||||
++receivers;
|
||||
}
|
||||
else
|
||||
{
|
||||
++senders;
|
||||
}
|
||||
|
||||
// Check once: if any involved account is frozen, the whole
|
||||
// issuance transfer is considered frozen. Only need to check for
|
||||
// frozen if there is a transfer of funds.
|
||||
if (!frozen && isFrozen(view, account, MPTIssue{mptID}))
|
||||
{
|
||||
frozen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A transfer between holders has occurred (senders > 0 && receivers > 0).
|
||||
// Fail if the issuance is frozen, does not permit transfers, or — for
|
||||
// DEX transactions — does not permit trading.
|
||||
if ((frozen || !canTransfer || (isDEX && !canTrade)) && senders > 0 && receivers > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: invalid MPToken transfer between holders";
|
||||
return enforce;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -8,15 +8,15 @@ AMMLiquidity<TIn, TOut>::AMMLiquidity(
|
||||
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)
|
||||
: ammContext_(ammContext)
|
||||
, ammAccountID_(ammAccountID)
|
||||
, tradingFee_(tradingFee)
|
||||
, issueIn_(in)
|
||||
, issueOut_(out)
|
||||
, assetIn_(in)
|
||||
, assetOut_(out)
|
||||
, initialBalances_{fetchBalances(view)}
|
||||
, j_(j)
|
||||
{
|
||||
@@ -26,13 +26,13 @@ template <typename TIn, typename TOut>
|
||||
TAmounts<TIn, TOut>
|
||||
AMMLiquidity<TIn, TOut>::fetchBalances(ReadView const& view) const
|
||||
{
|
||||
auto const assetIn = ammAccountHolds(view, ammAccountID_, issueIn_);
|
||||
auto const assetOut = ammAccountHolds(view, ammAccountID_, issueOut_);
|
||||
auto const amountIn = ammAccountHolds(view, ammAccountID_, assetIn_);
|
||||
auto const amountOut = ammAccountHolds(view, ammAccountID_, assetOut_);
|
||||
// This should not happen.
|
||||
if (assetIn < beast::zero || assetOut < beast::zero)
|
||||
if (amountIn < beast::zero || amountOut < beast::zero)
|
||||
Throw<std::runtime_error>("AMMLiquidity: invalid balances");
|
||||
|
||||
return TAmounts{get<TIn>(assetIn), get<TOut>(assetOut)};
|
||||
return TAmounts{get<TIn>(amountIn), get<TOut>(amountOut)};
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
@@ -42,7 +42,7 @@ AMMLiquidity<TIn, TOut>::generateFibSeqOffer(TAmounts<TIn, TOut> const& balances
|
||||
TAmounts<TIn, TOut> cur{};
|
||||
|
||||
cur.in = toAmount<TIn>(
|
||||
getIssue(balances.in),
|
||||
getAsset(balances.in),
|
||||
InitialFibSeqPct * initialBalances_.in,
|
||||
Number::rounding_mode::upward);
|
||||
cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
|
||||
@@ -60,7 +60,7 @@ AMMLiquidity<TIn, TOut>::generateFibSeqOffer(TAmounts<TIn, TOut> const& balances
|
||||
"xrpl::AMMLiquidity::generateFibSeqOffer : maximum iterations");
|
||||
|
||||
cur.out = toAmount<TOut>(
|
||||
getIssue(balances.out),
|
||||
getAsset(balances.out),
|
||||
cur.out * fib[ammContext_.curIters() - 1],
|
||||
Number::rounding_mode::downward);
|
||||
// swapAssetOut() returns negative in this case
|
||||
@@ -89,14 +89,18 @@ maxAmount()
|
||||
{
|
||||
return STAmount(STAmount::cMaxValue / 2, STAmount::cMaxOffset);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MPTAmount>)
|
||||
{
|
||||
return MPTAmount(maxMPTokenAmount);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T
|
||||
maxOut(T const& out, Issue const& iss)
|
||||
maxOut(T const& out, Asset const& asset)
|
||||
{
|
||||
Number const res = out * Number{99, -2};
|
||||
return toAmount<T>(iss, res, Number::rounding_mode::downward);
|
||||
return toAmount<T>(asset, res, Number::rounding_mode::downward);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -113,7 +117,7 @@ AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances, Rules con
|
||||
Quality{balances});
|
||||
}
|
||||
|
||||
auto const out = maxOut<TOut>(balances.out, issueOut());
|
||||
auto const out = maxOut<TOut>(balances.out, assetOut());
|
||||
if (out <= TOut{0} || out >= balances.out)
|
||||
return std::nullopt;
|
||||
return AMMOffer<TIn, TOut>(
|
||||
@@ -211,8 +215,8 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
|
||||
if (offer->amount().in > beast::zero && offer->amount().out > beast::zero)
|
||||
{
|
||||
JLOG(j_.trace()) << "AMMLiquidity::getOffer, created " << to_string(offer->amount().in)
|
||||
<< "/" << issueIn_ << " " << to_string(offer->amount().out) << "/"
|
||||
<< issueOut_;
|
||||
<< "/" << assetIn_ << " " << to_string(offer->amount().out) << "/"
|
||||
<< assetOut_;
|
||||
return offer;
|
||||
}
|
||||
|
||||
@@ -225,9 +229,13 @@ AMMLiquidity<TIn, TOut>::getOffer(ReadView const& view, std::optional<Quality> c
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template class AMMLiquidity<STAmount, STAmount>;
|
||||
template class AMMLiquidity<IOUAmount, IOUAmount>;
|
||||
template class AMMLiquidity<XRPAmount, IOUAmount>;
|
||||
template class AMMLiquidity<IOUAmount, XRPAmount>;
|
||||
template class AMMLiquidity<MPTAmount, MPTAmount>;
|
||||
template class AMMLiquidity<XRPAmount, MPTAmount>;
|
||||
template class AMMLiquidity<MPTAmount, XRPAmount>;
|
||||
template class AMMLiquidity<MPTAmount, IOUAmount>;
|
||||
template class AMMLiquidity<IOUAmount, MPTAmount>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
AMMOffer<TIn, TOut>::AMMOffer(
|
||||
AMMLiquidity<TIn, TOut> const& ammLiquidity,
|
||||
TAmounts<TIn, TOut> const& amounts,
|
||||
@@ -15,28 +15,35 @@ AMMOffer<TIn, TOut>::AMMOffer(
|
||||
{
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
Issue const&
|
||||
AMMOffer<TIn, TOut>::issueIn() const
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
Asset const&
|
||||
AMMOffer<TIn, TOut>::assetIn() const
|
||||
{
|
||||
return ammLiquidity_.issueIn();
|
||||
return ammLiquidity_.assetIn();
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
Asset const&
|
||||
AMMOffer<TIn, TOut>::assetOut() const
|
||||
{
|
||||
return ammLiquidity_.assetOut();
|
||||
}
|
||||
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
AccountID const&
|
||||
AMMOffer<TIn, TOut>::owner() const
|
||||
{
|
||||
return ammLiquidity_.ammAccount();
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
TAmounts<TIn, TOut> const&
|
||||
AMMOffer<TIn, TOut>::amount() const
|
||||
{
|
||||
return amounts_;
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
void
|
||||
AMMOffer<TIn, TOut>::consume(ApplyView& view, TAmounts<TIn, TOut> const& consumed)
|
||||
{
|
||||
@@ -52,7 +59,7 @@ AMMOffer<TIn, TOut>::consume(ApplyView& view, TAmounts<TIn, TOut> const& consume
|
||||
ammLiquidity_.context().setAMMUsed();
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
TAmounts<TIn, TOut>
|
||||
AMMOffer<TIn, TOut>::limitOut(
|
||||
TAmounts<TIn, TOut> const& offerAmount,
|
||||
@@ -77,7 +84,7 @@ AMMOffer<TIn, TOut>::limitOut(
|
||||
return {swapAssetOut(balances_, limit, ammLiquidity_.tradingFee()), limit};
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
TAmounts<TIn, TOut>
|
||||
AMMOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp)
|
||||
const
|
||||
@@ -94,7 +101,7 @@ AMMOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const&
|
||||
return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())};
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
QualityFunction
|
||||
AMMOffer<TIn, TOut>::getQualityFunc() const
|
||||
{
|
||||
@@ -103,7 +110,7 @@ AMMOffer<TIn, TOut>::getQualityFunc() const
|
||||
return QualityFunction{balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}};
|
||||
}
|
||||
|
||||
template <typename TIn, typename TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
bool
|
||||
AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::Journal j) const
|
||||
{
|
||||
@@ -133,9 +140,13 @@ AMMOffer<TIn, TOut>::checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::
|
||||
return false;
|
||||
}
|
||||
|
||||
template class AMMOffer<STAmount, STAmount>;
|
||||
template class AMMOffer<IOUAmount, IOUAmount>;
|
||||
template class AMMOffer<XRPAmount, IOUAmount>;
|
||||
template class AMMOffer<IOUAmount, XRPAmount>;
|
||||
template class AMMOffer<MPTAmount, MPTAmount>;
|
||||
template class AMMOffer<XRPAmount, MPTAmount>;
|
||||
template class AMMOffer<MPTAmount, XRPAmount>;
|
||||
template class AMMOffer<IOUAmount, MPTAmount>;
|
||||
template class AMMOffer<MPTAmount, IOUAmount>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMUtils.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
@@ -14,6 +15,7 @@
|
||||
#include <xrpl/tx/paths/OfferStream.h>
|
||||
#include <xrpl/tx/paths/detail/FlatSets.h>
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
@@ -40,7 +42,7 @@ protected:
|
||||
/** Number of offers consumed or partially consumed the last time
|
||||
the step ran, including expired and unfunded offers.
|
||||
|
||||
N.B. This this not the total number offers consumed by this step for the
|
||||
N.B. This is not the total number offers consumed by this step for the
|
||||
entire payment, it is only the number the last time it ran. Offers may
|
||||
be partially consumed multiple times during a payment.
|
||||
*/
|
||||
@@ -50,6 +52,7 @@ protected:
|
||||
// quality or there is no CLOB offer.
|
||||
std::optional<AMMLiquidity<TIn, TOut>> ammLiquidity_;
|
||||
beast::Journal const j_;
|
||||
Asset const strandDeliver_;
|
||||
|
||||
struct Cache
|
||||
{
|
||||
@@ -64,13 +67,14 @@ protected:
|
||||
std::optional<Cache> cache_;
|
||||
|
||||
private:
|
||||
BookStep(StrandContext const& ctx, Issue const& in, Issue const& out)
|
||||
BookStep(StrandContext const& ctx, Asset const& in, Asset const& out)
|
||||
: book_(in, out, ctx.domainID)
|
||||
, strandSrc_(ctx.strandSrc)
|
||||
, strandDst_(ctx.strandDst)
|
||||
, prevStep_(ctx.prevStep)
|
||||
, ownerPaysTransferFee_(ctx.ownerPaysTransferFee)
|
||||
, j_(ctx.j)
|
||||
, strandDeliver_(ctx.strandDeliver)
|
||||
{
|
||||
if (auto const ammSle = ctx.view.read(keylet::amm(in, out));
|
||||
ammSle && ammSle->getFieldAmount(sfLPTokenBalance) != beast::zero)
|
||||
@@ -163,11 +167,14 @@ protected:
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << name << ": "
|
||||
<< "\ninIss: " << book_.in.account << "\noutIss: " << book_.out.account
|
||||
<< "\ninCur: " << book_.in.currency << "\noutCur: " << book_.out.currency;
|
||||
<< "\ninIss: " << book_.in.getIssuer() << "\noutIss: " << book_.out.getIssuer()
|
||||
<< "\ninCur: " << to_string(book_.in) << "\noutCur: " << to_string(book_.out);
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
Rate
|
||||
rate(ReadView const& view, Asset const& asset, AccountID const& dstAccount) const;
|
||||
|
||||
private:
|
||||
friend bool
|
||||
operator==(BookStep const& lhs, BookStep const& rhs)
|
||||
@@ -188,7 +195,7 @@ private:
|
||||
// Unfunded offers and bad offers are skipped (and returned).
|
||||
// callback is called with the offer SLE, taker pays, taker gets.
|
||||
// If callback returns false, don't process any more offers.
|
||||
// Return the unfunded and bad offers and the number of offers consumed.
|
||||
// Return the unfunded, bad offers and the number of offers consumed.
|
||||
template <class Callback>
|
||||
std::pair<boost::container::flat_set<uint256>, std::uint32_t>
|
||||
forEachOffer(
|
||||
@@ -227,6 +234,11 @@ private:
|
||||
std::optional<QualityFunction>
|
||||
tipOfferQualityF(ReadView const& view) const;
|
||||
|
||||
// Check that takerPays/takerGets can be transferred/traded.
|
||||
// Applies to MPT assets.
|
||||
bool
|
||||
checkMPTDEX(ReadView const& view, AccountID const& owner) const;
|
||||
|
||||
friend TDerived;
|
||||
};
|
||||
|
||||
@@ -245,7 +257,7 @@ class BookPaymentStep : public BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>
|
||||
public:
|
||||
explicit BookPaymentStep() = default;
|
||||
|
||||
BookPaymentStep(StrandContext const& ctx, Issue const& in, Issue const& out)
|
||||
BookPaymentStep(StrandContext const& ctx, Asset const& in, Asset const& out)
|
||||
: BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>(ctx, in, out)
|
||||
{
|
||||
}
|
||||
@@ -310,17 +322,12 @@ public:
|
||||
// (the old code does not charge a fee)
|
||||
// Calculate amount that goes to the taker and the amount charged the
|
||||
// offer owner
|
||||
auto rate = [&](AccountID const& id) {
|
||||
if (isXRP(id) || id == this->strandDst_)
|
||||
return parityRate;
|
||||
return transferRate(v, id);
|
||||
};
|
||||
|
||||
auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate;
|
||||
auto const trIn =
|
||||
redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : parityRate;
|
||||
// Always charge the transfer fee, even if the owner is the issuer,
|
||||
// unless the fee is waived
|
||||
auto const trOut = (this->ownerPaysTransferFee_ && waiveFee == WaiveTransferFee::No)
|
||||
? rate(this->book_.out.account)
|
||||
? this->rate(v, this->book_.out, this->strandDst_)
|
||||
: parityRate;
|
||||
|
||||
Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))};
|
||||
@@ -355,7 +362,7 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
BookOfferCrossingStep(StrandContext const& ctx, Issue const& in, Issue const& out)
|
||||
BookOfferCrossingStep(StrandContext const& ctx, Asset const& in, Asset const& out)
|
||||
: BookStep<TIn, TOut, BookOfferCrossingStep<TIn, TOut>>(ctx, in, out)
|
||||
, defaultPath_(ctx.isDefaultPath)
|
||||
, qualityThreshold_(getQuality(ctx.limitQuality))
|
||||
@@ -454,7 +461,7 @@ public:
|
||||
auto const srcAcct = (prevStep != nullptr) ? prevStep->directStepSrcAcct() : std::nullopt;
|
||||
|
||||
return owner == srcAcct // If offer crossing && prevStep is DirectI
|
||||
? QUALITY_ONE // && src is offer owner
|
||||
? QUALITY_ONE // or MPTEndpoint && src is offer owner
|
||||
: trIn; // then rate = QUALITY_ONE
|
||||
}
|
||||
|
||||
@@ -502,13 +509,8 @@ public:
|
||||
return ofrQ;
|
||||
}
|
||||
|
||||
auto rate = [&](AccountID const& id) {
|
||||
if (isXRP(id) || id == this->strandDst_)
|
||||
return parityRate;
|
||||
return transferRate(v, id);
|
||||
};
|
||||
|
||||
auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate;
|
||||
auto const trIn =
|
||||
redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : parityRate;
|
||||
// AMM doesn't pay the transfer fee on the out amount
|
||||
auto const trOut = parityRate;
|
||||
|
||||
@@ -659,15 +661,11 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
// (the old code does not charge a fee)
|
||||
// Calculate amount that goes to the taker and the amount charged the offer
|
||||
// owner
|
||||
auto rate = [this, &sb](AccountID const& id) -> std::uint32_t {
|
||||
if (isXRP(id) || id == this->strandDst_)
|
||||
return QUALITY_ONE;
|
||||
return transferRate(sb, id).value;
|
||||
};
|
||||
|
||||
std::uint32_t const trIn = redeems(prevStepDir) ? rate(book_.in.account) : QUALITY_ONE;
|
||||
std::uint32_t const trIn =
|
||||
redeems(prevStepDir) ? rate(sb, book_.in, this->strandDst_).value : QUALITY_ONE;
|
||||
// Always charge the transfer fee, even if the owner is the issuer
|
||||
std::uint32_t const trOut = ownerPaysTransferFee_ ? rate(book_.out.account) : QUALITY_ONE;
|
||||
std::uint32_t const trOut =
|
||||
ownerPaysTransferFee_ ? rate(sb, book_.out, this->strandDst_).value : QUALITY_ONE;
|
||||
|
||||
typename FlowOfferStream<TIn, TOut>::StepCounter counter(MaxOffersToConsume, j_);
|
||||
|
||||
@@ -691,45 +689,49 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
strandSrc_, strandDst_, offer, ofrQ, offers, offerAttempted))
|
||||
return true;
|
||||
|
||||
// Make sure offer owner has authorization to own IOUs from issuer.
|
||||
// An account can always own XRP or their own IOUs.
|
||||
if (!isXRP(offer.issueIn().currency) && offer.owner() != offer.issueIn().account)
|
||||
Asset const& assetIn = offer.assetIn();
|
||||
bool const isAssetInMPT = assetIn.holds<MPTIssue>();
|
||||
auto const& owner = offer.owner();
|
||||
|
||||
if (isAssetInMPT)
|
||||
{
|
||||
auto const& issuerID = offer.issueIn().account;
|
||||
auto const issuer = afView.read(keylet::account(issuerID));
|
||||
if (issuer && ((*issuer)[sfFlags] & lsfRequireAuth))
|
||||
// Create MPToken for the offer's owner. No need to check
|
||||
// for the reserve since the offer is removed if it is consumed.
|
||||
// Therefore, the owner count remains the same.
|
||||
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
// Issuer requires authorization. See if offer owner has that.
|
||||
auto const& ownerID = offer.owner();
|
||||
auto const authFlag = issuerID > ownerID ? lsfHighAuth : lsfLowAuth;
|
||||
|
||||
auto const line =
|
||||
afView.read(keylet::line(ownerID, issuerID, offer.issueIn().currency));
|
||||
|
||||
if (!line || (((*line)[sfFlags] & authFlag) == 0))
|
||||
{
|
||||
// Offer owner not authorized to hold IOU from issuer.
|
||||
// Remove this offer even if no crossing occurs.
|
||||
if (auto const key = offer.key())
|
||||
offers.permRmOffer(*key);
|
||||
if (!offerAttempted)
|
||||
{
|
||||
// Change quality only if no previous offers were tried.
|
||||
ofrQ = std::nullopt;
|
||||
}
|
||||
// Returning true causes offers.step() to delete the offer.
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// It shouldn't matter from auth point of view whether it's sb
|
||||
// or afView. Amendment guard this change just in case.
|
||||
auto& applyView = sb.rules().enabled(featureMPTokensV2) ? sb : afView;
|
||||
// Make sure offer owner has authorization to own Assets from issuer
|
||||
// and MPT assets can be traded/transferred.
|
||||
// An account can always own XRP or their own Assets.
|
||||
if (!isTesSuccess(requireAuth(applyView, assetIn, owner)) || !checkMPTDEX(sb, owner))
|
||||
{
|
||||
// Offer owner not authorized to hold IOU/MPT from issuer.
|
||||
// Remove this offer even if no crossing occurs.
|
||||
if (auto const key = offer.key())
|
||||
offers.permRmOffer(*key);
|
||||
if (!offerAttempted)
|
||||
{
|
||||
// Change quality only if no previous offers were tried.
|
||||
ofrQ = std::nullopt;
|
||||
}
|
||||
// Returning true causes offers.step() to delete the offer.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!static_cast<TDerived const*>(this)->checkQualityThreshold(offer.quality()))
|
||||
return false;
|
||||
|
||||
auto const [ofrInRate, ofrOutRate] = offer.adjustRates(
|
||||
static_cast<TDerived const*>(this)->getOfrInRate(prevStep_, offer.owner(), trIn),
|
||||
static_cast<TDerived const*>(this)->getOfrOutRate(
|
||||
prevStep_, offer.owner(), strandDst_, trOut));
|
||||
static_cast<TDerived const*>(this)->getOfrInRate(prevStep_, owner, trIn),
|
||||
static_cast<TDerived const*>(this)->getOfrOutRate(prevStep_, owner, strandDst_, trOut));
|
||||
|
||||
auto ofrAmt = offer.amount();
|
||||
TAmounts stpAmt{mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true), ofrAmt.out};
|
||||
@@ -756,6 +758,26 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
|
||||
}
|
||||
|
||||
// Limit offer's input if MPT, BookStep is the first step (an issuer
|
||||
// is making a cross-currency payment), and this offer is not owned
|
||||
// by the issuer. Otherwise, OutstandingAmount may overflow.
|
||||
auto const& issuer = assetIn.getIssuer();
|
||||
if (isAssetInMPT && !prevStep_ && offer.owner() != issuer)
|
||||
{
|
||||
// Funds available to issue
|
||||
auto const available = toAmount<TIn>(accountFunds(
|
||||
sb,
|
||||
issuer,
|
||||
assetIn, // STAmount{0}, but the default is not used
|
||||
FreezeHandling::fhIGNORE_FREEZE,
|
||||
AuthHandling::ahIGNORE_AUTH,
|
||||
j_));
|
||||
if (stpAmt.in > available)
|
||||
{
|
||||
limitStepIn(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate, available);
|
||||
}
|
||||
}
|
||||
|
||||
offerAttempted = true;
|
||||
return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate);
|
||||
};
|
||||
@@ -820,8 +842,8 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
|
||||
// The offer owner gets the ofrAmt. The difference between ofrAmt and
|
||||
// stepAmt is a transfer fee that goes to book_.in.account
|
||||
{
|
||||
auto const dr =
|
||||
offer.send(sb, book_.in.account, offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_);
|
||||
auto const dr = offer.send(
|
||||
sb, book_.in.getIssuer(), offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_);
|
||||
if (!isTesSuccess(dr))
|
||||
Throw<FlowException>(dr);
|
||||
}
|
||||
@@ -829,10 +851,16 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
|
||||
// The offer owner pays `ownerGives`. The difference between ownerGives and
|
||||
// stepAmt is a transfer fee that goes to book_.out.account
|
||||
{
|
||||
auto const& issuer = book_.out.getIssuer();
|
||||
auto const cr =
|
||||
offer.send(sb, offer.owner(), book_.out.account, toSTAmount(ownerGives, book_.out), j_);
|
||||
offer.send(sb, offer.owner(), issuer, toSTAmount(ownerGives, book_.out), j_);
|
||||
if (!isTesSuccess(cr))
|
||||
Throw<FlowException>(cr);
|
||||
if constexpr (std::is_same_v<TOut, MPTAmount>)
|
||||
{
|
||||
if (offer.owner() == issuer)
|
||||
issuerSelfDebitHookMPT(sb, book_.out.get<MPTIssue>(), ofrAmt.out.value());
|
||||
}
|
||||
}
|
||||
|
||||
offer.consume(sb, ofrAmt);
|
||||
@@ -1260,20 +1288,20 @@ BookStep<TIn, TOut, TDerived>::check(StrandContext const& ctx) const
|
||||
// Do not allow two books to output the same issue. This may cause offers on
|
||||
// one step to unfund offers in another step.
|
||||
if (!ctx.seenBookOuts.insert(book_.out).second ||
|
||||
(ctx.seenDirectIssues[0].count(book_.out) != 0u))
|
||||
(ctx.seenDirectAssets[0].count(book_.out) != 0u))
|
||||
{
|
||||
JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
|
||||
if (ctx.seenDirectIssues[1].count(book_.out) != 0u)
|
||||
if (ctx.seenDirectAssets[1].count(book_.out) != 0u)
|
||||
{
|
||||
JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
|
||||
auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
|
||||
return isXRP(iss.account) || view.read(keylet::account(iss.account));
|
||||
auto issuerExists = [](ReadView const& view, Asset const& iss) -> bool {
|
||||
return isXRP(iss.getIssuer()) || view.exists(keylet::account(iss.getIssuer()));
|
||||
};
|
||||
|
||||
if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out))
|
||||
@@ -1287,19 +1315,101 @@ BookStep<TIn, TOut, TDerived>::check(StrandContext const& ctx) const
|
||||
if (auto const prev = ctx.prevStep->directStepSrcAcct())
|
||||
{
|
||||
auto const& view = ctx.view;
|
||||
auto const& cur = book_.in.account;
|
||||
auto const& cur = book_.in.getIssuer();
|
||||
|
||||
auto sle = view.read(keylet::line(*prev, cur, book_.in.currency));
|
||||
if (!sle)
|
||||
return terNO_LINE;
|
||||
if (((*sle)[sfFlags] & ((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple)) != 0u)
|
||||
return terNO_RIPPLE;
|
||||
auto const err = book_.in.visit(
|
||||
[&](Issue const& issue) -> std::optional<TER> {
|
||||
auto sle = view.read(keylet::line(*prev, cur, issue.currency));
|
||||
if (!sle)
|
||||
return terNO_LINE;
|
||||
if (((*sle)[sfFlags] & ((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple)) !=
|
||||
0u)
|
||||
return terNO_RIPPLE;
|
||||
return std::nullopt;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
// Check if can trade on DEX.
|
||||
if (auto const ter = canTrade(view, book_.in); !isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto const ter = canTrade(view, book_.out); !isTesSuccess(ter))
|
||||
return ter;
|
||||
return std::nullopt;
|
||||
});
|
||||
if (err)
|
||||
return *err;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
Rate
|
||||
BookStep<TIn, TOut, TDerived>::rate(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& dstAccount) const
|
||||
{
|
||||
auto const& issuer = asset.getIssuer();
|
||||
if (isXRP(issuer) || issuer == dstAccount)
|
||||
return parityRate;
|
||||
return asset.visit(
|
||||
[&](Issue const&) { return transferRate(view, issuer); },
|
||||
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
|
||||
};
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
bool
|
||||
BookStep<TIn, TOut, TDerived>::checkMPTDEX(ReadView const& view, AccountID const& owner) const
|
||||
{
|
||||
if (!isTesSuccess(canTrade(view, book_.in)) || !isTesSuccess(canTrade(view, book_.out)))
|
||||
return false;
|
||||
|
||||
if (book_.in.holds<MPTIssue>())
|
||||
{
|
||||
auto ret = [&]() {
|
||||
auto const& asset = book_.in;
|
||||
// Strand's source is an issuer
|
||||
if (!prevStep_)
|
||||
return true;
|
||||
// Offer's owner is an issuer
|
||||
if (asset.getIssuer() == owner)
|
||||
return true;
|
||||
// The previous step could be MPTEndpointStep with non issuer account or
|
||||
// BookStep. Fail both if in asset is locked. In the former case it is holder
|
||||
// to locked holder transfer. In the latter case it is not possible to tell if
|
||||
// it is issuer to holder or holder to holder transfer.
|
||||
if (isFrozen(view, owner, book_.in.get<MPTIssue>()))
|
||||
return false;
|
||||
// Previous step is BookStep. BookStep only sends if CanTransfer is
|
||||
// set and not locked or the offer is owned by an issuer
|
||||
if (prevStep_->bookStepBook())
|
||||
return true;
|
||||
// Previous step is MPTEndpointStep and offer's owner is not an
|
||||
// issuer
|
||||
return isTesSuccess(canTransfer(view, asset, owner, owner));
|
||||
}();
|
||||
if (!ret)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (book_.out.holds<MPTIssue>())
|
||||
{
|
||||
auto const& asset = book_.out;
|
||||
// Last step if the strand's destination is an issuer
|
||||
if (strandDeliver_ == asset && strandDst_ == asset.getIssuer())
|
||||
return true;
|
||||
// Offer's owner is an issuer
|
||||
if (asset.getIssuer() == owner)
|
||||
return true;
|
||||
|
||||
// Next step is BookStep and offer's owner is not an issuer.
|
||||
return isTesSuccess(canTransfer(view, asset, owner, owner));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace test {
|
||||
@@ -1317,22 +1427,21 @@ equalHelper(Step const& step, xrpl::Book const& book)
|
||||
bool
|
||||
bookStepEqual(Step const& step, xrpl::Book const& book)
|
||||
{
|
||||
bool const inXRP = isXRP(book.in.currency);
|
||||
bool const outXRP = isXRP(book.out.currency);
|
||||
if (inXRP && outXRP)
|
||||
if (isXRP(book.in) && isXRP(book.out))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::test::bookStepEqual : no XRP to XRP book step");
|
||||
return false; // no such thing as xrp/xrp book step
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
if (inXRP && !outXRP)
|
||||
return equalHelper<XRPAmount, IOUAmount, BookPaymentStep<XRPAmount, IOUAmount>>(step, book);
|
||||
if (!inXRP && outXRP)
|
||||
return equalHelper<IOUAmount, XRPAmount, BookPaymentStep<IOUAmount, XRPAmount>>(step, book);
|
||||
if (!inXRP && !outXRP)
|
||||
return equalHelper<IOUAmount, IOUAmount, BookPaymentStep<IOUAmount, IOUAmount>>(step, book);
|
||||
return false;
|
||||
return std::visit(
|
||||
[&]<typename TIn, typename TOut>(TIn const&, TOut const&) {
|
||||
using TIn_ = typename TIn::amount_type;
|
||||
using TOut_ = typename TOut::amount_type;
|
||||
return equalHelper<TIn_, TOut_, BookPaymentStep<TIn_, TOut_>>(step, book);
|
||||
},
|
||||
book.in.getAmountType(),
|
||||
book.out.getAmountType());
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
@@ -1340,7 +1449,7 @@ bookStepEqual(Step const& step, xrpl::Book const& book)
|
||||
|
||||
template <class TIn, class TOut>
|
||||
static std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepHelper(StrandContext const& ctx, Issue const& in, Issue const& out)
|
||||
make_BookStepHelper(StrandContext const& ctx, Asset const& in, Asset const& out)
|
||||
{
|
||||
TER ter = tefINTERNAL;
|
||||
std::unique_ptr<Step> r;
|
||||
@@ -1380,4 +1489,35 @@ make_BookStepXI(StrandContext const& ctx, Issue const& out)
|
||||
return make_BookStepHelper<XRPAmount, IOUAmount>(ctx, xrpIssue(), out);
|
||||
}
|
||||
|
||||
// MPT's
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepMM(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out)
|
||||
{
|
||||
return make_BookStepHelper<MPTAmount, MPTAmount>(ctx, in, out);
|
||||
}
|
||||
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepMI(StrandContext const& ctx, MPTIssue const& in, Issue const& out)
|
||||
{
|
||||
return make_BookStepHelper<MPTAmount, IOUAmount>(ctx, in, out);
|
||||
}
|
||||
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepIM(StrandContext const& ctx, Issue const& in, MPTIssue const& out)
|
||||
{
|
||||
return make_BookStepHelper<IOUAmount, MPTAmount>(ctx, in, out);
|
||||
}
|
||||
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepMX(StrandContext const& ctx, MPTIssue const& in)
|
||||
{
|
||||
return make_BookStepHelper<MPTAmount, XRPAmount>(ctx, in, xrpIssue());
|
||||
}
|
||||
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_BookStepXM(StrandContext const& ctx, MPTIssue const& out)
|
||||
{
|
||||
return make_BookStepHelper<XRPAmount, MPTAmount>(ctx, xrpIssue(), out);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -671,7 +671,7 @@ DirectStepI<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmo
|
||||
|
||||
auto const savCache = *cache_;
|
||||
|
||||
XRPL_ASSERT(!in.native, "xrpl::DirectStepI::validFwd : input is not XRP");
|
||||
XRPL_ASSERT(in.holds<IOUAmount>(), "xrpl::DirectStepI::validFwd : input is IOU");
|
||||
|
||||
auto const [maxSrcToDst, srcDebtDir] =
|
||||
static_cast<TDerived const*>(this)->maxFlow(sb, cache_->srcToDst);
|
||||
@@ -680,7 +680,7 @@ DirectStepI<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmo
|
||||
try
|
||||
{
|
||||
boost::container::flat_set<uint256> dummy;
|
||||
fwdImp(sb, afView, dummy, in.iou); // changes cache
|
||||
fwdImp(sb, afView, dummy, in.get<IOUAmount>()); // changes cache
|
||||
}
|
||||
catch (FlowException const&)
|
||||
{
|
||||
@@ -857,13 +857,13 @@ DirectStepI<TDerived>::check(StrandContext const& ctx) const
|
||||
// issue
|
||||
if (auto book = ctx.prevStep->bookStepBook())
|
||||
{
|
||||
if (book->out != srcIssue)
|
||||
if (book->out.get<Issue>() != srcIssue)
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.seenDirectIssues[0].insert(srcIssue).second ||
|
||||
!ctx.seenDirectIssues[1].insert(dstIssue).second)
|
||||
if (!ctx.seenDirectAssets[0].insert(srcIssue).second ||
|
||||
!ctx.seenDirectAssets[1].insert(dstIssue).second)
|
||||
{
|
||||
JLOG(j_.debug()) << "DirectStepI: loop detected: Index: " << ctx.strandSize << ' '
|
||||
<< *this;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace xrpl {
|
||||
|
||||
template <class FlowResult>
|
||||
static auto
|
||||
finishFlow(PaymentSandbox& sb, Issue const& srcIssue, Issue const& dstIssue, FlowResult&& f)
|
||||
finishFlow(PaymentSandbox& sb, Asset const& srcAsset, Asset const& dstAsset, FlowResult&& f)
|
||||
{
|
||||
path::RippleCalc::Output result;
|
||||
if (isTesSuccess(f.ter))
|
||||
@@ -25,8 +25,8 @@ finishFlow(PaymentSandbox& sb, Issue const& srcIssue, Issue const& dstIssue, Flo
|
||||
}
|
||||
|
||||
result.setResult(f.ter);
|
||||
result.actualAmountIn = toSTAmount(f.in, srcIssue);
|
||||
result.actualAmountOut = toSTAmount(f.out, dstIssue);
|
||||
result.actualAmountIn = toSTAmount(f.in, srcAsset);
|
||||
result.actualAmountOut = toSTAmount(f.out, dstAsset);
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -48,19 +48,23 @@ flow(
|
||||
beast::Journal j,
|
||||
path::detail::FlowDebugInfo* flowDebugInfo)
|
||||
{
|
||||
Issue const srcIssue = [&] {
|
||||
Asset const srcAsset = [&]() -> Asset {
|
||||
if (sendMax)
|
||||
return sendMax->issue();
|
||||
if (!isXRP(deliver.issue().currency))
|
||||
return Issue(deliver.issue().currency, src);
|
||||
return xrpIssue();
|
||||
return sendMax->asset();
|
||||
return deliver.asset().visit(
|
||||
[&](Issue const& issue) -> Asset {
|
||||
if (isXRP(issue))
|
||||
return xrpIssue();
|
||||
return Issue(issue.currency, src);
|
||||
},
|
||||
[&](MPTIssue const&) { return deliver.asset(); });
|
||||
}();
|
||||
|
||||
Issue const dstIssue = deliver.issue();
|
||||
Asset const dstAsset = deliver.asset();
|
||||
|
||||
std::optional<Issue> sendMaxIssue;
|
||||
std::optional<Asset> sendMaxAsset;
|
||||
if (sendMax)
|
||||
sendMaxIssue = sendMax->issue();
|
||||
sendMaxAsset = sendMax->asset();
|
||||
|
||||
AMMContext ammContext(src, false);
|
||||
|
||||
@@ -71,9 +75,9 @@ flow(
|
||||
sb,
|
||||
src,
|
||||
dst,
|
||||
dstIssue,
|
||||
dstAsset,
|
||||
limitQuality,
|
||||
sendMaxIssue,
|
||||
sendMaxAsset,
|
||||
paths,
|
||||
defaultPaths,
|
||||
ownerPaysTransferFee,
|
||||
@@ -93,8 +97,8 @@ flow(
|
||||
|
||||
if (j.trace())
|
||||
{
|
||||
j.trace() << "\nsrc: " << src << "\ndst: " << dst << "\nsrcIssue: " << srcIssue
|
||||
<< "\ndstIssue: " << dstIssue;
|
||||
j.trace() << "\nsrc: " << src << "\ndst: " << dst << "\nsrcAsset: " << srcAsset
|
||||
<< "\ndstAsset: " << dstAsset;
|
||||
j.trace() << "\nNumStrands: " << strands.size();
|
||||
for (auto const& curStrand : strands)
|
||||
{
|
||||
@@ -106,87 +110,32 @@ flow(
|
||||
}
|
||||
}
|
||||
|
||||
bool const srcIsXRP = isXRP(srcIssue.currency);
|
||||
bool const dstIsXRP = isXRP(dstIssue.currency);
|
||||
|
||||
auto const asDeliver = toAmountSpec(deliver);
|
||||
|
||||
// The src account may send either xrp or iou. The dst account may receive
|
||||
// either xrp or iou. Since XRP and IOU amounts are represented by different
|
||||
// types, use templates to tell `flow` about the amount types.
|
||||
if (srcIsXRP && dstIsXRP)
|
||||
{
|
||||
return finishFlow(
|
||||
sb,
|
||||
srcIssue,
|
||||
dstIssue,
|
||||
flow<XRPAmount, XRPAmount>(
|
||||
// The src account may send either xrp,iou,or mpt. The dst account may
|
||||
// receive either xrp,iou, or mpt. Since XRP, IOU, and MPT amounts are
|
||||
// represented by different types, use templates to tell `flow` about the
|
||||
// amount types.
|
||||
return std::visit(
|
||||
[&, &strands_ = strands]<typename TIn, typename TOut>(TIn const&, TOut const&) {
|
||||
using TIn_ = typename TIn::amount_type;
|
||||
using TOut_ = typename TOut::amount_type;
|
||||
return finishFlow(
|
||||
sb,
|
||||
strands,
|
||||
asDeliver.xrp,
|
||||
partialPayment,
|
||||
offerCrossing,
|
||||
limitQuality,
|
||||
sendMax,
|
||||
j,
|
||||
ammContext,
|
||||
flowDebugInfo));
|
||||
}
|
||||
|
||||
if (srcIsXRP && !dstIsXRP)
|
||||
{
|
||||
return finishFlow(
|
||||
sb,
|
||||
srcIssue,
|
||||
dstIssue,
|
||||
flow<XRPAmount, IOUAmount>(
|
||||
sb,
|
||||
strands,
|
||||
asDeliver.iou,
|
||||
partialPayment,
|
||||
offerCrossing,
|
||||
limitQuality,
|
||||
sendMax,
|
||||
j,
|
||||
ammContext,
|
||||
flowDebugInfo));
|
||||
}
|
||||
|
||||
if (!srcIsXRP && dstIsXRP)
|
||||
{
|
||||
return finishFlow(
|
||||
sb,
|
||||
srcIssue,
|
||||
dstIssue,
|
||||
flow<IOUAmount, XRPAmount>(
|
||||
sb,
|
||||
strands,
|
||||
asDeliver.xrp,
|
||||
partialPayment,
|
||||
offerCrossing,
|
||||
limitQuality,
|
||||
sendMax,
|
||||
j,
|
||||
ammContext,
|
||||
flowDebugInfo));
|
||||
}
|
||||
|
||||
XRPL_ASSERT(!srcIsXRP && !dstIsXRP, "xrpl::flow : neither is XRP");
|
||||
return finishFlow(
|
||||
sb,
|
||||
srcIssue,
|
||||
dstIssue,
|
||||
flow<IOUAmount, IOUAmount>(
|
||||
sb,
|
||||
strands,
|
||||
asDeliver.iou,
|
||||
partialPayment,
|
||||
offerCrossing,
|
||||
limitQuality,
|
||||
sendMax,
|
||||
j,
|
||||
ammContext,
|
||||
flowDebugInfo));
|
||||
srcAsset,
|
||||
dstAsset,
|
||||
flow<TIn_, TOut_>(
|
||||
sb,
|
||||
strands_,
|
||||
get<TOut_>(deliver),
|
||||
partialPayment,
|
||||
offerCrossing,
|
||||
limitQuality,
|
||||
sendMax,
|
||||
j,
|
||||
ammContext,
|
||||
flowDebugInfo));
|
||||
},
|
||||
srcAsset.getAmountType(),
|
||||
dstAsset.getAmountType());
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
919
src/libxrpl/tx/paths/MPTEndpointStep.cpp
Normal file
919
src/libxrpl/tx/paths/MPTEndpointStep.cpp
Normal file
@@ -0,0 +1,919 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class TDerived>
|
||||
class MPTEndpointStep : public StepImp<MPTAmount, MPTAmount, MPTEndpointStep<TDerived>>
|
||||
{
|
||||
protected:
|
||||
AccountID const src_;
|
||||
AccountID const dst_;
|
||||
MPTIssue const mptIssue_;
|
||||
|
||||
// Charge transfer fees when the prev step redeems
|
||||
Step const* const prevStep_ = nullptr;
|
||||
bool const isLast_;
|
||||
// Direct payment between the holders
|
||||
// Used by maxFlow's last step.
|
||||
bool const isDirectBetweenHolders_ = false;
|
||||
beast::Journal const j_;
|
||||
|
||||
struct Cache
|
||||
{
|
||||
MPTAmount in;
|
||||
MPTAmount srcToDst;
|
||||
MPTAmount out;
|
||||
DebtDirection srcDebtDir;
|
||||
|
||||
Cache(
|
||||
MPTAmount const& in_,
|
||||
MPTAmount const& srcToDst_,
|
||||
MPTAmount const& out_,
|
||||
DebtDirection srcDebtDir_)
|
||||
: in(in_), srcToDst(srcToDst_), out(out_), srcDebtDir(srcDebtDir_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<Cache> cache_;
|
||||
|
||||
// Compute the maximum value that can flow from src->dst at
|
||||
// the best available quality.
|
||||
// return: first element is max amount that can flow,
|
||||
// second is the debt direction of the source w.r.t. the dst
|
||||
std::pair<MPTAmount, DebtDirection>
|
||||
maxPaymentFlow(ReadView const& sb) const;
|
||||
|
||||
// Compute srcQOut and dstQIn when the source redeems.
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
qualitiesSrcRedeems(ReadView const& sb) const;
|
||||
|
||||
// Compute srcQOut and dstQIn when the source issues.
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) const;
|
||||
|
||||
// Returns srcQOut, dstQIn
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
qualities(ReadView const& sb, DebtDirection srcDebtDir, StrandDirection strandDir) const;
|
||||
|
||||
void
|
||||
resetCache(DebtDirection dir);
|
||||
|
||||
private:
|
||||
MPTEndpointStep(
|
||||
StrandContext const& ctx,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
MPTID const& mpt)
|
||||
: src_(src)
|
||||
, dst_(dst)
|
||||
, mptIssue_(mpt)
|
||||
, prevStep_(ctx.prevStep)
|
||||
, isLast_(ctx.isLast)
|
||||
, isDirectBetweenHolders_(
|
||||
mptIssue_ == ctx.strandDeliver && ctx.strandSrc != mptIssue_.getIssuer() &&
|
||||
ctx.strandDst != mptIssue_.getIssuer() &&
|
||||
(ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook())))
|
||||
, j_(ctx.j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
src_ == mptIssue_.getIssuer() || dst_ == mptIssue_.getIssuer(),
|
||||
"MPTEndpointStep::MPTEndpointStep src or dst must be an issuer");
|
||||
}
|
||||
|
||||
public:
|
||||
AccountID const&
|
||||
src() const
|
||||
{
|
||||
return src_;
|
||||
}
|
||||
AccountID const&
|
||||
dst() const
|
||||
{
|
||||
return dst_;
|
||||
}
|
||||
MPTID const&
|
||||
mptID() const
|
||||
{
|
||||
return mptIssue_.getMptID();
|
||||
}
|
||||
|
||||
std::optional<EitherAmount>
|
||||
cachedIn() const override
|
||||
{
|
||||
if (!cache_)
|
||||
return std::nullopt;
|
||||
return EitherAmount(cache_->in);
|
||||
}
|
||||
|
||||
std::optional<EitherAmount>
|
||||
cachedOut() const override
|
||||
{
|
||||
if (!cache_)
|
||||
return std::nullopt;
|
||||
return EitherAmount(cache_->out);
|
||||
}
|
||||
|
||||
std::optional<AccountID>
|
||||
directStepSrcAcct() const override
|
||||
{
|
||||
return src_;
|
||||
}
|
||||
|
||||
std::optional<std::pair<AccountID, AccountID>>
|
||||
directStepAccts() const override
|
||||
{
|
||||
return std::make_pair(src_, dst_);
|
||||
}
|
||||
|
||||
DebtDirection
|
||||
debtDirection(ReadView const& sb, StrandDirection dir) const override;
|
||||
|
||||
std::uint32_t
|
||||
lineQualityIn(ReadView const& v) const override;
|
||||
|
||||
std::pair<std::optional<Quality>, DebtDirection>
|
||||
qualityUpperBound(ReadView const& v, DebtDirection dir) const override;
|
||||
|
||||
std::pair<MPTAmount, MPTAmount>
|
||||
revImp(
|
||||
PaymentSandbox& sb,
|
||||
ApplyView& afView,
|
||||
boost::container::flat_set<uint256>& ofrsToRm,
|
||||
MPTAmount const& out);
|
||||
|
||||
std::pair<MPTAmount, MPTAmount>
|
||||
fwdImp(
|
||||
PaymentSandbox& sb,
|
||||
ApplyView& afView,
|
||||
boost::container::flat_set<uint256>& ofrsToRm,
|
||||
MPTAmount const& in);
|
||||
|
||||
std::pair<bool, EitherAmount>
|
||||
validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
|
||||
|
||||
// Check for error, existing liquidity, and violations of auth/frozen
|
||||
// constraints.
|
||||
TER
|
||||
check(StrandContext const& ctx) const;
|
||||
|
||||
void
|
||||
setCacheLimiting(
|
||||
MPTAmount const& fwdIn,
|
||||
MPTAmount const& fwdSrcToDst,
|
||||
MPTAmount const& fwdOut,
|
||||
DebtDirection srcDebtDir);
|
||||
|
||||
friend bool
|
||||
operator==(MPTEndpointStep const& lhs, MPTEndpointStep const& rhs)
|
||||
{
|
||||
return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && lhs.mptIssue_ == rhs.mptIssue_;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator!=(MPTEndpointStep const& lhs, MPTEndpointStep const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string
|
||||
logStringImpl(char const* name) const
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << name << ": "
|
||||
<< "\nSrc: " << src_ << "\nDst: " << dst_;
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
private:
|
||||
bool
|
||||
equal(Step const& rhs) const override
|
||||
{
|
||||
if (auto ds = dynamic_cast<MPTEndpointStep const*>(&rhs))
|
||||
{
|
||||
return *this == *ds;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
friend TDerived;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Flow is used in two different circumstances for transferring funds:
|
||||
// o Payments, and
|
||||
// o Offer crossing.
|
||||
// The rules for handling funds in these two cases are almost, but not
|
||||
// quite, the same.
|
||||
|
||||
// Payment MPTEndpointStep class (not offer crossing).
|
||||
class MPTEndpointPaymentStep : public MPTEndpointStep<MPTEndpointPaymentStep>
|
||||
{
|
||||
public:
|
||||
using MPTEndpointStep<MPTEndpointPaymentStep>::MPTEndpointStep;
|
||||
using MPTEndpointStep<MPTEndpointPaymentStep>::check;
|
||||
|
||||
MPTEndpointPaymentStep(
|
||||
StrandContext const& ctx,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
MPTID const& mpt)
|
||||
: MPTEndpointStep<MPTEndpointPaymentStep>(ctx, src, dst, mpt)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
verifyPrevStepDebtDirection(DebtDirection)
|
||||
{
|
||||
// A payment doesn't care regardless of prevStepRedeems.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify the consistency of the step. These checks are specific to
|
||||
// payments and assume that general checks were already performed.
|
||||
TER
|
||||
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc) const;
|
||||
|
||||
std::string
|
||||
logString() const override
|
||||
{
|
||||
return logStringImpl("MPTEndpointPaymentStep");
|
||||
}
|
||||
|
||||
// Not applicable for payment
|
||||
static TER
|
||||
checkCreateMPT(ApplyView&, DebtDirection)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// Offer crossing MPTEndpointStep class (not a payment).
|
||||
class MPTEndpointOfferCrossingStep : public MPTEndpointStep<MPTEndpointOfferCrossingStep>
|
||||
{
|
||||
public:
|
||||
using MPTEndpointStep<MPTEndpointOfferCrossingStep>::MPTEndpointStep;
|
||||
using MPTEndpointStep<MPTEndpointOfferCrossingStep>::check;
|
||||
|
||||
MPTEndpointOfferCrossingStep(
|
||||
StrandContext const& ctx,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
MPTID const& mpt)
|
||||
: MPTEndpointStep<MPTEndpointOfferCrossingStep>(ctx, src, dst, mpt)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
verifyPrevStepDebtDirection(DebtDirection prevStepDir)
|
||||
{
|
||||
// During offer crossing we rely on the fact that prevStepRedeems
|
||||
// will *always* issue. That's because:
|
||||
// o If there's a prevStep_, it will always be a BookStep.
|
||||
// o BookStep::debtDirection() always returns `issues` when offer
|
||||
// crossing.
|
||||
// An assert based on this return value will tell us if that
|
||||
// behavior changes.
|
||||
return issues(prevStepDir);
|
||||
}
|
||||
|
||||
// Verify the consistency of the step. These checks are specific to
|
||||
// offer crossing and assume that general checks were already performed.
|
||||
static TER
|
||||
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc);
|
||||
|
||||
std::string
|
||||
logString() const override
|
||||
{
|
||||
return logStringImpl("MPTEndpointOfferCrossingStep");
|
||||
}
|
||||
|
||||
// Can be created in rev or fwd (if limiting step) direction.
|
||||
TER
|
||||
checkCreateMPT(ApplyView& view, DebtDirection srcDebtDir);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc)
|
||||
const
|
||||
{
|
||||
// Since this is a payment, MPToken must be present. Perform all
|
||||
// MPToken related checks.
|
||||
|
||||
// requireAuth checks if MPTIssuance exist. Note that issuer to issuer
|
||||
// payment is invalid
|
||||
auto const& issuer = mptIssue_.getIssuer();
|
||||
if (src_ != issuer)
|
||||
{
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (dst_ != issuer)
|
||||
{
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Direct MPT payment, no DEX
|
||||
if (mptIssue_ == ctx.strandDeliver &&
|
||||
(ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook())))
|
||||
{
|
||||
// Between holders
|
||||
if (isDirectBetweenHolders_)
|
||||
{
|
||||
auto const& holder = ctx.isFirst ? src_ : dst_;
|
||||
// Payment between the holders
|
||||
if (isFrozen(ctx.view, holder, mptIssue_))
|
||||
return tecLOCKED;
|
||||
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
// Don't need to check if a payment is between issuer and holder
|
||||
// in either direction
|
||||
}
|
||||
// Cross-token MPT payment via DEX
|
||||
else
|
||||
{
|
||||
if (auto const ter = canTrade(ctx.view, mptIssue_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Can't check for creditBalance/Limit unless it's the first step.
|
||||
// Otherwise, even if OutstandingAmount is equal to MaximumAmount
|
||||
// a payment can still be successful. For instance, when a balance
|
||||
// is shifted from one holder to another.
|
||||
|
||||
if (prevStep_ == nullptr)
|
||||
{
|
||||
auto const owed =
|
||||
accountFunds(ctx.view, src_, mptIssue_, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_);
|
||||
// Already at MaximumAmount
|
||||
if (owed <= beast::zero)
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const&)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirection srcDebtDir)
|
||||
{
|
||||
// TakerPays is the last step if offer crossing
|
||||
if (isLast_)
|
||||
{
|
||||
// Create MPToken for the offer's owner. No need to check
|
||||
// for the reserve since the offer doesn't go on the books
|
||||
// if crossed. Insufficient reserve is allowed if the offer
|
||||
// crossed. See CreateOffer::applyGuts() for reserve check.
|
||||
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
|
||||
resetCache(srcDebtDir);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class TDerived>
|
||||
std::pair<MPTAmount, DebtDirection>
|
||||
MPTEndpointStep<TDerived>::maxPaymentFlow(ReadView const& sb) const
|
||||
{
|
||||
auto const maxFlow = accountFunds(sb, src_, mptIssue_, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_);
|
||||
|
||||
// From a holder to an issuer
|
||||
if (src_ != mptIssue_.getIssuer())
|
||||
return {toAmount<MPTAmount>(maxFlow), DebtDirection::redeems};
|
||||
|
||||
// From an issuer to a holder
|
||||
if (auto const sle = sb.read(keylet::mptIssuance(mptIssue_)))
|
||||
{
|
||||
// If issuer is the source account, and it is direct payment then
|
||||
// MPTEndpointStep is the only step. Provide available maxFlow.
|
||||
if (prevStep_ == nullptr)
|
||||
return {toAmount<MPTAmount>(maxFlow), DebtDirection::issues};
|
||||
|
||||
// MPTEndpointStep is the last step. It's always issuing in
|
||||
// this case. Can't infer at this point what the maxFlow is, because
|
||||
// the previous step may issue or redeem. Allow OutstandingAmount
|
||||
// to temporarily overflow. Let the previous step figure out how
|
||||
// to limit the flow.
|
||||
std::int64_t const maxAmount = maxMPTAmount(*sle);
|
||||
return {MPTAmount{maxAmount}, DebtDirection::issues};
|
||||
}
|
||||
|
||||
return {MPTAmount{0}, DebtDirection::issues};
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
DebtDirection
|
||||
MPTEndpointStep<TDerived>::debtDirection(ReadView const& sb, StrandDirection dir) const
|
||||
{
|
||||
if (dir == StrandDirection::forward && cache_)
|
||||
return cache_->srcDebtDir;
|
||||
|
||||
return (src_ == mptIssue_.getIssuer()) ? DebtDirection::issues : DebtDirection::redeems;
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
std::pair<MPTAmount, MPTAmount>
|
||||
MPTEndpointStep<TDerived>::revImp(
|
||||
PaymentSandbox& sb,
|
||||
ApplyView& /*afView*/,
|
||||
boost::container::flat_set<uint256>& /*ofrsToRm*/,
|
||||
MPTAmount const& out)
|
||||
{
|
||||
cache_.reset();
|
||||
|
||||
auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
|
||||
|
||||
auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::reverse);
|
||||
(void)dstQIn;
|
||||
|
||||
MPTIssue const srcToDstIss(mptIssue_);
|
||||
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " outReq: " << to_string(out)
|
||||
<< " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
|
||||
<< " dstQIn: " << dstQIn;
|
||||
|
||||
if (maxSrcToDst.signum() <= 0)
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev: dry";
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
|
||||
if (auto const err = static_cast<TDerived*>(this)->checkCreateMPT(sb, srcDebtDir);
|
||||
!isTesSuccess(err))
|
||||
return {beast::zero, beast::zero};
|
||||
|
||||
// Don't have to factor in dstQIn since it is always QUALITY_ONE
|
||||
MPTAmount const srcToDst = out;
|
||||
|
||||
if (srcToDst <= maxSrcToDst)
|
||||
{
|
||||
MPTAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
cache_.emplace(in, srcToDst, srcToDst, srcDebtDir);
|
||||
auto const ter = directSendNoFee(
|
||||
sb,
|
||||
src_,
|
||||
dst_,
|
||||
toSTAmount(srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ false,
|
||||
j_);
|
||||
if (!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev: error " << ter;
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev: Non-limiting"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
||||
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
||||
return {in, out};
|
||||
}
|
||||
|
||||
// limiting node
|
||||
MPTAmount const in = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
// Don't have to factor in dsqQIn since it's always QUALITY_ONE
|
||||
MPTAmount const actualOut = maxSrcToDst;
|
||||
cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir);
|
||||
|
||||
auto const ter = directSendNoFee(
|
||||
sb,
|
||||
src_,
|
||||
dst_,
|
||||
toSTAmount(maxSrcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ false,
|
||||
j_);
|
||||
if (!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev: error " << ter;
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::rev: Limiting"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
||||
<< " srcToDst: " << to_string(maxSrcToDst) << " out: " << to_string(out);
|
||||
return {in, actualOut};
|
||||
}
|
||||
|
||||
// The forward pass should never have more liquidity than the reverse
|
||||
// pass. But sometimes rounding differences cause the forward pass to
|
||||
// deliver more liquidity. Use the cached values from the reverse pass
|
||||
// to prevent this.
|
||||
template <class TDerived>
|
||||
void
|
||||
MPTEndpointStep<TDerived>::setCacheLimiting(
|
||||
MPTAmount const& fwdIn,
|
||||
MPTAmount const& fwdSrcToDst,
|
||||
MPTAmount const& fwdOut,
|
||||
DebtDirection srcDebtDir)
|
||||
{
|
||||
if (cache_->in < fwdIn)
|
||||
{
|
||||
MPTAmount const smallDiff(1);
|
||||
auto const diff = fwdIn - cache_->in;
|
||||
if (diff > smallDiff)
|
||||
{
|
||||
if (!cache_->in.value() ||
|
||||
(Number(fwdIn.value()) / Number(cache_->in.value())) > Number(101, -2))
|
||||
{
|
||||
// Detect large diffs on forward pass so they may be
|
||||
// investigated
|
||||
JLOG(j_.warn()) << "MPTEndpointStep::fwd: setCacheLimiting"
|
||||
<< " fwdIn: " << to_string(fwdIn)
|
||||
<< " cacheIn: " << to_string(cache_->in)
|
||||
<< " fwdSrcToDst: " << to_string(fwdSrcToDst)
|
||||
<< " cacheSrcToDst: " << to_string(cache_->srcToDst)
|
||||
<< " fwdOut: " << to_string(fwdOut)
|
||||
<< " cacheOut: " << to_string(cache_->out);
|
||||
cache_.emplace(fwdIn, fwdSrcToDst, fwdOut, srcDebtDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
cache_->in = fwdIn;
|
||||
if (fwdSrcToDst < cache_->srcToDst)
|
||||
cache_->srcToDst = fwdSrcToDst;
|
||||
if (fwdOut < cache_->out)
|
||||
cache_->out = fwdOut;
|
||||
cache_->srcDebtDir = srcDebtDir;
|
||||
};
|
||||
|
||||
template <class TDerived>
|
||||
std::pair<MPTAmount, MPTAmount>
|
||||
MPTEndpointStep<TDerived>::fwdImp(
|
||||
PaymentSandbox& sb,
|
||||
ApplyView& /*afView*/,
|
||||
boost::container::flat_set<uint256>& /*ofrsToRm*/,
|
||||
MPTAmount const& in)
|
||||
{
|
||||
XRPL_ASSERT(cache_, "MPTEndpointStep<TDerived>::fwdImp : valid cache");
|
||||
|
||||
auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
|
||||
|
||||
auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::forward);
|
||||
(void)dstQIn;
|
||||
|
||||
MPTIssue const srcToDstIss(mptIssue_);
|
||||
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " inReq: " << to_string(in)
|
||||
<< " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
|
||||
<< " dstQIn: " << dstQIn;
|
||||
|
||||
if (maxSrcToDst.signum() <= 0)
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd: dry";
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
|
||||
if (auto const err = static_cast<TDerived*>(this)->checkCreateMPT(sb, srcDebtDir);
|
||||
!isTesSuccess(err))
|
||||
return {beast::zero, beast::zero};
|
||||
|
||||
MPTAmount const srcToDst = mulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false);
|
||||
|
||||
if (srcToDst <= maxSrcToDst)
|
||||
{
|
||||
// Don't have to factor in dstQIn since it's always QUALITY_ONE
|
||||
MPTAmount const out = srcToDst;
|
||||
setCacheLimiting(in, srcToDst, out, srcDebtDir);
|
||||
auto const ter = directSendNoFee(
|
||||
sb,
|
||||
src_,
|
||||
dst_,
|
||||
toSTAmount(cache_->srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ false,
|
||||
j_);
|
||||
if (!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd: error " << ter;
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd: Non-limiting"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
||||
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
||||
}
|
||||
else
|
||||
{
|
||||
// limiting node
|
||||
MPTAmount const actualIn = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
||||
// Don't have to factor in dstQIn since it's always QUALITY_ONE
|
||||
MPTAmount const out = maxSrcToDst;
|
||||
setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir);
|
||||
auto const ter = directSendNoFee(
|
||||
sb,
|
||||
src_,
|
||||
dst_,
|
||||
toSTAmount(cache_->srcToDst, srcToDstIss),
|
||||
/*checkIssuer*/ false,
|
||||
j_);
|
||||
if (!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd: error " << ter;
|
||||
resetCache(srcDebtDir);
|
||||
return {beast::zero, beast::zero};
|
||||
}
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::fwd: Limiting"
|
||||
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(actualIn)
|
||||
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
||||
}
|
||||
return {cache_->in, cache_->out};
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
std::pair<bool, EitherAmount>
|
||||
MPTEndpointStep<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in)
|
||||
{
|
||||
if (!cache_)
|
||||
{
|
||||
JLOG(j_.trace()) << "Expected valid cache in validFwd";
|
||||
return {false, EitherAmount(MPTAmount(beast::zero))};
|
||||
}
|
||||
|
||||
auto const savCache = *cache_;
|
||||
|
||||
XRPL_ASSERT(in.holds<MPTAmount>(), "MPTEndpoint<TDerived>::validFwd : is MPT");
|
||||
|
||||
auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
|
||||
(void)srcDebtDir;
|
||||
|
||||
try
|
||||
{
|
||||
boost::container::flat_set<uint256> dummy;
|
||||
fwdImp(sb, afView, dummy, in.get<MPTAmount>()); // changes cache
|
||||
}
|
||||
catch (FlowException const&)
|
||||
{
|
||||
return {false, EitherAmount(MPTAmount(beast::zero))};
|
||||
}
|
||||
|
||||
if (maxSrcToDst < cache_->srcToDst)
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed."
|
||||
<< " Exceeded max src->dst limit"
|
||||
<< " max src->dst: " << to_string(maxSrcToDst)
|
||||
<< " actual src->dst: " << to_string(cache_->srcToDst);
|
||||
return {false, EitherAmount(cache_->out)};
|
||||
}
|
||||
|
||||
if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed."
|
||||
<< " ExpectedIn: " << to_string(savCache.in)
|
||||
<< " CachedIn: " << to_string(cache_->in)
|
||||
<< " ExpectedOut: " << to_string(savCache.out)
|
||||
<< " CachedOut: " << to_string(cache_->out);
|
||||
return {false, EitherAmount(cache_->out)};
|
||||
}
|
||||
return {true, EitherAmount(cache_->out)};
|
||||
}
|
||||
|
||||
// Returns srcQOut, dstQIn
|
||||
template <class TDerived>
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
MPTEndpointStep<TDerived>::qualitiesSrcRedeems(ReadView const& sb) const
|
||||
{
|
||||
if (prevStep_ == nullptr)
|
||||
return {QUALITY_ONE, QUALITY_ONE};
|
||||
|
||||
auto const prevStepQIn = prevStep_->lineQualityIn(sb);
|
||||
// Unlike trustline MPT doesn't have line quality field
|
||||
auto srcQOut = QUALITY_ONE;
|
||||
|
||||
srcQOut = std::max<std::uint32_t>(prevStepQIn, srcQOut);
|
||||
return {srcQOut, QUALITY_ONE};
|
||||
}
|
||||
|
||||
// Returns srcQOut, dstQIn
|
||||
template <class TDerived>
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
MPTEndpointStep<TDerived>::qualitiesSrcIssues(
|
||||
ReadView const& sb,
|
||||
DebtDirection prevStepDebtDirection) const
|
||||
{
|
||||
// Charge a transfer rate when issuing and previous step redeems
|
||||
|
||||
XRPL_ASSERT(
|
||||
static_cast<TDerived const*>(this)->verifyPrevStepDebtDirection(prevStepDebtDirection),
|
||||
"MPTEndpointStep<TDerived>::qualitiesSrcIssues : verify prev step debt "
|
||||
"direction");
|
||||
|
||||
std::uint32_t const srcQOut =
|
||||
redeems(prevStepDebtDirection) ? transferRate(sb, mptIssue_.getMptID()).value : QUALITY_ONE;
|
||||
|
||||
// Unlike trustline, MPT doesn't have line quality field
|
||||
return {srcQOut, QUALITY_ONE};
|
||||
}
|
||||
|
||||
// Returns srcQOut, dstQIn
|
||||
template <class TDerived>
|
||||
std::pair<std::uint32_t, std::uint32_t>
|
||||
MPTEndpointStep<TDerived>::qualities(
|
||||
ReadView const& sb,
|
||||
DebtDirection srcDebtDir,
|
||||
StrandDirection strandDir) const
|
||||
{
|
||||
if (redeems(srcDebtDir))
|
||||
{
|
||||
return qualitiesSrcRedeems(sb);
|
||||
}
|
||||
|
||||
auto const prevStepDebtDirection = [&] {
|
||||
if (prevStep_ != nullptr)
|
||||
return prevStep_->debtDirection(sb, strandDir);
|
||||
return DebtDirection::issues;
|
||||
}();
|
||||
return qualitiesSrcIssues(sb, prevStepDebtDirection);
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
std::uint32_t
|
||||
MPTEndpointStep<TDerived>::lineQualityIn(ReadView const& v) const
|
||||
{
|
||||
// dst quality in
|
||||
return QUALITY_ONE;
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
std::pair<std::optional<Quality>, DebtDirection>
|
||||
MPTEndpointStep<TDerived>::qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const
|
||||
{
|
||||
auto const dir = this->debtDirection(v, StrandDirection::forward);
|
||||
|
||||
auto const [srcQOut, dstQIn] =
|
||||
redeems(dir) ? qualitiesSrcRedeems(v) : qualitiesSrcIssues(v, prevStepDir);
|
||||
(void)dstQIn;
|
||||
|
||||
MPTIssue const iss{mptIssue_};
|
||||
// Be careful not to switch the parameters to `getRate`. The
|
||||
// `getRate(offerOut, offerIn)` function is usually used for offers. It
|
||||
// returns offerIn/offerOut. For a direct step, the rate is srcQOut/dstQIn
|
||||
// (Input*dstQIn/srcQOut = Output; So rate = srcQOut/dstQIn). Although the
|
||||
// first parameter is called `offerOut`, it should take the `dstQIn`
|
||||
// variable.
|
||||
return {Quality(getRate(STAmount(iss, QUALITY_ONE), STAmount(iss, srcQOut))), dir};
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
TER
|
||||
MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
{
|
||||
// The following checks apply for both payments and offer crossing.
|
||||
if (!src_ || !dst_)
|
||||
{
|
||||
JLOG(j_.debug()) << "MPTEndpointStep: specified bad account.";
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
if (src_ == dst_)
|
||||
{
|
||||
JLOG(j_.debug()) << "MPTEndpointStep: same src and dst.";
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
auto const sleSrc = ctx.view.read(keylet::account(src_));
|
||||
if (!sleSrc)
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: can't receive MPT from non-existent issuer: " << src_;
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
|
||||
// pure issue/redeem can't be frozen (issuer/holder)
|
||||
if (!(ctx.isLast && ctx.isFirst))
|
||||
{
|
||||
auto const& account = ctx.isFirst ? src_ : dst_;
|
||||
if (isFrozen(ctx.view, account, mptIssue_))
|
||||
return terLOCKED;
|
||||
}
|
||||
|
||||
if (ctx.seenBookOuts.count(mptIssue_) > 0)
|
||||
{
|
||||
if (ctx.prevStep == nullptr)
|
||||
{
|
||||
UNREACHABLE(
|
||||
"xrpl::MPTEndpointStep::check : prev seen book without a "
|
||||
"prev step");
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
|
||||
// This is OK if the previous step is a book step that outputs this
|
||||
// issue
|
||||
if (auto book = ctx.prevStep->bookStepBook())
|
||||
{
|
||||
if (book->out.get<MPTIssue>() != mptIssue_)
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ctx.isFirst && !ctx.seenDirectAssets[0].insert(mptIssue_).second) ||
|
||||
(ctx.isLast && !ctx.seenDirectAssets[1].insert(mptIssue_).second))
|
||||
{
|
||||
JLOG(j_.debug()) << "MPTEndpointStep: loop detected: Index: " << ctx.strandSize << ' '
|
||||
<< *this;
|
||||
return temBAD_PATH_LOOP;
|
||||
}
|
||||
|
||||
// MPT can only be an endpoint
|
||||
if (!ctx.isLast && !ctx.isFirst)
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: MPT can only be an endpoint";
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
auto const& issuer = mptIssue_.getIssuer();
|
||||
if ((src_ != issuer && dst_ != issuer) || (src_ == issuer && dst_ == issuer))
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: invalid src/dst";
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
void
|
||||
MPTEndpointStep<TDerived>::resetCache(xrpl::DebtDirection dir)
|
||||
{
|
||||
cache_.emplace(MPTAmount(beast::zero), MPTAmount(beast::zero), MPTAmount(beast::zero), dir);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::pair<TER, std::unique_ptr<Step>>
|
||||
make_MPTEndpointStep(
|
||||
StrandContext const& ctx,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
MPTID const& mpt)
|
||||
{
|
||||
TER ter = tefINTERNAL;
|
||||
std::unique_ptr<Step> r;
|
||||
if (ctx.offerCrossing != OfferCrossing::no)
|
||||
{
|
||||
auto offerCrossingStep = std::make_unique<MPTEndpointOfferCrossingStep>(ctx, src, dst, mpt);
|
||||
ter = offerCrossingStep->check(ctx);
|
||||
r = std::move(offerCrossingStep);
|
||||
}
|
||||
else // payment
|
||||
{
|
||||
auto paymentStep = std::make_unique<MPTEndpointPaymentStep>(ctx, src, dst, mpt);
|
||||
ter = paymentStep->check(ctx);
|
||||
r = std::move(paymentStep);
|
||||
}
|
||||
if (!isTesSuccess(ter))
|
||||
return {ter, nullptr};
|
||||
|
||||
return {tesSUCCESS, std::move(r)};
|
||||
}
|
||||
|
||||
namespace test {
|
||||
// Needed for testing
|
||||
bool
|
||||
mptEndpointStepEqual(
|
||||
Step const& step,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
MPTID const& mptid)
|
||||
{
|
||||
if (auto ds = dynamic_cast<MPTEndpointStep<MPTEndpointPaymentStep> const*>(&step))
|
||||
{
|
||||
return ds->src() == src && ds->dst() == dst && ds->mptID() == mptid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -13,14 +14,15 @@ namespace {
|
||||
bool
|
||||
checkIssuers(ReadView const& view, Book const& book)
|
||||
{
|
||||
auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
|
||||
return isXRP(iss.account) || view.read(keylet::account(iss.account));
|
||||
auto issuerExists = [](ReadView const& view, Asset const& asset) -> bool {
|
||||
auto const& issuer = asset.getIssuer();
|
||||
return isXRP(issuer) || view.exists(keylet::account(issuer));
|
||||
};
|
||||
return issuerExists(view, book.in) && issuerExists(view, book.out);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <class TIn, class TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
TOfferStreamBase<TIn, TOut>::TOfferStreamBase(
|
||||
ApplyView& view,
|
||||
ApplyView& cancelView,
|
||||
@@ -42,7 +44,7 @@ TOfferStreamBase<TIn, TOut>::TOfferStreamBase(
|
||||
|
||||
// Handle the case where a directory item with no corresponding ledger entry
|
||||
// is found. This shouldn't happen but if it does we clean it up.
|
||||
template <class TIn, class TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
void
|
||||
TOfferStreamBase<TIn, TOut>::erase(ApplyView& view)
|
||||
{
|
||||
@@ -75,67 +77,42 @@ TOfferStreamBase<TIn, TOut>::erase(ApplyView& view)
|
||||
<< tip_.dir();
|
||||
}
|
||||
|
||||
static STAmount
|
||||
template <StepAmount T>
|
||||
static T
|
||||
accountFundsHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
STAmount const& saDefault,
|
||||
Issue const&,
|
||||
T const& amtDefault,
|
||||
Asset const& asset,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
return accountFunds(view, id, saDefault, freezeHandling, j);
|
||||
}
|
||||
|
||||
static IOUAmount
|
||||
accountFundsHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
IOUAmount const& amtDefault,
|
||||
Issue const& issue,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (issue.account == id)
|
||||
if constexpr (std::is_same_v<T, IOUAmount>)
|
||||
{
|
||||
// self funded
|
||||
return amtDefault;
|
||||
if (id == asset.getIssuer())
|
||||
{
|
||||
// self funded
|
||||
return amtDefault;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MPTAmount>)
|
||||
{
|
||||
if (id == asset.getIssuer())
|
||||
{
|
||||
return toAmount<T>(issuerFundsToSelfIssue(view, asset.get<MPTIssue>()));
|
||||
}
|
||||
}
|
||||
|
||||
return toAmount<IOUAmount>(
|
||||
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
|
||||
return toAmount<T>(accountHolds(view, id, asset, freezeHandling, authHandling, j));
|
||||
}
|
||||
|
||||
static XRPAmount
|
||||
accountFundsHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& id,
|
||||
XRPAmount const& amtDefault,
|
||||
Issue const& issue,
|
||||
FreezeHandling freezeHandling,
|
||||
beast::Journal j)
|
||||
{
|
||||
return toAmount<XRPAmount>(
|
||||
accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
|
||||
}
|
||||
|
||||
template <class TIn, class TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
template <class TTakerPays, class TTakerGets>
|
||||
requires ValidTaker<TTakerPays, TTakerGets>
|
||||
bool
|
||||
TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
{
|
||||
static_assert(
|
||||
std::is_same_v<TTakerPays, IOUAmount> || std::is_same_v<TTakerPays, XRPAmount>,
|
||||
"STAmount is not supported");
|
||||
|
||||
static_assert(
|
||||
std::is_same_v<TTakerGets, IOUAmount> || std::is_same_v<TTakerGets, XRPAmount>,
|
||||
"STAmount is not supported");
|
||||
|
||||
static_assert(
|
||||
!std::is_same_v<TTakerPays, XRPAmount> || !std::is_same_v<TTakerGets, XRPAmount>,
|
||||
"Cannot have XRP/XRP offers");
|
||||
|
||||
// Consider removing the offer if:
|
||||
// o `TakerPays` is XRP (because of XRP drops granularity) or
|
||||
// o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
|
||||
@@ -156,14 +133,14 @@ TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
|
||||
if constexpr (!inIsXRP && !outIsXRP)
|
||||
{
|
||||
if (ofrAmts.in >= ofrAmts.out)
|
||||
if (Number(ofrAmts.in) >= Number(ofrAmts.out))
|
||||
return false;
|
||||
}
|
||||
|
||||
TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
|
||||
|
||||
auto const effectiveAmounts = [&] {
|
||||
if (offer_.owner() != offer_.issueOut().account && ownerFunds < ofrAmts.out)
|
||||
if (offer_.owner() != offer_.assetOut().getIssuer() && ownerFunds < ofrAmts.out)
|
||||
{
|
||||
// adjust the amounts by owner funds.
|
||||
//
|
||||
@@ -185,7 +162,7 @@ TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
return effectiveQuality < offer_.quality();
|
||||
}
|
||||
|
||||
template <class TIn, class TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
bool
|
||||
TOfferStreamBase<TIn, TOut>::step()
|
||||
{
|
||||
@@ -240,9 +217,7 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const deepFrozen = isDeepFrozen(
|
||||
view_, offer_.owner(), offer_.issueIn().currency, offer_.issueIn().account);
|
||||
if (deepFrozen)
|
||||
if (isDeepFrozen(view_, offer_.owner(), offer_.assetIn()))
|
||||
{
|
||||
JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key();
|
||||
permRmOffer(entry->key());
|
||||
@@ -262,7 +237,13 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
|
||||
// Calculate owner funds
|
||||
ownerFunds_ = accountFundsHelper(
|
||||
view_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
|
||||
view_,
|
||||
offer_.owner(),
|
||||
amount.out,
|
||||
offer_.assetOut(),
|
||||
fhZERO_IF_FROZEN,
|
||||
ahZERO_IF_UNAUTHORIZED,
|
||||
j_);
|
||||
|
||||
// Check for unfunded offer
|
||||
if (*ownerFunds_ <= beast::zero)
|
||||
@@ -271,7 +252,13 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
// we haven't modified the balance and therefore the
|
||||
// offer is "found unfunded" versus "became unfunded"
|
||||
auto const original_funds = accountFundsHelper(
|
||||
cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
|
||||
cancelView_,
|
||||
offer_.owner(),
|
||||
amount.out,
|
||||
offer_.assetOut(),
|
||||
fhZERO_IF_FROZEN,
|
||||
ahZERO_IF_UNAUTHORIZED,
|
||||
j_);
|
||||
|
||||
if (original_funds == *ownerFunds_)
|
||||
{
|
||||
@@ -287,44 +274,16 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const rmSmallIncreasedQOffer = [&] {
|
||||
bool const inIsXRP = isXRP(offer_.issueIn());
|
||||
bool const outIsXRP = isXRP(offer_.issueOut());
|
||||
if (inIsXRP && !outIsXRP)
|
||||
{
|
||||
// Without the `if constexpr`, the
|
||||
// `shouldRmSmallIncreasedQOffer` template will be instantiated
|
||||
// even if it is never used. This can cause compiler errors in
|
||||
// some cases, hence the `if constexpr` guard.
|
||||
// Note that TIn can be XRPAmount or STAmount, and TOut can be
|
||||
// IOUAmount or STAmount.
|
||||
if constexpr (!(std::is_same_v<TIn, IOUAmount> || std::is_same_v<TOut, XRPAmount>))
|
||||
return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
|
||||
}
|
||||
if (!inIsXRP && outIsXRP)
|
||||
{
|
||||
// See comment above for `if constexpr` rationale
|
||||
if constexpr (!(std::is_same_v<TIn, XRPAmount> || std::is_same_v<TOut, IOUAmount>))
|
||||
return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
|
||||
}
|
||||
if (!inIsXRP && !outIsXRP)
|
||||
{
|
||||
// See comment above for `if constexpr` rationale
|
||||
if constexpr (!(std::is_same_v<TIn, XRPAmount> || std::is_same_v<TOut, XRPAmount>))
|
||||
return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
|
||||
"vs XRP offer");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}();
|
||||
|
||||
if (rmSmallIncreasedQOffer)
|
||||
if (shouldRmSmallIncreasedQOffer<TIn, TOut>())
|
||||
{
|
||||
auto const original_funds = accountFundsHelper(
|
||||
cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
|
||||
cancelView_,
|
||||
offer_.owner(),
|
||||
amount.out,
|
||||
offer_.assetOut(),
|
||||
fhZERO_IF_FROZEN,
|
||||
ahZERO_IF_UNAUTHORIZED,
|
||||
j_);
|
||||
|
||||
if (original_funds == *ownerFunds_)
|
||||
{
|
||||
@@ -348,26 +307,28 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
OfferStream::permRmOffer(uint256 const& offerIndex)
|
||||
{
|
||||
offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
|
||||
}
|
||||
|
||||
template <class TIn, class TOut>
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
void
|
||||
FlowOfferStream<TIn, TOut>::permRmOffer(uint256 const& offerIndex)
|
||||
{
|
||||
permToRemove_.insert(offerIndex);
|
||||
}
|
||||
|
||||
template class FlowOfferStream<STAmount, STAmount>;
|
||||
template class FlowOfferStream<IOUAmount, IOUAmount>;
|
||||
template class FlowOfferStream<XRPAmount, IOUAmount>;
|
||||
template class FlowOfferStream<IOUAmount, XRPAmount>;
|
||||
template class FlowOfferStream<MPTAmount, MPTAmount>;
|
||||
template class FlowOfferStream<XRPAmount, MPTAmount>;
|
||||
template class FlowOfferStream<MPTAmount, XRPAmount>;
|
||||
template class FlowOfferStream<IOUAmount, MPTAmount>;
|
||||
template class FlowOfferStream<MPTAmount, IOUAmount>;
|
||||
|
||||
template class TOfferStreamBase<STAmount, STAmount>;
|
||||
template class TOfferStreamBase<IOUAmount, IOUAmount>;
|
||||
template class TOfferStreamBase<XRPAmount, IOUAmount>;
|
||||
template class TOfferStreamBase<IOUAmount, XRPAmount>;
|
||||
template class TOfferStreamBase<MPTAmount, MPTAmount>;
|
||||
template class TOfferStreamBase<XRPAmount, MPTAmount>;
|
||||
template class TOfferStreamBase<MPTAmount, XRPAmount>;
|
||||
template class TOfferStreamBase<IOUAmount, MPTAmount>;
|
||||
template class TOfferStreamBase<MPTAmount, IOUAmount>;
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -32,12 +32,6 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual)
|
||||
return r <= ratTol;
|
||||
};
|
||||
|
||||
bool
|
||||
checkNear(XRPAmount const& expected, XRPAmount const& actual)
|
||||
{
|
||||
return expected == actual;
|
||||
};
|
||||
|
||||
static bool
|
||||
isXRPAccount(STPathElement const& pe)
|
||||
{
|
||||
@@ -51,12 +45,12 @@ toStep(
|
||||
StrandContext const& ctx,
|
||||
STPathElement const* e1,
|
||||
STPathElement const* e2,
|
||||
Issue const& curIssue)
|
||||
Asset const& curAsset)
|
||||
{
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (ctx.isFirst && e1->isAccount() &&
|
||||
((e1->getNodeType() & STPathElement::typeCurrency) != 0u) && isXRP(e1->getCurrency()))
|
||||
((e1->getNodeType() & STPathElement::typeCurrency) != 0u) && e1->getPathAsset().isXRP())
|
||||
{
|
||||
return make_XRPEndpointStep(ctx, e1->getAccountID());
|
||||
}
|
||||
@@ -64,9 +58,32 @@ toStep(
|
||||
if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
|
||||
return make_XRPEndpointStep(ctx, e2->getAccountID());
|
||||
|
||||
// MPTEndpointStep is created in following cases:
|
||||
// 1 Direct payment between an issuer and a holder
|
||||
// e1 is issuer and e2 is holder or vise versa
|
||||
// There is only one step in this case: holder->issuer or
|
||||
// issuer->holder
|
||||
// 2 Direct payment between the holders
|
||||
// e1 is issuer and e2 is holder or vise versa
|
||||
// There are two steps in this case: holder->issuer->holder1
|
||||
// 3 Cross-token payment with Amount or SendMax or both MPT
|
||||
// If destination is an issuer then the last step is BookStep,
|
||||
// otherwise the last step is MPTEndpointStep where e1 is
|
||||
// the issuer and e2 is the holder.
|
||||
// In all cases MPTEndpointStep is always first or last step,
|
||||
// e1/e2 are always account types, and curAsset is always MPT.
|
||||
|
||||
if (e1->isAccount() && e2->isAccount())
|
||||
{
|
||||
return make_DirectStepI(ctx, e1->getAccountID(), e2->getAccountID(), curIssue.currency);
|
||||
return curAsset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
return make_MPTEndpointStep(
|
||||
ctx, e1->getAccountID(), e2->getAccountID(), issue.getMptID());
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
return make_DirectStepI(
|
||||
ctx, e1->getAccountID(), e2->getAccountID(), issue.currency);
|
||||
});
|
||||
}
|
||||
|
||||
if (e1->isOffer() && e2->isAccount())
|
||||
@@ -80,17 +97,16 @@ toStep(
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(e2->getNodeType() & STPathElement::typeCurrency) ||
|
||||
(e2->getNodeType() & STPathElement::typeAsset) ||
|
||||
(e2->getNodeType() & STPathElement::typeIssuer),
|
||||
"xrpl::toStep : currency or issuer");
|
||||
auto const outCurrency = ((e2->getNodeType() & STPathElement::typeCurrency) != 0u)
|
||||
? e2->getCurrency()
|
||||
: curIssue.currency;
|
||||
PathAsset const outAsset =
|
||||
((e2->getNodeType() & STPathElement::typeAsset) != 0u) ? e2->getPathAsset() : curAsset;
|
||||
auto const outIssuer = ((e2->getNodeType() & STPathElement::typeIssuer) != 0u)
|
||||
? e2->getIssuerID()
|
||||
: curIssue.account;
|
||||
: curAsset.getIssuer();
|
||||
|
||||
if (isXRP(curIssue.currency) && isXRP(outCurrency))
|
||||
if (isXRP(curAsset) && outAsset.isXRP())
|
||||
{
|
||||
JLOG(j.info()) << "Found xrp/xrp offer payment step";
|
||||
return {temBAD_PATH, std::unique_ptr<Step>{}};
|
||||
@@ -98,13 +114,35 @@ toStep(
|
||||
|
||||
XRPL_ASSERT(e2->isOffer(), "xrpl::toStep : is offer");
|
||||
|
||||
if (isXRP(outCurrency))
|
||||
return make_BookStepIX(ctx, curIssue);
|
||||
if (outAsset.isXRP())
|
||||
{
|
||||
return curAsset.visit(
|
||||
[&](MPTIssue const& issue) { return make_BookStepMX(ctx, issue); },
|
||||
[&](Issue const& issue) { return make_BookStepIX(ctx, issue); });
|
||||
}
|
||||
|
||||
if (isXRP(curIssue.currency))
|
||||
return make_BookStepXI(ctx, {outCurrency, outIssuer});
|
||||
if (isXRP(curAsset))
|
||||
{
|
||||
return outAsset.visit(
|
||||
[&](MPTID const& mpt) { return make_BookStepXM(ctx, mpt); },
|
||||
[&](Currency const& currency) { return make_BookStepXI(ctx, {currency, outIssuer}); });
|
||||
}
|
||||
|
||||
return make_BookStepII(ctx, curIssue, {outCurrency, outIssuer});
|
||||
return curAsset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
return outAsset.visit(
|
||||
[&](Currency const& currency) {
|
||||
return make_BookStepMI(ctx, issue, {currency, outIssuer});
|
||||
},
|
||||
[&](MPTID const& mpt) { return make_BookStepMM(ctx, issue, mpt); });
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
return outAsset.visit(
|
||||
[&](MPTID const& mpt) { return make_BookStepIM(ctx, issue, mpt); },
|
||||
[&](Currency const& currency) {
|
||||
return make_BookStepII(ctx, issue, {currency, outIssuer});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<TER, Strand>
|
||||
@@ -112,9 +150,9 @@ toStrand(
|
||||
ReadView const& view,
|
||||
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,
|
||||
@@ -123,15 +161,21 @@ toStrand(
|
||||
beast::Journal j)
|
||||
{
|
||||
if (isXRP(src) || isXRP(dst) || !isConsistent(deliver) ||
|
||||
(sendMaxIssue && !isConsistent(*sendMaxIssue)))
|
||||
(sendMaxAsset && !isConsistent(*sendMaxAsset)))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
if ((sendMaxIssue && sendMaxIssue->account == noAccount()) || (src == noAccount()) ||
|
||||
(dst == noAccount()) || (deliver.account == noAccount()))
|
||||
if ((sendMaxAsset && sendMaxAsset->getIssuer() == noAccount()) || (src == noAccount()) ||
|
||||
(dst == noAccount()) || (deliver.getIssuer() == noAccount()))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
for (auto const& pe : path)
|
||||
if ((deliver.holds<MPTIssue>() && deliver.getIssuer() == beast::zero) ||
|
||||
(sendMaxAsset && sendMaxAsset->holds<MPTIssue>() &&
|
||||
sendMaxAsset->getIssuer() == beast::zero))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
for (std::size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
auto const& pe = path[i];
|
||||
auto const t = pe.getNodeType();
|
||||
|
||||
if (((t & ~STPathElement::typeAll) != 0u) || (t == 0u))
|
||||
@@ -140,6 +184,8 @@ toStrand(
|
||||
bool const hasAccount = (t & STPathElement::typeAccount) != 0u;
|
||||
bool const hasIssuer = (t & STPathElement::typeIssuer) != 0u;
|
||||
bool const hasCurrency = (t & STPathElement::typeCurrency) != 0u;
|
||||
bool const hasMPT = (t & STPathElement::typeMPT) != 0u;
|
||||
bool const hasAsset = (t & STPathElement::typeAsset) != 0u;
|
||||
|
||||
if (hasAccount && (hasIssuer || hasCurrency))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
@@ -158,17 +204,33 @@ toStrand(
|
||||
|
||||
if (hasAccount && (pe.getAccountID() == noAccount()))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
if (hasMPT && (hasCurrency || hasAccount))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
if (hasMPT && hasIssuer && (pe.getIssuerID() != getMPTIssuer(pe.getMPTID())))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
|
||||
// No rippling if MPT
|
||||
if (i > 0 && path[i - 1].hasMPT() && (hasAccount || (hasIssuer && !hasAsset)))
|
||||
return {temBAD_PATH, Strand{}};
|
||||
}
|
||||
|
||||
Issue curIssue = [&] {
|
||||
auto const& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
|
||||
if (isXRP(currency))
|
||||
return xrpIssue();
|
||||
return Issue{currency, src};
|
||||
Asset curAsset = [&]() -> Asset {
|
||||
auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver;
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) -> Asset { return asset; },
|
||||
[&](Issue const& issue) -> Asset {
|
||||
if (isXRP(asset))
|
||||
return xrpIssue();
|
||||
// First step ripples from the source to the issuer.
|
||||
return Issue{issue.currency, src};
|
||||
});
|
||||
}();
|
||||
|
||||
auto hasCurrency = [](STPathElement const pe) {
|
||||
return pe.getNodeType() & STPathElement::typeCurrency;
|
||||
// Currency or MPT
|
||||
auto hasAsset = [](STPathElement const pe) {
|
||||
return pe.getNodeType() & STPathElement::typeAsset;
|
||||
};
|
||||
|
||||
std::vector<STPathElement> normPath;
|
||||
@@ -176,13 +238,28 @@ toStrand(
|
||||
// sendmax and deliver.
|
||||
normPath.reserve(4 + path.size());
|
||||
{
|
||||
normPath.emplace_back(STPathElement::typeAll, src, curIssue.currency, curIssue.account);
|
||||
// The first step of a path is always implied to be the sender of the
|
||||
// transaction, as defined by the transaction's Account field. The Asset
|
||||
// is either SendMax or Deliver.
|
||||
auto const t = [&]() {
|
||||
auto const t = STPathElement::typeAccount | STPathElement::typeIssuer;
|
||||
return curAsset.visit(
|
||||
[&](MPTIssue const&) { return t | STPathElement::typeMPT; },
|
||||
[&](Issue const&) { return t | STPathElement::typeCurrency; });
|
||||
}();
|
||||
// If MPT then the issuer is the actual issuer, it is never the source
|
||||
// account.
|
||||
normPath.emplace_back(t, src, curAsset, curAsset.getIssuer());
|
||||
|
||||
if (sendMaxIssue && sendMaxIssue->account != src &&
|
||||
// If transaction includes SendMax with the issuer, which is not
|
||||
// the sender of the transaction, that issuer is implied to be
|
||||
// the second step of the path. Unless the path starts at an address,
|
||||
// which is the issuer of SendMax.
|
||||
if (sendMaxAsset && sendMaxAsset->getIssuer() != src &&
|
||||
(path.empty() || !path[0].isAccount() ||
|
||||
path[0].getAccountID() != sendMaxIssue->account))
|
||||
path[0].getAccountID() != sendMaxAsset->getIssuer()))
|
||||
{
|
||||
normPath.emplace_back(sendMaxIssue->account, std::nullopt, std::nullopt);
|
||||
normPath.emplace_back(sendMaxAsset->getIssuer(), std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
for (auto const& i : path)
|
||||
@@ -190,22 +267,32 @@ toStrand(
|
||||
|
||||
{
|
||||
// Note that for offer crossing (only) we do use an offer book
|
||||
// even if all that is changing is the Issue.account.
|
||||
STPathElement const& lastCurrency =
|
||||
*std::find_if(normPath.rbegin(), normPath.rend(), hasCurrency);
|
||||
if ((lastCurrency.getCurrency() != deliver.currency) ||
|
||||
((offerCrossing != 0u) && lastCurrency.getIssuerID() != deliver.account))
|
||||
// even if all that is changing is the Issue.account. Note
|
||||
// that MPTIssue can't change the account.
|
||||
STPathElement const& lastAsset =
|
||||
*std::find_if(normPath.rbegin(), normPath.rend(), hasAsset);
|
||||
if (lastAsset.getPathAsset() != deliver ||
|
||||
(offerCrossing != OfferCrossing::no &&
|
||||
lastAsset.getIssuerID() != deliver.getIssuer()))
|
||||
{
|
||||
normPath.emplace_back(std::nullopt, deliver.currency, deliver.account);
|
||||
normPath.emplace_back(std::nullopt, deliver, deliver.getIssuer());
|
||||
}
|
||||
}
|
||||
|
||||
if (!((normPath.back().isAccount() && normPath.back().getAccountID() == deliver.account) ||
|
||||
(dst == deliver.account)))
|
||||
// If the Amount field of the transaction includes an issuer that is not
|
||||
// the same as the Destination of the transaction, that issuer is
|
||||
// implied to be the second-to-last step of the path. If normPath.back
|
||||
// is an offer, which sells MPT then the added path element account is
|
||||
// the MPT's issuer.
|
||||
if (!((normPath.back().isAccount() &&
|
||||
normPath.back().getAccountID() == deliver.getIssuer()) ||
|
||||
(dst == deliver.getIssuer())))
|
||||
{
|
||||
normPath.emplace_back(deliver.account, std::nullopt, std::nullopt);
|
||||
normPath.emplace_back(deliver.getIssuer(), std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
// Last step of a path is always implied to be the receiver of a
|
||||
// transaction, as defined by the transaction's Destination field.
|
||||
if (!normPath.back().isAccount() || normPath.back().getAccountID() != dst)
|
||||
{
|
||||
normPath.emplace_back(dst, std::nullopt, std::nullopt);
|
||||
@@ -227,11 +314,11 @@ toStrand(
|
||||
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 the same offer book more than once
|
||||
boost::container::flat_set<Issue> seenBookOuts;
|
||||
seenDirectIssues[0].reserve(normPath.size());
|
||||
seenDirectIssues[1].reserve(normPath.size());
|
||||
boost::container::flat_set<Asset> seenBookOuts;
|
||||
seenDirectAssets[0].reserve(normPath.size());
|
||||
seenDirectAssets[1].reserve(normPath.size());
|
||||
seenBookOuts.reserve(normPath.size());
|
||||
auto ctx = [&](bool isLast = false) {
|
||||
return StrandContext{
|
||||
@@ -245,7 +332,7 @@ toStrand(
|
||||
ownerPaysTransferFee,
|
||||
offerCrossing,
|
||||
isDefaultPath,
|
||||
seenDirectIssues,
|
||||
seenDirectAssets,
|
||||
seenBookOuts,
|
||||
ammContext,
|
||||
domainID,
|
||||
@@ -265,58 +352,96 @@ toStrand(
|
||||
auto cur = &normPath[i];
|
||||
auto const next = &normPath[i + 1];
|
||||
|
||||
if (cur->isAccount())
|
||||
// Switch over from MPT to Currency. In this case curAsset account
|
||||
// can be different from the issuer. If cur is MPT then curAsset
|
||||
// is just set to MPTID.
|
||||
if (curAsset.holds<MPTIssue>() && cur->hasCurrency())
|
||||
{
|
||||
curIssue.account = cur->getAccountID();
|
||||
}
|
||||
else if (cur->hasIssuer())
|
||||
{
|
||||
curIssue.account = cur->getIssuerID();
|
||||
curAsset = Issue{};
|
||||
}
|
||||
|
||||
// Can only update the account for Issue since MPTIssue's account
|
||||
// is immutable as it is part of MPTID.
|
||||
curAsset.visit(
|
||||
[&](Issue const&) {
|
||||
if (cur->isAccount())
|
||||
{
|
||||
curAsset.get<Issue>().account = cur->getAccountID();
|
||||
}
|
||||
else if (cur->hasIssuer())
|
||||
{
|
||||
curAsset.get<Issue>().account = cur->getIssuerID();
|
||||
}
|
||||
},
|
||||
[](MPTIssue const&) {});
|
||||
|
||||
if (cur->hasCurrency())
|
||||
{
|
||||
curIssue.currency = cur->getCurrency();
|
||||
if (isXRP(curIssue.currency))
|
||||
curIssue.account = xrpAccount();
|
||||
curAsset = Issue{cur->getCurrency(), curAsset.getIssuer()};
|
||||
if (isXRP(curAsset))
|
||||
curAsset.get<Issue>().account = xrpAccount();
|
||||
}
|
||||
else if (cur->hasMPT())
|
||||
{
|
||||
curAsset = cur->getPathAsset().get<MPTID>();
|
||||
}
|
||||
|
||||
using ImpliedStepRet = std::pair<TER, std::unique_ptr<Step>>;
|
||||
auto getImpliedStep = [&](AccountID const& src_,
|
||||
AccountID const& dst_,
|
||||
Asset const& asset_) -> ImpliedStepRet {
|
||||
return asset_.visit(
|
||||
[&](MPTIssue const&) -> ImpliedStepRet {
|
||||
JLOG(j.error()) << "MPT is invalid with rippling";
|
||||
return {temBAD_PATH, nullptr};
|
||||
},
|
||||
[&](Issue const& issue) -> ImpliedStepRet {
|
||||
return make_DirectStepI(ctx(), src_, dst_, issue.currency);
|
||||
});
|
||||
};
|
||||
|
||||
if (cur->isAccount() && next->isAccount())
|
||||
{
|
||||
if (!isXRP(curIssue.currency) && curIssue.account != cur->getAccountID() &&
|
||||
curIssue.account != next->getAccountID())
|
||||
// This block doesn't execute
|
||||
// since curAsset's account is set to cur's account above.
|
||||
// It should not execute for MPT either because MPT rippling
|
||||
// is invalid. Should this block be removed/amendment excluded?
|
||||
if (!isXRP(curAsset) && curAsset.getIssuer() != cur->getAccountID() &&
|
||||
curAsset.getIssuer() != next->getAccountID())
|
||||
{
|
||||
JLOG(j.trace()) << "Inserting implied account";
|
||||
auto msr = make_DirectStepI(
|
||||
ctx(), cur->getAccountID(), curIssue.account, curIssue.currency);
|
||||
auto msr = getImpliedStep(cur->getAccountID(), curAsset.getIssuer(), curAsset);
|
||||
if (!isTesSuccess(msr.first))
|
||||
return {msr.first, Strand{}};
|
||||
result.push_back(std::move(msr.second));
|
||||
impliedPE.emplace(
|
||||
STPathElement::typeAccount, curIssue.account, xrpCurrency(), xrpAccount());
|
||||
STPathElement::typeAccount, curAsset.getIssuer(), xrpCurrency(), xrpAccount());
|
||||
cur = &*impliedPE;
|
||||
}
|
||||
}
|
||||
else if (cur->isAccount() && next->isOffer())
|
||||
{
|
||||
if (curIssue.account != cur->getAccountID())
|
||||
// Same as above, this block doesn't execute.
|
||||
if (curAsset.getIssuer() != cur->getAccountID())
|
||||
{
|
||||
JLOG(j.trace()) << "Inserting implied account before offer";
|
||||
auto msr = make_DirectStepI(
|
||||
ctx(), cur->getAccountID(), curIssue.account, curIssue.currency);
|
||||
auto msr = getImpliedStep(cur->getAccountID(), curAsset.getIssuer(), curAsset);
|
||||
if (!isTesSuccess(msr.first))
|
||||
return {msr.first, Strand{}};
|
||||
result.push_back(std::move(msr.second));
|
||||
impliedPE.emplace(
|
||||
STPathElement::typeAccount, curIssue.account, xrpCurrency(), xrpAccount());
|
||||
STPathElement::typeAccount, curAsset.getIssuer(), xrpCurrency(), xrpAccount());
|
||||
cur = &*impliedPE;
|
||||
}
|
||||
}
|
||||
else if (cur->isOffer() && next->isAccount())
|
||||
{
|
||||
if (curIssue.account != next->getAccountID() && !isXRP(next->getAccountID()))
|
||||
// If the offer sells MPT, then next's account is always the issuer.
|
||||
// See how normPath step is added for second-to-last or last
|
||||
// step. Therefore, this block never executes if MPT.
|
||||
if (curAsset.getIssuer() != next->getAccountID() && !isXRP(next->getAccountID()))
|
||||
{
|
||||
if (isXRP(curIssue))
|
||||
if (isXRP(curAsset))
|
||||
{
|
||||
if (i != normPath.size() - 2)
|
||||
return {temBAD_PATH, Strand{}};
|
||||
@@ -330,8 +455,7 @@ toStrand(
|
||||
else
|
||||
{
|
||||
JLOG(j.trace()) << "Inserting implied account after offer";
|
||||
auto msr = make_DirectStepI(
|
||||
ctx(), curIssue.account, next->getAccountID(), curIssue.currency);
|
||||
auto msr = getImpliedStep(curAsset.getIssuer(), next->getAccountID(), curAsset);
|
||||
if (!isTesSuccess(msr.first))
|
||||
return {msr.first, Strand{}};
|
||||
result.push_back(std::move(msr.second));
|
||||
@@ -340,7 +464,7 @@ toStrand(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!next->isOffer() && next->hasCurrency() && next->getCurrency() != curIssue.currency)
|
||||
if (!next->isOffer() && next->hasAsset() && next->getPathAsset() != curAsset)
|
||||
{
|
||||
// Should never happen
|
||||
// LCOV_EXCL_START
|
||||
@@ -349,7 +473,7 @@ toStrand(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto s = toStep(ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curIssue);
|
||||
auto s = toStep(ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curAsset);
|
||||
if (isTesSuccess(s.first))
|
||||
{
|
||||
result.emplace_back(std::move(s.second));
|
||||
@@ -366,17 +490,21 @@ toStrand(
|
||||
if (auto r = s.directStepAccts())
|
||||
return *r;
|
||||
if (auto const r = s.bookStepBook())
|
||||
return std::make_pair(r->in.account, r->out.account);
|
||||
return std::make_pair(r->in.getIssuer(), r->out.getIssuer());
|
||||
Throw<FlowException>(tefEXCEPTION, "Step should be either a direct or book step");
|
||||
return std::make_pair(xrpAccount(), xrpAccount());
|
||||
};
|
||||
|
||||
auto curAcc = src;
|
||||
auto curIss = [&] {
|
||||
auto& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
|
||||
if (isXRP(currency))
|
||||
return xrpIssue();
|
||||
return Issue{currency, src};
|
||||
auto curAsset = [&]() -> Asset {
|
||||
auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver;
|
||||
return asset.visit(
|
||||
[&](MPTIssue const&) -> Asset { return asset; },
|
||||
[&](Issue const& issue) -> Asset {
|
||||
if (isXRP(asset))
|
||||
return xrpIssue();
|
||||
return Issue{issue.currency, src};
|
||||
});
|
||||
}();
|
||||
|
||||
for (auto const& s : result)
|
||||
@@ -387,22 +515,25 @@ toStrand(
|
||||
|
||||
if (auto const b = s->bookStepBook())
|
||||
{
|
||||
if (curIss != b->in)
|
||||
if (curAsset != b->in)
|
||||
return false;
|
||||
curIss = b->out;
|
||||
curAsset = b->out;
|
||||
}
|
||||
else
|
||||
else if (curAsset.holds<Issue>())
|
||||
{
|
||||
curIss.account = accts.second;
|
||||
curAsset.get<Issue>().account = accts.second;
|
||||
}
|
||||
|
||||
curAcc = accts.second;
|
||||
}
|
||||
if (curAcc != dst)
|
||||
return false;
|
||||
if (curIss.currency != deliver.currency)
|
||||
if (curAsset.holds<Issue>() != deliver.holds<Issue>() ||
|
||||
(curAsset.holds<Issue>() &&
|
||||
curAsset.get<Issue>().currency != deliver.get<Issue>().currency) ||
|
||||
(curAsset.holds<MPTIssue>() && curAsset.get<MPTIssue>() != deliver.get<MPTIssue>()))
|
||||
return false;
|
||||
if (curIss.account != deliver.account && curIss.account != dst)
|
||||
if (curAsset.getIssuer() != deliver.getIssuer() && curAsset.getIssuer() != dst)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
@@ -424,9 +555,9 @@ toStrands(
|
||||
ReadView const& view,
|
||||
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,
|
||||
@@ -539,14 +670,14 @@ StrandContext::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_,
|
||||
boost::container::flat_set<Issue>& seenBookOuts_,
|
||||
std::array<boost::container::flat_set<Asset>, 2>& seenDirectAssets_,
|
||||
boost::container::flat_set<Asset>& seenBookOuts_,
|
||||
AMMContext& ammContext_,
|
||||
std::optional<uint256> const& domainID_,
|
||||
beast::Journal j_)
|
||||
@@ -562,7 +693,7 @@ StrandContext::StrandContext(
|
||||
, isDefaultPath(isDefaultPath_)
|
||||
, strandSize(strand_.size())
|
||||
, prevStep(!strand_.empty() ? strand_.back().get() : nullptr)
|
||||
, seenDirectIssues(seenDirectIssues_)
|
||||
, seenDirectAssets(seenDirectAssets_)
|
||||
, seenBookOuts(seenBookOuts_)
|
||||
, ammContext(ammContext_)
|
||||
, domainID(domainID_)
|
||||
@@ -570,25 +701,4 @@ StrandContext::StrandContext(
|
||||
{
|
||||
}
|
||||
|
||||
template <class InAmt, class OutAmt>
|
||||
bool
|
||||
isDirectXrpToXrp(Strand const& strand)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool
|
||||
isDirectXrpToXrp<XRPAmount, XRPAmount>(Strand const& strand)
|
||||
{
|
||||
return (strand.size() == 2);
|
||||
}
|
||||
|
||||
template bool
|
||||
isDirectXrpToXrp<XRPAmount, IOUAmount>(Strand const& strand);
|
||||
template bool
|
||||
isDirectXrpToXrp<IOUAmount, XRPAmount>(Strand const& strand);
|
||||
template bool
|
||||
isDirectXrpToXrp<IOUAmount, IOUAmount>(Strand const& strand);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -54,7 +54,7 @@ RippleCalc::rippleCalculate(
|
||||
|
||||
auto const sendMax = [&]() -> std::optional<STAmount> {
|
||||
if (saMaxAmountReq >= beast::zero ||
|
||||
saMaxAmountReq.getCurrency() != saDstAmountReq.getCurrency() ||
|
||||
!equalTokens(saMaxAmountReq.asset(), saDstAmountReq.asset()) ||
|
||||
saMaxAmountReq.getIssuer() != uSrcAccountID)
|
||||
{
|
||||
return saMaxAmountReq;
|
||||
|
||||
@@ -179,13 +179,25 @@ private:
|
||||
// because the trust line was created after the XRP was removed.)
|
||||
// Return how much the reserve should be reduced.
|
||||
//
|
||||
// Note that reduced reserve only happens if the trust line does not
|
||||
// Note that reduced reserve only happens if the trust line or MPT does not
|
||||
// currently exist.
|
||||
static std::int32_t
|
||||
computeReserveReduction(StrandContext const& ctx, AccountID const& acc)
|
||||
{
|
||||
if (ctx.isFirst && !ctx.view.read(keylet::line(acc, ctx.strandDeliver)))
|
||||
return -1;
|
||||
if (ctx.isFirst)
|
||||
{
|
||||
return ctx.strandDeliver.visit(
|
||||
[&](Issue const& issue) {
|
||||
if (!ctx.view.exists(keylet::line(acc, issue)))
|
||||
return -1;
|
||||
return 0;
|
||||
},
|
||||
[&](MPTIssue const& issue) {
|
||||
if (!ctx.view.exists(keylet::mptoken(issue.getMptID(), acc)))
|
||||
return -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -283,9 +295,9 @@ XRPEndpointStep<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, Eithe
|
||||
return {false, EitherAmount(XRPAmount(beast::zero))};
|
||||
}
|
||||
|
||||
XRPL_ASSERT(in.native, "xrpl::XRPEndpointStep::validFwd : input is XRP");
|
||||
XRPL_ASSERT(in.holds<XRPAmount>(), "xrpl::XRPEndpointStep::validFwd : input is XRP");
|
||||
|
||||
auto const& xrpIn = in.xrp;
|
||||
auto const& xrpIn = in.get<XRPAmount>();
|
||||
auto const balance = static_cast<TDerived const*>(this)->xrpLiquid(sb);
|
||||
|
||||
if (!isLast_ && balance < xrpIn)
|
||||
@@ -336,7 +348,7 @@ XRPEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
return ter;
|
||||
|
||||
auto const issuesIndex = isLast_ ? 0 : 1;
|
||||
if (!ctx.seenDirectIssues[issuesIndex].insert(xrpIssue()).second)
|
||||
if (!ctx.seenDirectAssets[issuesIndex].insert(xrpIssue()).second)
|
||||
{
|
||||
JLOG(j_.debug()) << "XRPEndpointStep: loop detected: Index: " << ctx.strandSize << ' '
|
||||
<< *this;
|
||||
|
||||
@@ -642,7 +642,7 @@ finalizeClaimHelper(
|
||||
saveNumberRoundMode const _{Number::setround(round_mode)};
|
||||
|
||||
STAmount const den{rewardAccounts.size()};
|
||||
return divide(rewardPool, den, rewardPool.issue());
|
||||
return divide(rewardPool, den, rewardPool.asset());
|
||||
}();
|
||||
STAmount distributed = rewardPool.zeroed();
|
||||
for (auto const& rewardAccount : rewardAccounts)
|
||||
@@ -1167,7 +1167,7 @@ attestationPreflight(PreflightContext const& ctx)
|
||||
if (att->sendingAmount.signum() <= 0)
|
||||
return temXCHAIN_BAD_PROOF;
|
||||
auto const expectedIssue = bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
|
||||
if (att->sendingAmount.issue() != expectedIssue)
|
||||
if (att->sendingAmount.asset() != expectedIssue)
|
||||
return temXCHAIN_BAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -1578,8 +1578,8 @@ XChainClaim::preflight(PreflightContext const& ctx)
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
|
||||
if (amount.signum() <= 0 ||
|
||||
(amount.issue() != bridgeSpec.lockingChainIssue() &&
|
||||
amount.issue() != bridgeSpec.issuingChainIssue()))
|
||||
(amount.asset() != bridgeSpec.lockingChainIssue() &&
|
||||
amount.asset() != bridgeSpec.issuingChainIssue()))
|
||||
{
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
@@ -1628,12 +1628,12 @@ XChainClaim::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (isLockingChain)
|
||||
{
|
||||
if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
|
||||
if (bridgeSpec.lockingChainIssue() != thisChainAmount.asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
|
||||
if (bridgeSpec.issuingChainIssue() != thisChainAmount.asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
}
|
||||
}
|
||||
@@ -1820,8 +1820,8 @@ XChainCommit::preflight(PreflightContext const& ctx)
|
||||
if (amount.signum() <= 0 || !isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (amount.issue() != bridgeSpec.lockingChainIssue() &&
|
||||
amount.issue() != bridgeSpec.issuingChainIssue())
|
||||
if (amount.asset() != bridgeSpec.lockingChainIssue() &&
|
||||
amount.asset() != bridgeSpec.issuingChainIssue())
|
||||
return temBAD_ISSUER;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -1866,12 +1866,12 @@ XChainCommit::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (isLockingChain)
|
||||
{
|
||||
if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
|
||||
if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
|
||||
if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
}
|
||||
|
||||
@@ -2083,7 +2083,7 @@ XChainCreateAccountCommit::preflight(PreflightContext const& ctx)
|
||||
if (reward.signum() < 0 || !reward.native())
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (reward.issue() != amount.issue())
|
||||
if (reward.asset() != amount.asset())
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -2115,7 +2115,7 @@ XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx)
|
||||
if (amount < *minCreateAmount)
|
||||
return tecXCHAIN_INSUFF_CREATE_AMOUNT;
|
||||
|
||||
if (minCreateAmount->issue() != amount.issue())
|
||||
if (minCreateAmount->asset() != amount.asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
|
||||
AccountID const thisDoor = (*sleBridge)[sfAccount];
|
||||
@@ -2143,7 +2143,7 @@ XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
|
||||
|
||||
if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
|
||||
if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].asset())
|
||||
return tecXCHAIN_BAD_TRANSFER_ISSUE;
|
||||
|
||||
if (!isXRP(bridgeSpec.issue(dstChain)))
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/paths/Flow.h>
|
||||
#include <xrpl/tx/transactors/check/CheckCash.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
CheckCash::checkExtraFeatures(xrpl::PreflightContext const& ctx)
|
||||
{
|
||||
auto const optAmount = ctx.tx[~sfAmount];
|
||||
auto const optDeliverMin = ctx.tx[~sfDeliverMin];
|
||||
|
||||
return ctx.rules.enabled(featureMPTokensV2) ||
|
||||
(!(optAmount && optAmount->holds<MPTIssue>()) &&
|
||||
!(optDeliverMin && optDeliverMin->holds<MPTIssue>()));
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CheckCash::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -35,7 +47,7 @@ CheckCash::preflight(PreflightContext const& ctx)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (badCurrency() == value.getCurrency())
|
||||
if (badAsset() == value.asset())
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
|
||||
return temBAD_CURRENCY;
|
||||
@@ -106,8 +118,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
}(ctx.tx)};
|
||||
|
||||
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||
Currency const currency{value.getCurrency()};
|
||||
if (currency != sendMax.getCurrency())
|
||||
if (!equalTokens(value.asset(), sendMax.asset()))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Check cash does not match check currency.";
|
||||
return temMALFORMED;
|
||||
@@ -127,8 +138,13 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// Make sure the check owner holds at least value. If they have
|
||||
// less than value the check cannot be cashed.
|
||||
{
|
||||
STAmount availableFunds{
|
||||
accountFunds(ctx.view, sleCheck->at(sfAccount), value, fhZERO_IF_FROZEN, ctx.j)};
|
||||
STAmount availableFunds{accountFunds(
|
||||
ctx.view,
|
||||
sleCheck->at(sfAccount),
|
||||
value,
|
||||
fhZERO_IF_FROZEN,
|
||||
ahZERO_IF_UNAUTHORIZED,
|
||||
ctx.j)};
|
||||
|
||||
// Note that src will have one reserve's worth of additional XRP
|
||||
// once the check is cashed, since the check's reserve will no
|
||||
@@ -147,51 +163,95 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// An issuer can always accept their own currency.
|
||||
if (!value.native() && (value.getIssuer() != dstId))
|
||||
{
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Can't receive IOUs from non-existent issuer: " << to_string(issuerId);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
return value.asset().visit(
|
||||
[&](Issue const& issue) -> TER {
|
||||
Currency const currency{issue.currency};
|
||||
auto const sleTrustLine =
|
||||
ctx.view.read(keylet::line(dstId, issuerId, currency));
|
||||
|
||||
if ((sleIssuer->at(sfFlags) & lsfRequireAuth) != 0u)
|
||||
{
|
||||
auto const sleTrustLine = ctx.view.read(keylet::line(dstId, issuerId, currency));
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive IOUs from "
|
||||
"non-existent issuer: "
|
||||
<< to_string(issuerId);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (!sleTrustLine)
|
||||
{
|
||||
// We can only create a trust line if the issuer does not
|
||||
// have lsfRequireAuth set.
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
if ((sleIssuer->at(sfFlags) & lsfRequireAuth) != 0u)
|
||||
{
|
||||
if (!sleTrustLine)
|
||||
{
|
||||
// We can only create a trust line if the issuer
|
||||
// does not have lsfRequireAuth set.
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
|
||||
// Entries have a canonical representation, determined by a
|
||||
// lexicographical "greater than" comparison employing strict
|
||||
// weak ordering. Determine which entry we need to access.
|
||||
bool const canonical_gt(dstId > issuerId);
|
||||
// Entries have a canonical representation,
|
||||
// determined by a lexicographical "greater than"
|
||||
// comparison employing strict weak ordering.
|
||||
// Determine which entry we need to access.
|
||||
bool const canonical_gt(dstId > issuerId);
|
||||
|
||||
bool const is_authorized(
|
||||
(sleTrustLine->at(sfFlags) & (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u);
|
||||
bool const is_authorized(
|
||||
(sleTrustLine->at(sfFlags) &
|
||||
(canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u);
|
||||
|
||||
if (!is_authorized)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive IOUs from issuer without auth.";
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
}
|
||||
if (!is_authorized)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive IOUs from "
|
||||
"issuer without auth.";
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
}
|
||||
|
||||
// The trustline from source to issuer does not need to
|
||||
// be checked for freezing, since we already verified that the
|
||||
// source has sufficient non-frozen funds available.
|
||||
// The trustline from source to issuer does not need to
|
||||
// be checked for freezing, since we already verified
|
||||
// that the source has sufficient non-frozen funds
|
||||
// available.
|
||||
|
||||
// However, the trustline from destination to issuer may not
|
||||
// be frozen.
|
||||
if (isFrozen(ctx.view, dstId, currency, issuerId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
// However, the trustline from destination to issuer may
|
||||
// not be frozen.
|
||||
if (isFrozen(ctx.view, dstId, currency, issuerId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> TER {
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive MPTs from "
|
||||
"non-existent issuer: "
|
||||
<< to_string(issuerId);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (auto const err = requireAuth(ctx.view, issue, dstId, AuthType::WeakAuth);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a MPT requiring auth.";
|
||||
return err;
|
||||
}
|
||||
|
||||
if (isFrozen(ctx.view, dstId, issue))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen MPT.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
if (auto const err = canTransfer(ctx.view, issue, srcId, dstId);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "MPT transfer is disabled.";
|
||||
return err;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
});
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -287,94 +347,152 @@ CheckCash::doApply()
|
||||
// maximum possible currency because there might be a gateway
|
||||
// transfer rate to account for. Since the transfer rate cannot
|
||||
// exceed 200%, we use 1/2 maxValue as our limit.
|
||||
auto const maxDeliverMin = [&]() {
|
||||
return optDeliverMin->asset().visit(
|
||||
[&](Issue const&) {
|
||||
return STAmount(
|
||||
optDeliverMin->asset(), STAmount::cMaxValue / 2, STAmount::cMaxOffset);
|
||||
},
|
||||
[&](MPTIssue const&) {
|
||||
return STAmount(optDeliverMin->asset(), maxMPTokenAmount / 2);
|
||||
});
|
||||
};
|
||||
STAmount const flowDeliver{
|
||||
optDeliverMin
|
||||
? STAmount(
|
||||
optDeliverMin->issue(), STAmount::cMaxValue / 2, STAmount::cMaxOffset)
|
||||
: ctx_.tx.getFieldAmount(sfAmount)};
|
||||
optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)};
|
||||
|
||||
// If a trust line does not exist yet create one.
|
||||
Issue const& trustLineIssue = flowDeliver.issue();
|
||||
AccountID const issuer = flowDeliver.getIssuer();
|
||||
AccountID const truster = issuer == account_ ? srcId : account_;
|
||||
Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
|
||||
bool const destLow = issuer > account_;
|
||||
// Check reserve. Return destination account SLE if enough reserve,
|
||||
// otherwise return nullptr.
|
||||
auto checkReserve = [&]() -> std::shared_ptr<SLE> {
|
||||
auto sleDst = psb.peek(keylet::account(account_));
|
||||
|
||||
if (!psb.exists(trustLineKey))
|
||||
{
|
||||
// 1. Can the check casher meet the reserve for the trust line?
|
||||
// 2. Create trust line between destination (this) account
|
||||
// and the issuer.
|
||||
// 3. Apply correct noRipple settings on trust line. Use...
|
||||
// a. this (destination) account and
|
||||
// b. issuing account (not sending account).
|
||||
|
||||
auto const sleDst = psb.peek(keylet::account(account_));
|
||||
|
||||
// Can the account cover the trust line's reserve?
|
||||
// Can the account cover the trust line's or MPT reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||
"Insufficent reserve to create line.";
|
||||
"Insufficient reserve to create line.";
|
||||
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
return nullptr;
|
||||
}
|
||||
return sleDst;
|
||||
};
|
||||
|
||||
Currency const currency = flowDeliver.getCurrency();
|
||||
STAmount initialBalance(flowDeliver.issue());
|
||||
initialBalance.setIssuer(noAccount());
|
||||
std::optional<Keylet> trustLineKey;
|
||||
STAmount savedLimit;
|
||||
bool destLow = false;
|
||||
AccountID const& deliverIssuer = flowDeliver.getIssuer();
|
||||
auto const err = flowDeliver.asset().visit(
|
||||
[&](Issue const& issue) -> std::optional<TER> {
|
||||
// If a trust line does not exist yet create one.
|
||||
Issue const& trustLineIssue = issue;
|
||||
AccountID const truster = deliverIssuer == account_ ? srcId : account_;
|
||||
trustLineKey = keylet::line(truster, trustLineIssue);
|
||||
destLow = deliverIssuer > account_;
|
||||
|
||||
if (TER const ter = trustCreate(
|
||||
psb, // payment sandbox
|
||||
destLow, // is dest low?
|
||||
issuer, // source
|
||||
account_, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDst, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDst->getFlags() & lsfDefaultRipple) == 0, //
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, account_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
!isTesSuccess(ter))
|
||||
if (!psb.exists(*trustLineKey))
|
||||
{
|
||||
// 1. Can the check casher meet the reserve for the
|
||||
// trust line?
|
||||
// 2. Create trust line between destination (this)
|
||||
// account
|
||||
// and the issuer.
|
||||
// 3. Apply correct noRipple settings on trust line.
|
||||
// Use...
|
||||
// a. this (destination) account and
|
||||
// b. issuing account (not sending account).
|
||||
|
||||
auto const sleDst = checkReserve();
|
||||
if (sleDst == nullptr)
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
Currency const& currency = issue.currency;
|
||||
STAmount initialBalance(flowDeliver.asset());
|
||||
initialBalance.get<Issue>().account = noAccount();
|
||||
|
||||
if (TER const ter = trustCreate(
|
||||
psb, // payment sandbox
|
||||
destLow, // is dest low?
|
||||
deliverIssuer, // source
|
||||
account_, // destination
|
||||
trustLineKey->key, // ledger index
|
||||
sleDst, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDst->getFlags() & lsfDefaultRipple) == 0, //
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, account_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
psb.update(sleDst);
|
||||
|
||||
// Note that we _don't_ need to be careful about
|
||||
// destroying the trust line if the check cashing
|
||||
// fails. The transaction machinery will
|
||||
// automatically clean it up.
|
||||
}
|
||||
|
||||
// Since the destination is signing the check, they
|
||||
// clearly want the funds even if their new total funds
|
||||
// would exceed the limit on their trust line. So we
|
||||
// tweak the trust line limits before calling flow and
|
||||
// then restore the trust line limits afterwards.
|
||||
auto const sleTrustLine = psb.peek(*trustLineKey);
|
||||
if (!sleTrustLine)
|
||||
return tecNO_LINE;
|
||||
|
||||
SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
|
||||
savedLimit = sleTrustLine->at(tweakedLimit);
|
||||
|
||||
// Set the trust line limit to the highest possible
|
||||
// value while flow runs.
|
||||
STAmount const bigAmount(
|
||||
trustLineIssue, STAmount::cMaxValue, STAmount::cMaxOffset);
|
||||
sleTrustLine->at(tweakedLimit) = bigAmount;
|
||||
|
||||
return std::nullopt;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
if (account_ != deliverIssuer)
|
||||
{
|
||||
auto const& mptID = issue.getMptID();
|
||||
// Create MPT if it doesn't exist
|
||||
auto const mptokenKey = keylet::mptoken(mptID, account_);
|
||||
if (!psb.exists(mptokenKey))
|
||||
{
|
||||
auto sleDst = checkReserve();
|
||||
if (sleDst == nullptr)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (auto const err = checkCreateMPT(psb, mptID, account_, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
if (err)
|
||||
return *err;
|
||||
// Make sure the tweaked limits are restored when we leave
|
||||
// scope.
|
||||
scope_exit const fixup([&psb, &trustLineKey, destLow, &savedLimit]() {
|
||||
if (trustLineKey)
|
||||
{
|
||||
return ter;
|
||||
SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
|
||||
if (auto const sleTrustLine = psb.peek(*trustLineKey))
|
||||
sleTrustLine->at(tweakedLimit) = savedLimit;
|
||||
}
|
||||
|
||||
psb.update(sleDst);
|
||||
|
||||
// Note that we _don't_ need to be careful about destroying
|
||||
// the trust line if the check cashing fails. The transaction
|
||||
// machinery will automatically clean it up.
|
||||
}
|
||||
|
||||
// Since the destination is signing the check, they clearly want
|
||||
// the funds even if their new total funds would exceed the limit
|
||||
// on their trust line. So we tweak the trust line limits before
|
||||
// calling flow and then restore the trust line limits afterwards.
|
||||
auto const sleTrustLine = psb.peek(trustLineKey);
|
||||
if (!sleTrustLine)
|
||||
return tecNO_LINE;
|
||||
|
||||
SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
|
||||
STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
|
||||
|
||||
// Make sure the tweaked limits are restored when we leave scope.
|
||||
scope_exit const fixup([&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
|
||||
if (auto const sleTrustLine = psb.peek(trustLineKey))
|
||||
sleTrustLine->at(tweakedLimit) = savedLimit;
|
||||
});
|
||||
|
||||
// Set the trust line limit to the highest possible value
|
||||
// while flow runs.
|
||||
STAmount const bigAmount(trustLineIssue, STAmount::cMaxValue, STAmount::cMaxOffset);
|
||||
sleTrustLine->at(tweakedLimit) = bigAmount;
|
||||
|
||||
// Let flow() do the heavy lifting on a check for an IOU.
|
||||
auto const result = flow(
|
||||
psb,
|
||||
@@ -405,6 +523,7 @@ CheckCash::doApply()
|
||||
JLOG(ctx_.journal.warn()) << "flow did not produce DeliverMin.";
|
||||
return tecPATH_PARTIAL;
|
||||
}
|
||||
ctx_.deliver(result.actualAmountOut);
|
||||
}
|
||||
|
||||
// Set the delivered amount metadata in all cases, not just
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user