Compare commits

..

11 Commits

Author SHA1 Message Date
Vito
009e05a463 restores invariant tests 2026-03-23 16:49:03 +01:00
Vito
ce92da9161 fix: Clean up VaultInvariantData and deduplicate ValidVault
- Remove unused zero member, clear() method, and MPT accessors
- Fix std::move from const ref in resolveBeforeShares
- Fix assertion label to use VaultInvariantData class name
- Simplify dead ternary in VaultSet::finalizeInvariants
- Add [[nodiscard]] to afterVault()/beforeVault() accessors
- Add isTesSuccess early return guard to all transactor invariants
- Deduplicate ValidVault by delegating to VaultInvariantData
- Remove ReadView/STTx/Journal dependencies from VaultInvariantData
2026-03-22 13:23:06 +01:00
Vito
c59683d07c refactor: Move vault invariants from global to per-transactor
Move transaction-specific invariant checks from the global ValidVault
invariant into each vault transactor's finalizeInvariants method. Run
both transaction and protocol invariants unconditionally, returning
failure if either check fails (tef takes priority over tec).
2026-03-22 12:13:53 +01:00
Vito
e3d9e06345 fix: Run both transaction and protocol invariants unconditionally
Always run both invariant checks instead of short-circuiting on
transaction invariant failure. Return the most severe failure code
(tef > tec). Also switch logger from j_ to ctx_.journal.
2026-03-22 12:11:00 +01:00
Vito
038e50abed fix: Align invariant method class names with transactor class names
Rename class qualifiers in visitInvariantEntry and finalizeInvariants
definitions to match their actual transactor classes (e.g.,
DeleteAccount → AccountDelete, CancelCheck → CheckCancel).
2026-03-21 17:56:12 +01:00
Vito
34804eb53a Merge remote-tracking branch 'origin/tapanito/transaction-invariant' into tapanito/transaction-invariant 2026-03-21 17:55:00 +01:00
Vito
08f70c85d4 Merge remote-tracking branch 'origin/develop' into tapanito/transaction-invariant 2026-03-20 17:51:35 +01:00
Vito Tumas
b6e792cede Merge branch 'develop' into tapanito/transaction-invariant 2026-03-17 17:56:19 +01:00
Vito
23e117bde7 Merge remote-tracking branch 'origin/develop' into tapanito/transaction-invariant 2026-03-16 19:09:43 +01:00
Vito
40ee1e1ff3 feat: Add no-op transaction invariant overrides to all transactors 2026-03-16 19:05:11 +01:00
Vito
10cb46c3f0 feat: Add transaction-specific invariant checking
Introduce a two-phase visitor pattern (visitInvariantEntry /
finalizeInvariants) on Transactor so individual transaction types
can define their own post-condition checks.  These run before the
existing protocol-wide invariants and short-circuit on failure to
avoid misleading secondary errors.

- Add pure virtual visitInvariantEntry and finalizeInvariants to
  Transactor
- Implement checkTransactionInvariants to drive the visitor loop
- Extract checkInvariants to orchestrate transaction-specific then
  protocol-wide checks with reset-and-retry on failure
- Move failInvariantCheck from private to public in ApplyContext
2026-03-16 19:05:03 +01:00
174 changed files with 7511 additions and 5523 deletions

View File

@@ -298,7 +298,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
disable_search: true
disable_telem: true

1
compile_commands.json Symbolic link
View File

@@ -0,0 +1 @@
.build/compile_commands.json

View File

@@ -0,0 +1,43 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/STAmount.h>
namespace xrpl {
/** Calculate the maximum amount of IOUs that an account can hold
@param ledger the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
@return The maximum amount that can be held.
*/
/** @{ */
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
IOUAmount
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
/** @} */
/** Returns the amount of IOUs issued by issuer that are held by an account
@param ledger the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
*/
/** @{ */
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
/** @} */
} // namespace xrpl

View File

@@ -4,13 +4,6 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
@@ -28,6 +21,7 @@
namespace xrpl {
enum class WaiveTransferFee : bool { No = false, Yes };
enum class SkipEntry : bool { No = false, Yes };
//------------------------------------------------------------------------------
@@ -60,6 +54,24 @@ enum class SkipEntry : bool { No = false, Yes };
[[nodiscard]] bool
hasExpired(ReadView const& view, std::optional<std::uint32_t> const& exp);
/** Controls the treatment of frozen account balances */
enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN };
/** Controls the treatment of unauthorized MPT balances */
enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED };
/** Controls whether to include the account's full spendable balance */
enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
// Note, depth parameter is used to limit the recursion depth
[[nodiscard]] bool
isVaultPseudoAccountFrozen(
@@ -68,6 +80,175 @@ isVaultPseudoAccountFrozen(
MPTIssue const& mptShare,
int depth);
[[nodiscard]] bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isIndividualFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
}
[[nodiscard]] bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int = 0 /*ignored*/)
{
return isFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0)
{
return std::visit(
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
}
[[nodiscard]] inline TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
[[nodiscard]] inline TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
[[nodiscard]] inline TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth = 0);
[[nodiscard]] inline bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue)
{
for (auto const& account : accounts)
{
if (isFrozen(view, account, issue.currency, issue.account))
return true;
}
return false;
}
[[nodiscard]] inline bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth = 0)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
return isAnyFrozen(view, accounts, issue);
else
return isAnyFrozen(view, accounts, issue, depth);
},
asset.value());
}
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Issue const& issue,
int = 0 /*ignored*/)
{
return isDeepFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
int depth = 0)
{
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
// funds, so checking "deep frozen" is the same as checking "frozen".
return isFrozen(view, account, mptIssue, depth);
}
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] inline bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0)
{
return std::visit(
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
asset.value());
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
}
[[nodiscard]] bool
isLPTokenFrozen(
ReadView const& view,
@@ -75,6 +256,159 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2);
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Asset const& asset,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount an account can spend of the currency type saDefault, or
// returns saDefault if this account is the issuer of the currency in
// question. Should be used in favor of accountHolds when questioning how much
// an account can spend while also allowing currency issuers to spend
// unlimited amounts of their own currency (since they can always issue more).
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j);
// Return the account's liquid (not reserved) XRP. Generally prefer
// calling accountHolds() over this interface. However, this interface
// allows the caller to temporarily adjust the owner count should that be
// necessary.
//
// @param ownerCountAdj positive to add to count, negative to reduce count.
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Iterate all items in the given directory. */
void
forEachItem(
ReadView const& view,
Keylet const& root,
std::function<void(std::shared_ptr<SLE const> const&)> const& f);
/** Iterate all items after an item in the given directory.
@param after The key of the item to start after
@param hint The directory page containing `after`
@param limit The maximum number of items to return
@return `false` if the iteration failed
*/
bool
forEachItemAfter(
ReadView const& view,
Keylet const& root,
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
/** Iterate all items in an account's owner directory. */
inline void
forEachItem(
ReadView const& view,
AccountID const& id,
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
{
return forEachItem(view, keylet::ownerDir(id), f);
}
/** Iterate all items after an item in an owner directory.
@param after The key of the item to start after
@param hint The directory page containing `after`
@param limit The maximum number of items to return
@return `false` if the iteration failed
*/
inline bool
forEachItemAfter(
ReadView const& view,
AccountID const& id,
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
{
return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
}
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuer The IOU issuer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, AccountID const& issuer);
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, MPTID const& issuanceID);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
/** Returns `true` if the directory is empty
@param key The key of the directory
*/
[[nodiscard]] bool
dirIsEmpty(ReadView const& view, Keylet const& k);
// Return the list of enabled amendments
[[nodiscard]] std::set<uint256>
getEnabledAmendments(ReadView const& view);
@@ -140,6 +474,81 @@ areCompatible(
//
//------------------------------------------------------------------------------
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j);
/** @{ */
/** Returns the first entry in the directory, advancing the index
@deprecated These are legacy function that are considered deprecated
and will soon be replaced with an iterator-based model
that is easier to use. You should not use them in new code.
@param view The view against which to operate
@param root The root (i.e. first page) of the directory to iterate
@param page The current page
@param index The index inside the current page
@param entry The entry at the current index
@return true if the directory isn't empty; false otherwise
*/
bool
cdirFirst(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry);
bool
dirFirst(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry);
/** @} */
/** @{ */
/** Returns the next entry in the directory, advancing the index
@deprecated These are legacy function that are considered deprecated
and will soon be replaced with an iterator-based model
that is easier to use. You should not use them in new code.
@param view The view against which to operate
@param root The root (i.e. first page) of the directory to iterate
@param page The current page
@param index The index inside the current page
@param entry The entry at the current index
@return true if the directory isn't empty; false otherwise
*/
bool
cdirNext(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry);
bool
dirNext(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry);
/** @} */
[[nodiscard]] std::function<void(SLE::ref)>
describeOwnerDir(AccountID const& account);
[[nodiscard]] TER
dirLink(
ApplyView& view,
@@ -147,6 +556,63 @@ dirLink(
std::shared_ptr<SLE>& object,
SF_UINT64 const& node = sfOwnerNode);
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
/**
*
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
*
* The list of valid ownerField is maintained in View.cpp and the caller to
* this function must perform necessary amendment check(s) before using a
* field. The amendment check is **not** performed in createPseudoAccount.
*/
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
// Returns true if and only if sleAcct is a pseudo-account or specific
// pseudo-accounts in pseudoFieldFilter.
//
// Returns false if sleAcct is
// * NOT a pseudo-account OR
// * NOT a ltACCOUNT_ROOT OR
// * null pointer
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter = {});
// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if
// set
// Pseudo-account designator fields MUST be maintained by including the
// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
// since a non-active amendment will not set any field, by definition.
// Specific properties of a pseudo-account are NOT checked here, that's what
// InvariantCheck is for.
[[nodiscard]] std::vector<SField const*> const&
getPseudoAccountFields();
[[nodiscard]] inline bool
isPseudoAccount(
ReadView const& view,
AccountID const& accountId,
std::set<SField const*> const& pseudoFieldFilter = {})
{
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset);
/** Validates that the destination SLE and tag are valid
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
/** Checks that can withdraw funds from an object to itself or a destination.
*
* The receiver may be either the submitting account (sfAccount) or a different
@@ -220,6 +686,351 @@ doWithdraw(
STAmount const& amount,
beast::Journal j);
/// Any transactors that call addEmptyHolding() in doApply must call
/// canAddHolding() in preflight with the same View and Asset
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] inline TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
},
asset.value());
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
// VFALCO NOTE Both STAmount parameters should just
// be "Amount", a unit-less number.
//
/** Create a trust line
This can set an initial balance.
*/
[[nodiscard]] TER
trustCreate(
ApplyView& view,
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
std::uint32_t uSrcQualityIn,
std::uint32_t uSrcQualityOut,
beast::Journal j);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] inline TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
},
asset.value());
}
[[nodiscard]] TER
trustDelete(
ApplyView& view,
std::shared_ptr<SLE> const& sleRippleState,
AccountID const& uLowAccountID,
AccountID const& uHighAccountID,
beast::Journal j);
/** Delete an offer.
Requirements:
The passed `sle` be obtained from a prior
call to view.peek()
*/
// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
TER
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
//------------------------------------------------------------------------------
//
// Money Transfers
//
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line of needed.
// --> bCheckIssuer : normally require issuer to be involved.
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
/** Calls static rippleCreditIOU if saAmount represents Issue.
* Calls static rippleCreditMPT if saAmount represents MPTIssue.
*/
TER
rippleCredit(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
beast::Journal j);
TER
rippleLockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
STAmount const& saAmount,
beast::Journal j);
TER
rippleUnlockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
AccountID const& uGranteeID,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j);
/** Calls static accountSendIOU if saAmount represents Issue.
* Calls static accountSendMPT if saAmount represents MPTIssue.
*/
[[nodiscard]] TER
accountSend(
ApplyView& view,
AccountID const& from,
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
/** Like accountSend, except one account is sending multiple payments (with the
* same asset!) simultaneously
*
* Calls static accountSendMultiIOU if saAmount represents Issue.
* Calls static accountSendMultiMPT if saAmount represents MPTIssue.
*/
[[nodiscard]] TER
accountSendMulti(
ApplyView& view,
AccountID const& senderID,
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
[[nodiscard]] TER
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
[[nodiscard]] TER
redeemIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
[[nodiscard]] TER
transferXRP(
ApplyView& view,
AccountID const& from,
AccountID const& to,
STAmount const& amount,
beast::Journal j);
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
* - StrongAuth - before checking if authorization is required
* - WeakAuth
* for MPT - after checking lsfMPTRequireAuth flag
* for IOU - do not check if trust line exists
* - Legacy
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
* for IOU - do not check if trust line exists i.e. same as WeakAuth
*/
enum class AuthType { StrongAuth, WeakAuth, Legacy };
/** Check if the account lacks required authorization.
*
* Return tecNO_AUTH or tecNO_LINE if it does
* and tesSUCCESS otherwise.
*
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
* RippleState does exist, and the RippleState is not authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
* RippleState exists, and is not authorized. Return tecNO_LINE if
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
* WeakAuth and lsfRequireAuth is *not* set, this function will return
* tesSUCCESS even if RippleState does *not* exist.
*
* The default "Legacy" auth type is equivalent to WeakAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Issue const& issue,
AccountID const& account,
AuthType authType = AuthType::Legacy);
/** Check if the account lacks required authorization.
*
* This will also check for expired credentials. If it is called directly
* from preclaim, the user should convert result tecEXPIRED to tesSUCCESS and
* proceed to also check permissions with enforceMPTokenAuthorization inside
* doApply. This will ensure that any expired credentials are deleted.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
* lsfMPTRequireAuth is set and MPToken is not authorized. Vault and LoanBroker
* pseudo-accounts are implicitly authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken
* doesn't exist or is not authorized (explicitly or via credentials, if
* DomainID is set in MPTokenIssuance). Consequently, if WeakAuth and
* lsfMPTRequireAuth is *not* set, this function will return true even if
* MPToken does *not* exist.
*
* The default "Legacy" auth type is equivalent to StrongAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType = AuthType::Legacy,
int depth = 0);
[[nodiscard]] TER inline requireAuth(
ReadView const& view,
Asset const& asset,
AccountID const& account,
AuthType authType = AuthType::Legacy)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue_) {
return requireAuth(view, issue_, account, authType);
},
asset.value());
}
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim,
* which implies that preclaim should replace `tecEXPIRED` with `tesSUCCESS`
* in order for the transactor to proceed to doApply.
*
* This function will create MPToken (if needed) on the basis of any
* non-expired credentials and will delete any expired credentials, indirectly
* via verifyValidDomain, as per DomainID (if set in MPTokenIssuance).
*
* The caller does NOT need to ensure that DomainID is actually set - this
* function handles gracefully both cases when DomainID is set and when not.
*
* The caller does NOT need to look for existing MPToken to match
* mptIssue/account - this function checks lsfMPTAuthorized of an existing
* MPToken iff DomainID is not set.
*
* Do not use for accounts which hold implied permission e.g. object owners or
* if MPTokenIssuance does not require authorization. In both cases use
* MPTokenAuthorize::authorize if MPToken does not yet exist.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to);
[[nodiscard]] TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
[[nodiscard]] TER inline canTransfer(
ReadView const& view,
Asset const& asset,
AccountID const& from,
AccountID const& to)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return canTransfer(view, issue, from, to);
},
asset.value());
}
/** Deleter function prototype. Returns the status of the entry deletion
* (if should not be skipped) and if the entry should be skipped. The status
* is always tesSUCCESS if the entry should be skipped.
@@ -241,6 +1052,57 @@ cleanupOnAccountDelete(
beast::Journal j,
std::optional<std::uint16_t> maxNodesToDelete = std::nullopt);
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
* call to view.peek(). Fail if neither side of the trustline is AMM or
* if ammAccountID is seated and is not one of the trustline's side.
*/
[[nodiscard]] TER
deleteAMMTrustLine(
ApplyView& view,
std::shared_ptr<SLE> sleState,
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
// From the perspective of a vault, return the number of shares to give the
// depositor when they deposit a fixed amount of assets. Since shares are MPT
// this number is integral and always truncated in this calculation.
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets);
// From the perspective of a vault, return the number of assets to take from
// depositor when they receive a fixed amount of shares. Note, since shares are
// MPT, they are always an integral number.
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares);
enum class TruncateShares : bool { no = false, yes = true };
// From the perspective of a vault, return the number of shares to demand from
// the depositor when they ask to withdraw a fixed amount of assets. Since
// shares are MPT this number is integral, and it will be rounded to nearest
// unless explicitly requested to be truncated instead.
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::no);
// From the perspective of a vault, return the number of assets to give the
// depositor when they redeem a fixed amount of shares. Note, since shares are
// MPT, they are always an integral number.
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares);
/** Has the specified time passed?
@param now the current time

View File

@@ -1,112 +0,0 @@
#pragma once
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <memory>
#include <set>
#include <vector>
namespace xrpl {
/** Check if the issuer has the global freeze flag set.
@param issuer The account to check
@return true if the account has global freeze set
*/
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
// Calculate liquid XRP balance for an account.
// This function may be used to calculate the amount of XRP that
// the holder is able to freely spend. It subtracts reserve requirements.
//
// ownerCountAdj adjusts the owner count in case the caller calculates
// before ledger entries are added or removed. Positive to add, negative
// to subtract.
//
// @param ownerCountAdj positive to add to count, negative to reduce count.
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j);
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuer The IOU issuer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, AccountID const& issuer);
/** Generate a pseudo-account address from a pseudo owner key.
@param pseudoOwnerKey The key to generate the address from
@return The generated account ID
*/
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account
if set.
The list is constructed during initialization and is const after that.
Pseudo-account designator fields MUST be maintained by including the
SField::sMD_PseudoAccount flag in the SField definition.
*/
[[nodiscard]] std::vector<SField const*> const&
getPseudoAccountFields();
/** Returns true if and only if sleAcct is a pseudo-account or specific
pseudo-accounts in pseudoFieldFilter.
Returns false if sleAcct is:
- NOT a pseudo-account OR
- NOT a ltACCOUNT_ROOT OR
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter = {});
/** Convenience overload that reads the account from the view. */
[[nodiscard]] inline bool
isPseudoAccount(
ReadView const& view,
AccountID const& accountId,
std::set<SField const*> const& pseudoFieldFilter = {})
{
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
}
/**
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
*
* The list of valid ownerField is maintained in AccountRootHelpers.cpp and
* the caller to this function must perform necessary amendment check(s)
* before using a field. The amendment check is **not** performed in
* createPseudoAccount.
*/
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
/** Checks the destination and tag.
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
} // namespace xrpl

View File

@@ -1,223 +0,0 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <functional>
#include <memory>
#include <type_traits>
namespace xrpl {
namespace detail {
template <
class V,
class N,
class = std::enable_if_t<
std::is_same_v<std::remove_cv_t<N>, SLE> && std::is_base_of_v<ReadView, V>>>
bool
internalDirNext(
V& view,
uint256 const& root,
std::shared_ptr<N>& page,
unsigned int& index,
uint256& entry)
{
auto const& svIndexes = page->getFieldV256(sfIndexes);
XRPL_ASSERT(index <= svIndexes.size(), "xrpl::detail::internalDirNext : index inside range");
if (index >= svIndexes.size())
{
auto const next = page->getFieldU64(sfIndexNext);
if (!next)
{
entry.zero();
return false;
}
if constexpr (std::is_const_v<N>)
{
page = view.read(keylet::page(root, next));
}
else
{
page = view.peek(keylet::page(root, next));
}
XRPL_ASSERT(page, "xrpl::detail::internalDirNext : non-null root");
if (!page)
return false;
index = 0;
return internalDirNext(view, root, page, index, entry);
}
entry = svIndexes[index++];
return true;
}
template <
class V,
class N,
class = std::enable_if_t<
std::is_same_v<std::remove_cv_t<N>, SLE> && std::is_base_of_v<ReadView, V>>>
bool
internalDirFirst(
V& view,
uint256 const& root,
std::shared_ptr<N>& page,
unsigned int& index,
uint256& entry)
{
if constexpr (std::is_const_v<N>)
{
page = view.read(keylet::page(root));
}
else
{
page = view.peek(keylet::page(root));
}
if (!page)
return false;
index = 0;
return internalDirNext(view, root, page, index, entry);
}
} // namespace detail
/** @{ */
/** Returns the first entry in the directory, advancing the index
@deprecated These are legacy function that are considered deprecated
and will soon be replaced with an iterator-based model
that is easier to use. You should not use them in new code.
@param view The view against which to operate
@param root The root (i.e. first page) of the directory to iterate
@param page The current page
@param index The index inside the current page
@param entry The entry at the current index
@return true if the directory isn't empty; false otherwise
*/
bool
cdirFirst(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry);
bool
dirFirst(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry);
/** @} */
/** @{ */
/** Returns the next entry in the directory, advancing the index
@deprecated These are legacy function that are considered deprecated
and will soon be replaced with an iterator-based model
that is easier to use. You should not use them in new code.
@param view The view against which to operate
@param root The root (i.e. first page) of the directory to iterate
@param page The current page
@param index The index inside the current page
@param entry The entry at the current index
@return true if the directory isn't empty; false otherwise
*/
bool
cdirNext(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry);
bool
dirNext(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry);
/** @} */
/** Iterate all items in the given directory. */
void
forEachItem(
ReadView const& view,
Keylet const& root,
std::function<void(std::shared_ptr<SLE const> const&)> const& f);
/** Iterate all items after an item in the given directory.
@param after The key of the item to start after
@param hint The directory page containing `after`
@param limit The maximum number of items to return
@return `false` if the iteration failed
*/
bool
forEachItemAfter(
ReadView const& view,
Keylet const& root,
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
/** Iterate all items in an account's owner directory. */
inline void
forEachItem(
ReadView const& view,
AccountID const& id,
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
{
return forEachItem(view, keylet::ownerDir(id), f);
}
/** Iterate all items after an item in an owner directory.
@param after The key of the item to start after
@param hint The directory page containing `after`
@param limit The maximum number of items to return
@return `false` if the iteration failed
*/
inline bool
forEachItemAfter(
ReadView const& view,
AccountID const& id,
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
{
return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
}
/** Returns `true` if the directory is empty
@param key The key of the directory
*/
[[nodiscard]] bool
dirIsEmpty(ReadView const& view, Keylet const& k);
/** Returns a function that sets the owner on a directory SLE */
[[nodiscard]] std::function<void(SLE::ref)>
describeOwnerDir(AccountID const& account);
} // namespace xrpl

View File

@@ -1,160 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <initializer_list>
#include <optional>
namespace xrpl {
//------------------------------------------------------------------------------
//
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth = 0);
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, MPTID const& issuanceID);
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType = AuthType::Legacy,
int depth = 0);
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Escrow operations (MPT-specific)
//
//------------------------------------------------------------------------------
TER
rippleLockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
STAmount const& saAmount,
beast::Journal j);
TER
rippleUnlockEscrowMPT(
ApplyView& view,
AccountID const& uGrantorID,
AccountID const& uGranteeID,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j);
} // namespace xrpl

View File

@@ -1,28 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <memory>
namespace xrpl {
/** Delete an offer.
Requirements:
The offer must exist.
The caller must have already checked permissions.
@param view The ApplyView to modify.
@param sle The offer to delete.
@param j Journal for logging.
@return tesSUCCESS on success, otherwise an error code.
*/
// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
TER
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
} // namespace xrpl

View File

@@ -1,255 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
//------------------------------------------------------------------------------
//
// RippleState (Trustline) helpers
//
//------------------------------------------------------------------------------
namespace xrpl {
//------------------------------------------------------------------------------
//
// Credit functions (from Credit.h)
//
//------------------------------------------------------------------------------
/** Calculate the maximum amount of IOUs that an account can hold
@param view the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
@return The maximum amount that can be held.
*/
/** @{ */
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
IOUAmount
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
/** @} */
/** Returns the amount of IOUs issued by issuer that are held by an account
@param view the ledger to check against.
@param account the account of interest.
@param issuer the issuer of the IOU.
@param currency the IOU to check.
*/
/** @{ */
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency);
/** @} */
//------------------------------------------------------------------------------
//
// Freeze checking (IOU-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isIndividualFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue.currency, issue.account);
}
// Overload with depth parameter for uniformity with MPTIssue version.
// The depth parameter is ignored for IOUs since they don't have vault recursion.
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
{
return isFrozen(view, account, issue);
}
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Issue const& issue,
int = 0 /*ignored*/)
{
return isDeepFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Trust line operations
//
//------------------------------------------------------------------------------
/** Create a trust line
This can set an initial balance.
*/
[[nodiscard]] TER
trustCreate(
ApplyView& view,
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j);
[[nodiscard]] TER
trustDelete(
ApplyView& view,
std::shared_ptr<SLE> const& sleRippleState,
AccountID const& uLowAccountID,
AccountID const& uHighAccountID,
beast::Journal j);
//------------------------------------------------------------------------------
//
// IOU issuance/redemption
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
[[nodiscard]] TER
redeemIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (IOU-specific)
//
//------------------------------------------------------------------------------
/** Check if the account lacks required authorization.
*
* Return tecNO_AUTH or tecNO_LINE if it does
* and tesSUCCESS otherwise.
*
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
* RippleState does exist, and the RippleState is not authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
* RippleState exists, and is not authorized. Return tecNO_LINE if
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
* WeakAuth and lsfRequireAuth is *not* set, this function will return
* tesSUCCESS even if RippleState does *not* exist.
*
* The default "Legacy" auth type is equivalent to WeakAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Issue const& issue,
AccountID const& account,
AuthType authType = AuthType::Legacy);
/** Check if the destination account is allowed
* to receive IOU. Return terNO_RIPPLE if rippling is
* disabled on both sides and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)
//
//------------------------------------------------------------------------------
/// Any transactors that call addEmptyHolding() in doApply must call
/// canAddHolding() in preflight with the same View and Asset
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal);
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
* call to view.peek(). Fail if neither side of the trustline is AMM or
* if ammAccountID is seated and is not one of the trustline's side.
*/
[[nodiscard]] TER
deleteAMMTrustLine(
ApplyView& view,
std::shared_ptr<SLE> sleState,
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
} // namespace xrpl

View File

@@ -1,286 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <initializer_list>
#include <vector>
namespace xrpl {
//------------------------------------------------------------------------------
//
// Enums for token handling
//
//------------------------------------------------------------------------------
/** Controls the treatment of frozen account balances */
enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN };
/** Controls the treatment of unauthorized MPT balances */
enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED };
/** Controls whether to include the account's full spendable balance */
enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE };
enum class WaiveTransferFee : bool { No = false, Yes };
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
* - StrongAuth - before checking if authorization is required
* - WeakAuth
* for MPT - after checking lsfMPTRequireAuth flag
* for IOU - do not check if trust line exists
* - Legacy
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
* for IOU - do not check if trust line exists i.e. same as WeakAuth
*/
enum class AuthType { StrongAuth, WeakAuth, Legacy };
//------------------------------------------------------------------------------
//
// Freeze checking (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue);
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth = 0);
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
int depth = 0);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Asset const& asset,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount an account can spend of the currency type saDefault, or
// returns saDefault if this account is the issuer of the currency in
// question. Should be used in favor of accountHolds when questioning how much
// an account can spend while also allowing currency issuers to spend
// unlimited amounts of their own currency (since they can always issue more).
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Asset const& asset,
AccountID const& account,
AuthType authType = AuthType::Legacy);
[[nodiscard]] TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Money Transfers (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line of needed.
// --> bCheckIssuer : normally require issuer to be involved.
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
/** Calls static rippleCreditIOU if saAmount represents Issue.
* Calls static rippleCreditMPT if saAmount represents MPTIssue.
*/
TER
rippleCredit(
ApplyView& view,
AccountID const& uSenderID,
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
beast::Journal j);
/** Calls static accountSendIOU if saAmount represents Issue.
* Calls static accountSendMPT if saAmount represents MPTIssue.
*/
[[nodiscard]] TER
accountSend(
ApplyView& view,
AccountID const& from,
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
using MultiplePaymentDestinations = std::vector<std::pair<AccountID, Number>>;
/** Like accountSend, except one account is sending multiple payments (with the
* same asset!) simultaneously
*
* Calls static accountSendMultiIOU if saAmount represents Issue.
* Calls static accountSendMultiMPT if saAmount represents MPTIssue.
*/
[[nodiscard]] TER
accountSendMulti(
ApplyView& view,
AccountID const& senderID,
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
[[nodiscard]] TER
transferXRP(
ApplyView& view,
AccountID const& from,
AccountID const& to,
STAmount const& amount,
beast::Journal j);
} // namespace xrpl

View File

@@ -1,81 +0,0 @@
#pragma once
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <memory>
#include <optional>
namespace xrpl {
/** From the perspective of a vault, return the number of shares to give
depositor when they offer a fixed amount of assets. Note, since shares are
MPT, this number is integral and always truncated in this calculation.
@param vault The vault SLE.
@param issuance The MPTokenIssuance SLE for the vault's shares.
@param assets The amount of assets to convert.
@return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets);
/** From the perspective of a vault, return the number of assets to take from
depositor when they receive a fixed amount of shares. Note, since shares are
MPT, they are always an integral number.
@param vault The vault SLE.
@param issuance The MPTokenIssuance SLE for the vault's shares.
@param shares The amount of shares to convert.
@return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares);
/** Controls whether to truncate shares instead of rounding. */
enum class TruncateShares : bool { no = false, yes = true };
/** From the perspective of a vault, return the number of shares to demand from
the depositor when they ask to withdraw a fixed amount of assets. Since
shares are MPT this number is integral, and it will be rounded to nearest
unless explicitly requested to be truncated instead.
@param vault The vault SLE.
@param issuance The MPTokenIssuance SLE for the vault's shares.
@param assets The amount of assets to convert.
@param truncate Whether to truncate instead of rounding.
@return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::no);
/** From the perspective of a vault, return the number of assets to give the
depositor when they redeem a fixed amount of shares. Note, since shares are
MPT, they are always an integral number.
@param vault The vault SLE.
@param issuance The MPTokenIssuance SLE for the vault's shares.
@param shares The amount of shares to convert.
@return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares);
} // namespace xrpl

View File

@@ -116,12 +116,12 @@ protected:
AccountID const account_;
XRPAmount preFeeBalance_{}; // Balance before fees.
virtual ~Transactor() = default;
Transactor(Transactor const&) = delete;
Transactor&
operator=(Transactor const&) = delete;
public:
virtual ~Transactor() = default;
enum ConsequencesFactoryType { Normal, Blocker, Custom };
/** Process the transaction. */
ApplyResult
@@ -139,6 +139,20 @@ public:
return ctx_.view();
}
/** Check all invariants for the current transaction.
*
* Runs transaction-specific invariants first (visitInvariantEntry +
* finalizeInvariants), then protocol-level invariants. Protocol
* invariants are skipped if a transaction invariant already failed.
*
* @param result the tentative TER from transaction processing.
* @param fee the fee consumed by the transaction.
*
* @return the final TER after all invariant checks.
*/
[[nodiscard]] TER
checkInvariants(TER result, XRPAmount fee);
/////////////////////////////////////////////////////
/*
These static functions are called from invoke_preclaim<Tx>
@@ -229,6 +243,49 @@ protected:
virtual TER
doApply() = 0;
/** Inspect a single ledger entry modified by this transaction.
*
* Called once for every SLE created, modified, or deleted by the
* transaction, before finalizeInvariants. Implementations should
* accumulate whatever state they need to verify transaction-specific
* post-conditions.
*
* @param isDelete true if the entry was erased from the ledger.
* @param before the entry's state before the transaction (nullptr
* for newly created entries).
* @param after the entry's state after the transaction (nullptr
* when isDelete is true).
*/
virtual void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) = 0;
/** Check transaction-specific post-conditions after all entries have
* been visited.
*
* Called once after every modified ledger entry has been passed to
* visitInvariantEntry. Returns true if all transaction-specific
* invariants hold, or false to fail the transaction with
* tecINVARIANT_FAILED.
*
* @param tx the transaction being applied.
* @param result the tentative TER result so far.
* @param fee the fee consumed by the transaction.
* @param view read-only view of the ledger after the transaction.
* @param j journal for logging invariant failures.
*
* @return true if all invariants pass; false otherwise.
*/
[[nodiscard]] virtual bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) = 0;
/** Compute the minimum fee required to process a transaction
with a given baseFee based on the current server load.
@@ -334,6 +391,21 @@ private:
*/
static NotTEC
preflight2(PreflightContext const& ctx);
/** Check transaction-specific invariants only.
*
* Walks every modified ledger entry via visitInvariantEntry, then
* calls finalizeInvariants on the derived transactor. Returns
* tecINVARIANT_FAILED if any transaction invariant is violated.
*
* @param result the tentative TER from transaction processing.
* @param fee the fee consumed by the transaction.
*
* @return the original result if all invariants pass, or
* tecINVARIANT_FAILED otherwise.
*/
[[nodiscard]] TER
checkTransactionInvariants(TER result, XRPAmount fee);
};
inline bool

View File

@@ -3,6 +3,8 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <memory>
namespace xrpl {
class ServiceRegistry;
@@ -341,4 +343,15 @@ calculateDefaultBaseFee(ReadView const& view, STTx const& tx);
ApplyResult
doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view);
class ApplyContext;
class Transactor;
/** Create a concrete Transactor subclass for the given ApplyContext.
*
* The transaction type is determined from the STTx held in the context.
* Returns nullptr if the transaction type is unknown.
*/
std::unique_ptr<Transactor>
makeTransactor(ApplyContext& ctx);
} // namespace xrpl

View File

@@ -1,74 +1,30 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <unordered_map>
#include <vector>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
/*
* @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)
* @brief Protocol-level vault invariants.
*
* These checks apply to every transaction that touches a vault, regardless of
* transaction type. Transaction-specific invariants live in each transactor's
* finalizeInvariants method.
*/
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_ = {};
VaultInvariantData data_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -1,8 +1,7 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/Credit.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>

View File

@@ -27,6 +27,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -31,6 +31,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -41,6 +41,20 @@ public:
void
preCompute() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
// Interface used by AccountDelete
static TER
removeFromLedger(

View File

@@ -26,6 +26,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
class BridgeModify : public Transactor
@@ -48,6 +62,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
using XChainModifyBridge = BridgeModify;
@@ -81,6 +109,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------
@@ -108,6 +150,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------
@@ -137,6 +193,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------
@@ -166,6 +236,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
class XChainAddAccountCreateAttestation : public Transactor
@@ -186,6 +270,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------
@@ -230,6 +328,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
using XChainAccountCreateCommit = XChainCreateAccountCommit;

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -22,6 +22,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
// Interface used by AccountDelete
static TER
deleteDelegate(

View File

@@ -62,6 +62,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -25,6 +25,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
private:
TER
applyGuts(Sandbox& view);

View File

@@ -58,6 +58,20 @@ public:
/** Attempt to create the AMM instance. */
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -63,6 +63,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
private:
std::pair<TER, bool>
applyGuts(Sandbox& view);

View File

@@ -47,6 +47,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -70,6 +70,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
/** Equal-asset withdrawal (LPTokens) of some AMM instance pools
* shares represented by the number of LPTokens .
* The trading fee is not charged.

View File

@@ -22,6 +22,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -40,6 +40,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
private:
std::pair<TER, bool>
applyGuts(Sandbox& view, Sandbox& view_cancel);

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -18,6 +18,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -27,6 +27,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -58,6 +58,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
//------------------------------------------------------------------------------

View File

@@ -38,6 +38,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
public:
static std::uint32_t constexpr minPaymentTotal = 1;
static std::uint32_t constexpr defaultPaymentTotal = 1;

View File

@@ -34,6 +34,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
// Public to support unit tests.
static uint256
createNFTokenID(

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -31,6 +31,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
static TER
deleteOracle(
ApplyView& view,

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -25,6 +25,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
// Interface used by AccountDelete
static TER
removeFromLedger(ApplyView& view, uint256 const& delIndex, beast::Journal j);

View File

@@ -39,6 +39,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -27,6 +27,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -24,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -22,6 +22,20 @@ public:
/** Attempt to delete the Permissioned Domain. */
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -25,6 +25,20 @@ public:
/** Attempt to create the Permissioned Domain. */
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -33,6 +33,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
static constexpr auto disabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,

View File

@@ -18,6 +18,20 @@ public:
void
preCompute() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx)
{

View File

@@ -28,6 +28,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -59,6 +59,20 @@ public:
/** Precondition: fee collection is likely. Attempt to create ticket(s). */
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -40,6 +40,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -41,6 +41,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
static Expected<MPTID, TER>
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
};

View File

@@ -21,6 +21,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -30,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -28,6 +28,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultClawback : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -22,6 +25,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
private:
Expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultCreate : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -27,6 +30,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultDelete : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -21,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultDeposit : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -21,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -0,0 +1,87 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
#include <unordered_map>
#include <vector>
namespace xrpl {
class VaultInvariantData
{
public:
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&);
};
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
[[nodiscard]] std::optional<Number>
deltaAssets(Asset const& vaultAsset, AccountID const& id) const;
[[nodiscard]] std::optional<Number>
deltaAssetsTxAccount(
AccountID const& account,
std::optional<AccountID> const& delegate,
Asset const& vaultAsset,
XRPAmount fee) const;
[[nodiscard]] std::optional<Number>
deltaShares(AccountID const& pseudoId, uint192 const& shareMPTID, AccountID const& id) const;
[[nodiscard]] std::optional<Shares>
resolveUpdatedShares(Vault const& afterVault) const;
[[nodiscard]] std::optional<Shares>
resolveBeforeShares(Vault const& beforeVault) const;
[[nodiscard]] static bool
vaultHoldsNoAssets(Vault const& vault);
[[nodiscard]] std::vector<Vault> const&
afterVault() const
{
return afterVault_;
}
[[nodiscard]] std::vector<Vault> const&
beforeVault() const
{
return beforeVault_;
}
private:
std::vector<Vault> afterVault_;
std::vector<Shares> afterMPTs_;
std::vector<Vault> beforeVault_;
std::vector<Shares> beforeMPTs_;
std::unordered_map<uint256, Number> deltas_;
};
} // namespace xrpl

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultSet : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -24,6 +27,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,11 +1,14 @@
#pragma once
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/vault/VaultInvariantData.h>
namespace xrpl {
class VaultWithdraw : public Transactor
{
VaultInvariantData invariantData_;
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
@@ -21,6 +24,20 @@ public:
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,7 +1,5 @@
#include <xrpl/ledger/helpers/CredentialHelpers.h>
//
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/digest.h>

View File

@@ -0,0 +1,60 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STAmount.h>
namespace xrpl {
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
result.setIssuer(account);
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
return result;
}
IOUAmount
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
{
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
}
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
result = sleRippleState->getFieldAmount(sfBalance);
if (account < issuer)
result.negate();
result.setIssuer(account);
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
return result;
}
} // namespace xrpl

File diff suppressed because it is too large Load Diff

View File

@@ -1,247 +0,0 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/digest.h>
#include <algorithm>
#include <limits>
namespace xrpl {
bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
{
if (isXRP(issuer))
return false;
if (auto const sle = view.read(keylet::account(issuer)))
return sle->isFlag(lsfGlobalFreeze);
return false;
}
// An owner count cannot be negative. If adjustment would cause a negative
// owner count, clamp the owner count at 0. Similarly for overflow. This
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
// If id != std::nullopt, then do error reporting.
//
// Returns adjusted owner count.
static std::uint32_t
confineOwnerCount(
std::uint32_t current,
std::int32_t adjustment,
std::optional<AccountID> const& id = std::nullopt,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
std::uint32_t adjusted{current + adjustment};
if (adjustment > 0)
{
// Overflow is well defined on unsigned
if (adjusted < current)
{
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
}
adjusted = std::numeric_limits<std::uint32_t>::max();
}
}
else
{
// Underflow is well defined on unsigned
if (adjusted > current)
{
if (id)
{
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
}
adjusted = 0;
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
}
}
return adjusted;
}
XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
{
auto const sle = view.read(keylet::account(id));
if (sle == nullptr)
return beast::zero;
// Return balance minus reserve
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText() << " reserve=" << reserve
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
return amount.xrp();
}
Rate
transferRate(ReadView const& view, AccountID const& issuer)
{
auto const sle = view.read(keylet::account(issuer));
if (sle && sle->isFieldPresent(sfTransferRate))
return Rate{sle->getFieldU32(sfTransferRate)};
return parityRate;
}
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j)
{
if (!sle)
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
AccountID const id = (*sle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
view.update(sle);
}
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
// This number must not be changed without an amendment
constexpr std::uint16_t maxAccountAttempts = 256;
for (std::uint16_t i = 0; i < maxAccountAttempts; ++i)
{
ripesha_hasher rsh;
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
rsh(hash.data(), hash.size());
AccountID const ret{static_cast<ripesha_hasher::result_type>(rsh)};
if (!view.read(keylet::account(ret)))
return ret;
}
return beast::zero;
}
// Pseudo-account designator fields MUST be maintained by including the
// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to
// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated,
// since a non-active amendment will not set any field, by definition.
// Specific properties of a pseudo-account are NOT checked here, that's what
// InvariantCheck is for.
[[nodiscard]] std::vector<SField const*> const&
getPseudoAccountFields()
{
static std::vector<SField const*> const pseudoFields = []() {
auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT);
if (!ar)
{
// LCOV_EXCL_START
LogicError(
"xrpl::getPseudoAccountFields : unable to find account root "
"ledger format");
// LCOV_EXCL_STOP
}
auto const& soTemplate = ar->getSOTemplate();
std::vector<SField const*> pseudoFields;
for (auto const& field : soTemplate)
{
if (field.sField().shouldMeta(SField::sMD_PseudoAccount))
pseudoFields.emplace_back(&field.sField());
}
return pseudoFields;
}();
return pseudoFields;
}
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter)
{
auto const& fields = getPseudoAccountFields();
// Intentionally use defensive coding here because it's cheap and makes the
// semantics of true return value clean.
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
std::count_if(
fields.begin(), fields.end(), [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
return sleAcct->isFieldPresent(*sf) &&
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
}) > 0;
}
Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField)
{
[[maybe_unused]]
auto const& fields = getPseudoAccountFields();
XRPL_ASSERT(
std::count_if(
fields.begin(),
fields.end(),
[&ownerField](SField const* sf) -> bool { return *sf == ownerField; }) == 1,
"xrpl::createPseudoAccount : valid owner field");
auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey);
if (accountId == beast::zero)
return Unexpected(tecDUPLICATE);
// Create pseudo-account.
auto account = std::make_shared<SLE>(keylet::account(accountId));
account->setAccountID(sfAccount, accountId);
account->setFieldAmount(sfBalance, STAmount{});
// Pseudo-accounts can't submit transactions, so set the sequence number
// to 0 to make them easier to spot and verify, and add an extra level
// of protection.
std::uint32_t const seqno = //
view.rules().enabled(featureSingleAssetVault) || //
view.rules().enabled(featureLendingProtocol) //
? 0 //
: view.seq();
account->setFieldU32(sfSequence, seqno);
// Ignore reserves requirement, disable the master key, allow default
// rippling, and enable deposit authorization to prevent payments into
// pseudo-account.
account->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
// Link the pseudo-account with its owner object.
account->setFieldH256(ownerField, pseudoOwnerKey);
view.insert(account);
return account;
}
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
{
if (toSle == nullptr)
return tecNO_DST;
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
return tecDST_TAG_NEEDED; // Cannot send without a tag
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -1,177 +0,0 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
//
#include <xrpl/protocol/LedgerFormats.h>
namespace xrpl {
bool
dirFirst(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry)
{
return detail::internalDirFirst(view, root, page, index, entry);
}
bool
dirNext(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
unsigned int& index,
uint256& entry)
{
return detail::internalDirNext(view, root, page, index, entry);
}
bool
cdirFirst(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry)
{
return detail::internalDirFirst(view, root, page, index, entry);
}
bool
cdirNext(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
unsigned int& index,
uint256& entry)
{
return detail::internalDirNext(view, root, page, index, entry);
}
void
forEachItem(
ReadView const& view,
Keylet const& root,
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
{
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type");
if (root.type != ltDIR_NODE)
return;
auto pos = root;
while (true)
{
auto sle = view.read(pos);
if (!sle)
return;
for (auto const& key : sle->getFieldV256(sfIndexes))
f(view.read(keylet::child(key)));
auto const next = sle->getFieldU64(sfIndexNext);
if (!next)
return;
pos = keylet::page(root, next);
}
}
bool
forEachItemAfter(
ReadView const& view,
Keylet const& root,
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
{
XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type");
if (root.type != ltDIR_NODE)
return false;
auto currentIndex = root;
// If startAfter is not zero try jumping to that page using the hint
if (after.isNonZero())
{
auto const hintIndex = keylet::page(root, hint);
if (auto hintDir = view.read(hintIndex))
{
for (auto const& key : hintDir->getFieldV256(sfIndexes))
{
if (key == after)
{
// We found the hint, we can start here
currentIndex = hintIndex;
break;
}
}
}
bool found = false;
for (;;)
{
auto const ownerDir = view.read(currentIndex);
if (!ownerDir)
return found;
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
if (!found)
{
if (key == after)
found = true;
}
else if (f(view.read(keylet::child(key))) && limit-- <= 1)
{
return found;
}
}
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
if (uNodeNext == 0)
return found;
currentIndex = keylet::page(root, uNodeNext);
}
}
else
{
for (;;)
{
auto const ownerDir = view.read(currentIndex);
if (!ownerDir)
return true;
for (auto const& key : ownerDir->getFieldV256(sfIndexes))
{
if (f(view.read(keylet::child(key))) && limit-- <= 1)
return true;
}
auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext);
if (uNodeNext == 0)
return true;
currentIndex = keylet::page(root, uNodeNext);
}
}
}
bool
dirIsEmpty(ReadView const& view, Keylet const& k)
{
auto const sleNode = view.read(k);
if (!sleNode)
return true;
if (!sleNode->getFieldV256(sfIndexes).empty())
return false;
// The first page of a directory may legitimately be empty even if there
// are other pages (the first page is the anchor page) so check to see if
// there is another page. If there is, the directory isn't empty.
return sleNode->getFieldU64(sfIndexNext) == 0;
}
std::function<void(SLE::ref)>
describeOwnerDir(AccountID const& account)
{
return [account](std::shared_ptr<SLE> const& sle) { (*sle)[sfOwner] = account; };
}
} // namespace xrpl

View File

@@ -1,766 +0,0 @@
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TxFlags.h>
namespace xrpl {
// Forward declarations for functions that remain in View.h/cpp
bool
isVaultPseudoAccountFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptShare,
int depth);
[[nodiscard]] TER
dirLink(
ApplyView& view,
AccountID const& owner,
std::shared_ptr<SLE>& object,
SF_UINT64 const& node = sfOwnerNode);
bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth)
{
if (isGlobalFrozen(view, mptIssue))
return true;
for (auto const& account : accounts)
{
if (isIndividualFrozen(view, account, mptIssue))
return true;
}
for (auto const& account : accounts)
{
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
return true;
}
return false;
}
Rate
transferRate(ReadView const& view, MPTID const& issuanceID)
{
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
// which represents 50% of 1,000,000,000
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)};
return parityRate;
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
{
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptIssuance(mptID));
if (!issuance)
{
return tecOBJECT_NOT_FOUND;
}
if (!issuance->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
return tesSUCCESS;
}
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = view.peek(keylet::mptIssuance(mptID));
if (!mpt)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mpt->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (view.peek(keylet::mptoken(mptID, accountID)))
return tecDUPLICATE;
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags,
std::optional<AccountID> holderID)
{
auto const sleAcct = view.peek(keylet::account(account));
if (!sleAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted the tx is a holder
// Note: `account_` is holder's account
// `holderID` is NOT used
if (!holderID)
{
// When a holder wants to unauthorize/delete a MPT, the ledger must
// - delete mptokenKey from owner directory
// - delete the MPToken
if (flags & tfMPTUnauthorize)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleAcct, -1, journal);
view.erase(sleMpt);
return tesSUCCESS;
}
// A potential holder wants to authorize/hold a mpt, the ledger must:
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::zero)
: view.fees().accountReserve(uOwnerCount + 1));
if (priorBalance < reserveCreate)
return tecINSUFFICIENT_RESERVE;
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
if (!mpt || mpt->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
if (view.rules().enabled(featureLendingProtocol))
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(view, account, mptoken))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfFlags] = 0;
view.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, 1, journal);
return tesSUCCESS;
}
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleMptIssuance)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted this tx is the issuer of the MPT
// Note: `account_` is issuer's account
// `holderID` is holder's account
if (account != (*sleMptIssuance)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
std::uint32_t flagsOut = flagsIn;
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
// their MPToken
if (flags & tfMPTUnauthorize)
{
flagsOut &= ~lsfMPTAuthorized;
}
// Issuer wants to authorize a holder, set lsfMPTAuthorized on their
// MPToken
else
{
flagsOut |= lsfMPTAuthorized;
}
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
view.update(sleMpt);
return tesSUCCESS;
}
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
{
// If the account is the issuer, then no token should exist. MPTs do not
// have the legacy ability to create such a situation, but check anyway. If
// a token does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
auto const& mptID = mptIssue.getMptID();
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
if (!mptoken)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
// Unlike a trust line, if the account is the issuer, and the token has a
// balance, it can not just be deleted, because that will throw the issuance
// accounting out of balance, so fail. Since this should be impossible
// anyway, I'm not going to put any effort into it.
if (mptoken->at(sfMPTAmount) != 0)
return tecHAS_OBLIGATIONS;
return authorizeMPToken(
view,
{}, // priorBalance
mptID,
accountID,
journal,
tfMPTUnauthorize // flags
);
}
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType,
int depth)
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
// issuer is always "authorized"
if (mptIssuer == account) // Issuer won't have MPToken
return tesSUCCESS;
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
if (featureSAVEnabled)
{
if (depth >= maxAssetCheckDepth)
return tecINTERNAL; // LCOV_EXCL_LINE
// requireAuth is recursive if the issuer is a vault pseudo-account
auto const sleIssuer = view.read(keylet::account(mptIssuer));
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
if (sleIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
if (!sleVault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const asset = sleVault->at(sfAsset);
if (auto const err = std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return requireAuth(view, issue, account, authType);
}
else
{
return requireAuth(view, issue, account, authType, depth + 1);
}
},
asset.value());
!isTesSuccess(err))
return err;
}
}
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
return tecNO_AUTH;
// Note, this check is not amendment-gated because DomainID will be always
// empty **unless** writing to it has been enabled by an amendment
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
if (maybeDomainID)
{
XRPL_ASSERT(
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
"xrpl::requireAuth : issuance requires authorization");
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
if (isTesSuccess(ter))
{
return ter; // Note: sleToken might be null
}
if (!sleToken)
{
return ter;
}
// We ignore error from validDomain if we found sleToken, as it could
// belong to someone who is explicitly authorized e.g. a vault owner.
}
if (featureSAVEnabled)
{
// Implicitly authorize Vault and LoanBroker pseudo-accounts
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
return tesSUCCESS;
}
// mptoken must be authorized if issuance enabled requireAuth
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
return tecNO_AUTH;
return tesSUCCESS; // Note: sleToken might be null
}
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
sleIssuance->isFlag(lsfMPTRequireAuth),
"xrpl::enforceMPTokenAuthorization : authorization required");
if (account == sleIssuance->at(sfIssuer))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = view.read(keylet); // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
// NOTE: defensive here, should be checked in preclaim
if (!maybeDomainID.has_value())
return false; // LCOV_EXCL_LINE
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
if (isTesSuccess(ter))
return true;
if (ter == tecEXPIRED)
expired = true;
return false;
}();
if (!authorizedByDomain && sleToken == nullptr)
{
// Could not find MPToken and won't create one, could be either of:
//
// 1. Field sfDomainID not set in MPTokenIssuance or
// 2. Account has no matching and accepted credentials or
// 3. Account has all expired credentials (deleted in verifyValidDomain)
//
// Either way, return tecNO_AUTH and there is nothing else to do
return expired ? tecEXPIRED : tecNO_AUTH;
}
if (!authorizedByDomain && maybeDomainID.has_value())
{
// Found an MPToken but the account is not authorized and we expect
// it to have been authorized by the domain. This could be because the
// credentials used to create the MPToken have expired or been deleted.
return expired ? tecEXPIRED : tecNO_AUTH;
}
if (!authorizedByDomain)
{
// We found an MPToken, but sfDomainID is not set, so this is a classic
// MPToken which requires authorization by the token issuer.
XRPL_ASSERT(
sleToken != nullptr && !maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
return tecNO_AUTH;
}
if (authorizedByDomain && sleToken != nullptr)
{
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
return tesSUCCESS;
}
if (authorizedByDomain)
{
// Could not find MPToken but there should be one because we are
// authorized by domain. Proceed to create it, then return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value() && sleToken == nullptr,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
j);
!isTesSuccess(err))
return err;
return tesSUCCESS;
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::enforceMPTokenAuthorization : condition list is incomplete");
return tefINTERNAL;
// LCOV_EXCL_STOP
}
TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to)
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTCanTransfer))
{
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
return TER{tecNO_AUTH};
}
return tesSUCCESS;
}
TER
rippleLockEscrowMPT(
ApplyView& view,
AccountID const& sender,
STAmount const& amount,
beast::Journal j)
{
auto const mptIssue = amount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
if (amount.getIssuer() == sender)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
return tecINTERNAL;
} // LCOV_EXCL_STOP
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender;
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
auto const amt = sle->getFieldU64(sfMPTAmount);
auto const pay = amount.mpt().value();
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: insufficient MPTAmount for "
<< to_string(sender) << ": " << amt << " < " << pay;
return tecINTERNAL;
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] = amt - pay;
// Overflow check for addition
uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on locked amount for "
<< to_string(sender) << ": " << locked << " + " << pay;
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sle->isFieldPresent(sfLockedAmount))
{
(*sle)[sfLockedAmount] += pay;
}
else
{
sle->setFieldU64(sfLockedAmount, pay);
}
view.update(sle);
}
// 1. Increase the Issuance EscrowedAmount
// 2. DO NOT change the Issuance OutstandingAmount
{
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
auto const pay = amount.mpt().value();
// Overflow check for addition
if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
"locked amount for "
<< mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay;
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sleIssuance->isFieldPresent(sfLockedAmount))
{
(*sleIssuance)[sfLockedAmount] += pay;
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, pay);
}
view.update(sleIssuance);
}
return tesSUCCESS;
}
TER
rippleUnlockEscrowMPT(
ApplyView& view,
AccountID const& sender,
AccountID const& receiver,
STAmount const& netAmount,
STAmount const& grossAmount,
beast::Journal j)
{
if (!view.rules().enabled(fixTokenEscrowV1))
{
XRPL_ASSERT(
netAmount == grossAmount, "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount");
}
auto const& issuer = netAmount.getIssuer();
auto const& mptIssue = netAmount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
// Decrease the Issuance EscrowedAmount
{
if (!sleIssuance->isFieldPresent(sfLockedAmount))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
<< mptIssue.getMptID();
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
auto const redeem = grossAmount.mpt().value();
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
<< mptIssue.getMptID() << ": " << locked << " < " << redeem;
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const newLocked = locked - redeem;
if (newLocked == 0)
{
sleIssuance->makeFieldAbsent(sfLockedAmount);
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sleIssuance);
}
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
auto current = sle->getFieldU64(sfMPTAmount);
auto delta = netAmount.mpt().value();
// Overflow check for addition
if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: overflow on MPTAmount for "
<< to_string(receiver) << ": " << current << " + " << delta;
return tecINTERNAL;
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] += delta;
view.update(sle);
}
else
{
// Decrease the Issuance OutstandingAmount
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = netAmount.mpt().value();
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
<< mptIssue.getMptID() << ": " << outstanding << " < " << redeem;
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
}
if (issuer == sender)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
"cannot unlock MPTs.";
return tecINTERNAL;
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender;
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
if (!sle->isFieldPresent(sfLockedAmount))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for "
<< to_string(sender);
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = sle->getFieldU64(sfLockedAmount);
auto const delta = grossAmount.mpt().value();
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for "
<< to_string(sender) << ": " << locked << " < " << delta;
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const newLocked = locked - delta;
if (newLocked == 0)
{
sle->makeFieldAbsent(sfLockedAmount);
}
else
{
sle->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sle);
// Note: The gross amount is the amount that was locked, the net
// amount is the amount that is being unlocked. The difference is the fee
// that was charged for the transfer. If this difference is greater than
// zero, we need to update the outstanding amount.
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
if (diff != 0)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for "
<< mptIssue.getMptID() << ": " << outstanding << " < " << diff;
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
}
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -1,58 +0,0 @@
#include <xrpl/ledger/helpers/OfferHelpers.h>
//
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/st.h>
namespace xrpl {
TER
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
{
if (!sle)
return tesSUCCESS;
auto offerIndex = sle->key();
auto owner = sle->getAccountID(sfAccount);
// Detect legacy directories.
uint256 uDirectory = sle->getFieldH256(sfBookDirectory);
if (!view.dirRemove(keylet::ownerDir(owner), sle->getFieldU64(sfOwnerNode), offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
if (!view.dirRemove(keylet::page(uDirectory), sle->getFieldU64(sfBookNode), offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
if (sle->isFieldPresent(sfAdditionalBooks))
{
XRPL_ASSERT(
sle->isFlag(lsfHybrid) && sle->isFieldPresent(sfDomainID),
"xrpl::offerDelete : should be a hybrid domain offer");
auto const& additionalBookDirs = sle->getFieldArray(sfAdditionalBooks);
for (auto const& bookDir : additionalBookDirs)
{
auto const& dirIndex = bookDir.getFieldH256(sfBookDirectory);
auto const& dirNode = bookDir.getFieldU64(sfBookNode);
if (!view.dirRemove(keylet::page(dirIndex), dirNode, offerIndex, false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
}
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
view.erase(sle);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -1,759 +0,0 @@
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Rules.h>
namespace xrpl {
//------------------------------------------------------------------------------
//
// Credit functions (from Credit.cpp)
//
//------------------------------------------------------------------------------
STAmount
creditLimit(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit);
result.setIssuer(account);
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match");
return result;
}
IOUAmount
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
{
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
}
STAmount
creditBalance(
ReadView const& view,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
result = sleRippleState->getFieldAmount(sfBalance);
if (account < issuer)
result.negate();
result.setIssuer(account);
}
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match");
return result;
}
//------------------------------------------------------------------------------
//
// Freeze checking (IOU-specific)
//
//------------------------------------------------------------------------------
bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (isXRP(currency))
return false;
if (issuer != account)
{
// Check if the issuer froze the line
auto const sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
}
// Can the specified account spend the specified currency issued by
// the specified issuer or does the freeze flag prohibit it?
bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (isXRP(currency))
return false;
auto sle = view.read(keylet::account(issuer));
if (sle && sle->isFlag(lsfGlobalFreeze))
return true;
if (issuer != account)
{
// Check if the issuer froze the line
sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
}
bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
{
if (isXRP(currency))
{
return false;
}
if (issuer == account)
{
return false;
}
auto const sle = view.read(keylet::line(account, issuer, currency));
if (!sle)
{
return false;
}
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
}
//------------------------------------------------------------------------------
//
// Trust line operations
//
//------------------------------------------------------------------------------
TER
trustCreate(
ApplyView& view,
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j)
{
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
<< to_string(uDstAccountID) << ", " << saBalance.getFullText();
auto const& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
auto const& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
if (uLowAccountID == uHighAccountID)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::trustCreate : trust line to self");
if (view.rules().enabled(featureLendingProtocol))
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const sleRippleState = std::make_shared<SLE>(ltRIPPLE_STATE, uIndex);
view.insert(sleRippleState);
auto lowNode = view.dirInsert(
keylet::ownerDir(uLowAccountID), sleRippleState->key(), describeOwnerDir(uLowAccountID));
if (!lowNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
auto highNode = view.dirInsert(
keylet::ownerDir(uHighAccountID), sleRippleState->key(), describeOwnerDir(uHighAccountID));
if (!highNode)
return tecDIR_FULL; // LCOV_EXCL_LINE
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
bool const bSetHigh = bSrcHigh ^ bSetDst;
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
if (!sleAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
"xrpl::trustCreate : matching account ID");
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
if (!slePeer)
return tecNO_TARGET;
// Remember deletion hints.
sleRippleState->setFieldU64(sfLowNode, *lowNode);
sleRippleState->setFieldU64(sfHighNode, *highNode);
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
sleRippleState->setFieldAmount(
bSetHigh ? sfLowLimit : sfHighLimit,
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
if (uQualityIn)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
if (uQualityOut)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
if (bAuth)
{
uFlags |= (bSetHigh ? lsfHighAuth : lsfLowAuth);
}
if (bNoRipple)
{
uFlags |= (bSetHigh ? lsfHighNoRipple : lsfLowNoRipple);
}
if (bFreeze)
{
uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
}
if (bDeepFreeze)
{
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
}
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
{
// The other side's default is no rippling
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
}
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, 1, j);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed());
return tesSUCCESS;
}
TER
trustDelete(
ApplyView& view,
std::shared_ptr<SLE> const& sleRippleState,
AccountID const& uLowAccountID,
AccountID const& uHighAccountID,
beast::Journal j)
{
// Detect legacy dirs.
std::uint64_t uLowNode = sleRippleState->getFieldU64(sfLowNode);
std::uint64_t uHighNode = sleRippleState->getFieldU64(sfHighNode);
JLOG(j.trace()) << "trustDelete: Deleting ripple line: low";
if (!view.dirRemove(keylet::ownerDir(uLowAccountID), uLowNode, sleRippleState->key(), false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
JLOG(j.trace()) << "trustDelete: Deleting ripple line: high";
if (!view.dirRemove(keylet::ownerDir(uHighAccountID), uHighNode, sleRippleState->key(), false))
{
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
view.erase(sleRippleState);
return tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// IOU issuance/redemption
//
//------------------------------------------------------------------------------
static bool
updateTrustLine(
ApplyView& view,
SLE::pointer state,
bool bSenderHigh,
AccountID const& sender,
STAmount const& before,
STAmount const& after,
beast::Journal j)
{
if (!state)
return false;
std::uint32_t const flags(state->getFieldU32(sfFlags));
auto sle = view.peek(keylet::account(sender));
if (!sle)
return false;
// YYY Could skip this if rippling in reverse.
if (before > beast::zero
// Sender balance was positive.
&& after <= beast::zero
// Sender is zero or negative.
&& (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& static_cast<bool>(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
// Sender quality in is 0.
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
// Sender quality out is 0.
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, sle, -1, j);
// Clear reserve flag.
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
// Balance is zero, receiver reserve is clear.
if (!after // Balance is zero.
&& !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
return true;
}
return false;
}
TER
issueIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j)
{
XRPL_ASSERT(
!isXRP(account) && !isXRP(issue.account),
"xrpl::issueIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": " << amount.getFullText();
bool bSenderHigh = issue.account > account;
auto const index = keylet::line(issue.account, account, issue.currency);
if (auto state = view.peek(index))
{
STAmount final_balance = state->getFieldAmount(sfBalance);
if (bSenderHigh)
final_balance.negate(); // Put balance in sender terms.
STAmount const start_balance = final_balance;
final_balance -= amount;
auto const must_delete = updateTrustLine(
view, state, bSenderHigh, issue.account, start_balance, final_balance, j);
view.creditHook(issue.account, account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
// Adjust the balance on the trust line if necessary. We do this even
// if we are going to delete the line to reflect the correct balance
// at the time of deletion.
state->setFieldAmount(sfBalance, final_balance);
if (must_delete)
{
return trustDelete(
view,
state,
bSenderHigh ? account : issue.account,
bSenderHigh ? issue.account : account,
j);
}
view.update(state);
return tesSUCCESS;
}
// NIKB TODO: The limit uses the receiver's account as the issuer and
// this is unnecessarily inefficient as copying which could be avoided
// is now required. Consider available options.
STAmount const limit(Issue{issue.currency, account});
STAmount final_balance = amount;
final_balance.setIssuer(noAccount());
auto const receiverAccount = view.peek(keylet::account(account));
if (!receiverAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
bool noRipple = (receiverAccount->getFlags() & lsfDefaultRipple) == 0;
return trustCreate(
view,
bSenderHigh,
issue.account,
account,
index.key,
receiverAccount,
false,
noRipple,
false,
false,
final_balance,
limit,
0,
0,
j);
}
TER
redeemIOU(
ApplyView& view,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
beast::Journal j)
{
XRPL_ASSERT(
!isXRP(account) && !isXRP(issue.account),
"xrpl::redeemIOU : neither account nor issuer is XRP");
// Consistency check
XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue");
// Can't send to self!
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": " << amount.getFullText();
bool bSenderHigh = account > issue.account;
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
{
STAmount final_balance = state->getFieldAmount(sfBalance);
if (bSenderHigh)
final_balance.negate(); // Put balance in sender terms.
STAmount const start_balance = final_balance;
final_balance -= amount;
auto const must_delete =
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
view.creditHook(account, issue.account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
// Adjust the balance on the trust line if necessary. We do this even
// if we are going to delete the line to reflect the correct balance
// at the time of deletion.
state->setFieldAmount(sfBalance, final_balance);
if (must_delete)
{
return trustDelete(
view,
state,
bSenderHigh ? issue.account : account,
bSenderHigh ? account : issue.account,
j);
}
view.update(state);
return tesSUCCESS;
}
// In order to hold an IOU, a trust line *MUST* exist to track the
// balance. If it doesn't, then something is very wrong. Don't try
// to continue.
// LCOV_EXCL_START
JLOG(j.fatal()) << "redeemIOU: " << to_string(account) << " attempts to "
<< "redeem " << amount.getFullText() << " but no trust line exists!";
return tefINTERNAL;
// LCOV_EXCL_STOP
}
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (IOU-specific)
//
//------------------------------------------------------------------------------
TER
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
{
if (isXRP(issue) || issue.account == account)
return tesSUCCESS;
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
// If account has no line, and this is a strong check, fail
if (!trustLine && authType == AuthType::StrongAuth)
return tecNO_LINE;
// If this is a weak or legacy check, or if the account has a line, fail if
// auth is required and not set on the line
if (auto const issuerAccount = view.read(keylet::account(issue.account));
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
{
if (trustLine)
{
return ((*trustLine)[sfFlags] & ((account > issue.account) ? lsfLowAuth : lsfHighAuth))
? tesSUCCESS
: TER{tecNO_AUTH};
}
return TER{tecNO_LINE};
}
return tesSUCCESS;
}
TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
{
if (issue.native())
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
if (issuerId == from || issuerId == to)
return tesSUCCESS;
auto const sleIssuer = view.read(keylet::account(issuerId));
if (sleIssuer == nullptr)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const isRippleDisabled = [&](AccountID account) -> bool {
// Line might not exist, but some transfers can create it. If this
// is the case, just check the default ripple on the issuer account.
auto const line = view.read(keylet::line(account, issue));
if (line)
{
bool const issuerHigh = issuerId > account;
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
}
return sleIssuer->isFlag(lsfDefaultRipple) == false;
};
// Fail if rippling disabled on both trust lines
if (isRippleDisabled(from) && isRippleDisabled(to))
return terNO_RIPPLE;
return tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)
//
//------------------------------------------------------------------------------
TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal)
{
// Every account can hold XRP. An issuer can issue directly.
if (issue.native() || accountID == issue.getIssuer())
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
if (isGlobalFrozen(view, issuerId))
return tecFROZEN; // LCOV_EXCL_LINE
auto const& srcId = issuerId;
auto const& dstId = accountID;
auto const high = srcId > dstId;
auto const index = keylet::line(srcId, dstId, currency);
auto const sleSrc = view.peek(keylet::account(srcId));
auto const sleDst = view.peek(keylet::account(dstId));
if (!sleDst || !sleSrc)
return tefINTERNAL; // LCOV_EXCL_LINE
if (!sleSrc->isFlag(lsfDefaultRipple))
return tecINTERNAL; // LCOV_EXCL_LINE
// If the line already exists, don't create it again.
if (view.read(index))
return tecDUPLICATE;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
view,
high,
srcId,
dstId,
index.key,
sleDst,
/*bAuth=*/false,
/*bNoRipple=*/true,
/*bFreeze=*/false,
/*deepFreeze*/ false,
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
journal);
}
TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal)
{
if (issue.native())
{
auto const sle = view.read(keylet::account(accountID));
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const balance = sle->getFieldAmount(sfBalance);
if (balance.xrp() != 0)
return tecHAS_OBLIGATIONS;
return tesSUCCESS;
}
// `asset` is an IOU.
// If the account is the issuer, then no line should exist. Check anyway.
// If a line does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == issue.account;
auto const line = view.peek(keylet::line(accountID, issue));
if (!line)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
return tecHAS_OBLIGATIONS;
// Adjust the owner count(s)
if (line->isFlag(lsfLowReserve))
{
// Clear reserve for low account.
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleLowAccount, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfLowReserve);
}
if (line->isFlag(lsfHighReserve))
{
// Clear reserve for high account.
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleHighAccount, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfHighReserve);
}
return trustDelete(
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
}
TER
deleteAMMTrustLine(
ApplyView& view,
std::shared_ptr<SLE> sleState,
std::optional<AccountID> const& ammAccountID,
beast::Journal j)
{
if (!sleState || sleState->getType() != ltRIPPLE_STATE)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const& [low, high] = std::minmax(
sleState->getFieldAmount(sfLowLimit).getIssuer(),
sleState->getFieldAmount(sfHighLimit).getIssuer());
auto sleLow = view.peek(keylet::account(low));
auto sleHigh = view.peek(keylet::account(high));
if (!sleLow || !sleHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
// can't both be AMM
if (ammLow && ammHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
// at least one must be
if (!ammLow && !ammHigh)
return terNO_AMM;
// one must be the target amm
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
return terNO_AMM;
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
{
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
return ter;
}
auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
if (!(sleState->getFlags() & uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
return tesSUCCESS;
}
} // namespace xrpl

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +0,0 @@
#include <xrpl/ledger/helpers/VaultHelpers.h>
//
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/st.h>
namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
XRPL_ASSERT(
assets.asset() == vault->at(sfAsset),
"xrpl::assetsToSharesDeposit : assets and vault match");
if (assets.negative() || assets.asset() != vault->at(sfAsset))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = vault->at(sfAssetsTotal);
STAmount shares{vault->at(sfShareMPTID)};
if (assetTotal == 0)
{
return STAmount{
shares.asset(),
Number(assets.mantissa(), assets.exponent() + vault->at(sfScale)).truncate()};
}
Number const shareTotal = issuance->at(sfOutstandingAmount);
shares = ((shareTotal * assets) / assetTotal).truncate();
return shares;
}
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
shares.asset() == vault->at(sfShareMPTID),
"xrpl::sharesToAssetsDeposit : shares and vault match");
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = vault->at(sfAssetsTotal);
STAmount assets{vault->at(sfAsset)};
if (assetTotal == 0)
{
return STAmount{
assets.asset(), shares.mantissa(), shares.exponent() - vault->at(sfScale), false};
}
Number const shareTotal = issuance->at(sfOutstandingAmount);
assets = (assetTotal * shares) / shareTotal;
return assets;
}
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets,
TruncateShares truncate)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesWithdraw : non-negative assets");
XRPL_ASSERT(
assets.asset() == vault->at(sfAsset),
"xrpl::assetsToSharesWithdraw : assets and vault match");
if (assets.negative() || assets.asset() != vault->at(sfAsset))
return std::nullopt; // LCOV_EXCL_LINE
Number assetTotal = vault->at(sfAssetsTotal);
assetTotal -= vault->at(sfLossUnrealized);
STAmount shares{vault->at(sfShareMPTID)};
if (assetTotal == 0)
return shares;
Number const shareTotal = issuance->at(sfOutstandingAmount);
Number result = (shareTotal * assets) / assetTotal;
if (truncate == TruncateShares::yes)
result = result.truncate();
shares = result;
return shares;
}
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
shares.asset() == vault->at(sfShareMPTID),
"xrpl::sharesToAssetsWithdraw : shares and vault match");
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
return std::nullopt; // LCOV_EXCL_LINE
Number assetTotal = vault->at(sfAssetsTotal);
assetTotal -= vault->at(sfLossUnrealized);
STAmount assets{vault->at(sfAsset)};
if (assetTotal == 0)
return assets;
Number const shareTotal = issuance->at(sfOutstandingAmount);
assets = (assetTotal * shares) / shareTotal;
return assets;
}
} // namespace xrpl

View File

@@ -104,7 +104,7 @@ ApplyContext::checkInvariantsHelper(
// call each check's finalizer to see that it passes
if (!std::all_of(finalizers.cbegin(), finalizers.cend(), [](auto const& b) { return b; }))
{
JLOG(journal.fatal()) << "Transaction has failed one or more invariants: "
JLOG(journal.fatal()) << "Transaction has failed one or more global invariants: "
<< to_string(tx.getJson(JsonOptions::none));
return failInvariantCheck(result);
@@ -112,7 +112,7 @@ ApplyContext::checkInvariantsHelper(
}
catch (std::exception const& ex)
{
JLOG(journal.fatal()) << "Transaction caused an exception in an invariant"
JLOG(journal.fatal()) << "Transaction caused an exception in a global invariant"
<< ", ex: " << ex.what()
<< ", tx: " << to_string(tx.getJson(JsonOptions::none));

View File

@@ -2,8 +2,8 @@
#include <xrpl/basics/contract.h>
#include <xrpl/core/NetworkIDService.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
@@ -1060,6 +1060,60 @@ Transactor::trapTransaction(uint256 txHash) const
JLOG(j_.debug()) << "Transaction trapped: " << txHash;
}
[[nodiscard]] TER
Transactor::checkTransactionInvariants(TER result, XRPAmount fee)
{
try
{
// Phase 1: visit modified entries
ctx_.visit([this](
uint256 const&,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) {
this->visitInvariantEntry(isDelete, before, after);
});
// Phase 2: finalize
if (!this->finalizeInvariants(ctx_.tx, result, fee, ctx_.view(), ctx_.journal))
{
JLOG(ctx_.journal.fatal()) << //
"Transaction has failed one or more transaction invariants";
return tecINVARIANT_FAILED;
}
}
catch (std::exception const& ex)
{
JLOG(ctx_.journal.fatal()) << //
"Exception while checking transaction invariants: " << //
ex.what() << //
", tx: " << //
to_string(ctx_.tx.getJson(JsonOptions::none));
return tecINVARIANT_FAILED;
}
return result;
}
[[nodiscard]] TER
Transactor::checkInvariants(TER result, XRPAmount fee)
{
// Transaction invariants first (more specific). These check post-conditions of the specific
// transaction. If these fail, the transaction's core logic is wrong.
auto const txResult = checkTransactionInvariants(result, fee);
// Protocol invariants second (broader). These check properties that must hold regardless of
// transaction type.
auto const protoResult = ctx_.checkInvariants(result, fee);
// Fail if either check failed. tef (fatal) takes priority over tec.
if (txResult == tefINVARIANT_FAILED || protoResult == tefINVARIANT_FAILED)
return tefINVARIANT_FAILED;
if (txResult == tecINVARIANT_FAILED || protoResult == tecINVARIANT_FAILED)
return tecINVARIANT_FAILED;
return result;
}
//------------------------------------------------------------------------------
ApplyResult
Transactor::operator()()
@@ -1212,22 +1266,20 @@ Transactor::operator()()
if (applied)
{
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
// proceed to apply the tx
result = ctx_.checkInvariants(result, fee);
result = checkInvariants(result, fee);
if (result == tecINVARIANT_FAILED)
{
// if invariants checking failed again, reset the context and
// attempt to only claim a fee.
// Reset to fee-claim only
auto const resetResult = reset(fee);
if (!isTesSuccess(resetResult.first))
result = resetResult.first;
fee = resetResult.second;
// Check invariants again to ensure the fee claiming doesn't
// violate invariants.
// Check invariants again to ensure the fee claiming doesn't violate
// invariants. After reset, only protocol invariants are re-checked.
// Transaction invariants are not meaningful here — the transaction's
// effects have been rolled back.
if (isTesSuccess(result) || isTecClaim(result))
result = ctx_.checkInvariants(result, fee);
}

View File

@@ -295,6 +295,15 @@ invoke_apply(ApplyContext& ctx)
}
}
std::unique_ptr<Transactor>
makeTransactor(ApplyContext& ctx)
{
return with_txn_type(
ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() -> std::unique_ptr<Transactor> {
return std::make_unique<T>(ctx);
});
}
PreflightResult
preflight(
ServiceRegistry& registry,

View File

@@ -1,7 +1,7 @@
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
//
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFormats.h>

View File

@@ -2,133 +2,20 @@
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
namespace xrpl {
ValidVault::Vault
ValidVault::Vault::make(SLE const& from)
{
XRPL_ASSERT(from.getType() == ltVAULT, "ValidVault::Vault::make : from Vault object");
ValidVault::Vault self;
self.key = from.key();
self.asset = from.at(sfAsset);
self.pseudoId = from.getAccountID(sfAccount);
self.owner = from.at(sfOwner);
self.shareMPTID = from.getFieldH192(sfShareMPTID);
self.assetsTotal = from.at(sfAssetsTotal);
self.assetsAvailable = from.at(sfAssetsAvailable);
self.assetsMaximum = from.at(sfAssetsMaximum);
self.lossUnrealized = from.at(sfLossUnrealized);
return self;
}
ValidVault::Shares
ValidVault::Shares::make(SLE const& from)
{
XRPL_ASSERT(
from.getType() == ltMPTOKEN_ISSUANCE,
"ValidVault::Shares::make : from MPTokenIssuance object");
ValidVault::Shares self;
self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
self.sharesTotal = from.at(sfOutstandingAmount);
self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
return self;
}
void
ValidVault::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
// If `before` is empty, this means an object is being created, in which
// case `isDelete` must be false. Otherwise `before` and `after` are set and
// `isDelete` indicates whether an object is being deleted or modified.
XRPL_ASSERT(
after != nullptr && (before != nullptr || !isDelete),
"xrpl::ValidVault::visitEntry : some object is available");
// Number balanceDelta will capture the difference (delta) between "before"
// state (zero if created) and "after" state (zero if destroyed), so the
// invariants can validate that the change in account balances matches the
// change in vault balances, stored to deltas_ at the end of this function.
Number balanceDelta{};
std::int8_t sign = 0;
if (before)
{
switch (before->getType())
{
case ltVAULT:
beforeVault_.push_back(Vault::make(*before));
break;
case ltMPTOKEN_ISSUANCE:
// At this moment we have no way of telling if this object holds
// vault shares or something else. Save it for finalize.
beforeMPTs_.push_back(Shares::make(*before));
balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfOutstandingAmount));
sign = 1;
break;
case ltMPTOKEN:
balanceDelta = static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
sign = -1;
break;
case ltACCOUNT_ROOT:
case ltRIPPLE_STATE:
balanceDelta = before->getFieldAmount(sfBalance);
sign = -1;
break;
default:;
}
}
if (!isDelete && after)
{
switch (after->getType())
{
case ltVAULT:
afterVault_.push_back(Vault::make(*after));
break;
case ltMPTOKEN_ISSUANCE:
// At this moment we have no way of telling if this object holds
// vault shares or something else. Save it for finalize.
afterMPTs_.push_back(Shares::make(*after));
balanceDelta -=
Number(static_cast<std::int64_t>(after->getFieldU64(sfOutstandingAmount)));
sign = 1;
break;
case ltMPTOKEN:
balanceDelta -= Number(static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
sign = -1;
break;
case ltACCOUNT_ROOT:
case ltRIPPLE_STATE:
balanceDelta -= Number(after->getFieldAmount(sfBalance));
sign = -1;
break;
default:;
}
}
uint256 const key = (before ? before->key() : after->key());
// Append to deltas if sign is non-zero, i.e. an object of an interesting
// type has been updated. A transaction may update an object even when
// its balance has not changed, e.g. transaction fee equals the amount
// transferred to the account. We intentionally do not compare balanceDelta
// against zero, to avoid missing such updates.
if (sign != 0)
deltas_[key] = balanceDelta * sign;
data_.visitEntry(isDelete, before, after);
}
bool
@@ -144,13 +31,15 @@ ValidVault::finalize(
if (!isTesSuccess(ret))
return true; // Do not perform checks
auto const& afterVault_ = data_.afterVault();
auto const& beforeVault_ = data_.beforeVault();
if (afterVault_.empty() && beforeVault_.empty())
{
if (hasPrivilege(tx, mustModifyVault))
{
JLOG(j.fatal()) << //
"Invariant failed: vault operation succeeded without modifying "
"a vault";
"Invariant failed: vault operation succeeded without modifying a vault";
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault noop invariant");
return !enforce;
}
@@ -159,8 +48,7 @@ ValidVault::finalize(
}
if (!(hasPrivilege(tx, mustModifyVault) || hasPrivilege(tx, mayModifyVault)))
{
JLOG(j.fatal()) << //
"Invariant failed: vault updated by a wrong transaction type";
JLOG(j.fatal()) << "Invariant failed: vault updated by a wrong transaction type";
XRPL_ASSERT(
enforce,
"xrpl::ValidVault::finalize : illegal vault transaction "
@@ -170,104 +58,38 @@ ValidVault::finalize(
if (beforeVault_.size() > 1 || afterVault_.size() > 1)
{
JLOG(j.fatal()) << //
"Invariant failed: vault operation updated more than single vault";
JLOG(j.fatal()) << "Invariant failed: vault operation updated more than single vault";
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : single vault invariant");
return !enforce; // That's all we can do here
}
auto const txnType = tx.getTxnType();
// We do special handling for ttVAULT_DELETE first, because it's the only
// vault-modifying transaction without an "after" state of the vault
// ttVAULT_DELETE has no after state — its invariants are in the transactor.
// Other tx types without an after state means a vault was illegally deleted.
if (afterVault_.empty())
{
if (txnType != ttVAULT_DELETE)
{
JLOG(j.fatal()) << //
"Invariant failed: vault deleted by a wrong transaction type";
XRPL_ASSERT(
enforce,
"xrpl::ValidVault::finalize : illegal vault deletion "
"invariant");
return !enforce; // That's all we can do here
}
if (txnType == ttVAULT_DELETE)
return true;
// Note, if afterVault_ is empty then we know that beforeVault_ is not
// empty, as enforced at the top of this function
auto const& beforeVault = beforeVault_[0];
// At this moment we only know a vault is being deleted and there
// might be some MPTokenIssuance objects which are deleted in the
// same transaction. Find the one matching this vault.
auto const deletedShares = [&]() -> std::optional<Shares> {
for (auto const& e : beforeMPTs_)
{
if (e.share.getMptID() == beforeVault.shareMPTID)
return std::move(e);
}
return std::nullopt;
}();
if (!deletedShares)
{
JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
"delete shares";
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant");
return !enforce; // That's all we can do here
}
bool result = true;
if (deletedShares->sharesTotal != 0)
{
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
"shares outstanding";
result = false;
}
if (beforeVault.assetsTotal != zero)
{
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
"assets outstanding";
result = false;
}
if (beforeVault.assetsAvailable != zero)
{
JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
"assets available";
result = false;
}
return result;
}
if (txnType == ttVAULT_DELETE)
{
JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
"deleting a vault";
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault deletion invariant");
return !enforce; // That's all we can do here
JLOG(j.fatal()) << "Invariant failed: vault deleted by a wrong transaction type";
XRPL_ASSERT(
enforce,
"xrpl::ValidVault::finalize : illegal vault deletion "
"invariant");
return !enforce;
}
// Note, `afterVault_.empty()` is handled above
auto const& afterVault = afterVault_[0];
XRPL_ASSERT(
beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
"xrpl::ValidVault::finalize : single vault operation");
auto const updatedShares = [&]() -> std::optional<Shares> {
// At this moment we only know that a vault is being updated and there
// might be some MPTokenIssuance objects which are also updated in the
// same transaction. Find the one matching the shares to this vault.
// Note, we expect updatedMPTs collection to be extremely small. For
// such collections linear search is faster than lookup.
for (auto const& e : afterMPTs_)
{
if (e.share.getMptID() == afterVault.shareMPTID)
return e;
}
auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID));
return sleShares ? std::optional<Shares>(Shares::make(*sleShares)) : std::nullopt;
auto const updatedShares = [&]() -> std::optional<VaultInvariantData::Shares> {
if (auto s = data_.resolveUpdatedShares(afterVault))
return s;
auto const sle = view.read(keylet::mptIssuance(afterVault.shareMPTID));
return sle ? std::optional(VaultInvariantData::Shares::make(*sle)) : std::nullopt;
}();
bool result = true;
@@ -293,16 +115,16 @@ ValidVault::finalize(
if (updatedShares->sharesTotal == 0)
{
if (afterVault.assetsTotal != zero)
if (afterVault.assetsTotal != beast::zero)
{
JLOG(j.fatal()) << "Invariant failed: updated zero sized "
"vault must have no assets outstanding";
JLOG(j.fatal()) << //
"Invariant failed: updated zero sized vault must have no assets outstanding";
result = false;
}
if (afterVault.assetsAvailable != zero)
if (afterVault.assetsAvailable != beast::zero)
{
JLOG(j.fatal()) << "Invariant failed: updated zero sized "
"vault must have no assets available";
JLOG(j.fatal()) << //
"Invariant failed: updated zero sized vault must have no assets available";
result = false;
}
}
@@ -314,7 +136,7 @@ ValidVault::finalize(
result = false;
}
if (afterVault.assetsAvailable < zero)
if (afterVault.assetsAvailable < beast::zero)
{
JLOG(j.fatal()) << "Invariant failed: assets available must be positive";
result = false;
@@ -322,8 +144,8 @@ ValidVault::finalize(
if (afterVault.assetsAvailable > afterVault.assetsTotal)
{
JLOG(j.fatal()) << "Invariant failed: assets available must "
"not be greater than assets outstanding";
JLOG(j.fatal()) << //
"Invariant failed: assets available must not be greater than assets outstanding";
result = false;
}
else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable)
@@ -334,13 +156,13 @@ ValidVault::finalize(
result = false;
}
if (afterVault.assetsTotal < zero)
if (afterVault.assetsTotal < beast::zero)
{
JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive";
result = false;
}
if (afterVault.assetsMaximum < zero)
if (afterVault.assetsMaximum < beast::zero)
{
JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
result = false;
@@ -365,553 +187,20 @@ ValidVault::finalize(
result = false;
}
auto const beforeShares = [&]() -> std::optional<Shares> {
if (beforeVault_.empty())
return std::nullopt;
auto const& beforeVault = beforeVault_[0];
for (auto const& e : beforeMPTs_)
{
if (e.share.getMptID() == beforeVault.shareMPTID)
return std::move(e);
}
return std::nullopt;
}();
auto const beforeShares =
beforeVault_.empty() ? std::nullopt : data_.resolveBeforeShares(beforeVault_[0]);
if (!beforeShares &&
(tx.getTxnType() == ttVAULT_DEPOSIT || //
tx.getTxnType() == ttVAULT_WITHDRAW || //
tx.getTxnType() == ttVAULT_CLAWBACK))
{
JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
"without updating shares";
JLOG(j.fatal()) << //
"Invariant failed: vault operation succeeded without updating shares";
XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares noop invariant");
return !enforce; // That's all we can do here
}
auto const& vaultAsset = afterVault.asset;
auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
auto const get = //
[&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
if (it == deltas_.end())
return std::nullopt;
return it->second * sign;
};
return std::visit(
[&]<typename TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
if (isXRP(issue))
return get(deltas_.find(keylet::account(id).key));
return get(
deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1);
}
else if constexpr (std::is_same_v<TIss, MPTIssue>)
{
return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key));
}
},
vaultAsset.value());
};
auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
auto ret = deltaAssets(tx[sfAccount]);
// Nothing returned or not XRP transaction
if (!ret.has_value() || !vaultAsset.native())
return ret;
// Delegated transaction; no need to compensate for fees
if (auto const delegate = tx[~sfDelegate];
delegate.has_value() && *delegate != tx[sfAccount])
return ret;
*ret += fee.drops();
if (*ret == zero)
return std::nullopt;
return ret;
};
auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
auto const it = [&]() {
if (id == afterVault.pseudoId)
return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key);
return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
}();
return it != deltas_.end() ? std::optional<Number>(it->second) : std::nullopt;
};
auto const vaultHoldsNoAssets = [&](Vault const& vault) {
return vault.assetsAvailable == 0 && vault.assetsTotal == 0;
};
// Technically this does not need to be a lambda, but it's more
// convenient thanks to early "return false"; the not-so-nice
// alternatives are several layers of nested if/else or more complex
// (i.e. brittle) if statements.
result &= [&]() {
switch (txnType)
{
case ttVAULT_CREATE: {
bool result = true;
if (!beforeVault_.empty())
{
JLOG(j.fatal()) //
<< "Invariant failed: create operation must not have "
"updated a vault";
result = false;
}
if (afterVault.assetsAvailable != zero || afterVault.assetsTotal != zero ||
afterVault.lossUnrealized != zero || updatedShares->sharesTotal != 0)
{
JLOG(j.fatal()) //
<< "Invariant failed: created vault must be empty";
result = false;
}
if (afterVault.pseudoId != updatedShares->share.getIssuer())
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer and vault "
"pseudo-account must be the same";
result = false;
}
auto const sleSharesIssuer =
view.read(keylet::account(updatedShares->share.getIssuer()));
if (!sleSharesIssuer)
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer must exist";
return false;
}
if (!isPseudoAccount(sleSharesIssuer))
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer must be a "
"pseudo-account";
result = false;
}
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
!vaultId || *vaultId != afterVault.key)
{
JLOG(j.fatal()) //
<< "Invariant failed: shares issuer pseudo-account "
"must point back to the vault";
result = false;
}
return result;
}
case ttVAULT_SET: {
bool result = true;
XRPL_ASSERT(
!beforeVault_.empty(), "xrpl::ValidVault::finalize : set updated a vault");
auto const& beforeVault = beforeVault_[0];
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
if (vaultDeltaAssets)
{
JLOG(j.fatal()) << //
"Invariant failed: set must not change vault balance";
result = false;
}
if (beforeVault.assetsTotal != afterVault.assetsTotal)
{
JLOG(j.fatal()) << //
"Invariant failed: set must not change assets "
"outstanding";
result = false;
}
if (afterVault.assetsMaximum > zero &&
afterVault.assetsTotal > afterVault.assetsMaximum)
{
JLOG(j.fatal()) << //
"Invariant failed: set assets outstanding must not "
"exceed assets maximum";
result = false;
}
if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
{
JLOG(j.fatal()) << //
"Invariant failed: set must not change assets "
"available";
result = false;
}
if (beforeShares && updatedShares &&
beforeShares->sharesTotal != updatedShares->sharesTotal)
{
JLOG(j.fatal()) << //
"Invariant failed: set must not change shares "
"outstanding";
result = false;
}
return result;
}
case ttVAULT_DEPOSIT: {
bool result = true;
XRPL_ASSERT(
!beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault");
auto const& beforeVault = beforeVault_[0];
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
if (!vaultDeltaAssets)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change vault balance";
return false; // That's all we can do
}
if (*vaultDeltaAssets > tx[sfAmount])
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must not change vault "
"balance by more than deposited amount";
result = false;
}
if (*vaultDeltaAssets <= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must increase vault balance";
result = false;
}
// Any payments (including deposits) made by the issuer
// do not change their balance, but create funds instead.
bool const issuerDeposit = [&]() -> bool {
if (vaultAsset.native())
return false;
return tx[sfAccount] == vaultAsset.getIssuer();
}();
if (!issuerDeposit)
{
auto const accountDeltaAssets = deltaAssetsTxAccount();
if (!accountDeltaAssets)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change depositor "
"balance";
return false;
}
if (*accountDeltaAssets >= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must decrease depositor "
"balance";
result = false;
}
if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change vault and "
"depositor balance by equal amount";
result = false;
}
}
if (afterVault.assetsMaximum > zero &&
afterVault.assetsTotal > afterVault.assetsMaximum)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit assets outstanding must not "
"exceed assets maximum";
result = false;
}
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
if (!accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change depositor "
"shares";
return false; // That's all we can do
}
if (*accountDeltaShares <= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must increase depositor "
"shares";
result = false;
}
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
if (!vaultDeltaShares || *vaultDeltaShares == zero)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change vault shares";
return false; // That's all we can do
}
if (*vaultDeltaShares * -1 != *accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: deposit must change depositor and "
"vault shares by equal amount";
result = false;
}
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
{
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
"outstanding must add up";
result = false;
}
if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
{
JLOG(j.fatal()) << "Invariant failed: deposit and assets "
"available must add up";
result = false;
}
return result;
}
case ttVAULT_WITHDRAW: {
bool result = true;
XRPL_ASSERT(
!beforeVault_.empty(),
"xrpl::ValidVault::finalize : withdrawal updated a "
"vault");
auto const& beforeVault = beforeVault_[0];
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
if (!vaultDeltaAssets)
{
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
"change vault balance";
return false; // That's all we can do
}
if (*vaultDeltaAssets >= zero)
{
JLOG(j.fatal()) << "Invariant failed: withdrawal must "
"decrease vault balance";
result = false;
}
// Any payments (including withdrawal) going to the issuer
// do not change their balance, but destroy funds instead.
bool const issuerWithdrawal = [&]() -> bool {
if (vaultAsset.native())
return false;
auto const destination = tx[~sfDestination].value_or(tx[sfAccount]);
return destination == vaultAsset.getIssuer();
}();
if (!issuerWithdrawal)
{
auto const accountDeltaAssets = deltaAssetsTxAccount();
auto const otherAccountDelta = [&]() -> std::optional<Number> {
if (auto const destination = tx[~sfDestination];
destination && *destination != tx[sfAccount])
return deltaAssets(*destination);
return std::nullopt;
}();
if (accountDeltaAssets.has_value() == otherAccountDelta.has_value())
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must change one "
"destination balance";
return false;
}
auto const destinationDelta = //
accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta;
if (destinationDelta <= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must increase "
"destination balance";
result = false;
}
if (*vaultDeltaAssets * -1 != destinationDelta)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must change vault "
"and destination balance by equal amount";
result = false;
}
}
auto const accountDeltaShares = deltaShares(tx[sfAccount]);
if (!accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must change depositor "
"shares";
return false;
}
if (*accountDeltaShares >= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must decrease depositor "
"shares";
result = false;
}
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
if (!vaultDeltaShares || *vaultDeltaShares == zero)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must change vault shares";
return false; // That's all we can do
}
if (*vaultDeltaShares * -1 != *accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: withdrawal must change depositor "
"and vault shares by equal amount";
result = false;
}
// Note, vaultBalance is negative (see check above)
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
{
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
"assets outstanding must add up";
result = false;
}
if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable)
{
JLOG(j.fatal()) << "Invariant failed: withdrawal and "
"assets available must add up";
result = false;
}
return result;
}
case ttVAULT_CLAWBACK: {
bool result = true;
XRPL_ASSERT(
!beforeVault_.empty(), "xrpl::ValidVault::finalize : clawback updated a vault");
auto const& beforeVault = beforeVault_[0];
if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount])
{
// The owner can use clawback to force-burn shares when the
// vault is empty but there are outstanding shares
if (!(beforeShares && beforeShares->sharesTotal > 0 &&
vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount]))
{
JLOG(j.fatal()) << //
"Invariant failed: clawback may only be performed "
"by the asset issuer, or by the vault owner of an "
"empty vault";
return false; // That's all we can do
}
}
auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
if (vaultDeltaAssets)
{
if (*vaultDeltaAssets >= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must decrease vault "
"balance";
result = false;
}
if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback and assets outstanding "
"must add up";
result = false;
}
if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
afterVault.assetsAvailable)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback and assets available "
"must add up";
result = false;
}
}
else if (!vaultHoldsNoAssets(beforeVault))
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must change vault balance";
return false; // That's all we can do
}
auto const accountDeltaShares = deltaShares(tx[sfHolder]);
if (!accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must change holder shares";
return false; // That's all we can do
}
if (*accountDeltaShares >= zero)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must decrease holder "
"shares";
result = false;
}
auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
if (!vaultDeltaShares || *vaultDeltaShares == zero)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must change vault shares";
return false; // That's all we can do
}
if (*vaultDeltaShares * -1 != *accountDeltaShares)
{
JLOG(j.fatal()) << //
"Invariant failed: clawback must change holder and "
"vault shares by equal amount";
result = false;
}
return result;
}
case ttLOAN_SET:
case ttLOAN_MANAGE:
case ttLOAN_PAY: {
// TBD
return true;
}
default:
// LCOV_EXCL_START
UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type");
return false;
// LCOV_EXCL_STOP
}
}();
if (!result)
{
// The comment at the top of this file starting with "assert(enforce)"

View File

@@ -1,5 +1,5 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/Credit.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/paths/Flow.h>

View File

@@ -1,7 +1,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
@@ -399,4 +399,23 @@ AccountDelete::doApply()
return tesSUCCESS;
}
void
AccountDelete::visitInvariantEntry(
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)
{
}
bool
AccountDelete::finalizeInvariants(
STTx const&,
TER,
XRPAmount,
ReadView const&,
beast::Journal const&)
{
return true;
}
} // namespace xrpl

Some files were not shown because too many files have changed in this diff Show More