Merge branch 'tapanito/lending-fix-amendment' into tapanito/vault-block-deposit

This commit is contained in:
Vito
2026-02-19 11:40:38 +01:00
254 changed files with 742 additions and 703 deletions

View File

@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
namespace xrpl {
/** Service that provides access to the network ID.
This service provides read-only access to the network ID configured
for this server. The network ID identifies which network (mainnet,
testnet, devnet, or custom network) this server is configured to
connect to.
Well-known network IDs:
- 0: Mainnet
- 1: Testnet
- 2: Devnet
- 1025+: Custom networks (require NetworkID field in transactions)
*/
class NetworkIDService
{
public:
virtual ~NetworkIDService() = default;
/** Get the configured network ID
*
* @return The network ID this server is configured for
*/
virtual std::uint32_t
getNetworkID() const noexcept = 0;
};
} // namespace xrpl

View File

@@ -41,6 +41,7 @@ class LoadFeeTrack;
class LoadManager;
class ManifestCache;
class NetworkOPs;
class NetworkIDService;
class OpenLedger;
class OrderBookDB;
class Overlay;
@@ -99,6 +100,9 @@ public:
virtual CachedSLEs&
cachedSLEs() = 0;
virtual NetworkIDService&
getNetworkIDService() = 0;
// Protocol and validation services
virtual AmendmentTable&
getAmendmentTable() = 0;

View File

@@ -0,0 +1,175 @@
#pragma once
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STValidation.h>
#include <xrpl/shamap/SHAMap.h>
#include <optional>
namespace xrpl {
class ServiceRegistry;
/** The amendment table stores the list of enabled and potential amendments.
Individuals amendments are voted on by validators during the consensus
process.
*/
class AmendmentTable
{
public:
struct FeatureInfo
{
FeatureInfo() = delete;
FeatureInfo(std::string const& n, uint256 const& f, VoteBehavior v) : name(n), feature(f), vote(v)
{
}
std::string const name;
uint256 const feature;
VoteBehavior const vote;
};
virtual ~AmendmentTable() = default;
virtual uint256
find(std::string const& name) const = 0;
virtual bool
veto(uint256 const& amendment) = 0;
virtual bool
unVeto(uint256 const& amendment) = 0;
virtual bool
enable(uint256 const& amendment) = 0;
virtual bool
isEnabled(uint256 const& amendment) const = 0;
virtual bool
isSupported(uint256 const& amendment) const = 0;
/**
* @brief returns true if one or more amendments on the network
* have been enabled that this server does not support
*
* @return true if an unsupported feature is enabled on the network
*/
virtual bool
hasUnsupportedEnabled() const = 0;
virtual std::optional<NetClock::time_point>
firstUnsupportedExpected() const = 0;
virtual Json::Value
getJson(bool isAdmin) const = 0;
/** Returns a Json::objectValue. */
virtual Json::Value
getJson(uint256 const& amendment, bool isAdmin) const = 0;
/** Called when a new fully-validated ledger is accepted. */
void
doValidatedLedger(std::shared_ptr<ReadView const> const& lastValidatedLedger)
{
if (needValidatedLedger(lastValidatedLedger->seq()))
doValidatedLedger(
lastValidatedLedger->seq(),
getEnabledAmendments(*lastValidatedLedger),
getMajorityAmendments(*lastValidatedLedger));
}
/** Called to determine whether the amendment logic needs to process
a new validated ledger. (If it could have changed things.)
*/
virtual bool
needValidatedLedger(LedgerIndex seq) const = 0;
virtual void
doValidatedLedger(
LedgerIndex ledgerSeq,
std::set<uint256> const& enabled,
majorityAmendments_t const& majority) = 0;
// Called when the set of trusted validators changes.
virtual void
trustChanged(hash_set<PublicKey> const& allTrusted) = 0;
// Called by the consensus code when we need to
// inject pseudo-transactions
virtual std::map<uint256, std::uint32_t>
doVoting(
Rules const& rules,
NetClock::time_point closeTime,
std::set<uint256> const& enabledAmendments,
majorityAmendments_t const& majorityAmendments,
std::vector<std::shared_ptr<STValidation>> const& valSet) = 0;
// Called by the consensus code when we need to
// add feature entries to a validation
virtual std::vector<uint256>
doValidation(std::set<uint256> const& enabled) const = 0;
// The set of amendments to enable in the genesis ledger
// This will return all known, non-vetoed amendments.
// If we ever have two amendments that should not both be
// enabled at the same time, we should ensure one is vetoed.
virtual std::vector<uint256>
getDesired() const = 0;
// The function below adapts the API callers expect to the
// internal amendment table API. This allows the amendment
// table implementation to be independent of the ledger
// implementation. These APIs will merge when the view code
// supports a full ledger API
void
doVoting(
std::shared_ptr<ReadView const> const& lastClosedLedger,
std::vector<std::shared_ptr<STValidation>> const& parentValidations,
std::shared_ptr<SHAMap> const& initialPosition,
beast::Journal j)
{
// Ask implementation what to do
auto actions = doVoting(
lastClosedLedger->rules(),
lastClosedLedger->parentCloseTime(),
getEnabledAmendments(*lastClosedLedger),
getMajorityAmendments(*lastClosedLedger),
parentValidations);
// Inject appropriate pseudo-transactions
for (auto const& it : actions)
{
STTx amendTx(ttAMENDMENT, [&it, seq = lastClosedLedger->seq() + 1](auto& obj) {
obj.setAccountID(sfAccount, AccountID());
obj.setFieldH256(sfAmendment, it.first);
obj.setFieldU32(sfLedgerSequence, seq);
if (it.second != 0)
obj.setFieldU32(sfFlags, it.second);
});
Serializer s;
amendTx.add(s);
JLOG(j.debug()) << "Amendments: Adding pseudo-transaction: " << amendTx.getTransactionID() << ": "
<< strHex(s.slice()) << ": " << amendTx;
initialPosition->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM, make_shamapitem(amendTx.getTransactionID(), s.slice()));
}
}
};
std::unique_ptr<AmendmentTable>
make_AmendmentTable(
ServiceRegistry& registry,
std::chrono::seconds majorityTime,
std::vector<AmendmentTable::FeatureInfo> const& supported,
Section const& enabled,
Section const& vetoed,
beast::Journal journal);
} // namespace xrpl

View File

@@ -253,6 +253,16 @@ std::uint8_t constexpr maxAssetCheckDepth = 5;
/** A ledger index. */
using LedgerIndex = std::uint32_t;
std::uint32_t constexpr FLAG_LEDGER_INTERVAL = 256;
/** Returns true if the given ledgerIndex is a voting ledgerIndex */
bool
isVotingLedger(LedgerIndex seq);
/** Returns true if the given ledgerIndex is a flag ledgerIndex */
bool
isFlagLedger(LedgerIndex seq);
/** A transaction identifier.
The value is computed as the hash of the
canonicalized, serialized transaction object.

View File

@@ -8,11 +8,11 @@
* To ease maintenance, you may replace any unneeded values with "..."
* e.g. #define TRANSACTION(tag, value, name, ...)
*
* You must define a transactor class in the `ripple` namespace named `name`,
* You must define a transactor class in the `xrpl` namespace named `name`,
* and include its header alongside the TRANSACTOR definition using this
* format:
* #if TRANSACTION_INCLUDE
* # include <xrpld/app/tx/detail/HEADER.h>
* # include <xrpl/tx/transactors/HEADER.h>
* #endif
*
* The `privileges` parameter of the TRANSACTION macro is a bitfield
@@ -22,7 +22,7 @@
/** This transaction type executes a payment. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Payment.h>
# include <xrpl/tx/transactors/Payment.h>
#endif
TRANSACTION(ttPAYMENT, 0, Payment,
Delegation::delegable,
@@ -42,7 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
/** This transaction type creates an escrow object. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
# include <xrpl/tx/transactors/Escrow.h>
#endif
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
Delegation::delegable,
@@ -73,7 +73,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
/** This transaction type adjusts various account settings. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetAccount.h>
# include <xrpl/tx/transactors/SetAccount.h>
#endif
TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
Delegation::notDelegable,
@@ -94,7 +94,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
/** This transaction type cancels an existing escrow. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Escrow.h>
# include <xrpl/tx/transactors/Escrow.h>
#endif
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
Delegation::delegable,
@@ -107,7 +107,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
/** This transaction type sets or clears an account's "regular key". */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetRegularKey.h>
# include <xrpl/tx/transactors/SetRegularKey.h>
#endif
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
Delegation::notDelegable,
@@ -121,7 +121,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
/** This transaction type creates an offer to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateOffer.h>
# include <xrpl/tx/transactors/Offer/CreateOffer.h>
#endif
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
Delegation::delegable,
@@ -137,7 +137,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
/** This transaction type cancels existing offers to trade one asset for another. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelOffer.h>
# include <xrpl/tx/transactors/Offer/CancelOffer.h>
#endif
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
Delegation::delegable,
@@ -151,7 +151,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
/** This transaction type creates a new set of tickets. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateTicket.h>
# include <xrpl/tx/transactors/CreateTicket.h>
#endif
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
Delegation::delegable,
@@ -167,7 +167,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
// The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries.
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetSignerList.h>
# include <xrpl/tx/transactors/SetSignerList.h>
#endif
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
Delegation::notDelegable,
@@ -180,7 +180,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
/** This transaction type creates a new unidirectional XRP payment channel. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PayChan.h>
# include <xrpl/tx/transactors/PayChan.h>
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
Delegation::delegable,
@@ -222,7 +222,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
/** This transaction type creates a new check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CreateCheck.h>
# include <xrpl/tx/transactors/Check/CreateCheck.h>
#endif
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
Delegation::delegable,
@@ -238,7 +238,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
/** This transaction type cashes an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CashCheck.h>
# include <xrpl/tx/transactors/Check/CashCheck.h>
#endif
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
Delegation::delegable,
@@ -252,7 +252,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash,
/** This transaction type cancels an existing check. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/CancelCheck.h>
# include <xrpl/tx/transactors/Check/CancelCheck.h>
#endif
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
Delegation::delegable,
@@ -264,7 +264,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
/** This transaction type grants or revokes authorization to transfer funds. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DepositPreauth.h>
# include <xrpl/tx/transactors/DepositPreauth.h>
#endif
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
Delegation::delegable,
@@ -279,7 +279,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
/** This transaction type modifies a trustline between two accounts. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetTrust.h>
# include <xrpl/tx/transactors/SetTrust.h>
#endif
TRANSACTION(ttTRUST_SET, 20, TrustSet,
Delegation::delegable,
@@ -293,7 +293,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet,
/** This transaction type deletes an existing account. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteAccount.h>
# include <xrpl/tx/transactors/DeleteAccount.h>
#endif
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
Delegation::notDelegable,
@@ -309,7 +309,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
/** This transaction mints a new NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenMint.h>
# include <xrpl/tx/transactors/NFT/NFTokenMint.h>
#endif
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
Delegation::delegable,
@@ -327,7 +327,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
/** This transaction burns (i.e. destroys) an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenBurn.h>
# include <xrpl/tx/transactors/NFT/NFTokenBurn.h>
#endif
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
Delegation::delegable,
@@ -340,7 +340,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
/** This transaction creates a new offer to buy or sell an NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenCreateOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
Delegation::delegable,
@@ -356,7 +356,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenCancelOffer.h>
#endif
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
Delegation::delegable,
@@ -368,7 +368,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
# include <xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h>
#endif
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
Delegation::delegable,
@@ -382,7 +382,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
/** This transaction claws back issued tokens. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Clawback.h>
# include <xrpl/tx/transactors/Clawback.h>
#endif
TRANSACTION(ttCLAWBACK, 30, Clawback,
Delegation::delegable,
@@ -395,7 +395,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback,
/** This transaction claws back tokens from an AMM pool. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMClawback.h>
# include <xrpl/tx/transactors/AMM/AMMClawback.h>
#endif
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
Delegation::delegable,
@@ -410,7 +410,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
/** This transaction type creates an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMCreate.h>
# include <xrpl/tx/transactors/AMM/AMMCreate.h>
#endif
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
Delegation::delegable,
@@ -424,7 +424,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
/** This transaction type deposits into an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDeposit.h>
# include <xrpl/tx/transactors/AMM/AMMDeposit.h>
#endif
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
Delegation::delegable,
@@ -442,7 +442,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
/** This transaction type withdraws from an AMM instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMWithdraw.h>
# include <xrpl/tx/transactors/AMM/AMMWithdraw.h>
#endif
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
Delegation::delegable,
@@ -459,7 +459,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
/** This transaction type votes for the trading fee */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMVote.h>
# include <xrpl/tx/transactors/AMM/AMMVote.h>
#endif
TRANSACTION(ttAMM_VOTE, 38, AMMVote,
Delegation::delegable,
@@ -473,7 +473,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
/** This transaction type bids for the auction slot */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMBid.h>
# include <xrpl/tx/transactors/AMM/AMMBid.h>
#endif
TRANSACTION(ttAMM_BID, 39, AMMBid,
Delegation::delegable,
@@ -489,7 +489,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
/** This transaction type deletes AMM in the empty state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/AMMDelete.h>
# include <xrpl/tx/transactors/AMM/AMMDelete.h>
#endif
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
Delegation::delegable,
@@ -502,7 +502,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
/** This transactions creates a crosschain sequence number */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/XChainBridge.h>
# include <xrpl/tx/transactors/XChainBridge.h>
#endif
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
Delegation::delegable,
@@ -617,7 +617,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
/** This transaction type creates or updates a DID */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DID.h>
# include <xrpl/tx/transactors/DID.h>
#endif
TRANSACTION(ttDID_SET, 49, DIDSet,
Delegation::delegable,
@@ -638,7 +638,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete,
/** This transaction type creates an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/SetOracle.h>
# include <xrpl/tx/transactors/SetOracle.h>
#endif
TRANSACTION(ttORACLE_SET, 51, OracleSet,
Delegation::delegable,
@@ -655,7 +655,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
/** This transaction type deletes an Oracle instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DeleteOracle.h>
# include <xrpl/tx/transactors/DeleteOracle.h>
#endif
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
Delegation::delegable,
@@ -667,7 +667,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
/** This transaction type fixes a problem in the ledger state */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LedgerStateFix.h>
# include <xrpl/tx/transactors/LedgerStateFix.h>
#endif
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
Delegation::delegable,
@@ -680,7 +680,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
/** This transaction type creates a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceCreate.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
Delegation::delegable,
@@ -697,7 +697,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
/** This transaction type destroys a MPTokensIssuance instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
Delegation::delegable,
@@ -709,7 +709,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
# include <xrpl/tx/transactors/MPT/MPTokenIssuanceSet.h>
#endif
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
Delegation::delegable,
@@ -726,7 +726,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
/** This transaction type authorizes a MPToken instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
# include <xrpl/tx/transactors/MPT/MPTokenAuthorize.h>
#endif
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
Delegation::delegable,
@@ -739,7 +739,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
/** This transaction type create an Credential instance */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Credentials.h>
# include <xrpl/tx/transactors/Credentials.h>
#endif
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
Delegation::delegable,
@@ -775,7 +775,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
/** This transaction type modify a NFToken */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/NFTokenModify.h>
# include <xrpl/tx/transactors/NFT/NFTokenModify.h>
#endif
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
Delegation::delegable,
@@ -789,7 +789,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
/** This transaction type creates or modifies a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
# include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
Delegation::delegable,
@@ -802,7 +802,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
/** This transaction type deletes a Permissioned Domain */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
# include <xrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.h>
#endif
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
Delegation::delegable,
@@ -814,7 +814,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
/** This transaction type delegates authorized account specified permissions */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/DelegateSet.h>
# include <xrpl/tx/transactors/Delegate/DelegateSet.h>
#endif
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
Delegation::notDelegable,
@@ -827,7 +827,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
/** This transaction creates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultCreate.h>
# include <xrpl/tx/transactors/Vault/VaultCreate.h>
#endif
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
Delegation::delegable,
@@ -845,7 +845,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
/** This transaction updates a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultSet.h>
# include <xrpl/tx/transactors/Vault/VaultSet.h>
#endif
TRANSACTION(ttVAULT_SET, 66, VaultSet,
Delegation::delegable,
@@ -860,7 +860,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
/** This transaction deletes a single asset vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDelete.h>
# include <xrpl/tx/transactors/Vault/VaultDelete.h>
#endif
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
Delegation::delegable,
@@ -872,7 +872,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
/** This transaction trades assets for shares with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultDeposit.h>
# include <xrpl/tx/transactors/Vault/VaultDeposit.h>
#endif
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
Delegation::delegable,
@@ -885,7 +885,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
/** This transaction trades shares for assets with a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultWithdraw.h>
# include <xrpl/tx/transactors/Vault/VaultWithdraw.h>
#endif
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
Delegation::delegable,
@@ -900,7 +900,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
/** This transaction claws back tokens from a vault. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/VaultClawback.h>
# include <xrpl/tx/transactors/Vault/VaultClawback.h>
#endif
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
Delegation::delegable,
@@ -914,7 +914,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
/** This transaction type batches together transactions. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Batch.h>
# include <xrpl/tx/transactors/Batch.h>
#endif
TRANSACTION(ttBATCH, 71, Batch,
Delegation::notDelegable,
@@ -929,7 +929,7 @@ TRANSACTION(ttBATCH, 71, Batch,
/** This transaction creates and updates a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerSet.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerSet.h>
#endif
TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
Delegation::delegable,
@@ -946,7 +946,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
/** This transaction deletes a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerDelete.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerDelete.h>
#endif
TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
Delegation::delegable,
@@ -957,7 +957,7 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
/** This transaction deposits First Loss Capital into a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverDeposit.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
Delegation::delegable,
@@ -969,7 +969,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
/** This transaction withdraws First Loss Capital from a Loan Broker */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverWithdraw.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
Delegation::delegable,
@@ -984,7 +984,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
/** This transaction claws back First Loss Capital from a Loan Broker to
the issuer of the capital */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanBrokerCoverClawback.h>
# include <xrpl/tx/transactors/Lending/LoanBrokerCoverClawback.h>
#endif
TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
Delegation::delegable,
@@ -996,7 +996,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
/** This transaction creates a Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanSet.h>
# include <xrpl/tx/transactors/Lending/LoanSet.h>
#endif
TRANSACTION(ttLOAN_SET, 80, LoanSet,
Delegation::delegable,
@@ -1023,7 +1023,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
/** This transaction deletes an existing Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanDelete.h>
# include <xrpl/tx/transactors/Lending/LoanDelete.h>
#endif
TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
Delegation::delegable,
@@ -1034,7 +1034,7 @@ TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
/** This transaction is used to change the delinquency status of an existing Loan */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanManage.h>
# include <xrpl/tx/transactors/Lending/LoanManage.h>
#endif
TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
Delegation::delegable,
@@ -1048,7 +1048,7 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
/** The Borrower uses this transaction to make a Payment on the Loan. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanPay.h>
# include <xrpl/tx/transactors/Lending/LoanPay.h>
#endif
TRANSACTION(ttLOAN_PAY, 84, LoanPay,
Delegation::delegable,
@@ -1063,7 +1063,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
For details, see: https://xrpl.org/amendments.html
*/
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/Change.h>
# include <xrpl/tx/transactors/Change.h>
#endif
TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
Delegation::notDelegable,

View File

@@ -0,0 +1,137 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
#include <cstdint>
#include <mutex>
namespace xrpl {
struct Fees;
/** Manages the current fee schedule.
The "base" fee is the cost to send a reference transaction under no load,
expressed in millionths of one XRP.
The "load" fee is how much the local server currently charges to send a
reference transaction. This fee fluctuates based on the load of the
server.
*/
class LoadFeeTrack final
{
public:
explicit LoadFeeTrack(beast::Journal journal = beast::Journal(beast::Journal::getNullSink()))
: j_(journal)
, localTxnLoadFee_(lftNormalFee)
, remoteTxnLoadFee_(lftNormalFee)
, clusterTxnLoadFee_(lftNormalFee)
, raiseCount_(0)
{
}
~LoadFeeTrack() = default;
void
setRemoteFee(std::uint32_t f)
{
JLOG(j_.trace()) << "setRemoteFee: " << f;
std::lock_guard sl(lock_);
remoteTxnLoadFee_ = f;
}
std::uint32_t
getRemoteFee() const
{
std::lock_guard sl(lock_);
return remoteTxnLoadFee_;
}
std::uint32_t
getLocalFee() const
{
std::lock_guard sl(lock_);
return localTxnLoadFee_;
}
std::uint32_t
getClusterFee() const
{
std::lock_guard sl(lock_);
return clusterTxnLoadFee_;
}
std::uint32_t
getLoadBase() const
{
return lftNormalFee;
}
std::uint32_t
getLoadFactor() const
{
std::lock_guard sl(lock_);
return std::max({clusterTxnLoadFee_, localTxnLoadFee_, remoteTxnLoadFee_});
}
std::pair<std::uint32_t, std::uint32_t>
getScalingFactors() const
{
std::lock_guard sl(lock_);
return std::make_pair(
std::max(localTxnLoadFee_, remoteTxnLoadFee_), std::max(remoteTxnLoadFee_, clusterTxnLoadFee_));
}
void
setClusterFee(std::uint32_t fee)
{
JLOG(j_.trace()) << "setClusterFee: " << fee;
std::lock_guard sl(lock_);
clusterTxnLoadFee_ = fee;
}
bool
raiseLocalFee();
bool
lowerLocalFee();
bool
isLoadedLocal() const
{
std::lock_guard sl(lock_);
return (raiseCount_ != 0) || (localTxnLoadFee_ != lftNormalFee);
}
bool
isLoadedCluster() const
{
std::lock_guard sl(lock_);
return (raiseCount_ != 0) || (localTxnLoadFee_ != lftNormalFee) || (clusterTxnLoadFee_ != lftNormalFee);
}
private:
static std::uint32_t constexpr lftNormalFee = 256; // 256 is the minimum/normal load factor
static std::uint32_t constexpr lftFeeIncFraction = 4; // increase fee by 1/4
static std::uint32_t constexpr lftFeeDecFraction = 4; // decrease fee by 1/4
static std::uint32_t constexpr lftFeeMax = lftNormalFee * 1000000;
beast::Journal const j_;
std::mutex mutable lock_;
std::uint32_t localTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t remoteTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t clusterTxnLoadFee_; // Scale factor, lftNormalFee = normal fee
std::uint32_t raiseCount_;
};
//------------------------------------------------------------------------------
// Scale using load as well as base rate
XRPAmount
scaleFeeLoad(XRPAmount fee, LoadFeeTrack const& feeTrack, Fees const& fees, bool bUnlimited);
} // namespace xrpl

View File

@@ -0,0 +1,129 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
namespace xrpl {
/** State information when applying a tx. */
class ApplyContext
{
public:
explicit ApplyContext(
ServiceRegistry& registry,
OpenView& base,
std::optional<uint256 const> const& parentBatchId,
STTx const& tx,
TER preclaimResult,
XRPAmount baseFee,
ApplyFlags flags,
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});
explicit ApplyContext(
ServiceRegistry& registry,
OpenView& base,
STTx const& tx,
TER preclaimResult,
XRPAmount baseFee,
ApplyFlags flags,
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
{
XRPL_ASSERT((flags & tapBATCH) == 0, "Batch apply flag should not be set");
}
ServiceRegistry& registry;
STTx const& tx;
TER const preclaimResult;
XRPAmount const baseFee;
beast::Journal const journal;
ApplyView&
view()
{
return *view_;
}
ApplyView const&
view() const
{
return *view_;
}
// VFALCO Unfortunately this is necessary
RawView&
rawView()
{
return *view_;
}
ApplyFlags const&
flags() const
{
return flags_;
}
/** Sets the DeliveredAmount field in the metadata */
void
deliver(STAmount const& amount)
{
view_->deliver(amount);
}
/** Discard changes and start fresh. */
void
discard();
/** Apply the transaction result to the base. */
std::optional<TxMeta> apply(TER);
/** Get the number of unapplied changes. */
std::size_t
size();
/** Visit unapplied changes. */
void
visit(
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func);
void
destroyXRP(XRPAmount const& fee)
{
view_->rawDestroyXRP(fee);
}
/** Applies all invariant checkers one by one.
@param result the result generated by processing this transaction.
@param fee the fee charged for this transaction
@return the result code that should be returned for this transaction.
*/
TER
checkInvariants(TER const result, XRPAmount const fee);
private:
TER
failInvariantCheck(TER const result);
template <std::size_t... Is>
TER
checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence<Is...>);
OpenView& base_;
ApplyFlags flags_;
std::optional<ApplyViewImpl> view_;
// The ID of the batch transaction we are executing under, if seated.
std::optional<uint256 const> parentBatchId_;
};
} // namespace xrpl

View File

@@ -0,0 +1,723 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <tuple>
#include <unordered_set>
namespace xrpl {
class ReadView;
#if GENERATING_DOCS
/**
* @brief Prototype for invariant check implementations.
*
* __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to
* communicate the interface required of any invariant checker. Any invariant
* check implementation should implement the public methods documented here.
*
*/
class InvariantChecker_PROTOTYPE
{
public:
explicit InvariantChecker_PROTOTYPE() = default;
/**
* @brief called for each ledger entry in the current transaction.
*
* @param isDelete true if the SLE is being deleted
* @param before ledger entry before modification by the transaction
* @param after ledger entry after modification by the transaction
*/
void
visitEntry(bool isDelete, std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
/**
* @brief called after all ledger entries have been visited to determine
* the final status of the check
*
* @param tx the transaction being applied
* @param tec the current TER result of the transaction
* @param fee the fee actually charged for this transaction
* @param view a ReadView of the ledger being modified
* @param j journal for logging
*
* @return true if check passes, false if it fails
*/
bool
finalize(STTx const& tx, TER const tec, XRPAmount const fee, ReadView const& view, beast::Journal const& j);
};
#endif
/**
* @brief Invariant: We should never charge a transaction a negative fee or a
* fee that is larger than what the transaction itself specifies.
*
* We can, in some circumstances, charge less.
*/
class TransactionFeeCheck
{
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&);
};
/**
* @brief Invariant: A transaction must not create XRP and should only destroy
* the XRP fee.
*
* We iterate through all account roots, payment channels and escrow entries
* that were modified and calculate the net change in XRP caused by the
* transactions.
*/
class XRPNotCreated
{
std::int64_t drops_ = 0;
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&);
};
/**
* @brief Invariant: we cannot remove an account ledger entry
*
* We iterate all account roots that were modified, and ensure that any that
* were present before the transaction was applied continue to be present
* afterwards unless they were explicitly deleted by a successful
* AccountDelete transaction.
*/
class AccountRootsNotDeleted
{
std::uint32_t accountsDeleted_ = 0;
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&);
};
/**
* @brief Invariant: a deleted account must not have any objects left
*
* We iterate all deleted account roots, and ensure that there are no
* objects left that are directly accessible with that account's ID.
*
* There should only be one deleted account, but that's checked by
* AccountRootsNotDeleted. This invariant will handle multiple deleted account
* roots without a problem.
*/
class AccountRootsDeletedClean
{
// Pair is <before, after>. Before is used for most of the checks, so that
// if, for example, an object ID field is cleared, but the object is not
// deleted, it can still be found. After is used specifically for any checks
// that are expected as part of the deletion, such as zeroing out the
// balance.
std::vector<std::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
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&);
};
/**
* @brief Invariant: An account XRP balance must be in XRP and take a value
* between 0 and INITIAL_XRP drops, inclusive.
*
* We iterate all account roots modified by the transaction and ensure that
* their XRP balances are reasonable.
*/
class XRPBalanceChecks
{
bool bad_ = false;
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&);
};
/**
* @brief Invariant: corresponding modified ledger entries should match in type
* and added entries should be a valid type.
*/
class LedgerEntryTypesMatch
{
bool typeMismatch_ = false;
bool invalidTypeAdded_ = false;
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&);
};
/**
* @brief Invariant: Trust lines using XRP are not allowed.
*
* We iterate all the trust lines created by this transaction and ensure
* that they are against a valid issuer.
*/
class NoXRPTrustLines
{
bool xrpTrustLine_ = false;
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&);
};
/**
* @brief Invariant: Trust lines with deep freeze flag are not allowed if normal
* freeze flag is not set.
*
* We iterate all the trust lines created by this transaction and ensure
* that they don't have deep freeze flag set without normal freeze flag set.
*/
class NoDeepFreezeTrustLinesWithoutFreeze
{
bool deepFreezeWithoutFreeze_ = false;
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&);
};
/**
* @brief Invariant: frozen trust line balance change is not allowed.
*
* We iterate all affected trust lines and ensure that they don't have
* unexpected change of balance if they're frozen.
*/
class TransfersNotFrozen
{
struct BalanceChange
{
std::shared_ptr<SLE const> const line;
int const balanceChangeSign;
};
struct IssuerChanges
{
std::vector<BalanceChange> senders;
std::vector<BalanceChange> receivers;
};
using ByIssuer = std::map<Issue, IssuerChanges>;
ByIssuer balanceChanges_;
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
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&);
private:
bool
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
STAmount
calculateBalanceChange(
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after,
bool isDelete);
void
recordBalance(Issue const& issue, BalanceChange change);
void
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
std::shared_ptr<SLE const>
findIssuer(AccountID const& issuerID, ReadView const& view);
bool
validateIssuerChanges(
std::shared_ptr<SLE const> const& issuer,
IssuerChanges const& changes,
STTx const& tx,
beast::Journal const& j,
bool enforce);
bool
validateFrozenState(
BalanceChange const& change,
bool high,
STTx const& tx,
beast::Journal const& j,
bool enforce,
bool globalFreeze);
};
/**
* @brief Invariant: offers should be for non-negative amounts and must not
* be XRP to XRP.
*
* Examine all offers modified by the transaction and ensure that there are
* no offers which contain negative amounts or which exchange XRP for XRP.
*/
class NoBadOffers
{
bool bad_ = false;
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&);
};
/**
* @brief Invariant: an escrow entry must take a value between 0 and
* INITIAL_XRP drops exclusive.
*/
class NoZeroEscrow
{
bool bad_ = false;
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&);
};
/**
* @brief Invariant: a new account root must be the consequence of a payment,
* must have the right starting sequence, and the payment
* may not create more than one new account root.
*/
class ValidNewAccountRoot
{
std::uint32_t accountsCreated_ = 0;
std::uint32_t accountSeq_ = 0;
bool pseudoAccount_ = false;
std::uint32_t flags_ = 0;
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&);
};
/**
* @brief Invariant: Validates several invariants for NFToken pages.
*
* The following checks are made:
* - The page is correctly associated with the owner.
* - The page is correctly ordered between the next and previous links.
* - The page contains at least one and no more than 32 NFTokens.
* - The NFTokens on this page do not belong on a lower or higher page.
* - The NFTokens are correctly sorted on the page.
* - Each URI, if present, is not empty.
*/
class ValidNFTokenPage
{
bool badEntry_ = false;
bool badLink_ = false;
bool badSort_ = false;
bool badURI_ = false;
bool invalidSize_ = false;
bool deletedFinalPage_ = false;
bool deletedLink_ = false;
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&);
};
/**
* @brief Invariant: Validates counts of NFTokens after all transaction types.
*
* The following checks are made:
* - The number of minted or burned NFTokens can only be changed by
* NFTokenMint or NFTokenBurn transactions.
* - A successful NFTokenMint must increase the number of NFTokens.
* - A failed NFTokenMint must not change the number of minted NFTokens.
* - An NFTokenMint transaction cannot change the number of burned NFTokens.
* - A successful NFTokenBurn must increase the number of burned NFTokens.
* - A failed NFTokenBurn must not change the number of burned NFTokens.
* - An NFTokenBurn transaction cannot change the number of minted NFTokens.
*/
class NFTokenCountTracking
{
std::uint32_t beforeMintedTotal = 0;
std::uint32_t beforeBurnedTotal = 0;
std::uint32_t afterMintedTotal = 0;
std::uint32_t afterBurnedTotal = 0;
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&);
};
/**
* @brief Invariant: Token holder's trustline balance cannot be negative after
* Clawback.
*
* We iterate all the trust lines affected by this transaction and ensure
* that no more than one trustline is modified, and also holder's balance is
* non-negative.
*/
class ValidClawback
{
std::uint32_t trustlinesChanged = 0;
std::uint32_t mptokensChanged = 0;
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 ValidMPTIssuance
{
std::uint32_t mptIssuancesCreated_ = 0;
std::uint32_t mptIssuancesDeleted_ = 0;
std::uint32_t mptokensCreated_ = 0;
std::uint32_t mptokensDeleted_ = 0;
// non-MPT transactions may attempt to create
// MPToken by an issuer
bool mptCreatedByIssuer_ = false;
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&);
};
/**
* @brief Invariants: Permissioned Domains must have some rules and
* AcceptedCredentials must have length between 1 and 10 inclusive.
*
* Since only permissions constitute rules, an empty credentials list
* means that there are no rules and the invariant is violated.
*
* Credentials must be sorted and no duplicates allowed
*
*/
class ValidPermissionedDomain
{
struct SleStatus
{
std::size_t credentialsSize_{0};
bool isSorted_ = false;
bool isUnique_ = false;
bool isDelete_ = false;
};
std::vector<SleStatus> sleStatus_;
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&);
};
/**
* @brief Invariants: Pseudo-accounts have valid and consistent properties
*
* Pseudo-accounts have certain properties, and some of those properties are
* unique to pseudo-accounts. Check that all pseudo-accounts are following the
* rules, and that only pseudo-accounts look like pseudo-accounts.
*
*/
class ValidPseudoAccounts
{
std::vector<std::string> errors_;
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 ValidPermissionedDEX
{
bool regularOffers_ = false;
bool badHybrids_ = false;
hash_set<uint256> domains_;
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 ValidAMM
{
std::optional<AccountID> ammAccount_;
std::optional<STAmount> lptAMMBalanceAfter_;
std::optional<STAmount> lptAMMBalanceBefore_;
bool ammPoolChanged_;
public:
enum class ZeroAllowed : bool { No = false, Yes = true };
ValidAMM() : ammPoolChanged_{false}
{
}
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&);
private:
bool
finalizeBid(bool enforce, beast::Journal const&) const;
bool
finalizeVote(bool enforce, beast::Journal const&) const;
bool
finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
bool
finalizeDelete(bool enforce, TER res, beast::Journal const&) const;
bool
finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
// Includes clawback
bool
finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const;
bool
finalizeDEX(bool enforce, beast::Journal const&) const;
bool
generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&) const;
};
/**
* @brief Invariants: Some fields are unmodifiable
*
* Check that any fields specified as unmodifiable are not modified when the
* object is modified. Creation and deletion are ignored.
*
*/
class NoModifiedUnmodifiableFields
{
// Pair is <before, after>.
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
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&);
};
/**
* @brief Invariants: Loan brokers are internally consistent
*
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
* node (the root), which will only hold entries for `RippleState` or
* `MPToken` objects.
*
*/
class ValidLoanBroker
{
// Not all of these elements will necessarily be populated. Remaining items
// will be looked up as needed.
struct BrokerInfo
{
SLE::const_pointer brokerBefore = nullptr;
// After is used for most of the checks, except
// those that check changed values.
SLE::const_pointer brokerAfter = nullptr;
};
// Collect all the LoanBrokers found directly or indirectly through
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
// LoanBroker object if brokerBefore and brokerAfter are nullptr
std::map<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
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&);
};
/**
* @brief Invariants: Loans are internally consistent
*
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
*
*/
class ValidLoan
{
// Pair is <before, after>. After is used for most of the checks, except
// those that check changed values.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
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&);
};
/*
* @brief Invariants: Vault object and MPTokenIssuance for vault shares
*
* - vault deleted and vault created is empty
* - vault created must be linked to pseudo-account for shares and assets
* - vault must have MPTokenIssuance for shares
* - vault without shares outstanding must have no shares
* - loss unrealized does not exceed the difference between assets total and
* assets available
* - assets available do not exceed assets total
* - vault deposit increases assets and share issuance, and adds to:
* total assets, assets available, shares outstanding
* - vault withdrawal and clawback reduce assets and share issuance, and
* subtracts from: total assets, assets available, shares outstanding
* - vault set must not alter the vault assets or shares balance
* - no vault transaction can change loss unrealized (it's updated by loan
* transactions)
*
*/
class ValidVault
{
Number static constexpr zero{};
struct Vault final
{
uint256 key = beast::zero;
Asset asset = {};
AccountID pseudoId = {};
AccountID owner = {};
uint192 shareMPTID = beast::zero;
Number assetsTotal = 0;
Number assetsAvailable = 0;
Number assetsMaximum = 0;
Number lossUnrealized = 0;
Vault static make(SLE const&);
};
struct Shares final
{
MPTIssue share = {};
std::uint64_t sharesTotal = 0;
std::uint64_t sharesMaximum = 0;
Shares static make(SLE const&);
};
std::vector<Vault> afterVault_ = {};
std::vector<Shares> afterMPTs_ = {};
std::vector<Vault> beforeVault_ = {};
std::vector<Shares> beforeMPTs_ = {};
std::unordered_map<uint256, Number> deltas_ = {};
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&);
};
// additional invariant checks can be declared above and then added to this
// tuple
using InvariantChecks = std::tuple<
TransactionFeeCheck,
AccountRootsNotDeleted,
AccountRootsDeletedClean,
LedgerEntryTypesMatch,
XRPBalanceChecks,
XRPNotCreated,
NoXRPTrustLines,
NoDeepFreezeTrustLinesWithoutFreeze,
TransfersNotFrozen,
NoBadOffers,
NoZeroEscrow,
ValidNewAccountRoot,
ValidNFTokenPage,
NFTokenCountTracking,
ValidClawback,
ValidMPTIssuance,
ValidPermissionedDomain,
ValidPermissionedDEX,
ValidAMM,
NoModifiedUnmodifiableFields,
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,
ValidVault>;
/**
* @brief get a tuple of all invariant checks
*
* @return std::tuple of instances that implement the required invariant check
* methods
*
* @see xrpl::InvariantChecker_PROTOTYPE
*/
inline InvariantChecks
getInvariantChecks()
{
return InvariantChecks{};
}
} // namespace xrpl

View File

@@ -0,0 +1,64 @@
#pragma once
#include <xrpl/basics/Expected.h> //
#include <xrpl/beast/utility/Journal.h> // beast::Journal
#include <xrpl/protocol/TER.h> // temMALFORMED
#include <xrpl/protocol/UintTypes.h> // AccountID
#include <xrpl/tx/Transactor.h> // NotTEC
#include <optional>
#include <string_view>
namespace xrpl {
// Forward declarations
class STObject;
// Support for SignerEntries that is needed by a few Transactors.
//
// SignerEntries is represented as a std::vector<SignerEntries::SignerEntry>.
// There is no direct constructor for SignerEntries.
//
// o A std::vector<SignerEntries::SignerEntry> is a SignerEntries.
// o More commonly, SignerEntries are extracted from an STObject by
// calling SignerEntries::deserialize().
class SignerEntries
{
public:
explicit SignerEntries() = delete;
struct SignerEntry
{
AccountID account;
std::uint16_t weight;
std::optional<uint256> tag;
SignerEntry(AccountID const& inAccount, std::uint16_t inWeight, std::optional<uint256> inTag)
: account(inAccount), weight(inWeight), tag(inTag)
{
}
// For sorting to look for duplicate accounts
friend bool
operator<(SignerEntry const& lhs, SignerEntry const& rhs)
{
return lhs.account < rhs.account;
}
friend bool
operator==(SignerEntry const& lhs, SignerEntry const& rhs)
{
return lhs.account == rhs.account;
}
};
// Deserialize a SignerEntries array from the network or from the ledger.
//
// obj Contains a SignerEntries field that is an STArray.
// journal For reporting error conditions.
// annotation Source of SignerEntries, like "ledger" or "transaction".
static Expected<std::vector<SignerEntry>, NotTEC>
deserialize(STObject const& obj, beast::Journal journal, std::string_view annotation);
};
} // namespace xrpl

View File

@@ -0,0 +1,419 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/applySteps.h>
namespace xrpl {
/** State information when preflighting a tx. */
struct PreflightContext
{
public:
ServiceRegistry& registry;
STTx const& tx;
Rules const rules;
ApplyFlags flags;
std::optional<uint256 const> parentBatchId;
beast::Journal const j;
PreflightContext(
ServiceRegistry& registry_,
STTx const& tx_,
uint256 parentBatchId_,
Rules const& rules_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_), tx(tx_), rules(rules_), flags(flags_), parentBatchId(parentBatchId_), j(j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == tapBATCH, "Batch apply flag should be set");
}
PreflightContext(
ServiceRegistry& registry_,
STTx const& tx_,
Rules const& rules_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_), tx(tx_), rules(rules_), flags(flags_), j(j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
}
PreflightContext&
operator=(PreflightContext const&) = delete;
};
/** State information when determining if a tx is likely to claim a fee. */
struct PreclaimContext
{
public:
ServiceRegistry& registry;
ReadView const& view;
TER preflightResult;
ApplyFlags flags;
STTx const& tx;
std::optional<uint256 const> const parentBatchId;
beast::Journal const j;
PreclaimContext(
ServiceRegistry& registry_,
ReadView const& view_,
TER preflightResult_,
STTx const& tx_,
ApplyFlags flags_,
std::optional<uint256> parentBatchId_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: registry(registry_)
, view(view_)
, preflightResult(preflightResult_)
, flags(flags_)
, tx(tx_)
, parentBatchId(parentBatchId_)
, j(j_)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
"Parent Batch ID should be set if batch apply flag is set");
}
PreclaimContext(
ServiceRegistry& registry_,
ReadView const& view_,
TER preflightResult_,
STTx const& tx_,
ApplyFlags flags_,
beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()})
: PreclaimContext(registry_, view_, preflightResult_, tx_, flags_, std::nullopt, j_)
{
XRPL_ASSERT((flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
}
PreclaimContext&
operator=(PreclaimContext const&) = delete;
};
class TxConsequences;
struct PreflightResult;
// Needed for preflight specialization
class Change;
class Transactor
{
protected:
ApplyContext& ctx_;
beast::WrappedSink sink_;
beast::Journal const j_;
AccountID const account_;
XRPAmount mPriorBalance; // Balance before fees.
XRPAmount mSourceBalance; // Balance after fees.
virtual ~Transactor() = default;
Transactor(Transactor const&) = delete;
Transactor&
operator=(Transactor const&) = delete;
public:
enum ConsequencesFactoryType { Normal, Blocker, Custom };
/** Process the transaction. */
ApplyResult
operator()();
ApplyView&
view()
{
return ctx_.view();
}
ApplyView const&
view() const
{
return ctx_.view();
}
/////////////////////////////////////////////////////
/*
These static functions are called from invoke_preclaim<Tx>
using name hiding to accomplish compile-time polymorphism,
so derived classes can override for different or extra
functionality. Use with care, as these are not really
virtual and so don't have the compiler-time protection that
comes with it.
*/
static NotTEC
checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j);
static NotTEC
checkPriorTxAndLastLedger(PreclaimContext const& ctx);
static TER
checkFee(PreclaimContext const& ctx, XRPAmount baseFee);
static NotTEC
checkSign(PreclaimContext const& ctx);
static NotTEC
checkBatchSign(PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
/* Do NOT define an invokePreflight function in a derived class.
Instead, define:
// Optional if the transaction is gated on an amendment that
// isn't specified in transactions.macro
static bool
checkExtraFeatures(PreflightContext const& ctx);
// Optional if the transaction uses any flags other than tfUniversal
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
// Required, even if it just returns tesSUCCESS.
static NotTEC
preflight(PreflightContext const& ctx);
// Optional, rarely needed, if the transaction does any expensive
// checks after the signature is verified.
static NotTEC preflightSigValidated(PreflightContext const& ctx);
* Do not try to call preflight1 or preflight2 directly.
* Do not check whether relevant amendments are enabled in preflight.
Instead, define checkExtraFeatures.
* Do not check flags in preflight. Instead, define getFlagsMask.
*/
template <class T>
static NotTEC
invokePreflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx)
{
// Most transactors do nothing
// after checkSeq/Fee/Sign.
return tesSUCCESS;
}
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by DeleteAccount
static TER
ticketDelete(ApplyView& view, AccountID const& account, uint256 const& ticketIndex, beast::Journal j);
protected:
TER
apply();
explicit Transactor(ApplyContext& ctx);
virtual void
preCompute();
virtual TER
doApply() = 0;
/** Compute the minimum fee required to process a transaction
with a given baseFee based on the current server load.
@param registry The service registry.
@param baseFee The base fee of a candidate transaction
@see xrpl::calculateBaseFee
@param fees Fee settings from the current ledger
@param flags Transaction processing fees
*/
static XRPAmount
minimumFee(ServiceRegistry& registry, XRPAmount baseFee, Fees const& fees, ApplyFlags flags);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateOwnerReserveFee(ReadView const& view, STTx const& tx);
static NotTEC
checkSign(
ReadView const& view,
ApplyFlags flags,
std::optional<uint256 const> const& parentBatchId,
AccountID const& idAccount,
STObject const& sigObject,
beast::Journal const j);
// Base class always returns true
static bool
checkExtraFeatures(PreflightContext const& ctx);
// Base class always returns tfUniversalMask
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
// Base class always returns tesSUCCESS
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static bool
validDataLength(std::optional<Slice> const& slice, std::size_t maxLength);
template <class T>
static bool
validNumericRange(std::optional<T> value, T max, T min = T{});
template <class T, class Unit>
static bool
validNumericRange(
std::optional<T> value,
unit::ValueUnit<Unit, T> max,
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
/// Minimum will usually be zero.
template <class T>
static bool
validNumericMinimum(std::optional<T> value, T min = T{});
/// Minimum will usually be zero.
template <class T, class Unit>
static bool
validNumericMinimum(std::optional<T> value, unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER
payFee();
static NotTEC
checkSingleSign(
ReadView const& view,
AccountID const& idSigner,
AccountID const& idAccount,
std::shared_ptr<SLE const> sleAccount,
beast::Journal const j);
static NotTEC
checkMultiSign(
ReadView const& view,
ApplyFlags flags,
AccountID const& id,
STObject const& sigObject,
beast::Journal const j);
void trapTransaction(uint256) const;
/** Performs early sanity checks on the account and fee fields.
(And passes flagMask to preflight0)
Do not try to call preflight1 from preflight() in derived classes. See
the description of invokePreflight for details.
*/
static NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask);
/** Checks whether the signature appears valid
Do not try to call preflight2 from preflight() in derived classes. See
the description of invokePreflight for details.
*/
static NotTEC
preflight2(PreflightContext const& ctx);
};
inline bool
Transactor::checkExtraFeatures(PreflightContext const& ctx)
{
return true;
}
/** Performs early sanity checks on the txid and flags */
NotTEC
preflight0(PreflightContext const& ctx, std::uint32_t flagMask);
namespace detail {
/** Checks the validity of the transactor signing key.
*
* Normally called from preflight1 with ctx.tx.
*/
NotTEC
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j);
/** Checks the special signing key state needed for simulation
*
* Normally called from preflight2 with ctx.tx.
*/
std::optional<NotTEC>
preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j);
} // namespace detail
// Defined in Change.cpp
template <>
NotTEC
Transactor::invokePreflight<Change>(PreflightContext const& ctx);
template <class T>
NotTEC
Transactor::invokePreflight(PreflightContext const& ctx)
{
// Using this lookup does NOT require checking the fixDelegateV1_1. The data
// exists regardless of whether it is enabled.
auto const feature = Permission::getInstance().getTxFeature(ctx.tx.getTxnType());
if (feature && !ctx.rules.enabled(*feature))
return temDISABLED;
if (!T::checkExtraFeatures(ctx))
return temDISABLED;
if (auto const ret = preflight1(ctx, T::getFlagsMask(ctx)))
return ret;
if (auto const ret = T::preflight(ctx))
return ret;
if (auto const ret = preflight2(ctx))
return ret;
return T::preflightSigValidated(ctx);
}
template <class T>
bool
Transactor::validNumericRange(std::optional<T> value, T max, T min)
{
if (!value)
return true;
return value >= min && value <= max;
}
template <class T, class Unit>
bool
Transactor::validNumericRange(std::optional<T> value, unit::ValueUnit<Unit, T> max, unit::ValueUnit<Unit, T> min)
{
return validNumericRange(value, max.value(), min.value());
}
template <class T>
bool
Transactor::validNumericMinimum(std::optional<T> value, T min)
{
if (!value)
return true;
return value >= min;
}
template <class T, class Unit>
bool
Transactor::validNumericMinimum(std::optional<T> value, unit::ValueUnit<Unit, T> min)
{
return validNumericMinimum(value, min.value());
}
} // namespace xrpl

129
include/xrpl/tx/apply.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/tx/applySteps.h>
#include <utility>
namespace xrpl {
class HashRouter;
class ServiceRegistry;
/** Describes the pre-processing validity of a transaction.
@see checkValidity, forceValidity
*/
enum class Validity {
/// Signature is bad. Didn't do local checks.
SigBad,
/// Signature is good, but local checks fail.
SigGoodOnly,
/// Signature and local checks are good / passed.
Valid
};
/** Checks transaction signature and local checks.
@return A `Validity` enum representing how valid the
`STTx` is and, if not `Valid`, a reason string.
@note Results are cached internally, so tests will not be
repeated over repeated calls, unless cache expires.
@return `std::pair`, where `.first` is the status, and
`.second` is the reason if appropriate.
@see Validity
*/
std::pair<Validity, std::string>
checkValidity(HashRouter& router, STTx const& tx, Rules const& rules);
/** Sets the validity of a given transaction in the cache.
@warning Use with extreme care.
@note Can only raise the validity to a more valid state,
and can not override anything cached bad.
@see checkValidity, Validity
*/
void
forceValidity(HashRouter& router, uint256 const& txid, Validity validity);
/** Apply a transaction to an `OpenView`.
This function is the canonical way to apply a transaction
to a ledger. It rolls the validation and application
steps into one function. To do the steps manually, the
correct calling order is:
@code{.cpp}
preflight -> preclaim -> doApply
@endcode
The result of one function must be passed to the next.
The `preflight` result can be safely cached and reused
asynchronously, but `preclaim` and `doApply` must be called
in the same thread and with the same view.
@note Does not throw.
For open ledgers, the `Transactor` will catch exceptions
and return `tefEXCEPTION`. For closed ledgers, the
`Transactor` will attempt to only charge a fee,
and return `tecFAILED_PROCESSING`.
If the `Transactor` gets an exception while trying
to charge the fee, it will be caught and
turned into `tefEXCEPTION`.
For network health, a `Transactor` makes its
best effort to at least charge a fee if the
ledger is closed.
@param app The current running `Application`.
@param view The open ledger that the transaction
will attempt to be applied to.
@param tx The transaction to be checked.
@param flags `ApplyFlags` describing processing options.
@param journal A journal.
@see preflight, preclaim, doApply
@return A pair with the `TER` and a `bool` indicating
whether or not the transaction was applied.
*/
ApplyResult
apply(ServiceRegistry& registry, OpenView& view, STTx const& tx, ApplyFlags flags, beast::Journal journal);
/** Enum class for return value from `applyTransaction`
@see applyTransaction
*/
enum class ApplyTransactionResult {
/// Applied to this ledger
Success,
/// Should not be retried in this ledger
Fail,
/// Should be retried in this ledger
Retry
};
/** Transaction application helper
Provides more detailed logging and decodes the
correct behavior based on the `TER` type
@see ApplyTransactionResult
*/
ApplyTransactionResult
applyTransaction(
ServiceRegistry& registry,
OpenView& view,
STTx const& tx,
bool retryAssured,
ApplyFlags flags,
beast::Journal journal);
} // namespace xrpl

View File

@@ -0,0 +1,338 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyViewImpl.h>
namespace xrpl {
class ServiceRegistry;
class STTx;
class TxQ;
struct ApplyResult
{
TER ter;
bool applied;
std::optional<TxMeta> metadata;
ApplyResult(TER t, bool a, std::optional<TxMeta> m = std::nullopt) : ter(t), applied(a), metadata(std::move(m))
{
}
};
/** Return true if the transaction can claim a fee (tec),
and the `ApplyFlags` do not allow soft failures.
*/
inline bool
isTecClaimHardFail(TER ter, ApplyFlags flags)
{
return isTecClaim(ter) && !(flags & tapRETRY);
}
/** Class describing the consequences to the account
of applying a transaction if the transaction consumes
the maximum XRP allowed.
*/
class TxConsequences
{
public:
/// Describes how the transaction affects subsequent
/// transactions
enum Category {
/// Moves currency around, creates offers, etc.
normal = 0,
/// Affects the ability of subsequent transactions
/// to claim a fee. Eg. `SetRegularKey`
blocker
};
private:
/// Describes how the transaction affects subsequent
/// transactions
bool isBlocker_;
/// Transaction fee
XRPAmount fee_;
/// Does NOT include the fee.
XRPAmount potentialSpend_;
/// SeqProxy of transaction.
SeqProxy seqProx_;
/// Number of sequences consumed.
std::uint32_t sequencesConsumed_;
public:
// Constructor if preflight returns a value other than tesSUCCESS.
// Asserts if tesSUCCESS is passed.
explicit TxConsequences(NotTEC pfResult);
/// Constructor if the STTx has no notable consequences for the TxQ.
explicit TxConsequences(STTx const& tx);
/// Constructor for a blocker.
TxConsequences(STTx const& tx, Category category);
/// Constructor for an STTx that may consume more XRP than the fee.
TxConsequences(STTx const& tx, XRPAmount potentialSpend);
/// Constructor for an STTx that consumes more than the usual sequences.
TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed);
/// Copy constructor
TxConsequences(TxConsequences const&) = default;
/// Copy assignment operator
TxConsequences&
operator=(TxConsequences const&) = default;
/// Move constructor
TxConsequences(TxConsequences&&) = default;
/// Move assignment operator
TxConsequences&
operator=(TxConsequences&&) = default;
/// Fee
XRPAmount
fee() const
{
return fee_;
}
/// Potential Spend
XRPAmount const&
potentialSpend() const
{
return potentialSpend_;
}
/// SeqProxy
SeqProxy
seqProxy() const
{
return seqProx_;
}
/// Sequences consumed
std::uint32_t
sequencesConsumed() const
{
return sequencesConsumed_;
}
/// Returns true if the transaction is a blocker.
bool
isBlocker() const
{
return isBlocker_;
}
// Return the SeqProxy that would follow this.
SeqProxy
followingSeq() const
{
SeqProxy following = seqProx_;
following.advanceBy(sequencesConsumed());
return following;
}
};
/** Describes the results of the `preflight` check
@note All members are const to make it more difficult
to "fake" a result without calling `preflight`.
@see preflight, preclaim, doApply, apply
*/
struct PreflightResult
{
public:
/// From the input - the transaction
STTx const& tx;
/// From the input - the batch identifier, if part of a batch
std::optional<uint256 const> const parentBatchId;
/// From the input - the rules
Rules const rules;
/// Consequences of the transaction
TxConsequences const consequences;
/// From the input - the flags
ApplyFlags const flags;
/// From the input - the journal
beast::Journal const j;
/// Intermediate transaction result
NotTEC const ter;
/// Constructor
template <class Context>
PreflightResult(Context const& ctx_, std::pair<NotTEC, TxConsequences> const& result)
: tx(ctx_.tx)
, parentBatchId(ctx_.parentBatchId)
, rules(ctx_.rules)
, consequences(result.second)
, flags(ctx_.flags)
, j(ctx_.j)
, ter(result.first)
{
}
PreflightResult(PreflightResult const&) = default;
/// Deleted copy assignment operator
PreflightResult&
operator=(PreflightResult const&) = delete;
};
/** Describes the results of the `preclaim` check
@note All members are const to make it more difficult
to "fake" a result without calling `preclaim`.
@see preflight, preclaim, doApply, apply
*/
struct PreclaimResult
{
public:
/// From the input - the ledger view
ReadView const& view;
/// From the input - the transaction
STTx const& tx;
/// From the input - the batch identifier, if part of a batch
std::optional<uint256 const> const parentBatchId;
/// From the input - the flags
ApplyFlags const flags;
/// From the input - the journal
beast::Journal const j;
/// Intermediate transaction result
TER const ter;
/// Success flag - whether the transaction is likely to
/// claim a fee
bool const likelyToClaimFee;
/// Constructor
template <class Context>
PreclaimResult(Context const& ctx_, TER ter_)
: view(ctx_.view)
, tx(ctx_.tx)
, parentBatchId(ctx_.parentBatchId)
, flags(ctx_.flags)
, j(ctx_.j)
, ter(ter_)
, likelyToClaimFee(ter == tesSUCCESS || isTecClaimHardFail(ter, flags))
{
}
PreclaimResult(PreclaimResult const&) = default;
/// Deleted copy assignment operator
PreclaimResult&
operator=(PreclaimResult const&) = delete;
};
/** Gate a transaction based on static information.
The transaction is checked against all possible
validity constraints that do not require a ledger.
@param app The current running `Application`.
@param rules The `Rules` in effect at the time of the check.
@param tx The transaction to be checked.
@param flags `ApplyFlags` describing processing options.
@param j A journal.
@see PreflightResult, preclaim, doApply, apply
@return A `PreflightResult` object containing, among
other things, the `TER` code.
*/
/** @{ */
PreflightResult
preflight(ServiceRegistry& registry, Rules const& rules, STTx const& tx, ApplyFlags flags, beast::Journal j);
PreflightResult
preflight(
ServiceRegistry& registry,
Rules const& rules,
uint256 const& parentBatchId,
STTx const& tx,
ApplyFlags flags,
beast::Journal j);
/** @} */
/** Gate a transaction based on static ledger information.
The transaction is checked against all possible
validity constraints that DO require a ledger.
If preclaim succeeds, then the transaction is very
likely to claim a fee. This will determine if the
transaction is safe to relay without being applied
to the open ledger.
"Succeeds" in this case is defined as returning a
`tes` or `tec`, since both lead to claiming a fee.
@pre The transaction has been checked
and validated using `preflight`
@param preflightResult The result of a previous
call to `preflight` for the transaction.
@param app The current running `Application`.
@param view The open ledger that the transaction
will attempt to be applied to.
@see PreclaimResult, preflight, doApply, apply
@return A `PreclaimResult` object containing, among
other things the `TER` code and the base fee value for
this transaction.
*/
PreclaimResult
preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view);
/** Compute only the expected base fee for a transaction.
Base fees are transaction specific, so any calculation
needing them must get the base fee for each transaction.
No validation is done or implied by this function.
Caller is responsible for handling any exceptions.
Since none should be thrown, that will usually
mean terminating.
@param view The current open ledger.
@param tx The transaction to be checked.
@return The base fee.
*/
XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
/** Return the minimum fee that an "ordinary" transaction would pay.
When computing the FeeLevel for a transaction the TxQ sometimes needs
the know what an "ordinary" or reference transaction would be required
to pay.
@param view The current open ledger.
@param tx The transaction so the correct multisigner count is used.
@return The base fee in XRPAmount.
*/
XRPAmount
calculateDefaultBaseFee(ReadView const& view, STTx const& tx);
/** Apply a prechecked transaction to an OpenView.
@pre The transaction has been checked
and validated using `preflight` and `preclaim`
@param preclaimResult The result of a previous
call to `preclaim` for the transaction.
@param registry The service registry.
@param view The open ledger that the transaction
will attempt to be applied to.
@see preflight, preclaim, apply
@return A pair with the `TER` and a `bool` indicating
whether or not the transaction was applied.
*/
ApplyResult
doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view);
} // namespace xrpl

View File

@@ -0,0 +1,63 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Quality.h>
namespace xrpl {
class Logs;
/** Iterates and consumes raw offers in an order book.
Offers are presented from highest quality to lowest quality. This will
return all offers present including missing, invalid, unfunded, etc.
*/
class BookTip
{
private:
ApplyView& view_;
bool m_valid;
uint256 m_book;
uint256 m_end;
uint256 m_dir;
uint256 m_index;
std::shared_ptr<SLE> m_entry;
Quality m_quality;
public:
/** Create the iterator. */
BookTip(ApplyView& view, Book const& book);
uint256 const&
dir() const noexcept
{
return m_dir;
}
uint256 const&
index() const noexcept
{
return m_index;
}
Quality const&
quality() const noexcept
{
return m_quality;
}
SLE::pointer const&
entry() const noexcept
{
return m_entry;
}
/** Erases the current offer and advance to the next offer.
Complexity: Constant
@return `true` if there is a next offer
*/
bool
step(beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/protocol/Quality.h>
#include <xrpl/tx/paths/RippleCalc.h>
#include <xrpl/tx/paths/detail/Steps.h>
namespace xrpl {
namespace path {
namespace detail {
struct FlowDebugInfo;
}
} // namespace path
/**
Make a payment from the src account to the dst account
@param view Trust lines and balances
@param deliver Amount to deliver to the dst account
@param src Account providing input funds for the payment
@param dst Account receiving the payment
@param paths Set of paths to explore for liquidity
@param defaultPaths Include defaultPaths in the path set
@param partialPayment If the payment cannot deliver the entire
requested amount, deliver as much as possible, given the constraints
@param ownerPaysTransferFee If true then owner, not sender, pays fee
@param offerCrossing If Yes or Sell then flow is executing offer crossing, not
payments
@param limitQuality Do not use liquidity below this quality threshold
@param sendMax Do not spend more than this amount
@param j Journal to write journal messages to
@param flowDebugInfo If non-null a pointer to FlowDebugInfo for debugging
@return Actual amount in and out, and the result code
*/
path::RippleCalc::Output
flow(
PaymentSandbox& view,
STAmount const& deliver,
AccountID const& src,
AccountID const& dst,
STPathSet const& paths,
bool defaultPaths,
bool partialPayment,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
std::optional<Quality> const& limitQuality,
std::optional<STAmount> const& sendMax,
std::optional<uint256> const& domainID,
beast::Journal j,
path::detail::FlowDebugInfo* flowDebugInfo = nullptr);
} // namespace xrpl

View File

@@ -0,0 +1,302 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <stdexcept>
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>
{
private:
SLE::pointer m_entry;
Quality m_quality;
AccountID m_account;
TAmounts<TIn, TOut> m_amounts;
void
setFieldAmounts();
public:
TOffer() = default;
TOffer(SLE::pointer const& entry, Quality quality);
/** Returns the quality of the offer.
Conceptually, the quality is the ratio of output to input currency.
The implementation calculates it as the ratio of input to output
currency (so it sorts ascending). The quality is computed at the time
the offer is placed, and never changes for the lifetime of the offer.
This is an important business rule that maintains accuracy when an
offer is partially filled; Subsequent partial fills will use the
original quality.
*/
Quality
quality() const noexcept
{
return m_quality;
}
/** Returns the account id of the offer's owner. */
AccountID const&
owner() const
{
return m_account;
}
/** Returns the in and out amounts.
Some or all of the out amount may be unfunded.
*/
TAmounts<TIn, TOut> const&
amount() const
{
return m_amounts;
}
/** Returns `true` if no more funds can flow through this offer. */
bool
fully_consumed() const
{
if (m_amounts.in <= beast::zero)
return true;
if (m_amounts.out <= beast::zero)
return true;
return false;
}
/** Adjusts the offer to indicate that we consumed some (or all) of it. */
void
consume(ApplyView& view, TAmounts<TIn, TOut> const& consumed)
{
if (consumed.in > m_amounts.in)
Throw<std::logic_error>("can't consume more than is available.");
if (consumed.out > m_amounts.out)
Throw<std::logic_error>("can't produce more than is available.");
m_amounts -= consumed;
setFieldAmounts();
view.update(m_entry);
}
std::string
id() const
{
return to_string(m_entry->key());
}
std::optional<uint256>
key() const
{
return m_entry->key();
}
Issue const&
issueIn() const;
Issue const&
issueOut() const;
TAmounts<TIn, TOut>
limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const;
TAmounts<TIn, TOut>
limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp) const;
template <typename... Args>
static TER
send(Args&&... args);
bool
isFunded() const
{
// Offer owner is issuer; they have unlimited funds
return m_account == issueOut().account;
}
static std::pair<std::uint32_t, std::uint32_t>
adjustRates(std::uint32_t ofrInRate, std::uint32_t ofrOutRate)
{
// CLOB offer pays the transfer fee
return {ofrInRate, ofrOutRate};
}
/** Check any required invariant. Limit order book offer
* always returns true.
*/
bool
checkInvariant(TAmounts<TIn, TOut> const& consumed, beast::Journal j) const
{
if (!isFeatureEnabled(fixAMMv1_3))
return true;
if (consumed.in > m_amounts.in || consumed.out > m_amounts.out)
{
// LCOV_EXCL_START
JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in) << " "
<< to_string(consumed.out) << " amounts " << to_string(m_amounts.in) << " "
<< to_string(m_amounts.out);
return false;
// LCOV_EXCL_STOP
}
return true;
}
};
using Offer = TOffer<>;
template <class TIn, class TOut>
TOffer<TIn, TOut>::TOffer(SLE::pointer const& entry, Quality quality)
: m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount))
{
auto const tp = m_entry->getFieldAmount(sfTakerPays);
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();
}
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>
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
}
template <class TIn, class TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitOut(TAmounts<TIn, TOut> const& offerAmount, TOut const& limit, bool roundUp) const
{
// It turns out that the ceil_out implementation has some slop in
// it, which ceil_out_strict removes.
return quality().ceil_out_strict(offerAmount, limit, roundUp);
}
template <class TIn, class TOut>
TAmounts<TIn, TOut>
TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offerAmount, TIn const& limit, bool roundUp) const
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixReducedOffersV2))
// It turns out that the ceil_in implementation has some slop in
// it. ceil_in_strict removes that slop. But removing that slop
// affects transaction outcomes, so the change must be made using
// an amendment.
return quality().ceil_in_strict(offerAmount, limit, roundUp);
return m_quality.ceil_in(offerAmount, limit);
}
template <class TIn, class TOut>
template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)...);
}
template <>
inline void
TOffer<STAmount, STAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
}
template <>
inline void
TOffer<IOUAmount, IOUAmount>::setFieldAmounts()
{
m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
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>
inline std::ostream&
operator<<(std::ostream& os, TOffer<TIn, TOut> const& offer)
{
return os << offer.id();
}
} // namespace xrpl

View File

@@ -0,0 +1,174 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/tx/paths/BookTip.h>
#include <xrpl/tx/paths/Offer.h>
#include <boost/container/flat_set.hpp>
namespace xrpl {
template <class TIn, class TOut>
class TOfferStreamBase
{
public:
class StepCounter
{
private:
std::uint32_t const limit_;
std::uint32_t count_;
beast::Journal j_;
public:
StepCounter(std::uint32_t limit, beast::Journal j) : limit_(limit), count_(0), j_(j)
{
}
bool
step()
{
if (count_ >= limit_)
{
JLOG(j_.debug()) << "Exceeded " << limit_ << " step limit.";
return false;
}
count_++;
return true;
}
std::uint32_t
count() const
{
return count_;
}
};
protected:
beast::Journal const j_;
ApplyView& view_;
ApplyView& cancelView_;
Book book_;
bool validBook_;
NetClock::time_point const expire_;
BookTip tip_;
TOffer<TIn, TOut> offer_;
std::optional<TOut> ownerFunds_;
StepCounter& counter_;
void
erase(ApplyView& view);
virtual void
permRmOffer(uint256 const& offerIndex) = 0;
template <class TTakerPays, class TTakerGets>
bool
shouldRmSmallIncreasedQOffer() const;
public:
TOfferStreamBase(
ApplyView& view,
ApplyView& cancelView,
Book const& book,
NetClock::time_point when,
StepCounter& counter,
beast::Journal journal);
virtual ~TOfferStreamBase() = default;
/** Returns the offer at the tip of the order book.
Offers are always presented in decreasing quality.
Only valid if step() returned `true`.
*/
TOffer<TIn, TOut>&
tip() const
{
return const_cast<TOfferStreamBase*>(this)->offer_;
}
/** Advance to the next valid offer.
This automatically removes:
- Offers with missing ledger entries
- Offers found unfunded
- expired offers
@return `true` if there is a valid offer.
*/
bool
step();
TOut
ownerFunds() const
{
return *ownerFunds_;
}
};
/** 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.
The `cancelView_` is used to determine if an offer is found
unfunded or became unfunded.
The `permToRemove` collection identifies offers that should be
removed even if the strand associated with this OfferStream
is not applied.
Certain invalid offers are added to the `permToRemove` collection:
- 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.
*/
template <class TIn, class TOut>
class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
{
private:
boost::container::flat_set<uint256> permToRemove_;
public:
using TOfferStreamBase<TIn, TOut>::TOfferStreamBase;
// The following interface allows offer crossing to permanently
// remove self crossed offers. The motivation is somewhat
// unintuitive. See the discussion in the comments for
// BookOfferCrossingStep::limitSelfCrossQuality().
void
permRmOffer(uint256 const& offerIndex) override;
boost::container::flat_set<uint256> const&
permToRemove() const
{
return permToRemove_;
}
};
} // namespace xrpl

View File

@@ -0,0 +1,109 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <boost/container/flat_set.hpp>
namespace xrpl {
class Config;
namespace path {
namespace detail {
struct FlowDebugInfo;
}
/** RippleCalc calculates the quality of a payment path.
Quality is the amount of input required to produce a given output along a
specified path - another name for this is exchange rate.
*/
class RippleCalc
{
public:
struct Input
{
explicit Input() = default;
bool partialPaymentAllowed = false;
bool defaultPathsAllowed = true;
bool limitQuality = false;
bool isLedgerOpen = true;
};
struct Output
{
explicit Output() = default;
// The computed input amount.
STAmount actualAmountIn;
// The computed output amount.
STAmount actualAmountOut;
// Collection of offers found expired or unfunded. When a payment
// succeeds, unfunded and expired offers are removed. When a payment
// fails, they are not removed. This vector contains the offers that
// could have been removed but were not because the payment fails. It is
// useful for offer crossing, which does remove the offers.
boost::container::flat_set<uint256> removableOffers;
private:
TER calculationResult_ = temUNKNOWN;
public:
TER
result() const
{
return calculationResult_;
}
void
setResult(TER const value)
{
calculationResult_ = value;
}
};
static Output
rippleCalculate(
PaymentSandbox& view,
// Compute paths using this ledger entry set. Up to caller to actually
// apply to ledger.
// Issuer:
// XRP: xrpAccount()
// non-XRP: uSrcAccountID (for any issuer) or another account with
// trust node.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
// Issuer:
// XRP: xrpAccount()
// non-XRP: uDstAccountID (for any issuer) or another account with
// trust node.
STAmount const& saDstAmountReq,
AccountID const& uDstAccountID,
AccountID const& uSrcAccountID,
// A set of paths that are included in the transaction that we'll
// explore for liquidity.
STPathSet const& spsPaths,
std::optional<uint256> const& domainID,
Logs& l,
Input const* const pInputs = nullptr);
// The view we are currently working on
PaymentSandbox& view;
// If the transaction fails to meet some constraint, still need to delete
// unfunded offers in a deterministic order (hence the ordered container).
//
// Offers that were found unfunded.
boost::container::flat_set<uint256> permanentlyUnfundedOffers_;
};
} // namespace path
} // namespace xrpl

View File

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

View File

@@ -0,0 +1,24 @@
#pragma once
#include <boost/container/flat_set.hpp>
namespace xrpl {
/** Given two flat sets dst and src, compute dst = dst union src
@param dst set to store the resulting union, and also a source of elements
for the union
@param src second source of elements for the union
*/
template <class T>
void
SetUnion(boost::container::flat_set<T>& dst, boost::container::flat_set<T> const& src)
{
if (src.empty())
return;
dst.reserve(dst.size() + src.size());
dst.insert(boost::container::ordered_unique_range_t{}, src.begin(), src.end());
}
} // namespace xrpl

View File

@@ -0,0 +1,329 @@
#pragma once
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <boost/container/flat_map.hpp>
#include <chrono>
#include <optional>
#include <sstream>
namespace xrpl {
namespace path {
namespace detail {
// Track performance information of a single payment
struct FlowDebugInfo
{
using clock = std::chrono::high_resolution_clock;
using time_point = clock::time_point;
boost::container::flat_map<std::string, std::pair<time_point, time_point>> timePoints;
boost::container::flat_map<std::string, std::size_t> counts;
struct PassInfo
{
PassInfo() = delete;
PassInfo(bool nativeIn_, bool nativeOut_) : nativeIn(nativeIn_), nativeOut(nativeOut_)
{
}
bool const nativeIn;
bool const nativeOut;
std::vector<EitherAmount> in;
std::vector<EitherAmount> out;
std::vector<size_t> numActive;
std::vector<std::vector<EitherAmount>> liquiditySrcIn;
std::vector<std::vector<EitherAmount>> liquiditySrcOut;
void
reserve(size_t s)
{
in.reserve(s);
out.reserve(s);
liquiditySrcIn.reserve(s);
liquiditySrcOut.reserve(s);
numActive.reserve(s);
}
size_t
size() const
{
return in.size();
}
void
push_back(EitherAmount const& in_amt, EitherAmount const& out_amt, std::size_t active)
{
in.push_back(in_amt);
out.push_back(out_amt);
numActive.push_back(active);
}
void
pushLiquiditySrc(EitherAmount const& eIn, EitherAmount const& eOut)
{
XRPL_ASSERT(
!liquiditySrcIn.empty(),
"xrpl::path::detail::FlowDebugInfo::pushLiquiditySrc : "
"non-empty liquidity source");
liquiditySrcIn.back().push_back(eIn);
liquiditySrcOut.back().push_back(eOut);
}
void
newLiquidityPass()
{
auto const s = liquiditySrcIn.size();
size_t const r = !numActive.empty() ? numActive.back() : 16;
liquiditySrcIn.resize(s + 1);
liquiditySrcIn.back().reserve(r);
liquiditySrcOut.resize(s + 1);
liquiditySrcOut.back().reserve(r);
}
};
PassInfo passInfo;
FlowDebugInfo() = delete;
FlowDebugInfo(bool nativeIn, bool nativeOut) : passInfo(nativeIn, nativeOut)
{
timePoints.reserve(16);
counts.reserve(16);
passInfo.reserve(64);
}
auto
duration(std::string const& tag) const
{
auto i = timePoints.find(tag);
if (i == timePoints.end())
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::path::detail::FlowDebugInfo::duration : timepoint not "
"found");
return std::chrono::duration<double>(0);
// LCOV_EXCL_STOP
}
auto const& t = i->second;
return std::chrono::duration_cast<std::chrono::duration<double>>(t.second - t.first);
}
std::size_t
count(std::string const& tag) const
{
auto i = counts.find(tag);
if (i == counts.end())
return 0;
return i->second;
}
// Time the duration of the existence of the result
auto
timeBlock(std::string name)
{
struct Stopper
{
std::string tag;
FlowDebugInfo* info;
Stopper(std::string name, FlowDebugInfo& pi) : tag(std::move(name)), info(&pi)
{
auto const start = FlowDebugInfo::clock::now();
info->timePoints.emplace(tag, std::make_pair(start, start));
}
~Stopper()
{
auto const end = FlowDebugInfo::clock::now();
info->timePoints[tag].second = end;
}
Stopper(Stopper&&) = default;
};
return Stopper(std::move(name), *this);
}
void
inc(std::string const& tag)
{
auto i = counts.find(tag);
if (i == counts.end())
{
counts[tag] = 1;
}
++i->second;
}
void
setCount(std::string const& tag, std::size_t c)
{
counts[tag] = c;
}
std::size_t
passCount() const
{
return passInfo.size();
}
void
pushPass(EitherAmount const& in, EitherAmount const& out, std::size_t activeStrands)
{
passInfo.push_back(in, out, activeStrands);
}
void
pushLiquiditySrc(EitherAmount const& in, EitherAmount const& out)
{
passInfo.pushLiquiditySrc(in, out);
}
void
newLiquidityPass()
{
passInfo.newLiquidityPass();
}
std::string
to_string(bool writePassInfo) const
{
std::ostringstream ostr;
auto const d = duration("main");
ostr << "duration: " << d.count() << ", pass_count: " << passCount();
if (writePassInfo)
{
auto write_list = [&ostr](auto const& vals, auto&& fun, char delim = ';') {
ostr << '[';
if (!vals.empty())
{
ostr << fun(vals[0]);
for (size_t i = 1, e = vals.size(); i < e; ++i)
ostr << delim << fun(vals[i]);
}
ostr << ']';
};
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); };
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); };
write_list(amts, get_val, delim);
};
auto writeIntList = [&write_list](std::vector<size_t> const& vals, char delim = ';') {
auto get_val = [](size_t const& v) -> size_t const& { return v; };
write_list(vals, get_val);
};
auto writeNestedIouAmtList = [&ostr, &writeIouAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
{
writeIouAmtList(amts[0], '|');
for (size_t i = 1, e = amts.size(); i < e; ++i)
{
ostr << ';';
writeIouAmtList(amts[i], '|');
}
}
ostr << ']';
};
auto writeNestedXrpAmtList = [&ostr, &writeXrpAmtList](std::vector<std::vector<EitherAmount>> const& amts) {
ostr << '[';
if (!amts.empty())
{
writeXrpAmtList(amts[0], '|');
for (size_t i = 1, e = amts.size(); i < e; ++i)
{
ostr << ';';
writeXrpAmtList(amts[i], '|');
}
}
ostr << ']';
};
ostr << ", in_pass: ";
if (passInfo.nativeIn)
writeXrpAmtList(passInfo.in);
else
writeIouAmtList(passInfo.in);
ostr << ", out_pass: ";
if (passInfo.nativeOut)
writeXrpAmtList(passInfo.out);
else
writeIouAmtList(passInfo.out);
ostr << ", num_active: ";
writeIntList(passInfo.numActive);
if (!passInfo.liquiditySrcIn.empty() && !passInfo.liquiditySrcIn.back().empty())
{
ostr << ", l_src_in: ";
if (passInfo.nativeIn)
writeNestedXrpAmtList(passInfo.liquiditySrcIn);
else
writeNestedIouAmtList(passInfo.liquiditySrcIn);
ostr << ", l_src_out: ";
if (passInfo.nativeOut)
writeNestedXrpAmtList(passInfo.liquiditySrcOut);
else
writeNestedIouAmtList(passInfo.liquiditySrcOut);
}
}
return ostr.str();
}
};
inline void
writeDiffElement(std::ostringstream& ostr, std::pair<std::tuple<AccountID, AccountID, Currency>, STAmount> const& elem)
{
using namespace std;
auto const k = elem.first;
auto const v = elem.second;
ostr << '[' << get<0>(k) << '|' << get<1>(k) << '|' << get<2>(k) << '|' << v << ']';
};
template <class Iter>
void
writeDiffs(std::ostringstream& ostr, Iter begin, Iter end)
{
ostr << '[';
if (begin != end)
{
writeDiffElement(ostr, *begin);
++begin;
}
for (; begin != end; ++begin)
{
ostr << ';';
writeDiffElement(ostr, *begin);
}
ostr << ']';
};
using BalanceDiffs = std::pair<std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>, XRPAmount>;
inline BalanceDiffs
balanceDiffs(PaymentSandbox const& sb, ReadView const& rv)
{
return {sb.balanceChanges(rv), sb.xrpDestroyed()};
}
inline std::string
balanceDiffsToString(std::optional<BalanceDiffs> const& bd)
{
if (!bd)
return std::string{};
auto const& diffs = bd->first;
auto const& xrpDestroyed = bd->second;
std::ostringstream ostr;
ostr << ", xrpDestroyed: " << to_string(xrpDestroyed);
ostr << ", balanceDiffs: ";
writeDiffs(ostr, diffs.begin(), diffs.end());
return ostr.str();
};
} // namespace detail
} // namespace path
} // namespace xrpl

View File

@@ -0,0 +1,591 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.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 <boost/container/flat_set.hpp>
#include <optional>
namespace xrpl {
class PaymentSandbox;
class ReadView;
class ApplyView;
class AMMContext;
enum class DebtDirection { issues, redeems };
enum class QualityDirection { in, out };
enum class StrandDirection { forward, reverse };
enum OfferCrossing { no = 0, yes = 1, sell = 2 };
inline bool
redeems(DebtDirection dir)
{
return dir == DebtDirection::redeems;
}
inline bool
issues(DebtDirection dir)
{
return dir == DebtDirection::issues;
}
/**
A step in a payment path
There are five concrete step classes:
DirectStepI is an IOU step between accounts
BookStepII is an IOU/IOU offer book
BookStepIX is an IOU/XRP offer book
BookStepXI is an XRP/IOU offer book
XRPEndpointStep is the source or destination account for XRP
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
find the amount the step would output given an input amount. In the reverse
direction, the function `rev` is used to find the amount of input needed to
produce the desired output.
Amounts are always transformed using liquidity with the same quality (quality
is the amount out/amount in). For example, a BookStep may use multiple offers
when executing `fwd` or `rev`, but all those offers will be from the same
quality directory.
A step may not have enough liquidity to transform the entire requested
amount. Both `fwd` and `rev` return a pair of amounts (one for input amount,
one for output amount) that show how much of the requested amount the step
was actually able to use.
*/
class Step
{
public:
virtual ~Step() = default;
/**
Find the amount we need to put into the step to get the requested out
subject to liquidity limits
@param sb view with the strand's state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to
this collection
@param out requested step output
@return actual step input and output
*/
virtual std::pair<EitherAmount, EitherAmount>
rev(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& out) = 0;
/**
Find the amount we get out of the step given the input
subject to liquidity limits
@param sb view with the strand's state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param ofrsToRm offers found unfunded or in an error state are added to
this collection
@param in requested step input
@return actual step input and output
*/
virtual std::pair<EitherAmount, EitherAmount>
fwd(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& in) = 0;
/**
Amount of currency computed coming into the Step the last time the
step ran in reverse.
*/
virtual std::optional<EitherAmount>
cachedIn() const = 0;
/**
Amount of currency computed coming out of the Step the last time the
step ran in reverse.
*/
virtual std::optional<EitherAmount>
cachedOut() const = 0;
/**
If this step is DirectStepI (IOU->IOU direct step), return the src
account. This is needed for checkNoRipple.
*/
virtual std::optional<AccountID>
directStepSrcAcct() const
{
return std::nullopt;
}
// for debugging. Return the src and dst accounts for a direct step
// For XRP endpoints, one of src or dst will be the root account
virtual std::optional<std::pair<AccountID, AccountID>>
directStepAccts() const
{
return std::nullopt;
}
/**
If this step is a DirectStepI and the src redeems to the dst, return
true, otherwise return false. If this step is a BookStep, return false if
the owner pays the transfer fee, otherwise return true.
@param sb view with the strand's state of balances and offers
@param dir reverse -> called from rev(); forward -> called from fwd().
*/
virtual DebtDirection
debtDirection(ReadView const& sb, StrandDirection dir) const = 0;
/**
If this step is a DirectStepI, return the quality in of the dst account.
*/
virtual std::uint32_t
lineQualityIn(ReadView const&) const
{
return QUALITY_ONE;
}
// clang-format off
/**
Find an upper bound of quality for the step
@param v view to query the ledger state from
@param prevStepDir Set to DebtDirection::redeems if the previous step redeems.
@return A pair. The first element is the upper bound of quality for the step, or std::nullopt if the
step is dry. The second element will be set to DebtDirection::redeems if this steps redeems,
DebtDirection:issues if this step issues.
@note it is an upper bound because offers on the books may be unfunded.
If there is always a funded offer at the tip of the book, then we could
rename this `theoreticalQuality` rather than `qualityUpperBound`. It
could still differ from the actual quality, but except for "dust" amounts,
it should be a good estimate for the actual quality.
*/
// clang-format on
virtual std::pair<std::optional<Quality>, DebtDirection>
qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const = 0;
/** Get QualityFunction. Used in one path optimization where
* the quality function is non-constant (has AMM) and there is
* limitQuality. QualityFunction allows calculation of
* required path output given requested limitQuality.
* All steps, except for BookStep have the default
* implementation.
*/
virtual std::pair<std::optional<QualityFunction>, DebtDirection>
getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const;
/** Return the 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
entire payment, it is only the number the last time it ran. Offers may
be partially consumed multiple times during a payment.
*/
virtual std::uint32_t
offersUsed() const
{
return 0;
}
/**
If this step is a BookStep, return the book.
*/
virtual std::optional<Book>
bookStepBook() const
{
return std::nullopt;
}
/**
Check if amount is zero
*/
virtual bool
isZero(EitherAmount const& out) const = 0;
/**
Return true if the step should be considered inactive.
A strand that has additional liquidity may be marked inactive if a step
has consumed too many offers.
*/
virtual bool
inactive() const
{
return false;
}
/**
Return true if Out of lhs == Out of rhs.
*/
virtual bool
equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const = 0;
/**
Return true if In of lhs == In of rhs.
*/
virtual bool
equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const = 0;
/**
Check that the step can correctly execute in the forward direction
@param sb view with the strands state of balances and offers
@param afView view the state of balances before the strand runs
this determines if an offer becomes unfunded or is found unfunded
@param in requested step input
@return first element is true if step is valid, second element is out
amount
*/
virtual std::pair<bool, EitherAmount>
validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) = 0;
/** Return true if lhs == rhs.
@param lhs Step to compare.
@param rhs Step to compare.
@return true if lhs == rhs.
*/
friend bool
operator==(Step const& lhs, Step const& rhs)
{
return lhs.equal(rhs);
}
/** Return true if lhs != rhs.
@param lhs Step to compare.
@param rhs Step to compare.
@return true if lhs != rhs.
*/
friend bool
operator!=(Step const& lhs, Step const& rhs)
{
return !(lhs == rhs);
}
/** Streaming operator for a Step. */
friend std::ostream&
operator<<(std::ostream& stream, Step const& step)
{
stream << step.logString();
return stream;
}
private:
virtual std::string
logString() const = 0;
virtual bool
equal(Step const& rhs) const = 0;
};
inline std::pair<std::optional<QualityFunction>, DebtDirection>
Step::getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const
{
if (auto const res = qualityUpperBound(v, prevStepDir); res.first)
return {QualityFunction{*res.first, QualityFunction::CLOBLikeTag{}}, res.second};
else
return {std::nullopt, res.second};
}
/// @cond INTERNAL
using Strand = std::vector<std::unique_ptr<Step>>;
inline std::uint32_t
offersUsed(Strand const& strand)
{
std::uint32_t r = 0;
for (auto const& step : strand)
{
if (step)
r += step->offersUsed();
}
return r;
}
/// @endcond
/// @cond INTERNAL
inline bool
operator==(Strand const& lhs, Strand const& rhs)
{
if (lhs.size() != rhs.size())
return false;
for (size_t i = 0, e = lhs.size(); i != e; ++i)
if (*lhs[i] != *rhs[i])
return false;
return true;
}
/// @endcond
/*
Normalize a path by inserting implied accounts and offers
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param sendMax 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.
@return error code and normalized path
*/
std::pair<TER, STPath>
normalizePath(
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Issue> const& sendMaxIssue,
STPath const& path);
/**
Create a Strand for the specified path
@param sb view for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param limitQuality Offer crossing BookSteps use this value in an
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 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.
@param ownerPaysTransferFee false -> charge sender; true -> charge offer
owner
@param offerCrossing false -> payment; true -> offer crossing
@param ammContext counts iterations with AMM offers
@param domainID the domain that order books will use
@param j Journal for logging messages
@return Error code and constructed Strand
*/
std::pair<TER, Strand>
toStrand(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMaxIssue,
STPath const& path,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
AMMContext& ammContext,
std::optional<uint256> const& domainID,
beast::Journal j);
/**
Create a Strand for each specified path (including the default path, if
indicated)
@param sb View for trust lines, balances, and attributes like auth and freeze
@param src Account that is sending assets
@param dst Account that is receiving assets
@param deliver Asset the dst account will receive
(if issuer of deliver == dst, then accept any issuer)
@param limitQuality Offer crossing BookSteps use this value in an
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 sendMax Optional asset to send.
@param paths Paths to use to fulfill the payment. Each path in the pathset
contains an ordered collection of the offer books to use and
accounts to ripple through.
@param addDefaultPath Determines if the default path should be included
@param ownerPaysTransferFee false -> charge sender; true -> charge offer
owner
@param offerCrossing false -> payment; true -> offer crossing
@param ammContext counts iterations with AMM offers
@param domainID the domain that order books will use
@param j Journal for logging messages
@return error code and collection of strands
*/
std::pair<TER, std::vector<Strand>>
toStrands(
ReadView const& sb,
AccountID const& src,
AccountID const& dst,
Issue const& deliver,
std::optional<Quality> const& limitQuality,
std::optional<Issue> const& sendMax,
STPathSet const& paths,
bool addDefaultPath,
bool ownerPaysTransferFee,
OfferCrossing offerCrossing,
AMMContext& ammContext,
std::optional<uint256> const& domainID,
beast::Journal j);
/// @cond INTERNAL
template <class TIn, class TOut, class TDerived>
struct StepImp : public Step
{
explicit StepImp() = default;
std::pair<EitherAmount, EitherAmount>
rev(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& out) override
{
auto const r = static_cast<TDerived*>(this)->revImp(sb, afView, ofrsToRm, get<TOut>(out));
return {EitherAmount(r.first), EitherAmount(r.second)};
}
// Given the requested amount to consume, compute the amount produced.
// Return the consumed/produced
std::pair<EitherAmount, EitherAmount>
fwd(PaymentSandbox& sb,
ApplyView& afView,
boost::container::flat_set<uint256>& ofrsToRm,
EitherAmount const& in) override
{
auto const r = static_cast<TDerived*>(this)->fwdImp(sb, afView, ofrsToRm, get<TIn>(in));
return {EitherAmount(r.first), EitherAmount(r.second)};
}
bool
isZero(EitherAmount const& out) const override
{
return get<TOut>(out) == beast::zero;
}
bool
equalOut(EitherAmount const& lhs, EitherAmount const& rhs) const override
{
return get<TOut>(lhs) == get<TOut>(rhs);
}
bool
equalIn(EitherAmount const& lhs, EitherAmount const& rhs) const override
{
return get<TIn>(lhs) == get<TIn>(rhs);
}
};
/// @endcond
/// @cond INTERNAL
// Thrown when unexpected errors occur
class FlowException : public std::runtime_error
{
public:
TER ter;
FlowException(TER t, std::string const& msg) : std::runtime_error(msg), ter(t)
{
}
explicit FlowException(TER t) : std::runtime_error(transHuman(t)), ter(t)
{
}
};
/// @endcond
/// @cond INTERNAL
// Check equal with tolerance
bool
checkNear(IOUAmount const& expected, IOUAmount const& actual);
bool
checkNear(XRPAmount const& expected, XRPAmount const& actual);
/// @endcond
/**
Context needed to build Strand Steps and for error checking
*/
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
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
bool const ownerPaysTransferFee; ///< true if owner, not sender, pays fee
OfferCrossing const offerCrossing; ///< Yes/Sell if offer crossing, not payment
bool const isDefaultPath; ///< true if Strand is default path
size_t const strandSize; ///< Length of Strand
/** The previous step in the strand. Needed to check the no ripple
constraint
*/
Step const* const prevStep = nullptr;
/** A strand may not include the same account node more than once
in the same currency. In a direct step, an account will show up
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;
/** A strand may not include an offer that output the same issue more
than once
*/
boost::container::flat_set<Issue>& seenBookOuts;
AMMContext& ammContext;
std::optional<uint256> domainID; // the domain the order book will use
beast::Journal const j;
/** StrandContext constructor. */
StrandContext(
ReadView const& view_,
std::vector<std::unique_ptr<Step>> const& strand_,
// A strand may not include an inner node that
// replicates the source or destination.
AccountID const& strandSrc_,
AccountID const& strandDst_,
Issue 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
AMMContext& ammContext_,
std::optional<uint256> const& domainID,
beast::Journal j_); ///< Journal for logging
};
/// @cond INTERNAL
namespace test {
// Needed for testing
bool
directStepEqual(Step const& step, AccountID const& src, AccountID const& dst, Currency const& currency);
bool
xrpEndpointStepEqual(Step const& step, AccountID const& acc);
bool
bookStepEqual(Step const& step, xrpl::Book const& book);
} // namespace test
std::pair<TER, std::unique_ptr<Step>>
make_DirectStepI(StrandContext const& ctx, AccountID const& src, AccountID const& dst, Currency const& c);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out);
std::pair<TER, std::unique_ptr<Step>>
make_BookStepIX(StrandContext const& ctx, Issue const& in);
std::pair<TER, std::unique_ptr<Step>>
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>
bool
isDirectXrpToXrp(Strand const& strand);
/// @endcond
} // namespace xrpl

View File

@@ -0,0 +1,784 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/Credit.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/paths/Flow.h>
#include <xrpl/tx/paths/detail/AmountSpec.h>
#include <xrpl/tx/paths/detail/FlatSets.h>
#include <xrpl/tx/paths/detail/FlowDebugInfo.h>
#include <xrpl/tx/paths/detail/Steps.h>
#include <xrpl/tx/transactors/AMM/AMMContext.h>
#include <xrpl/tx/transactors/AMM/AMMHelpers.h>
#include <boost/container/flat_set.hpp>
#include <algorithm>
#include <iterator>
#include <numeric>
namespace xrpl {
/** Result of flow() execution of a single Strand. */
template <class TInAmt, class TOutAmt>
struct StrandResult
{
bool success; ///< Strand succeeded
TInAmt in = beast::zero; ///< Currency amount in
TOutAmt out = beast::zero; ///< Currency amount out
std::optional<PaymentSandbox> sandbox; ///< Resulting Sandbox state
boost::container::flat_set<uint256> ofrsToRm; ///< Offers to remove
// Num offers consumed or partially consumed (includes expired and unfunded
// offers)
std::uint32_t ofrsUsed = 0;
// strand can be inactive if there is no more liquidity or too many offers
// have been consumed
bool inactive = false; ///< Strand should not considered as a further
///< source of liquidity (dry)
/** Strand result constructor */
StrandResult() = default;
StrandResult(
Strand const& strand,
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_,
boost::container::flat_set<uint256> ofrsToRm_,
bool inactive_)
: success(true)
, in(in_)
, out(out_)
, sandbox(std::move(sandbox_))
, ofrsToRm(std::move(ofrsToRm_))
, ofrsUsed(offersUsed(strand))
, inactive(inactive_)
{
}
StrandResult(Strand const& strand, boost::container::flat_set<uint256> ofrsToRm_)
: success(false), ofrsToRm(std::move(ofrsToRm_)), ofrsUsed(offersUsed(strand))
{
}
};
/**
Request `out` amount from a strand
@param baseView Trust lines and balances
@param strand Steps of Accounts to ripple through and offer books to use
@param maxIn Max amount of input allowed
@param out Amount of output requested from the strand
@param j Journal to write log messages to
@return Actual amount in and out from the strand, errors, offers to remove,
and payment sandbox
*/
template <class TInAmt, class TOutAmt>
StrandResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,
Strand const& strand,
std::optional<TInAmt> const& maxIn,
TOutAmt const& out,
beast::Journal j)
{
using Result = StrandResult<TInAmt, TOutAmt>;
if (strand.empty())
{
JLOG(j.warn()) << "Empty strand passed to Liquidity";
return {};
}
boost::container::flat_set<uint256> ofrsToRm;
if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
{
return Result{strand, std::move(ofrsToRm)};
}
try
{
std::size_t const s = strand.size();
std::size_t limitingStep = strand.size();
std::optional<PaymentSandbox> sb(&baseView);
// The "all funds" view determines if an offer becomes unfunded or is
// found unfunded
// These are the account balances before the strand executes
std::optional<PaymentSandbox> afView(&baseView);
EitherAmount limitStepOut;
{
EitherAmount stepOut(out);
for (auto i = s; i--;)
{
auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
if (strand[i]->isZero(r.second))
{
JLOG(j.trace()) << "Strand found dry in rev";
return Result{strand, std::move(ofrsToRm)};
}
if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
{
// limiting - exceeded maxIn
// Throw out previous results
sb.emplace(&baseView);
limitingStep = i;
// re-execute the limiting step
r = strand[i]->fwd(*sb, *afView, ofrsToRm, EitherAmount(*maxIn));
limitStepOut = r.second;
if (strand[i]->isZero(r.second))
{
JLOG(j.trace()) << "First step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (get<TInAmt>(r.first) != *maxIn)
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
// LCOV_EXCL_START
JLOG(j.fatal()) << "Re-executed limiting step failed. r.first: "
<< to_string(get<TInAmt>(r.first)) << " maxIn: " << to_string(*maxIn);
UNREACHABLE(
"xrpl::flow : first step re-executing the "
"limiting step failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
}
else if (!strand[i]->equalOut(r.second, stepOut))
{
// limiting
// Throw out previous results
sb.emplace(&baseView);
afView.emplace(&baseView);
limitingStep = i;
// re-execute the limiting step
stepOut = r.second;
r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
limitStepOut = r.second;
if (strand[i]->isZero(r.second))
{
// A tiny input amount can cause this step to output
// zero. I.e. 10^-80 IOU into an IOU -> XRP offer.
JLOG(j.trace()) << "Limiting step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (!strand[i]->equalOut(r.second, stepOut))
{
// Something is very wrong
// throwing out the sandbox can only increase liquidity
// yet the limiting is still limiting
// LCOV_EXCL_START
#ifndef NDEBUG
JLOG(j.fatal()) << "Re-executed limiting step failed. r.second: " << r.second
<< " stepOut: " << stepOut;
#else
JLOG(j.fatal()) << "Re-executed limiting step failed";
#endif
UNREACHABLE(
"xrpl::flow : limiting step re-executing the "
"limiting step failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
}
// prev node needs to produce what this node wants to consume
stepOut = r.first;
}
}
{
EitherAmount stepIn(limitStepOut);
for (auto i = limitingStep + 1; i < s; ++i)
{
auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
if (strand[i]->isZero(r.second))
{
// A tiny input amount can cause this step to output zero.
// I.e. 10^-80 IOU into an IOU -> XRP offer.
JLOG(j.trace()) << "Non-limiting step found dry";
return Result{strand, std::move(ofrsToRm)};
}
if (!strand[i]->equalIn(r.first, stepIn))
{
// The limits should already have been found, so executing a
// strand forward from the limiting step should not find a
// new limit
// LCOV_EXCL_START
#ifndef NDEBUG
JLOG(j.fatal()) << "Re-executed forward pass failed. r.first: " << r.first << " stepIn: " << stepIn;
#else
JLOG(j.fatal()) << "Re-executed forward pass failed";
#endif
UNREACHABLE(
"xrpl::flow : non-limiting step re-executing the "
"forward pass failed");
return Result{strand, std::move(ofrsToRm)};
// LCOV_EXCL_STOP
}
stepIn = r.second;
}
}
auto const strandIn = *strand.front()->cachedIn();
auto const strandOut = *strand.back()->cachedOut();
#ifndef NDEBUG
{
// Check that the strand will execute as intended
// Re-executing the strand will change the cached values
PaymentSandbox checkSB(&baseView);
PaymentSandbox checkAfView(&baseView);
EitherAmount stepIn(*strand[0]->cachedIn());
for (auto i = 0; i < s; ++i)
{
bool valid;
std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
if (!valid)
{
JLOG(j.warn()) << "Strand re-execute check failed. Step: " << i;
break;
}
}
}
#endif
bool const inactive = std::any_of(
strand.begin(), strand.end(), [](std::unique_ptr<Step> const& step) { return step->inactive(); });
return Result(
strand, get<TInAmt>(strandIn), get<TOutAmt>(strandOut), std::move(*sb), std::move(ofrsToRm), inactive);
}
catch (FlowException const&)
{
return Result{strand, std::move(ofrsToRm)};
}
}
/// @cond INTERNAL
template <class TInAmt, class TOutAmt>
struct FlowResult
{
TInAmt in = beast::zero;
TOutAmt out = beast::zero;
std::optional<PaymentSandbox> sandbox;
boost::container::flat_set<uint256> removableOffers;
TER ter = temUNKNOWN;
FlowResult() = default;
FlowResult(
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sandbox_,
boost::container::flat_set<uint256> ofrsToRm)
: in(in_), out(out_), sandbox(std::move(sandbox_)), removableOffers(std::move(ofrsToRm)), ter(tesSUCCESS)
{
}
FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm) : removableOffers(std::move(ofrsToRm)), ter(ter_)
{
}
FlowResult(TER ter_, TInAmt const& in_, TOutAmt const& out_, boost::container::flat_set<uint256> ofrsToRm)
: in(in_), out(out_), removableOffers(std::move(ofrsToRm)), ter(ter_)
{
}
};
/// @endcond
/// @cond INTERNAL
inline std::optional<Quality>
qualityUpperBound(ReadView const& v, Strand const& strand)
{
Quality q{STAmount::uRateOne};
std::optional<Quality> stepQ;
DebtDirection dir = DebtDirection::issues;
for (auto const& step : strand)
{
if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
q = composed_quality(q, *stepQ);
else
return std::nullopt;
}
return q;
};
/// @endcond
/// @cond INTERNAL
/** Limit remaining out only if one strand and limitQuality is included.
* Targets one path payment with AMM where the average quality is linear
* and instant quality is quadratic function of output. Calculating quality
* function for the whole strand enables figuring out required output
* to produce requested strand's limitQuality. Reducing the output,
* increases quality of AMM steps, increasing the strand's composite
* quality as the result.
*/
template <typename TOutAmt>
inline TOutAmt
limitOut(ReadView const& v, Strand const& strand, TOutAmt const& remainingOut, Quality const& limitQuality)
{
std::optional<QualityFunction> stepQualityFunc;
std::optional<QualityFunction> qf;
DebtDirection dir = DebtDirection::issues;
for (auto const& step : strand)
{
if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
{
if (!qf)
qf = stepQualityFunc;
else
qf->combine(*stepQualityFunc);
}
else
return remainingOut;
}
// QualityFunction is constant
if (!qf || qf->isConst())
return remainingOut;
auto const out = [&]() {
if (auto const out = qf->outFromAvgQ(limitQuality); !out)
return remainingOut;
else if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
return XRPAmount{*out};
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
return IOUAmount{*out};
else
return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()};
}();
// A tiny difference could be due to the round off
if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
return remainingOut;
return std::min(out, remainingOut);
};
/// @endcond
/// @cond INTERNAL
/* Track the non-dry strands
flow will search the non-dry strands (stored in `cur_`) for the best
available liquidity If flow doesn't use all the liquidity of a strand, that
strand is added to `next_`. The strands in `next_` are searched after the
current best liquidity is used.
*/
class ActiveStrands
{
private:
// Strands to be explored for liquidity
std::vector<Strand const*> cur_;
// Strands that may be explored for liquidity on the next iteration
std::vector<Strand const*> next_;
public:
ActiveStrands(std::vector<Strand> const& strands)
{
cur_.reserve(strands.size());
next_.reserve(strands.size());
for (auto& strand : strands)
next_.push_back(&strand);
}
// Start a new iteration in the search for liquidity
// Set the current strands to the strands in `next_`
void
activateNext(ReadView const& v, std::optional<Quality> const& limitQuality)
{
// add the strands in `next_` to `cur_`, sorted by theoretical quality.
// Best quality first.
cur_.clear();
if (!next_.empty())
{
std::vector<std::pair<Quality, Strand const*>> strandQualities;
strandQualities.reserve(next_.size());
if (next_.size() > 1) // no need to sort one strand
{
for (Strand const* strand : next_)
{
if (!strand)
{
// should not happen
continue;
}
if (auto const qual = qualityUpperBound(v, *strand))
{
if (limitQuality && *qual < *limitQuality)
{
// If a strand's quality is ever over limitQuality
// it is no longer part of the candidate set. Note
// that when transfer fees are charged, and an
// account goes from redeeming to issuing then
// strand quality _can_ increase; However, this is
// an unusual corner case.
continue;
}
strandQualities.push_back({*qual, strand});
}
}
// must stable sort for deterministic order across different c++
// standard library implementations
std::stable_sort(strandQualities.begin(), strandQualities.end(), [](auto const& lhs, auto const& rhs) {
// higher qualities first
return std::get<Quality>(lhs) > std::get<Quality>(rhs);
});
next_.clear();
next_.reserve(strandQualities.size());
for (auto const& sq : strandQualities)
{
next_.push_back(std::get<Strand const*>(sq));
}
}
}
std::swap(cur_, next_);
}
Strand const*
get(size_t i) const
{
if (i >= cur_.size())
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::ActiveStrands::get : input out of range");
return nullptr;
// LCOV_EXCL_STOP
}
return cur_[i];
}
void
push(Strand const* s)
{
next_.push_back(s);
}
// Push the strands from index i to the end of cur_ to next_
void
pushRemainingCurToNext(size_t i)
{
if (i >= cur_.size())
return;
next_.insert(next_.end(), std::next(cur_.begin(), i), cur_.end());
}
auto
size() const
{
return cur_.size();
}
void
removeIndex(std::size_t i)
{
if (i >= next_.size())
return;
next_.erase(next_.begin() + i);
}
};
/// @endcond
/**
Request `out` amount from a collection of strands
Attempt to fulfill the payment by using liquidity from the strands in order
from least expensive to most expensive
@param baseView Trust lines and balances
@param strands Each strand contains the steps of accounts to ripple through
and offer books to use
@param outReq Amount of output requested from the strand
@param partialPayment If true allow less than the full payment
@param offerCrossing If true offer crossing, not handling a standard payment
@param limitQuality If present, the minimum quality for any strand taken
@param sendMaxST If present, the maximum STAmount to send
@param j Journal to write journal messages to
@param ammContext counts iterations with AMM offers
@param flowDebugInfo If pointer is non-null, write flow debug info here
@return Actual amount in and out from the strands, errors, and payment
sandbox
*/
template <class TInAmt, class TOutAmt>
FlowResult<TInAmt, TOutAmt>
flow(
PaymentSandbox const& baseView,
std::vector<Strand> const& strands,
TOutAmt const& outReq,
bool partialPayment,
OfferCrossing offerCrossing,
std::optional<Quality> const& limitQuality,
std::optional<STAmount> const& sendMaxST,
beast::Journal j,
AMMContext& ammContext,
path::detail::FlowDebugInfo* flowDebugInfo = nullptr)
{
// Used to track the strand that offers the best quality (output/input
// ratio)
struct BestStrand
{
TInAmt in;
TOutAmt out;
PaymentSandbox sb;
Strand const& strand;
Quality quality;
BestStrand(
TInAmt const& in_,
TOutAmt const& out_,
PaymentSandbox&& sb_,
Strand const& strand_,
Quality const& quality_)
: in(in_), out(out_), sb(std::move(sb_)), strand(strand_), quality(quality_)
{
}
};
std::size_t const maxTries = 1000;
std::size_t curTry = 0;
std::uint32_t maxOffersToConsider = 1500;
std::uint32_t offersConsidered = 0;
// There is a bug in gcc that incorrectly warns about using uninitialized
// values if `remainingIn` is initialized through a copy constructor. We can
// get similar warnings for `sendMax` if it is initialized in the most
// natural way. Using `make_optional`, allows us to work around this bug.
TInAmt const sendMaxInit = sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
std::optional<TInAmt> const sendMax =
(sendMaxST && sendMaxInit >= beast::zero) ? std::make_optional(sendMaxInit) : std::nullopt;
std::optional<TInAmt> remainingIn = !!sendMax ? std::make_optional(sendMaxInit) : std::nullopt;
// std::optional<TInAmt> remainingIn{sendMax};
TOutAmt remainingOut(outReq);
PaymentSandbox sb(&baseView);
// non-dry strands
ActiveStrands activeStrands(strands);
// Keeping a running sum of the amount in the order they are processed
// will not give the best precision. Keep a collection so they may be summed
// from smallest to largest
boost::container::flat_multiset<TInAmt> savedIns;
savedIns.reserve(maxTries);
boost::container::flat_multiset<TOutAmt> savedOuts;
savedOuts.reserve(maxTries);
auto sum = [](auto const& col) {
using TResult = std::decay_t<decltype(*col.begin())>;
if (col.empty())
return TResult{beast::zero};
return std::accumulate(col.begin() + 1, col.end(), *col.begin());
};
// These offers only need to be removed if the payment is not
// successful
boost::container::flat_set<uint256> ofrsToRmOnFail;
while (remainingOut > beast::zero && (!remainingIn || *remainingIn > beast::zero))
{
++curTry;
if (curTry >= maxTries)
{
return {telFAILED_PROCESSING, std::move(ofrsToRmOnFail)};
}
activeStrands.activateNext(sb, limitQuality);
ammContext.setMultiPath(activeStrands.size() > 1);
// Limit only if one strand and limitQuality
auto const limitRemainingOut = [&]() {
if (activeStrands.size() == 1 && limitQuality)
if (auto const strand = activeStrands.get(0))
return limitOut(sb, *strand, remainingOut, *limitQuality);
return remainingOut;
}();
auto const adjustedRemOut = limitRemainingOut != remainingOut;
boost::container::flat_set<uint256> ofrsToRm;
std::optional<BestStrand> best;
if (flowDebugInfo)
flowDebugInfo->newLiquidityPass();
// Index of strand to mark as inactive (remove from the active list) if
// the liquidity is used. This is used for strands that consume too many
// offers Constructed as `false,0` to workaround a gcc warning about
// uninitialized variables
std::optional<std::size_t> markInactiveOnUse;
for (size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
{
Strand const* strand = activeStrands.get(strandIndex);
if (!strand)
{
// should not happen
continue;
}
// Clear AMM liquidity used flag. The flag might still be set if
// the previous strand execution failed. It has to be reset
// since this strand might not have AMM liquidity.
ammContext.clear();
if (offerCrossing && limitQuality)
{
auto const strandQ = qualityUpperBound(sb, *strand);
if (!strandQ || *strandQ < *limitQuality)
continue;
}
auto f = flow<TInAmt, TOutAmt>(sb, *strand, remainingIn, limitRemainingOut, j);
// rm bad offers even if the strand fails
SetUnion(ofrsToRm, f.ofrsToRm);
offersConsidered += f.ofrsUsed;
if (!f.success || f.out == beast::zero)
continue;
if (flowDebugInfo)
flowDebugInfo->pushLiquiditySrc(EitherAmount(f.in), EitherAmount(f.out));
XRPL_ASSERT(
f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
"xrpl::flow : remaining constraints");
Quality const q(f.out, f.in);
JLOG(j.trace()) << "New flow iter (iter, in, out): " << curTry - 1 << " " << to_string(f.in) << " "
<< to_string(f.out);
// limitOut() finds output to generate exact requested
// limitQuality. But the actual limit quality might be slightly
// off due to the round off.
if (limitQuality && q < *limitQuality &&
(!adjustedRemOut || !withinRelativeDistance(q, *limitQuality, Number(1, -7))))
{
JLOG(j.trace()) << "Path rejected by limitQuality"
<< " limit: " << *limitQuality << " path q: " << q;
continue;
}
XRPL_ASSERT(!best, "xrpl::flow : best is unset");
if (!f.inactive)
activeStrands.push(strand);
best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
activeStrands.pushRemainingCurToNext(strandIndex + 1);
break;
}
bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
if (best)
{
if (markInactiveOnUse)
{
activeStrands.removeIndex(*markInactiveOnUse);
markInactiveOnUse.reset();
}
savedIns.insert(best->in);
savedOuts.insert(best->out);
remainingOut = outReq - sum(savedOuts);
if (sendMax)
remainingIn = *sendMax - sum(savedIns);
if (flowDebugInfo)
flowDebugInfo->pushPass(EitherAmount(best->in), EitherAmount(best->out), activeStrands.size());
JLOG(j.trace()) << "Best path: in: " << to_string(best->in) << " out: " << to_string(best->out)
<< " remainingOut: " << to_string(remainingOut);
best->sb.apply(sb);
ammContext.update();
}
else
{
JLOG(j.trace()) << "All strands dry.";
}
best.reset(); // view in best must be destroyed before modifying base
// view
if (!ofrsToRm.empty())
{
SetUnion(ofrsToRmOnFail, ofrsToRm);
for (auto const& o : ofrsToRm)
{
if (auto ok = sb.peek(keylet::offer(o)))
offerDelete(sb, ok, j);
}
}
if (shouldBreak)
break;
}
auto const actualOut = sum(savedOuts);
auto const actualIn = sum(savedIns);
JLOG(j.trace()) << "Total flow: in: " << to_string(actualIn) << " out: " << to_string(actualOut);
/* flowCross doesn't handle offer crossing with tfFillOrKill flag correctly.
* 1. If tfFillOrKill is set then the owner must receive the full
* TakerPays. We reverse pays and gets because during crossing
* we are taking, therefore the owner must deliver the full TakerPays and
* the entire TakerGets doesn't have to be spent.
* Pre-fixFillOrKill amendment code fails if the entire TakerGets
* is not spent. fixFillOrKill addresses this issue.
* 2. If tfSell is also set then the owner must spend the entire TakerGets
* even if it means obtaining more than TakerPays. Since the pays and gets
* are reversed, the owner must send the entire TakerGets.
*/
bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
if (actualOut != outReq)
{
if (actualOut > outReq)
{
// Rounding in the payment engine is causing this assert to
// sometimes fire with "dust" amounts. This is causing issues when
// running debug builds of rippled. While this issue still needs to
// be resolved, the assert is causing more harm than good at this
// point.
// UNREACHABLE("xrpl::flow : rounding error");
return {tefEXCEPTION, std::move(ofrsToRmOnFail)};
}
if (!partialPayment)
{
// If we're offerCrossing a !partialPayment, then we're
// handling tfFillOrKill.
// Pre-fixFillOrKill amendment:
// That case is handled below; not here.
// fixFillOrKill amendment:
// That case is handled here if tfSell is also not set; i.e,
// case 1.
if (!offerCrossing || (fillOrKillEnabled && offerCrossing != OfferCrossing::sell))
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
}
else if (actualOut == beast::zero)
{
return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
}
}
if (offerCrossing && (!partialPayment && (!fillOrKillEnabled || offerCrossing == OfferCrossing::sell)))
{
// If we're offer crossing and partialPayment is *not* true, then
// we're handling a FillOrKill offer. In this case remainingIn must
// be zero (all funds must be consumed) or else we kill the offer.
// Pre-fixFillOrKill amendment:
// Handles both cases 1. and 2.
// fixFillOrKill amendment:
// Handles 2. 1. is handled above and falls through for tfSell.
XRPL_ASSERT(remainingIn, "xrpl::flow : nonzero remainingIn");
if (remainingIn && *remainingIn != beast::zero)
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
}
return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
}
} // namespace xrpl

View File

@@ -0,0 +1,67 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMBid implements AMM bid Transactor.
* This is a mechanism for an AMM instance to auction-off
* the trading advantages to users (arbitrageurs) at a discounted
* TradingFee for a 24 hour slot. Any account that owns corresponding
* LPTokens can bid for the auction slot of that AMM instance.
* Part of the proceeds from the auction, i.e. LPTokens are refunded
* to the current slot-holder computed on a pro rata basis.
* Remaining part of the proceeds - in the units of LPTokens- is burnt,
* thus effectively increasing the LPs shares.
* Total slot time of 24 hours is divided into 20 equal intervals.
* The auction slot can be in any of the following states at any time:
* - Empty - no account currently holds the slot.
* - Occupied - an account owns the slot with at least 5% of the remaining
* slot time (in one of 1-19 intervals).
* - Tailing - an account owns the slot with less than 5% of the remaining time.
* The slot-holder owns the slot privileges when in state Occupied or Tailing.
* If x is the fraction of used slot time for the current slot holder
* and X is the price at which the slot can be bought specified in LPTokens
* then: The minimum bid price for the slot in first interval is
* f(x) = X * 1.05 + min_slot_price
* The bid price of slot any time is
* f(x) = X * 1.05 * (1 - x^60) + min_slot_price, where min_slot_price
* is a constant fraction of the total LPTokens.
* The revenue from a successful bid is split between the current slot-holder
* and the pool. The current slot holder is always refunded the remaining slot
* value f(x) = (1 - x) * X.
* The remaining LPTokens are burnt.
* The auction information is maintained in AuctionSlot of ltAMM object.
* AuctionSlot contains:
* Account - account id, which owns the slot.
* Expiration - slot expiration time
* DiscountedFee - trading fee charged to the account, default is 0.
* Price - price paid for the slot in LPTokens.
* AuthAccounts - up to four accounts authorized to trade at
* the discounted fee.
* @see [XLS30d:Continuous Auction
* Mechanism](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMBid : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMBid(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
class AMMClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
TER
applyGuts(Sandbox& view);
/** Withdraw both assets by providing maximum amount of asset1,
* asset2's amount will be calculated according to the current proportion.
* Since it is two-asset withdrawal, tfee is omitted.
* @param view
* @param ammAccount current AMM account
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @return
*/
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawMatchingOneAmount(
Sandbox& view,
SLE const& ammSle,
AccountID const& holder,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& holdLPtokens,
STAmount const& amount);
};
} // namespace xrpl

View File

@@ -0,0 +1,96 @@
#pragma once
#include <xrpl/protocol/AccountID.h>
#include <cstdint>
namespace xrpl {
/** Maintains AMM info per overall payment engine execution and
* individual iteration.
* Only one instance of this class is created in Flow.cpp::flow().
* The reference is percolated through calls to AMMLiquidity class,
* which handles AMM offer generation.
*/
class AMMContext
{
public:
// Restrict number of AMM offers. If this restriction is removed
// then need to restrict in some other way because AMM offers are
// not counted in the BookStep offer counter.
constexpr static std::uint8_t MaxIterations = 30;
private:
// Tx account owner is required to get the AMM trading fee in BookStep
AccountID account_;
// true if payment has multiple paths
bool multiPath_{false};
// Is true if AMM offer is consumed during a payment engine iteration.
bool ammUsed_{false};
// Counter of payment engine iterations with consumed AMM
std::uint16_t ammIters_{0};
public:
AMMContext(AccountID const& account, bool multiPath) : account_(account), multiPath_(multiPath)
{
}
~AMMContext() = default;
AMMContext(AMMContext const&) = delete;
AMMContext&
operator=(AMMContext const&) = delete;
bool
multiPath() const
{
return multiPath_;
}
void
setMultiPath(bool fs)
{
multiPath_ = fs;
}
void
setAMMUsed()
{
ammUsed_ = true;
}
void
update()
{
if (ammUsed_)
++ammIters_;
ammUsed_ = false;
}
bool
maxItersReached() const
{
return ammIters_ >= MaxIterations;
}
std::uint16_t
curIters() const
{
return ammIters_;
}
AccountID
account() const
{
return account_;
}
/** Strand execution may fail. Reset the flag at the start
* of each payment engine iteration.
*/
void
clear()
{
ammUsed_ = false;
}
};
} // namespace xrpl

View File

@@ -0,0 +1,63 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMCreate implements Automatic Market Maker(AMM) creation Transactor.
* It creates a new AMM instance with two tokens. Any trader, or Liquidity
* Provider (LP), can create the AMM instance and receive in return shares
* of the AMM pool in the form of LPTokens. The number of tokens that LP gets
* are determined by LPTokens = sqrt(A * B), where A and B is the current
* composition of the AMM pool. LP can add (AMMDeposit) or withdraw
* (AMMWithdraw) tokens from AMM and
* AMM can be used transparently in the payment or offer crossing transactions.
* Trading fee is charged to the traders for the trades executed against
* AMM instance. The fee is added to the AMM pool and distributed to the LPs
* in proportion to the LPTokens upon liquidity removal. The fee can be voted
* on by LP's (AMMVote). LP's can continuously bid (AMMBid) for the 24 hour
* auction slot, which enables LP's to trade at zero trading fee.
* AMM instance creates AccountRoot object with disabled master key
* for book-keeping of XRP balance if one of the tokens
* is XRP, a trustline for each IOU token, a trustline to keep track
* of LPTokens, and ltAMM ledger object. AccountRoot ID is generated
* internally from the parent's hash. ltAMM's object ID is
* hash{token1.currency, token1.issuer, token2.currency, token2.issuer}, where
* issue1 < issue2. ltAMM object provides mapping from the hash to AccountRoot
* ID and contains: AMMAccount - AMM AccountRoot ID. TradingFee - AMM voted
* TradingFee. VoteSlots - Array of VoteEntry, contains fee vote information.
* AuctionSlot - Auction slot, contains discounted fee bid information.
* LPTokenBalance - LPTokens outstanding balance.
* AMMToken - currency/issuer information for AMM tokens.
* AMMDeposit, AMMWithdraw, AMMVote, and AMMBid transactions use the hash
* to access AMM instance.
* @see [XLS30d:Creating AMM instance on
* XRPL](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
/** Attempt to create the AMM instance. */
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMDelete implements AMM delete transactor. This is a mechanism to
* delete AMM in an empty state when the number of LP tokens is 0.
* AMMDelete deletes the trustlines up to configured maximum. If all
* trustlines are deleted then AMM ltAMM and root account are deleted.
* Otherwise AMMDelete should be called again.
*/
class AMMDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,231 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
/** AMMDeposit implements AMM deposit Transactor.
* The deposit transaction is used to add liquidity to the AMM instance pool,
* thus obtaining some share of the instance's pools in the form of LPTokens.
* If the trader deposits proportional values of both assets without changing
* their relative price, then no trading fee is charged on the transaction.
* The trader can specify different combination of the fields in the deposit.
* LPTokens - transaction assumes proportional deposit of pools assets in
* exchange for the specified amount of LPTokens of the AMM instance.
* Asset1In - transaction assumes single asset deposit of the amount of asset
* specified by Asset1In. This is essentially a swap and an equal asset
* deposit.
* Asset1In and Asset2In - transaction assumes proportional deposit of pool
* assets with the constraints on the maximum amount of each asset that
* the trader is willing to deposit.
* Asset1In and LPTokens - transaction assumes that a single asset asset1
* is deposited to obtain some share of the AMM instance's pools
* represented by amount of LPTokens.
* Asset1In and EPrice - transaction assumes single asset deposit with
* the following two constraints:
* a. amount of asset1 if specified (not 0) in Asset1In specifies the
* maximum amount of asset1 that the trader is willing to deposit b. The
* effective-price of the LPTokens traded out does not exceed the specified
* EPrice. Following updates after a successful AMMDeposit transaction: The
* deposited asset, if XRP, is transferred from the account that initiated the
* transaction to the AMM instance account, thus changing the Balance field of
* each account. The deposited asset, if tokens, are balanced between the AMM
* account and the issuer account trustline. The LPTokens are issued by the AMM
* instance account to the account that initiated the transaction and a new
* trustline is created, if there does not exist one. The pool composition is
* updated.
* @see [XLS30d:AMMDeposit
* transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMDeposit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMDeposit(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);
/** Deposit requested assets and token amount into LP account.
* Return new total LPToken balance.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amountDeposit
* @param amount2Deposit
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param depositMin minimum accepted amount deposit
* @param deposit2Min minimum accepted amount2 deposit
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
deposit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amountDeposit,
std::optional<STAmount> const& amount2Deposit,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::optional<STAmount> const& depositMin,
std::optional<STAmount> const& deposit2Min,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Equal asset deposit (LPTokens) for the specified share of
* the AMM instance pools. The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param depositMin minimum accepted amount deposit
* @param deposit2Min minimum accepted amount2 deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalDepositTokens(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::optional<STAmount> const& depositMin,
std::optional<STAmount> const& deposit2Min,
std::uint16_t tfee);
/** Equal asset deposit (Asset1In, Asset2In) with the constraint on
* the maximum amount of both assets that the trader is willing to deposit.
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount maximum asset1 deposit amount
* @param amount2 maximum asset2 deposit amount
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalDepositLimit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& amount2,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Single asset deposit (Asset1In) by the amount.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount requested asset1 deposit amount
* @param lpTokensDepositMin minimum accepted LPTokens deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleDeposit(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee);
/** Single asset deposit (Asset1In, LPTokens) by the tokens.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount max asset1 to deposit
* @param lptAMMBalance current AMM LPT balance
* @param lpTokensDeposit amount of tokens to deposit
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleDepositTokens(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& lpTokensDeposit,
std::uint16_t tfee);
/** Single asset deposit (Asset1In, EPrice) with two constraints.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount requested asset1 deposit amount
* @param lptAMMBalance current AMM LPT balance
* @param ePrice maximum effective price
* @param tfee
* @return
*/
std::pair<TER, STAmount>
singleDepositEPrice(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& ePrice,
std::uint16_t tfee);
/** Equal deposit in empty AMM state (LP tokens balance is 0)
* @param view
* @param ammAccount
* @param amount requested asset1 deposit amount
* @param amount2 requested asset2 deposit amount
* @param tfee
* @return
*/
std::pair<TER, STAmount>
equalDepositInEmptyState(
Sandbox& view,
AccountID const& ammAccount,
STAmount const& amount,
STAmount const& amount2,
Issue const& lptIssue,
std::uint16_t tfee);
};
} // namespace xrpl

View File

@@ -0,0 +1,689 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
namespace xrpl {
namespace detail {
Number
reduceOffer(auto const& amount)
{
static Number const reducedOfferPct(9999, -4);
// Make sure the result is always less than amount or zero.
NumberRoundModeGuard mg(Number::towards_zero);
return amount * reducedOfferPct;
}
} // namespace detail
enum class IsDeposit : bool { No = false, Yes = true };
/** Calculate LP Tokens given AMM pool reserves.
* @param asset1 AMM one side of the pool reserve
* @param asset2 AMM another side of the pool reserve
* @return LP Tokens as IOU
*/
STAmount
ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue);
/** Calculate LP Tokens given asset's deposit amount.
* @param asset1Balance current AMM asset1 balance
* @param asset1Deposit requested asset1 deposit amount
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return tokens
*/
STAmount
lpTokensOut(
STAmount const& asset1Balance,
STAmount const& asset1Deposit,
STAmount const& lptAMMBalance,
std::uint16_t tfee);
/** Calculate asset deposit given LP Tokens.
* @param asset1Balance current AMM asset1 balance
* @param lpTokens LP Tokens
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return
*/
STAmount
ammAssetIn(STAmount const& asset1Balance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
/** Calculate LP Tokens given asset's withdraw amount. Return 0
* if can't calculate.
* @param asset1Balance current AMM asset1 balance
* @param asset1Withdraw requested asset1 withdraw amount
* @param lptAMMBalance AMM LPT balance
* @param tfee trading fee in basis points
* @return tokens out amount
*/
STAmount
lpTokensIn(
STAmount const& asset1Balance,
STAmount const& asset1Withdraw,
STAmount const& lptAMMBalance,
std::uint16_t tfee);
/** Calculate asset withdrawal by tokens
* @param assetBalance balance of the asset being withdrawn
* @param lptAMMBalance total AMM Tokens balance
* @param lpTokens LP Tokens balance
* @param tfee trading fee in basis points
* @return calculated asset amount
*/
STAmount
ammAssetOut(STAmount const& assetBalance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
/** Check if the relative distance between the qualities
* is within the requested distance.
* @param calcQuality calculated quality
* @param reqQuality requested quality
* @param dist requested relative distance
* @return true if within dist, false otherwise
*/
inline bool
withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Number const& dist)
{
if (calcQuality == reqQuality)
return true;
auto const [min, max] = std::minmax(calcQuality, reqQuality);
// Relative distance is (max - min)/max. Can't use basic operations
// on Quality. Have to use Quality::rate() instead, which
// is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
return ((min.rate() - max.rate()) / min.rate()) < dist;
}
/** Check if the relative distance between the amounts
* is within the requested distance.
* @param calc calculated amount
* @param req requested amount
* @param dist requested relative distance
* @return true if within dist, false otherwise
*/
// clang-format off
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>)
bool
withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
{
if (calc == req)
return true;
auto const [min, max] = std::minmax(calc, req);
return ((max - min) / max) < dist;
}
// clang-format on
/** Solve quadratic equation to find takerGets or takerPays. Round
* to minimize the amount in order to maximize the quality.
*/
std::optional<Number>
solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
/** Generate AMM offer starting with takerGets when AMM pool
* from the payment perspective is IOU(in)/XRP(out)
* Equations:
* Spot Price Quality after the offer is consumed:
* Qsp = (O - o) / (I + i) -- equation (1)
* where O is poolPays, I is poolGets, o is takerGets, i is takerPays
* Swap out:
* i = (I * o) / (O - o) * f -- equation (2)
* where f is (1 - tfee/100000), tfee is in basis points
* Effective price targetQuality:
* Qep = o / i -- equation (3)
* There are two scenarios to consider
* A) Qsp = Qep. Substitute i in (1) with (2) and solve for o
* and Qsp = targetQuality(Qt):
* o**2 + o * (I * Qt * (1 - 1 / f) - 2 * O) + O**2 - Qt * I * O = 0
* B) Qep = Qsp. Substitute i in (3) with (2) and solve for o
* and Qep = targetQuality(Qt):
* o = O - I * Qt / f
* Since the scenario is not known a priori, both A and B are solved and
* the lowest value of o is takerGets. takerPays is calculated with
* swap out eq (2). If o is less or equal to 0 then the offer can't
* be generated.
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
getAMMOfferStartWithTakerGets(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t const& tfee)
{
if (targetQuality.rate() == beast::zero)
return std::nullopt;
NumberRoundModeGuard mg(Number::to_nearest);
auto const f = feeMult(tfee);
auto const a = 1;
auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
auto const c = pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
if (!nTakerGets || *nTakerGets <= 0)
return std::nullopt; // LCOV_EXCL_LINE
auto const nTakerGetsConstraint = pool.out - pool.in / (targetQuality.rate() * f);
if (nTakerGetsConstraint <= 0)
return std::nullopt;
// Select the smallest to maximize the quality
if (nTakerGetsConstraint < *nTakerGets)
nTakerGets = nTakerGetsConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
// 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);
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
};
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto const amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.out));
else
return amounts;
}
/** Generate AMM offer starting with takerPays when AMM pool
* from the payment perspective is XRP(in)/IOU(out) or IOU(in)/IOU(out).
* Equations:
* Spot Price Quality after the offer is consumed:
* Qsp = (O - o) / (I + i) -- equation (1)
* where O is poolPays, I is poolGets, o is takerGets, i is takerPays
* Swap in:
* o = (O * i * f) / (I + i * f) -- equation (2)
* where f is (1 - tfee/100000), tfee is in basis points
* Effective price quality:
* Qep = o / i -- equation (3)
* There are two scenarios to consider
* A) Qsp = Qep. Substitute o in (1) with (2) and solve for i
* and Qsp = targetQuality(Qt):
* i**2 * f + i * I * (1 + f) + I**2 - I * O / Qt = 0
* B) Qep = Qsp. Substitute i in (3) with (2) and solve for i
* and Qep = targetQuality(Qt):
* i = O / Qt - I / f
* Since the scenario is not known a priori, both A and B are solved and
* the lowest value of i is takerPays. takerGets is calculated with
* swap in eq (2). If i is less or equal to 0 then the offer can't
* be generated.
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
getAMMOfferStartWithTakerPays(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t tfee)
{
if (targetQuality.rate() == beast::zero)
return std::nullopt;
NumberRoundModeGuard mg(Number::to_nearest);
auto const f = feeMult(tfee);
auto const& a = f;
auto const b = pool.in * (1 + f);
auto const c = pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
if (!nTakerPays || nTakerPays <= 0)
return std::nullopt; // LCOV_EXCL_LINE
auto const nTakerPaysConstraint = pool.out * targetQuality.rate() - pool.in / f;
if (nTakerPaysConstraint <= 0)
return std::nullopt;
// Select the smallest to maximize the quality
if (nTakerPaysConstraint < *nTakerPays)
nTakerPays = nTakerPaysConstraint;
auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
// 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);
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
};
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto const amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.in));
else
return amounts;
}
/** Generate AMM offer so that either updated Spot Price Quality (SPQ)
* is equal to LOB quality (in this case AMM offer quality is
* better than LOB quality) or AMM offer is equal to LOB quality
* (in this case SPQ is better than LOB quality).
* Pre-amendment code calculates takerPays first. If takerGets is XRP,
* it is rounded down, which results in worse offer quality than
* LOB quality, and the offer might fail to generate.
* Post-amendment code calculates the XRP offer side first. The result
* is rounded down, which makes the offer quality better.
* It might not be possible to match either SPQ or AMM offer to LOB
* quality. This generally happens at higher fees.
* @param pool AMM pool balances
* @param quality requested quality
* @param tfee trading fee in basis points
* @return seated in/out amounts if the quality can be changed
*/
template <typename TIn, typename TOut>
std::optional<TAmounts<TIn, TOut>>
changeSpotPriceQuality(
TAmounts<TIn, TOut> const& pool,
Quality const& quality,
std::uint16_t tfee,
Rules const& rules,
beast::Journal j)
{
if (!rules.enabled(fixAMMv1_1))
{
// Finds takerPays (i) and takerGets (o) such that given pool
// composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
// Where takerGets is calculated as the swapAssetIn (see below).
// The above equation produces the quadratic equation:
// i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
// which is solved for i, and o is found with swapAssetIn().
auto const f = feeMult(tfee); // 1 - fee
auto const& a = f;
auto const b = pool.in * (1 + f);
Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate();
if (auto const res = b * b - 4 * a * c; res < 0)
return std::nullopt; // LCOV_EXCL_LINE
else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0)
{
auto const nTakerPays = [&]() {
// The fee might make the AMM offer quality less than CLOB
// quality. Therefore, AMM offer has to satisfy this constraint:
// o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
// q - I / (1 - fee).
auto const nTakerPaysConstraint = pool.out * quality.rate() - pool.in / f;
if (nTakerPaysPropose > nTakerPaysConstraint)
return nTakerPaysConstraint;
return nTakerPaysPropose;
}();
if (nTakerPays <= 0)
{
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " "
<< to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto const amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality && !withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
{
JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out)
<< " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
else
{
JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
<< to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
return amounts;
}
}
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " " << to_string(pool.out)
<< " " << quality << " " << tfee;
return std::nullopt;
}
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto const amounts = [&]() {
if (isXRP(getIssue(pool.out)))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
}();
if (!amounts)
{
JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< quality << " " << tfee << std::endl;
return std::nullopt;
}
if (Quality{*amounts} < quality)
{
JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
return std::nullopt;
}
JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " " << to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
return amounts;
}
/** AMM pool invariant - the product (A * B) after swap in/out has to remain
* at least the same: (A + in) * (B - out) >= A * B
* XRP round-off may result in a smaller product after swap in/out.
* To address this:
* - if on swapIn the out is XRP then the amount is round-off
* downward, making the product slightly larger since out
* value is reduced.
* - if on swapOut the in is XRP then the amount is round-off
* upward, making the product slightly larger since in
* value is increased.
*/
/** Swap assetIn into the pool and swap out a proportional amount
* of the other asset. Implements AMM Swap in.
* @see [XLS30d:AMM
* Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78)
* @param pool current AMM pool balances
* @param assetIn amount to swap in
* @param tfee trading fee in basis points
* @return
*/
template <typename TIn, typename TOut>
TOut
swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t tfee)
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// and explicitly set the rounding modes
// Favoring the amm means we should:
// minimize:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// maximize:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out)
// minimize:
// (pool.in + assetIn * feeMult(tfee)),
// minimize:
// assetIn * feeMult(tfee)
// feeMult is: (1-fee), fee is tfee/100000
// minimize:
// 1-fee
// maximize:
// fee
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0)
return toAmount<TOut>(getIssue(pool.out), 0);
Number::setround(Number::upward);
auto const ratio = numerator / denom;
Number::setround(Number::downward);
auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0)
return toAmount<TOut>(getIssue(pool.out), 0);
return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
}
else
{
return toAmount<TOut>(
getIssue(pool.out),
pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::downward);
}
}
/** Swap assetOut out of the pool and swap in a proportional amount
* of the other asset. Implements AMM Swap out.
* @see [XLS30d:AMM
* Swap](https://github.com/XRPLF/XRPL-Standards/discussions/78)
* @param pool current AMM pool balances
* @param assetOut amount to swap out
* @param tfee trading fee in basis points
* @return
*/
template <typename TIn, typename TOut>
TIn
swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_t tfee)
{
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
saveNumberRoundMode _{Number::getround()};
Number::setround(Number::upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getIssue(pool.in));
}
Number::setround(Number::upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::downward);
auto const feeMult = 1 - fee;
Number::setround(Number::upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getIssue(pool.in), 0);
return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
}
else
{
return toAmount<TIn>(
getIssue(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
Number::upward);
}
}
/** Return square of n.
*/
Number
square(Number const& n);
/** Adjust LP tokens to deposit/withdraw.
* Amount type keeps 16 digits. Maintaining the LP balance by adding
* deposited tokens or subtracting withdrawn LP tokens from LP balance
* results in losing precision in LP balance. I.e. the resulting LP balance
* is less than the actual sum of LP tokens. To adjust for this, subtract
* old tokens balance from the new one for deposit or vice versa for
* withdraw to cancel out the precision loss.
* @param lptAMMBalance LPT AMM Balance
* @param lpTokens LP tokens to deposit or withdraw
* @param isDeposit Yes if deposit, No if withdraw
*/
STAmount
adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit);
/** Calls adjustLPTokens() and adjusts deposit or withdraw amounts if
* the adjusted LP tokens are less than the provided LP tokens.
* @param amountBalance asset1 pool balance
* @param amount asset1 to deposit or withdraw
* @param amount2 asset2 to deposit or withdraw
* @param lptAMMBalance LPT AMM Balance
* @param lpTokens LP tokens to deposit or withdraw
* @param tfee trading fee in basis points
* @param isDeposit Yes if deposit, No if withdraw
* @return
*/
std::tuple<STAmount, std::optional<STAmount>, STAmount>
adjustAmountsByLPTokens(
STAmount const& amountBalance,
STAmount const& amount,
std::optional<STAmount> const& amount2,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
std::uint16_t tfee,
IsDeposit isDeposit);
/** Positive solution for quadratic equation:
* x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
*/
Number
solveQuadraticEq(Number const& a, Number const& b, Number const& c);
STAmount
multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
namespace detail {
inline Number::rounding_mode
getLPTokenRounding(IsDeposit isDeposit)
{
// Minimize on deposit, maximize on withdraw to ensure
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
}
inline Number::rounding_mode
getAssetRounding(IsDeposit isDeposit)
{
// Maximize on deposit, minimize on withdraw to ensure
// AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
}
} // namespace detail
/** Round AMM equal deposit/withdrawal amount. Deposit/withdrawal formulas
* calculate the amount as a fractional value of the pool balance. The rounding
* takes place on the last step of multiplying the balance by the fraction if
* AMMv1_3 is enabled.
*/
template <typename A>
STAmount
getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDeposit isDeposit)
{
if (!rules.enabled(fixAMMv1_3))
{
if constexpr (std::is_same_v<A, STAmount>)
return multiply(balance, frac, balance.issue());
else
return toSTAmount(balance.issue(), balance * frac);
}
auto const rm = detail::getAssetRounding(isDeposit);
return multiply(balance, frac, rm);
}
/** Round AMM single deposit/withdrawal amount.
* The lambda's are used to delay evaluation until the function
* is executed so that the calculation is not done twice. noRoundCb() is
* called if AMMv1_3 is disabled. Otherwise, the rounding is set and
* the amount is:
* isDeposit is Yes - the balance multiplied by productCb()
* isDeposit is No - the result of productCb(). The rounding is
* the same for all calculations in productCb()
*/
STAmount
getRoundedAsset(
Rules const& rules,
std::function<Number()>&& noRoundCb,
STAmount const& balance,
std::function<Number()>&& productCb,
IsDeposit isDeposit);
/** Round AMM deposit/withdrawal LPToken amount. Deposit/withdrawal formulas
* calculate the lptokens as a fractional value of the AMM total lptokens.
* The rounding takes place on the last step of multiplying the balance by
* the fraction if AMMv1_3 is enabled. The tokens are then
* adjusted to factor in the loss in precision (we only keep 16 significant
* digits) when adding the lptokens to the balance.
*/
STAmount
getRoundedLPTokens(Rules const& rules, STAmount const& balance, Number const& frac, IsDeposit isDeposit);
/** Round AMM single deposit/withdrawal LPToken amount.
* The lambda's are used to delay evaluation until the function is executed
* so that the calculations are not done twice.
* noRoundCb() is called if AMMv1_3 is disabled. Otherwise, the rounding is set
* and the lptokens are:
* if isDeposit is Yes - the result of productCb(). The rounding is
* the same for all calculations in productCb()
* if isDeposit is No - the balance multiplied by productCb()
* The lptokens are then adjusted to factor in the loss in precision
* (we only keep 16 significant digits) when adding the lptokens to the balance.
*/
STAmount
getRoundedLPTokens(
Rules const& rules,
std::function<Number()>&& noRoundCb,
STAmount const& lptAMMBalance,
std::function<Number()>&& productCb,
IsDeposit isDeposit);
/* Next two functions adjust asset in/out amount to factor in the adjusted
* lptokens. The lptokens are calculated from the asset in/out. The lptokens are
* then adjusted to factor in the loss in precision. The adjusted lptokens might
* be less than the initially calculated tokens. Therefore, the asset in/out
* must be adjusted. The rounding might result in the adjusted amount being
* greater than the original asset in/out amount. If this happens,
* then the original amount is reduced by the difference in the adjusted amount
* and the original amount. The actual tokens and the actual adjusted amount
* are then recalculated. The minimum of the original and the actual
* adjusted amount is returned.
*/
std::pair<STAmount, STAmount>
adjustAssetInByTokens(
Rules const& rules,
STAmount const& balance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& tokens,
std::uint16_t tfee);
std::pair<STAmount, STAmount>
adjustAssetOutByTokens(
Rules const& rules,
STAmount const& balance,
STAmount const& amount,
STAmount const& lptAMMBalance,
STAmount const& tokens,
std::uint16_t tfee);
/** Find a fraction of tokens after the tokens are adjusted. The fraction
* is used to adjust equal deposit/withdraw amount.
*/
Number
adjustFracByTokens(Rules const& rules, STAmount const& lptAMMBalance, STAmount const& tokens, Number const& frac);
} // namespace xrpl

View File

@@ -0,0 +1,101 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.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

View File

@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/** AMMVote implements AMM vote Transactor.
* This transactor allows for the TradingFee of the AMM instance be a votable
* parameter. Any account (LP) that holds the corresponding LPTokens can cast
* a vote using the new AMMVote transaction. VoteSlots array in ltAMM object
* keeps track of upto eight active votes (VoteEntry) for the instance.
* VoteEntry contains:
* Account - account id that cast the vote.
* FeeVal - proposed fee in basis points.
* VoteWeight - LPTokens owned by the account in basis points.
* TradingFee is calculated as sum(VoteWeight_i * fee_i)/sum(VoteWeight_i).
* Every time AMMVote transaction is submitted, the transactor
* - Fails the transaction if the account doesn't hold LPTokens
* - Removes VoteEntry for accounts that don't hold LPTokens
* - If there are fewer than eight VoteEntry objects then add new VoteEntry
* object for the account.
* - If all eight VoteEntry slots are full, then remove VoteEntry that
* holds less LPTokens than the account. If all accounts hold more
* LPTokens then fail transaction.
* - If the account already holds a vote, then update VoteEntry.
* - Calculate and update TradingFee.
* @see [XLS30d:Governance: Trading Fee Voting
* Mechanism](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
class AMMVote : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMVote(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,294 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Sandbox;
/** AMMWithdraw implements AMM withdraw Transactor.
* The withdraw transaction is used to remove liquidity from the AMM instance
* pool, thus redeeming some share of the pools that one owns in the form
* of LPTokens. If the trader withdraws proportional values of both assets
* without changing their relative pricing, no trading fee is charged on
* the transaction. The trader can specify different combination of
* the fields in the withdrawal.
* LPTokens - transaction assumes proportional withdrawal of pool assets
* for the amount of LPTokens.
* Asset1Out - transaction assumes withdrawal of single asset equivalent
* to the amount specified in Asset1Out.
* Asset1Out and Asset2Out - transaction assumes all assets withdrawal
* with the constraints on the maximum amount of each asset that
* the trader is willing to withdraw.
* Asset1Out and LPTokens - transaction assumes withdrawal of single
* asset specified in Asset1Out proportional to the share represented
* by the amount of LPTokens.
* Asset1Out and EPrice - transaction assumes withdrawal of single
* asset with the following constraints:
* a. Amount of asset1 if specified (not 0) in Asset1Out specifies
* the minimum amount of asset1 that the trader is willing
* to withdraw.
* b. The effective price of asset traded out does not exceed
* the amount specified in EPrice.
* Following updates after a successful transaction:
* The withdrawn asset, if XRP, is transferred from AMM instance account
* to the account that initiated the transaction, thus changing
* the Balance field of each account.
* The withdrawn asset, if token, is balanced between the AMM instance
* account and the issuer account.
* The LPTokens ~ are balanced between the AMM instance account and
* the account that initiated the transaction.
* The pool composition is updated.
* @see [XLS30d:AMMWithdraw
* transaction](https://github.com/XRPLF/XRPL-Standards/discussions/78)
*/
enum class WithdrawAll : bool { No = false, Yes };
class AMMWithdraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit AMMWithdraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current LP asset1 balance
* @param amount2Balance current LP asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdrawing all lptokens
* @param priorBalance balance before fees
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHanding,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance and the withdrawn amounts for both
* assets.
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @param tfee trading fee in basis points
* @param withdrawAll if withdraw all lptokens
* @param priorBalance balance before fees
* @return
*/
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee,
FreezeHandling freezeHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
static std::pair<TER, bool>
deleteAMMAccountIfEmpty(
Sandbox& sb,
std::shared_ptr<SLE> const ammSle,
STAmount const& lpTokenBalance,
Issue const& issue1,
Issue const& issue2,
beast::Journal const& journal);
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);
/** Withdraw requested assets and token from AMM into LP account.
* Return new total LPToken balance.
* @param view
* @param ammSle AMM ledger entry
* @param ammAccount AMM account
* @param amountBalance current LP asset1 balance
* @param amountWithdraw asset1 withdraw amount
* @param amount2Withdraw asset2 withdraw amount
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @return
*/
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amountWithdraw,
std::optional<STAmount> const& amount2Withdraw,
STAmount const& lpTokensAMMBalance,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current LP asset1 balance
* @param amount2Balance current LP asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& lpTokens,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Withdraw both assets (Asset1Out, Asset2Out) with the constraints
* on the maximum amount of each asset that the trader is willing
* to withdraw. The trading fee is not charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param amount2Balance current AMM asset2 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param amount2 max asset2 withdraw amount
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& amount2Balance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& amount2,
std::uint16_t tfee);
/** Single asset withdrawal (Asset1Out) equivalent to the amount specified
* in Asset1Out. The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
std::uint16_t tfee);
/** Single asset withdrawal (Asset1Out, LPTokens) proportional
* to the share specified by tokens. The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& lpTokensWithdraw,
std::uint16_t tfee);
/** Withdraw single asset (Asset1Out, EPrice) with two constraints.
* The trading fee is charged.
* @param view
* @param ammAccount
* @param amountBalance current AMM asset1 balance
* @param lptAMMBalance current AMM LPT balance
* @param amount asset1 withdraw amount
* @param ePrice maximum asset1 effective price
* @param tfee trading fee in basis points
* @return
*/
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
STAmount const& lptAMMBalance,
STAmount const& amount,
STAmount const& ePrice,
std::uint16_t tfee);
/** Check from the flags if it's withdraw all */
static WithdrawAll
isWithdrawAll(STTx const& tx);
};
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Batch : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Batch(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static NotTEC
checkSign(PreclaimContext const& ctx);
TER
doApply() override;
static constexpr auto disabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY,
});
};
} // namespace xrpl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Change : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Change(ApplyContext& ctx) : Transactor(ctx)
{
}
TER
doApply() override;
void
preCompute() override;
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx)
{
return XRPAmount{0};
}
static TER
preclaim(PreclaimContext const& ctx);
private:
TER
applyAmendment();
TER
applyFee();
TER
applyUNLModify();
};
using EnableAmendment = Change;
using SetFee = Change;
using UNLModify = Change;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CancelCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CancelCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCancel = CancelCheck;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CashCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CashCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCash = CashCheck;
} // namespace xrpl

View File

@@ -0,0 +1,28 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CreateCheck : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CreateCheck(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using CheckCreate = CreateCheck;
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Clawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit Clawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,66 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CreateTicket : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
constexpr static std::uint32_t minValidCount = 1;
// A note on how the maxValidCount was determined. The goal is for
// a single TicketCreate transaction to not use more compute power than
// a single compute-intensive Payment.
//
// Timing was performed using a MacBook Pro laptop and a release build
// with asserts off. 20 measurements were taken of each of the Payment
// and TicketCreate transactions and averaged to get timings.
//
// For the example compute-intensive Payment a Discrepancy unit test
// unit test Payment with 3 paths was chosen. With all the latest
// amendments enabled, that Payment::doApply() operation took, on
// average, 1.25 ms.
//
// Using that same test set up creating 250 Tickets in a single
// CreateTicket::doApply() in a unit test took, on average, 1.21 ms.
//
// So, for the moment, a single transaction creating 250 Tickets takes
// about the same compute time as a single compute-intensive payment.
//
// October 2018.
constexpr static std::uint32_t maxValidCount = 250;
// The maximum number of Tickets an account may hold. If a
// TicketCreate would cause an account to own more than this many
// tickets, then the TicketCreate will fail.
//
// The number was chosen arbitrarily and is an effort toward avoiding
// ledger-stuffing with Tickets.
constexpr static std::uint32_t maxTicketThreshold = 250;
explicit CreateTicket(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static NotTEC
preflight(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static TER
preclaim(PreclaimContext const& ctx);
/** Precondition: fee collection is likely. Attempt to create ticket(s). */
TER
doApply() override;
};
using TicketCreate = CreateTicket;
} // namespace xrpl

View File

@@ -0,0 +1,77 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CredentialCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class CredentialDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class CredentialAccept : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CredentialAccept(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DIDSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class DIDDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DIDDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner);
static TER
deleteSLE(ApplyView& view, std::shared_ptr<SLE> sle, AccountID const owner, beast::Journal j);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,30 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DelegateSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DelegateSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
// Interface used by DeleteAccount
static TER
deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, AccountID const& account, beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
namespace xrpl {
/**
* Check if the delegate account has permission to execute the transaction.
* @param delegate The delegate account.
* @param tx The transaction that the delegate account intends to execute.
* @return tesSUCCESS if the transaction is allowed, terNO_DELEGATE_PERMISSION
* if not.
*/
NotTEC
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
/**
* Load the granular permissions granted to the delegate account for the
* specified transaction type
* @param delegate The delegate account.
* @param type Used to determine which granted granular permissions to load,
* based on the transaction type.
* @param granularPermissions Granted granular permissions tied to the
* transaction type.
*/
void
loadGranularPermission(
std::shared_ptr<SLE const> const& delegate,
TxType const& type,
std::unordered_set<GranularPermissionType>& granularPermissions);
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DeleteAccount : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit DeleteAccount(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using AccountDelete = DeleteAccount;
} // namespace xrpl

View File

@@ -0,0 +1,40 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
Price Oracle is a system that acts as a bridge between
a blockchain network and the external world, providing off-chain price data
to decentralized applications (dApps) on the blockchain. This implementation
conforms to the requirements specified in the XLS-47d.
The DeleteOracle transactor implements the deletion of Oracle objects.
*/
class DeleteOracle : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DeleteOracle(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
static TER
deleteOracle(ApplyView& view, std::shared_ptr<SLE> const& sle, AccountID const& account, beast::Journal j);
};
using OracleDelete = DeleteOracle;
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class DepositPreauth : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit DepositPreauth(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
// Interface used by DeleteAccount
static TER
removeFromLedger(ApplyView& view, uint256 const& delIndex, beast::Journal j);
};
} // namespace xrpl

View File

@@ -0,0 +1,80 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class EscrowCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit EscrowCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class EscrowFinish : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit EscrowFinish(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class EscrowCancel : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit EscrowCancel(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LedgerStateFix : public Transactor
{
public:
enum FixType : std::uint16_t {
nfTokenPageLink = 1,
};
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LedgerStateFix(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,467 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/st.h>
namespace xrpl {
struct PreflightContext;
// Lending protocol has dependencies, so capture them here.
bool
checkLendingProtocolDependencies(PreflightContext const& ctx);
static constexpr std::uint32_t secondsInYear = 365 * 24 * 60 * 60;
Number
loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
/// Ensure the periodic payment is always rounded consistently
inline Number
roundPeriodicPayment(Asset const& asset, Number const& periodicPayment, std::int32_t scale)
{
return roundToAsset(asset, periodicPayment, scale, Number::upward);
}
/* Represents the breakdown of amounts to be paid and changes applied to the
* Loan object while processing a loan payment.
*
* This structure is returned after processing a loan payment transaction and
* captures the amounts that need to be paid. The actual ledger entry changes
* are made in LoanPay based on this structure values.
*
* The sum of principalPaid, interestPaid, and feePaid represents the total
* amount to be deducted from the borrower's account. The valueChange field
* tracks whether the loan's total value increased or decreased beyond normal
* amortization.
*
* This structure is explained in the XLS-66 spec, section 3.2.4.2 (Payment
* Processing).
*/
struct LoanPaymentParts
{
// The amount of principal paid that reduces the loan balance.
// This amount is subtracted from sfPrincipalOutstanding in the Loan object
// and paid to the Vault
Number principalPaid = numZero;
// The total amount of interest paid to the Vault.
// This includes:
// - Tracked interest from the amortization schedule
// - Untracked interest (e.g., late payment penalty interest)
// This value is always non-negative.
Number interestPaid = numZero;
// The change in the loan's total value outstanding.
// - If valueChange < 0: Loan value decreased
// - If valueChange > 0: Loan value increased
// - If valueChange = 0: No value adjustment
//
// For regular on-time payments, this is always 0. Non-zero values occur
// when:
// - Overpayments reduce the loan balance beyond the scheduled amount
// - Late payments add penalty interest to the loan value
// - Early full payment may increase or decrease the loan value based on
// terms
Number valueChange = numZero;
/* The total amount of fees paid to the Broker.
* This includes:
* - Tracked management fees from the amortization schedule
* - Untracked fees (e.g., late payment fees, service fees, origination
* fees) This value is always non-negative.
*/
Number feePaid = numZero;
LoanPaymentParts&
operator+=(LoanPaymentParts const& other);
bool
operator==(LoanPaymentParts const& other) const;
};
/** This structure captures the parts of a loan state.
*
* Whether the values are theoretical (unrounded) or rounded will depend on how
* it was computed.
*
* Many of the fields can be derived from each other, but they're all provided
* here to reduce code duplication and possible mistakes.
* e.g.
* * interestOutstanding = valueOutstanding - principalOutstanding
* * interestDue = interestOutstanding - managementFeeDue
*/
struct LoanState
{
// Total value still due to be paid by the borrower.
Number valueOutstanding;
// Principal still due to be paid by the borrower.
Number principalOutstanding;
// Interest still due to be paid to the Vault.
// This is a portion of interestOutstanding
Number interestDue;
// Management fee still due to be paid to the broker.
// This is a portion of interestOutstanding
Number managementFeeDue;
// Interest still due to be paid by the borrower.
Number
interestOutstanding() const
{
XRPL_ASSERT_PARTS(
interestDue + managementFeeDue == valueOutstanding - principalOutstanding,
"xrpl::LoanState::interestOutstanding",
"other values add up correctly");
return interestDue + managementFeeDue;
}
};
/* Describes the initial computed properties of a loan.
*
* This structure contains the fundamental calculated values that define a
* loan's payment structure and amortization schedule. These properties are
* computed:
* - At loan creation (LoanSet transaction)
* - When loan terms change (e.g., after an overpayment that reduces the loan
* balance)
*/
struct LoanProperties
{
// The unrounded amount to be paid at each regular payment period.
// Calculated using the standard amortization formula based on principal,
// interest rate, and number of payments.
// The actual amount paid in the LoanPay transaction must be rounded up to
// the precision of the asset and loan.
Number periodicPayment;
// The loan's current state, with all values rounded to the loan's scale.
LoanState loanState;
// The scale (decimal places) used for rounding all loan amounts.
// This is the maximum of:
// - The asset's native scale
// - A minimum scale required to represent the periodic payment accurately
// All loan state values (principal, interest, fees) are rounded to this
// scale.
std::int32_t loanScale;
// The principal portion of the first payment.
Number firstPaymentPrincipal;
};
// Some values get re-rounded to the vault scale any time they are adjusted. In
// addition, they are prevented from ever going below zero. This helps avoid
// accumulated rounding errors and leftover dust amounts.
template <class NumberProxy>
void
adjustImpreciseNumber(NumberProxy value, Number const& adjustment, Asset const& asset, int vaultScale)
{
value = roundToAsset(asset, value + adjustment, vaultScale);
if (*value < beast::zero)
value = 0;
}
inline int
getAssetsTotalScale(SLE::const_ref vaultSle)
{
if (!vaultSle)
return Number::minExponent - 1; // LCOV_EXCL_LINE
return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}.exponent();
}
TER
checkLoanGuards(
Asset const& vaultAsset,
Number const& principalRequested,
bool expectInterest,
std::uint32_t paymentTotal,
LoanProperties const& properties,
beast::Journal j);
LoanState
computeTheoreticalLoanState(
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t const paymentRemaining,
TenthBips32 const managementFeeRate);
// Constructs a valid LoanState object from arbitrary inputs
LoanState
constructLoanState(
Number const& totalValueOutstanding,
Number const& principalOutstanding,
Number const& managementFeeOutstanding);
// Constructs a valid LoanState object from a Loan object, which always has
// rounded values
LoanState
constructRoundedLoanState(SLE::const_ref loan);
Number
computeManagementFee(Asset const& asset, Number const& interest, TenthBips32 managementFeeRate, std::int32_t scale);
Number
computeFullPaymentInterest(
Number const& theoreticalPrincipalOutstanding,
Number const& periodicRate,
NetClock::time_point parentCloseTime,
std::uint32_t paymentInterval,
std::uint32_t prevPaymentDate,
std::uint32_t startDate,
TenthBips32 closeInterestRate);
namespace detail {
// These classes and functions should only be accessed by LendingHelper
// functions and unit tests
enum class PaymentSpecialCase { none, final, extra };
/* Represents a single loan payment component parts.
* This structure captures the "delta" (change) values that will be applied to
* the tracked fields in the Loan ledger object when a payment is processed.
*
* These are called "deltas" because they represent the amount by which each
* corresponding field in the Loan object will be reduced.
* They are "tracked" as they change tracked loan values.
*/
struct PaymentComponents
{
// The change in total value outstanding for this payment.
// This amount will be subtracted from sfTotalValueOutstanding in the Loan
// object. Equal to the sum of trackedPrincipalDelta,
// trackedInterestPart(), and trackedManagementFeeDelta.
Number trackedValueDelta;
// The change in principal outstanding for this payment.
// This amount will be subtracted from sfPrincipalOutstanding in the Loan
// object, representing the portion of the payment that reduces the
// original loan amount.
Number trackedPrincipalDelta;
// The change in management fee outstanding for this payment.
// This amount will be subtracted from sfManagementFeeOutstanding in the
// Loan object. This represents only the tracked management fees from the
// amortization schedule and does not include additional untracked fees
// (such as late payment fees) that go directly to the broker.
Number trackedManagementFeeDelta;
// Indicates if this payment has special handling requirements.
// - none: Regular scheduled payment
// - final: The last payment that closes out the loan
// - extra: An additional payment beyond the regular schedule (overpayment)
PaymentSpecialCase specialCase = PaymentSpecialCase::none;
// Calculates the tracked interest portion of this payment.
// This is derived from the other components as:
// trackedValueDelta - trackedPrincipalDelta - trackedManagementFeeDelta
//
// @return The amount of tracked interest included in this payment that
// will be paid to the vault.
Number
trackedInterestPart() const;
};
/* Extends PaymentComponents with untracked payment amounts.
*
* This structure adds untracked fees and interest to the base
* PaymentComponents, representing amounts that don't affect the Loan object's
* tracked state but are still part of the total payment due from the borrower.
*
* Untracked amounts include:
* - Late payment fees that go directly to the Broker
* - Late payment penalty interest that goes directly to the Vault
* - Service fees
*
* The key distinction is that tracked amounts reduce the Loan object's state
* (sfTotalValueOutstanding, sfPrincipalOutstanding,
* sfManagementFeeOutstanding), while untracked amounts are paid directly to the
* recipient without affecting the loan's amortization schedule.
*/
struct ExtendedPaymentComponents : public PaymentComponents
{
// Additional management fees that go directly to the Broker.
// This includes fees not part of the standard amortization schedule
// (e.g., late fees, service fees, origination fees).
// This value may be negative, though the final value returned in
// LoanPaymentParts.feePaid will never be negative.
Number untrackedManagementFee;
// Additional interest that goes directly to the Vault.
// This includes interest not part of the standard amortization schedule
// (e.g., late payment penalty interest).
// This value may be negative, though the final value returned in
// LoanPaymentParts.interestPaid will never be negative.
Number untrackedInterest;
// The complete amount due from the borrower for this payment.
// Calculated as: trackedValueDelta + untrackedInterest +
// untrackedManagementFee
//
// This value is used to validate that the payment amount provided by the
// borrower is sufficient to cover all components of the payment.
Number totalDue;
ExtendedPaymentComponents(PaymentComponents const& p, Number fee, Number interest = numZero)
: PaymentComponents(p)
, untrackedManagementFee(fee)
, untrackedInterest(interest)
, totalDue(trackedValueDelta + untrackedInterest + untrackedManagementFee)
{
}
};
/* Represents the differences between two loan states.
*
* This structure is used to capture the change in each component of a loan's
* state, typically when computing the difference between two LoanState objects
* (e.g., before and after a payment). It is a convenient way to capture changes
* in each component. How that difference is used depends on the context.
*/
struct LoanStateDeltas
{
// The difference in principal outstanding between two loan states.
Number principal;
// The difference in interest due between two loan states.
Number interest;
// The difference in management fee outstanding between two loan states.
Number managementFee;
/* Calculates the total change across all components.
* @return The sum of principal, interest, and management fee deltas.
*/
Number
total() const
{
return principal + interest + managementFee;
}
// Ensures all delta values are non-negative.
void
nonNegative();
};
Expected<std::pair<LoanPaymentParts, LoanProperties>, TER>
tryOverpayment(
Asset const& asset,
std::int32_t loanScale,
ExtendedPaymentComponents const& overpaymentComponents,
LoanState const& roundedLoanState,
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentRemaining,
TenthBips16 const managementFeeRate,
beast::Journal j);
Number
computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining);
Number
computePaymentFactor(Number const& periodicRate, std::uint32_t paymentsRemaining);
std::pair<Number, Number>
computeInterestAndFeeParts(
Asset const& asset,
Number const& interest,
TenthBips16 managementFeeRate,
std::int32_t loanScale);
Number
loanPeriodicPayment(Number const& principalOutstanding, Number const& periodicRate, std::uint32_t paymentsRemaining);
Number
loanPrincipalFromPeriodicPayment(
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentsRemaining);
Number
loanLatePaymentInterest(
Number const& principalOutstanding,
TenthBips32 lateInterestRate,
NetClock::time_point parentCloseTime,
std::uint32_t nextPaymentDueDate);
Number
loanAccruedInterest(
Number const& principalOutstanding,
Number const& periodicRate,
NetClock::time_point parentCloseTime,
std::uint32_t startDate,
std::uint32_t prevPaymentDate,
std::uint32_t paymentInterval);
ExtendedPaymentComponents
computeOverpaymentComponents(
Asset const& asset,
int32_t const loanScale,
Number const& overpayment,
TenthBips32 const overpaymentInterestRate,
TenthBips32 const overpaymentFeeRate,
TenthBips16 const managementFeeRate);
PaymentComponents
computePaymentComponents(
Asset const& asset,
std::int32_t scale,
Number const& totalValueOutstanding,
Number const& principalOutstanding,
Number const& managementFeeOutstanding,
Number const& periodicPayment,
Number const& periodicRate,
std::uint32_t paymentRemaining,
TenthBips16 managementFeeRate);
} // namespace detail
detail::LoanStateDeltas
operator-(LoanState const& lhs, LoanState const& rhs);
LoanState
operator-(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
LoanState
operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs);
LoanProperties
computeLoanProperties(
Asset const& asset,
Number const& principalOutstanding,
TenthBips32 interestRate,
std::uint32_t paymentInterval,
std::uint32_t paymentsRemaining,
TenthBips32 managementFeeRate,
std::int32_t minimumScale);
LoanProperties
computeLoanProperties(
Asset const& asset,
Number const& principalOutstanding,
Number const& periodicRate,
std::uint32_t paymentsRemaining,
TenthBips32 managementFeeRate,
std::int32_t minimumScale);
bool
isRounded(Asset const& asset, Number const& value, std::int32_t scale);
// Indicates what type of payment is being made.
// regular, late, and full are mutually exclusive.
// overpayment is an "add on" to a regular payment, and follows that path with
// potential extra work at the end.
enum class LoanPaymentType { regular = 0, late, full, overpayment };
Expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::ref loan,
SLE::const_ref brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
beast::Journal j);
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverDeposit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverDeposit(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerCoverWithdraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerCoverWithdraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanBrokerSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static std::vector<OptionaledField<STNumber>> const&
getValueFields();
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanManage : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanManage(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
/** Helper function that might be needed by other transactors
*/
static TER
defaultLoan(
ApplyView& view,
SLE::ref loanSle,
SLE::ref brokerSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
/** Helper function that might be needed by other transactors
*/
static TER
impairLoan(ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, Asset const& vaultAsset, beast::Journal j);
/** Helper function that might be needed by other transactors
*/
[[nodiscard]] static TER
unimpairLoan(ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, Asset const& vaultAsset, beast::Journal j);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class LoanPay : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanPay(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,56 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/Lending/LendingHelpers.h>
namespace xrpl {
class LoanSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkSign(PreclaimContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static std::vector<OptionaledField<STNumber>> const&
getValueFields();
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
public:
static std::uint32_t constexpr minPaymentTotal = 1;
static std::uint32_t constexpr defaultPaymentTotal = 1;
static_assert(defaultPaymentTotal >= minPaymentTotal);
static std::uint32_t constexpr minPaymentInterval = 60;
static std::uint32_t constexpr defaultPaymentInterval = 60;
static_assert(defaultPaymentInterval >= minPaymentInterval);
static std::uint32_t constexpr defaultGracePeriod = 60;
static_assert(defaultGracePeriod >= minPaymentInterval);
};
//------------------------------------------------------------------------------
} // namespace xrpl

View File

@@ -0,0 +1,41 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
struct MPTAuthorizeArgs
{
XRPAmount const& priorBalance;
MPTID const& mptIssuanceID;
AccountID const& account;
std::uint32_t flags{};
std::optional<AccountID> holderID{};
};
class MPTokenAuthorize : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit MPTokenAuthorize(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
static TER
createMPToken(ApplyView& view, MPTID const& mptIssuanceID, AccountID const& account, std::uint32_t const flags);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,48 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
struct MPTCreateArgs
{
std::optional<XRPAmount> priorBalance;
AccountID const& account;
std::uint32_t sequence;
std::uint32_t flags = 0;
std::optional<std::uint64_t> maxAmount{};
std::optional<std::uint8_t> assetScale{};
std::optional<std::uint16_t> transferFee{};
std::optional<Slice> const& metadata{};
std::optional<uint256> domainId{};
std::optional<std::uint32_t> mutableFlags{};
};
class MPTokenIssuanceCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit MPTokenIssuanceCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
static Expected<MPTID, TER>
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
};
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class MPTokenIssuanceDestroy : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit MPTokenIssuanceDestroy(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class MPTokenIssuanceSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit MPTokenIssuanceSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,42 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class NFTokenAcceptOffer : public Transactor
{
private:
TER
pay(AccountID const& from, AccountID const& to, STAmount const& amount);
TER
acceptOffer(std::shared_ptr<SLE> const& offer);
TER
bridgeOffers(std::shared_ptr<SLE> const& buy, std::shared_ptr<SLE> const& sell);
TER
transferNFToken(AccountID const& buyer, AccountID const& seller, uint256 const& nfTokenID);
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenAcceptOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class NFTokenBurn : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenBurn(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class NFTokenCancelOffer : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenCancelOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class NFTokenCreateOffer : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenCreateOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,43 @@
#pragma once
#include <xrpl/protocol/nft.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/NFT/NFTokenUtils.h>
namespace xrpl {
class NFTokenMint : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenMint(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
// Public to support unit tests.
static uint256
createNFTokenID(
std::uint16_t flags,
std::uint16_t fee,
AccountID const& issuer,
nft::Taxon taxon,
std::uint32_t tokenSeq);
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class NFTokenModify : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit NFTokenModify(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,128 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/nft.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
namespace nft {
/** Delete up to a specified number of offers from the specified token offer
* directory. */
std::size_t
removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t maxDeletableOffers);
/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID);
/** Finds the specified token in the owner's token directory. */
std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);
/** Finds the token in the owner's token directory. Returns token and page. */
struct TokenAndPage
{
STObject token;
std::shared_ptr<SLE> page;
TokenAndPage(STObject const& token_, std::shared_ptr<SLE> page_) : token(token_), page(std::move(page_))
{
}
};
std::optional<TokenAndPage>
findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftokenID);
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
/** Remove the token from the owner's token directory. */
TER
removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID);
TER
removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, std::shared_ptr<SLE>&& page);
/** Deletes the given token offer.
An offer is tracked in two separate places:
- The token's 'buy' directory, if it's a buy offer; or
- The token's 'sell' directory, if it's a sell offer; and
- The owner directory of the account that placed the offer.
The offer also consumes one incremental reserve.
*/
bool
deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer);
/** Repairs the links in an NFTokenPage directory.
Returns true if a repair took place, otherwise false.
*/
bool
repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner);
bool
compareTokens(uint256 const& a, uint256 const& b);
TER
changeTokenURI(
ApplyView& view,
AccountID const& owner,
uint256 const& nftokenID,
std::optional<xrpl::Slice> const& uri);
/** Preflight checks shared by NFTokenCreateOffer and NFTokenMint */
NotTEC
tokenOfferCreatePreflight(
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,
std::optional<std::uint32_t> const& expiration,
std::uint16_t nftFlags,
Rules const& rules,
std::optional<AccountID> const& owner = std::nullopt,
std::uint32_t txFlags = lsfSellNFToken);
/** Preclaim checks shared by NFTokenCreateOffer and NFTokenMint */
TER
tokenOfferCreatePreclaim(
ReadView const& view,
AccountID const& acctID,
AccountID const& nftIssuer,
STAmount const& amount,
std::optional<AccountID> const& dest,
std::uint16_t nftFlags,
std::uint16_t xferFee,
beast::Journal j,
std::optional<AccountID> const& owner = std::nullopt,
std::uint32_t txFlags = lsfSellNFToken);
/** doApply implementation shared by NFTokenCreateOffer and NFTokenMint */
TER
tokenOfferCreateApply(
ApplyView& view,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,
std::optional<std::uint32_t> const& expiration,
SeqProxy seqProxy,
uint256 const& nftokenID,
XRPAmount const& priorBalance,
beast::Journal j,
std::uint32_t txFlags = lsfSellNFToken);
TER
checkTrustlineAuthorized(ReadView const& view, AccountID const id, beast::Journal const j, Issue const& issue);
TER
checkTrustlineDeepFrozen(ReadView const& view, AccountID const id, beast::Journal const j, Issue const& issue);
} // namespace nft
} // namespace xrpl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class CancelOffer : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit CancelOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using OfferCancel = CancelOffer;
} // namespace xrpl

View File

@@ -0,0 +1,79 @@
#pragma once
#include <xrpl/protocol/Quality.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class PaymentSandbox;
class Sandbox;
/** Transactor specialized for creating offers in the ledger. */
class CreateOffer : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
/** Construct a Transactor subclass that creates an offer in the ledger. */
explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static NotTEC
preflight(PreflightContext const& ctx);
/** Enforce constraints beyond those of the Transactor base class. */
static TER
preclaim(PreclaimContext const& ctx);
/** Precondition: fee collection is likely. Attempt to create the offer. */
TER
doApply() override;
private:
std::pair<TER, bool>
applyGuts(Sandbox& view, Sandbox& view_cancel);
// Determine if we are authorized to hold the asset we want to get.
static TER
checkAcceptAsset(
ReadView const& view,
ApplyFlags const flags,
AccountID const id,
beast::Journal const j,
Issue const& issue);
// Use the payment flow code to perform offer crossing.
std::pair<TER, Amounts>
flowCross(
PaymentSandbox& psb,
PaymentSandbox& psbCancel,
Amounts const& takerAmount,
std::optional<uint256> const& domainID);
static std::string
format_amount(STAmount const& amount);
TER
applyHybrid(
Sandbox& sb,
std::shared_ptr<STLedgerEntry> sleOffer,
Keylet const& offer_index,
STAmount const& saTakerPays,
STAmount const& saTakerGets,
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
};
using OfferCreate = CreateOffer;
} // namespace xrpl

View File

@@ -0,0 +1,83 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class PayChanCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit PayChanCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using PaymentChannelCreate = PayChanCreate;
//------------------------------------------------------------------------------
class PayChanFund : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit PayChanFund(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
using PaymentChannelFund = PayChanFund;
//------------------------------------------------------------------------------
class PayChanClaim : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit PayChanClaim(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using PaymentChannelClaim = PayChanClaim;
} // namespace xrpl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class Payment : public Transactor
{
/* The largest number of paths we allow */
static std::size_t const MaxPathSize = 6;
/* The longest path we allow */
static std::size_t const MaxPathLength = 8;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit Payment(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,17 @@
#pragma once
#include <xrpl/ledger/View.h>
namespace xrpl {
namespace permissioned_dex {
// Check if an account is in a permissioned domain
[[nodiscard]] bool
accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID);
// Check if an offer is in the permissioned domain
[[nodiscard]] bool
offerInDomain(ReadView const& view, uint256 const& offerID, Domain const& domainID, beast::Journal j);
} // namespace permissioned_dex
} // namespace xrpl

View File

@@ -0,0 +1,27 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class PermissionedDomainDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit PermissionedDomainDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
/** Attempt to delete the Permissioned Domain. */
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,30 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class PermissionedDomainSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit PermissionedDomainSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
/** Attempt to create the Permissioned Domain. */
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,38 @@
#pragma once
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SetAccount : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit SetAccount(ApplyContext& ctx) : Transactor(ctx)
{
}
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using AccountSet = SetAccount;
} // namespace xrpl

View File

@@ -0,0 +1,37 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
Price Oracle is a system that acts as a bridge between
a blockchain network and the external world, providing off-chain price data
to decentralized applications (dApps) on the blockchain. This implementation
conforms to the requirements specified in the XLS-47d.
The SetOracle transactor implements creating or updating Oracle objects.
*/
class SetOracle : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit SetOracle(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using OracleSet = SetOracle;
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SetRegularKey : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit SetRegularKey(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,71 @@
#pragma once
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/tx/SignerEntries.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
#include <vector>
namespace xrpl {
/**
See the README.md for an overview of the SetSignerList transaction that
this class implements.
*/
class SetSignerList : public Transactor
{
private:
// Values determined during preCompute for use later.
enum Operation { unknown, set, destroy };
Operation do_{unknown};
std::uint32_t quorum_{0};
std::vector<SignerEntries::SignerEntry> signers_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit SetSignerList(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
void
preCompute() override;
// Interface used by DeleteAccount
static TER
removeFromLedger(ServiceRegistry& registry, ApplyView& view, AccountID const& account, beast::Journal j);
private:
static std::tuple<NotTEC, std::uint32_t, std::vector<SignerEntries::SignerEntry>, Operation>
determineOperation(STTx const& tx, ApplyFlags flags, beast::Journal j);
static NotTEC
validateQuorumAndSignerEntries(
std::uint32_t quorum,
std::vector<SignerEntries::SignerEntry> const& signers,
AccountID const& account,
beast::Journal j,
Rules const&);
TER
replaceSignerList();
TER
destroySignerList();
void
writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t flags) const;
};
using SignerListSet = SetSignerList;
} // namespace xrpl

View File

@@ -0,0 +1,35 @@
#pragma once
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SetTrust : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit SetTrust(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using TrustSet = SetTrust;
} // namespace xrpl

View File

@@ -0,0 +1,34 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
private:
Expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(
std::shared_ptr<SLE> const& vault,
std::shared_ptr<SLE const> const& sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount);
};
} // namespace xrpl

View File

@@ -0,0 +1,32 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultCreate : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultDelete(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultDeposit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultDeposit(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,32 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,26 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class VaultWithdraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit VaultWithdraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,239 @@
#pragma once
#include <xrpl/protocol/XChainAttestations.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
constexpr size_t xbridgeMaxAccountCreateClaims = 128;
// Attach a new bridge to a door account. Once this is done, the cross-chain
// transfer transactions may be used to transfer funds from this account.
class XChainCreateBridge : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateBridge(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
class BridgeModify : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit BridgeModify(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using XChainModifyBridge = BridgeModify;
//------------------------------------------------------------------------------
// Claim funds from a `XChainCommit` transaction. This is normally not needed,
// but may be used to handle transaction failures or if the destination account
// was not specified in the `XChainCommit` transaction. It may only be used
// after a quorum of signatures have been sent from the witness servers.
//
// If the transaction succeeds in moving funds, the referenced `XChainClaimID`
// ledger object will be destroyed. This prevents transaction replay. If the
// transaction fails, the `XChainClaimID` will not be destroyed and the
// transaction may be re-run with different parameters.
class XChainClaim : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainClaim(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Put assets into trust on the locking-chain so they may be wrapped on the
// issuing-chain, or return wrapped assets on the issuing-chain so they can be
// unlocked on the locking-chain. The second step in a cross-chain transfer.
class XChainCommit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
explicit XChainCommit(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Create a new claim id owned by the account. This is the first step in a
// cross-chain transfer. The claim id must be created on the destination chain
// before the `XChainCommit` transaction (which must reference this number) can
// be sent on the source chain. The account that will send the `XChainCommit` on
// the source chain must be specified in this transaction (see note on the
// `SourceAccount` field in the `XChainClaimID` ledger object for
// justification). The actual sequence number must be retrieved from a validated
// ledger.
class XChainCreateClaimID : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateClaimID(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Provide attestations from a witness server attesting to events on
// the other chain. The signatures must be from one of the keys on the door's
// signer's list at the time the signature was provided. However, if the
// signature list changes between the time the signature was submitted and the
// quorum is reached, the new signature set is used and some of the currently
// collected signatures may be removed. Also note the reward is only sent to
// accounts that have keys on the current list.
class XChainAddClaimAttestation : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainAddClaimAttestation(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
class XChainAddAccountCreateAttestation : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainAddAccountCreateAttestation(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// This is a special transaction used for creating accounts through a
// cross-chain transfer. A normal cross-chain transfer requires a "chain claim
// id" (which requires an existing account on the destination chain). One
// purpose of the "chain claim id" is to prevent transaction replay. For this
// transaction, we use a different mechanism: the accounts must be claimed on
// the destination chain in the same order that the `XChainCreateAccountCommit`
// transactions occurred on the source chain.
//
// This transaction can only be used for XRP to XRP bridges.
//
// IMPORTANT: This transaction should only be enabled if the witness
// attestations will be reliably delivered to the destination chain. If the
// signatures are not delivered (for example, the chain relies on user wallets
// to collect signatures) then account creation would be blocked for all
// transactions that happened after the one waiting on attestations. This could
// be used maliciously. To disable this transaction on XRP to XRP bridges, the
// bridge's `MinAccountCreateAmount` should not be present.
//
// Note: If this account already exists, the XRP is transferred to the existing
// account. However, note that unlike the `XChainCommit` transaction, there is
// no error handling mechanism. If the claim transaction fails, there is no
// mechanism for refunds. The funds are permanently lost. This transaction
// should still only be used for account creation.
class XChainCreateAccountCommit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateAccountCommit(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
using XChainAccountCreateCommit = XChainCreateAccountCommit;
//------------------------------------------------------------------------------
} // namespace xrpl