Compare commits

...

10 Commits

Author SHA1 Message Date
yinyiqian1
a1613e5b0d Support compact AND-composed sigma proof (#6861) 2026-04-10 17:42:23 -07:00
Kenny Lei
ac4f14298a Turn amendments to supported yes for attackathon (#6857) 2026-04-10 14:42:27 -07:00
Kenny Lei
2570c7df0e Sponsored Fees and Reserves for Attackathon (#6842)
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: tequ <git@tequ.dev>
Co-authored-by: Ed Hennis <ed@ripple.com>
Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com>
2026-04-10 13:51:22 -07:00
Gregory Tsipenyuk
24241a6f90 fix: MPT/DEX assorted fixes (#6840)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-10 11:07:09 -07:00
Shawn Xie
ad5adc14be fix: Fix pipeline for attackathon (#6852) 2026-04-10 12:33:02 -04:00
yinyiqian1
7cdbbc4d90 Fix DynamicMPT for Attackathon (#6834) 2026-04-09 22:47:46 -04:00
Kenny Lei
4e2b41c425 Batch for Attackathon (#6732)
Co-authored-by: Denis Angell <dangell@transia.co>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-04-09 14:27:54 -07:00
Kenny Lei
6eaf0bf180 fix: Bidirection Object Storage for delegation for Attackathon (#6830)
Co-authored-by: Zhiyuan Wang <1830604455@qq.com>
2026-04-09 12:12:23 -07:00
Kenny Lei
87e951470d Fix granular permission for Attackathon (#6831)
Co-authored-by: yinyiqian1 <yqian@ripple.com>
2026-04-09 12:08:18 -07:00
Shawn Xie
dfa1da47fa Confidential Transfer for Attackathon (#6828) 2026-04-09 13:01:55 -04:00
210 changed files with 30516 additions and 1461 deletions

View File

@@ -88,6 +88,7 @@ find_package(ed25519 REQUIRED)
find_package(gRPC REQUIRED)
find_package(LibArchive REQUIRED)
find_package(lz4 REQUIRED)
find_package(mpt-crypto REQUIRED)
find_package(nudb REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(secp256k1 REQUIRED)
@@ -100,6 +101,7 @@ target_link_libraries(
INTERFACE
ed25519::ed25519
lz4::lz4
mpt-crypto::mpt-crypto
OpenSSL::Crypto
OpenSSL::SSL
secp256k1::secp256k1

View File

@@ -12,6 +12,7 @@
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1774883011.384",
"mpt-crypto/0.2.0-rc2#2236dfc0cc11be70d84a5fa17a50bfb1%1775853786.434",
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
@@ -34,6 +35,7 @@
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87",
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
@@ -58,6 +60,12 @@
],
"lz4/[>=1.9.4 <2]": [
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504"
],
"openssl/3.5.5": [
"openssl/3.6.1"
],
"openssl/[>=3 <4]": [
"openssl/3.6.1"
]
},
"config_requires": []

View File

@@ -31,6 +31,7 @@ class Xrpl(ConanFile):
"ed25519/2015.03",
"grpc/1.78.1",
"libarchive/3.8.1",
"mpt-crypto/0.2.0-rc2",
"nudb/2.0.9",
"openssl/3.6.1",
"secp256k1/0.7.1",
@@ -214,6 +215,7 @@ class Xrpl(ConanFile):
"grpc::grpc++",
"libarchive::libarchive",
"lz4::lz4",
"mpt-crypto::mpt-crypto",
"nudb::nudb",
"openssl::crypto",
"protobuf::libprotobuf",

View File

@@ -59,6 +59,7 @@ words:
- autobridging
- bimap
- bindir
- blindings
- bookdir
- Bougalis
- Britto
@@ -91,6 +92,7 @@ words:
- daria
- dcmake
- dearmor
- decryptor
- deleteme
- demultiplexer
- deserializaton
@@ -100,6 +102,7 @@ words:
- distro
- doxyfile
- dxrpl
- elgamal
- enabled
- endmacro
- exceptioned
@@ -110,6 +113,7 @@ words:
- fmtdur
- fsanitize
- funclets
- Gamal
- gcov
- gcovr
- ghead
@@ -199,6 +203,7 @@ words:
- partitioner
- paychan
- paychans
- Pedersen
- permdex
- perminute
- permissioned
@@ -235,6 +240,7 @@ words:
- sahyadri
- Satoshi
- scons
- Schnorr
- secp
- sendq
- seqit
@@ -249,6 +255,8 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- sslws
- statsd
- STATSDCOLLECTOR
@@ -262,6 +270,7 @@ words:
- stvar
- stvector
- stxchainattestations
- summands
- superpeer
- superpeers
- takergets

View File

@@ -28,7 +28,7 @@ inline constexpr struct open_ledger_t
/** Batch view construction tag.
Views constructed with this tag are part of a stack of views
used during batch transaction applied.
used during batch transaction application.
*/
inline constexpr struct batch_view_t
{

View File

@@ -125,6 +125,22 @@ areCompatible(
beast::Journal::Stream& s,
char const* reason);
uint32_t
ownerCount(SLE::const_ref sponsorSle);
XRPAmount
calculateReserve(SLE::const_ref sle, Fees const& fees);
TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
std::shared_ptr<SLE const> accSle,
STAmount const& accBalance,
std::shared_ptr<SLE const> const& sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t accountCountDelta = 0);
//------------------------------------------------------------------------------
//
// Modifiers

View File

@@ -38,10 +38,27 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::shared_ptr<SLE> const& accountSle,
std::shared_ptr<SLE> const& sponsorSle,
std::int32_t amount,
beast::Journal j);
inline void
adjustOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::int32_t amount,
beast::Journal j)
{
return adjustOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : std::shared_ptr<SLE>(),
amount,
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.

View File

@@ -26,10 +26,7 @@ checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
* @param granularPermissions Granted granular permissions tied to the
* transaction type.
*/
void
loadGranularPermission(
std::shared_ptr<SLE const> const& delegate,
TxType const& type,
std::unordered_set<GranularPermissionType>& granularPermissions);
std::unordered_set<GranularPermissionType>
getGranularPermission(std::shared_ptr<SLE const> const& delegate, TxType const& type);
} // namespace xrpl

View File

@@ -6,6 +6,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTAmount.h>
@@ -17,6 +18,7 @@ template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
@@ -31,6 +33,7 @@ template <>
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
@@ -56,8 +59,12 @@ escrowUnlockApplyHelper<Issue>(
if (!view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
auto const sponsorAccountID = getTxReserveSponsorAccountID(tx);
std::shared_ptr<SLE> sponsorSle = {};
if (sponsorAccountID)
sponsorSle = view.peek(keylet::account(*sponsorAccountID));
if (auto const ret = checkInsufficientReserve(view, tx, sleDest, xrpBalance, sponsorSle, 1);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
@@ -84,6 +91,7 @@ escrowUnlockApplyHelper<Issue>(
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
sponsorAccountID, // sponsor
journal); // journal
!isTesSuccess(ter))
{
@@ -161,6 +169,7 @@ template <>
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
STAmount const& xrpBalance,
@@ -176,24 +185,30 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptIssuance(mptID);
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorAccountID = getTxReserveSponsorAccountID(tx);
std::shared_ptr<SLE> sponsorSle = {};
if (sponsorAccountID)
sponsorSle = view.peek(keylet::account(*sponsorAccountID));
if (auto const ret = checkInsufficientReserve(view, tx, sleDest, xrpBalance, sponsorSle, 1);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
if (auto const ter = createMPToken(view, mptID, receiver, sponsorAccountID, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
adjustOwnerCount(view, sleDest, 1, journal);
adjustOwnerCount(view, sleDest, sponsorSle, 1, journal);
auto mptSle = view.peek(mptKeylet);
addSponsorToLedgerEntry(mptSle, sponsorSle);
}
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
if (!view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(view, amount);

View File

@@ -68,6 +68,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -99,6 +100,7 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
@@ -121,6 +123,15 @@ canTransfer(
[[nodiscard]] TER
canTrade(ReadView const& view, Asset const& asset);
/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue.
*/
[[nodiscard]] TER
canMPTTradeAndTransfer(
ReadView const& v,
Asset const& asset,
AccountID const& from,
AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
@@ -130,6 +141,7 @@ canTrade(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -138,6 +150,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
@@ -169,6 +182,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::uint32_t const flags);
TER
@@ -176,6 +190,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
std::optional<xrpl::AccountID> const& sponsor,
beast::Journal j);
//------------------------------------------------------------------------------
@@ -227,17 +242,4 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
//------------------------------------------------------------------------------
//
// MPT DEX
//
//------------------------------------------------------------------------------
/* Return true if a transaction is allowed for the specified MPT/account. The
* function checks MPTokenIssuance and MPToken objects flags to determine if the
* transaction is allowed.
*/
TER
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
} // namespace xrpl

View File

@@ -44,7 +44,12 @@ findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftoken
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
insertToken(
ApplyView& view,
STTx const& tx,
AccountID owner,
std::optional<AccountID> const& sponsor,
STObject&& nft);
/** Remove the token from the owner's token directory. */
TER
@@ -116,6 +121,7 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,

View File

@@ -149,6 +149,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
std::optional<AccountID> const& sponsorAccountID,
beast::Journal j);
[[nodiscard]] TER
@@ -229,6 +230,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,

View File

@@ -0,0 +1,114 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
namespace xrpl {
inline bool
isReserveSponsored(STTx const& tx)
{
return tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve;
}
inline bool
isSponsorReserveCoSigning(STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsorSignature))
return false;
return isReserveSponsored(tx);
}
inline std::optional<AccountID>
getTxReserveSponsorAccountID(STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx))
{
return tx.getAccountID(sfSponsor);
}
return {};
}
inline std::shared_ptr<SLE>
getTxReserveSponsor(ApplyView& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
return view.peek(keylet::account(*sponsorID));
return {};
}
inline std::shared_ptr<SLE const>
getTxReserveSponsor(ReadView const& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
return view.read(keylet::account(*sponsorID));
return {};
}
inline std::optional<AccountID>
getLedgerEntryReserveSponsorAccountID(
std::shared_ptr<SLE const> const& sle,
SF_ACCOUNT const& field = sfSponsor)
{
if (sle->isFieldPresent(field))
return sle->getAccountID(field);
return {};
}
inline std::shared_ptr<SLE>
getLedgerEntryReserveSponsor(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.peek(keylet::account(*sponsorID));
return {};
}
inline std::shared_ptr<SLE const>
getLedgerEntryReserveSponsor(
ReadView const& view,
std::shared_ptr<SLE const> const& sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.read(keylet::account(*sponsorID));
return {};
}
inline void
addSponsorToLedgerEntry(
std::shared_ptr<SLE> const& sle,
std::shared_ptr<SLE> const& sponsorSle,
SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"addSponsorToLedgerEntry : Invalid field to the LedgerEntry");
if (sponsorSle)
sle->setAccountID(field, sponsorSle->getAccountID(sfAccount));
}
inline void
removeSponsorFromLedgerEntry(std::shared_ptr<SLE> const& sle, SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry");
if (sle->isFieldPresent(field))
sle->makeFieldAbsent(field);
}
} // namespace xrpl

View File

@@ -54,9 +54,15 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
checkGlobalFrozen(ReadView const& view, Asset const& asset);
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] TER
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
@@ -208,6 +214,7 @@ canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -216,6 +223,7 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
@@ -270,6 +278,7 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID = std::nullopt,
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
@@ -287,6 +296,7 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
[[nodiscard]] TER

View File

@@ -1,3 +1,5 @@
#pragma once
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Serializer.h>

View File

@@ -0,0 +1,391 @@
#pragma once
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/detail/secp256k1.h>
#include <secp256k1_mpt.h>
namespace xrpl {
/**
* @brief Bundles an ElGamal public key with its associated encrypted amount.
*
* Used to represent a recipient in confidential transfers, containing both
* the recipient's ElGamal public key and the ciphertext encrypting the
* transfer amount under that key.
*/
struct ConfidentialRecipient
{
Slice publicKey; ///< The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
Slice encryptedAmount; ///< The encrypted amount ciphertext
///< (size=xrpl::ecGamalEncryptedTotalLength).
};
/// Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2).
struct EcPair
{
secp256k1_pubkey c1;
secp256k1_pubkey c2;
};
/**
* @brief Increments the confidential balance version counter on an MPToken.
*
* The version counter is used to prevent replay attacks by binding proofs
* to a specific state of the account's confidential balance. Wraps to 0
* on overflow (defined behavior for unsigned integers).
*
* @param mptoken The MPToken ledger entry to update.
*/
inline void
incrementConfidentialVersion(STObject& mptoken)
{
// Retrieve current version and increment.
// Unsigned integer overflow is defined behavior in C++ (wraps to 0),
// which is acceptable here.
mptoken[sfConfidentialBalanceVersion] =
mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
}
/**
* @brief Generates the context hash for ConfidentialMPTSend transactions.
*
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
* this specific send transaction, preventing proof reuse across transactions.
*
* @param account The sender's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or ticket number.
* @param destination The destination account ID.
* @param version The sender's confidential balance version.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getSendContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& destination,
std::uint32_t version);
/**
* @brief Generates the context hash for ConfidentialMPTClawback transactions.
*
* Creates a unique 256-bit hash that binds the equality proof to this
* specific clawback transaction.
*
* @param account The issuer's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or ticket number.
* @param holder The holder's account ID being clawed back from.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getClawbackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& holder);
/**
* @brief Generates the context hash for ConfidentialMPTConvert transactions.
*
* Creates a unique 256-bit hash that binds the Schnorr proof (for key
* registration) to this specific convert transaction.
*
* @param account The holder's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or a ticket number.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence);
/**
* @brief Generates the context hash for ConfidentialMPTConvertBack transactions.
*
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
* this specific convert-back transaction.
*
* @param account The holder's account ID.
* @param issuanceID The MPToken Issuance ID.
* @param sequence The transaction sequence number or a ticket number.
* @param version The holder's confidential balance version.
* @return A 256-bit context hash unique to this transaction.
*/
uint256
getConvertBackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
std::uint32_t version);
/**
* @brief Parses an ElGamal ciphertext into two secp256k1 public key components.
*
* Breaks a 66-byte encrypted amount (two 33-byte compressed EC points) into
* a pair containing (C1, C2) for use in cryptographic operations.
*
* @param buffer The 66-byte buffer containing the compressed ciphertext.
* @return The parsed pair (c1, c2) if successful, std::nullopt if the buffer is invalid.
*/
std::optional<EcPair>
makeEcPair(Slice const& buffer);
/**
* @brief Serializes an EcPair into compressed form.
*
* Converts an EcPair (C1, C2) back into a 66-byte buffer containing
* two 33-byte compressed EC points.
*
* @param pair The EcPair to serialize.
* @return The 66-byte buffer, or std::nullopt if serialization fails.
*/
std::optional<Buffer>
serializeEcPair(EcPair const& pair);
/**
* @brief Verifies that a buffer contains two valid, parsable EC public keys.
*
* @param buffer The input buffer containing two concatenated components.
* @return true if both components can be parsed successfully, false otherwise.
*/
bool
isValidCiphertext(Slice const& buffer);
/**
* @brief Verifies that a buffer contains a valid, parsable compressed EC point.
*
* Can be used to validate both compressed public keys and Pedersen commitments.
* Fails early if the prefix byte is not 0x02 or 0x03.
*
* @param buffer The input buffer containing a compressed EC point (33 bytes).
* @return true if the point can be parsed successfully, false otherwise.
*/
bool
isValidCompressedECPoint(Slice const& buffer);
/**
* @brief Homomorphically adds two ElGamal ciphertexts.
*
* Uses the additive homomorphic property of ElGamal encryption to compute
* Enc(a + b) from Enc(a) and Enc(b) without decryption.
*
* @param a The first ciphertext (66 bytes).
* @param b The second ciphertext (66 bytes).
* @return The resulting ciphertext Enc(a + b), or std::nullopt on failure.
*/
std::optional<Buffer>
homomorphicAdd(Slice const& a, Slice const& b);
/**
* @brief Homomorphically subtracts two ElGamal ciphertexts.
*
* Uses the additive homomorphic property of ElGamal encryption to compute
* Enc(a - b) from Enc(a) and Enc(b) without decryption.
*
* @param a The minuend ciphertext (66 bytes).
* @param b The subtrahend ciphertext (66 bytes).
* @return The resulting ciphertext Enc(a - b), or std::nullopt on failure.
*/
std::optional<Buffer>
homomorphicSubtract(Slice const& a, Slice const& b);
/**
* @brief Encrypts an amount using ElGamal encryption.
*
* Produces a ciphertext C = (C1, C2) where C1 = r*G and C2 = m*G + r*Pk,
* using the provided blinding factor r.
*
* @param amt The plaintext amount to encrypt.
* @param pubKeySlice The recipient's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param blindingFactor The randomness used as blinding factor r
* (size=xrpl::ecBlindingFactorLength).
* @return The ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt on failure.
*/
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor);
/**
* @brief Generates the canonical zero encryption for a specific MPToken.
*
* Creates a deterministic encryption of zero that is unique to the account
* and MPT issuance. Used to initialize confidential balance fields.
*
* @param pubKeySlice The holder's ElGamal public key (size=xrpl::ecPubKeyLength).
* @param account The account ID of the token holder.
* @param mptId The MPToken Issuance ID.
* @return The canonical zero ciphertext (size=xrpl::ecGamalEncryptedTotalLength), or std::nullopt
* on failure.
*/
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId);
/**
* @brief Verifies a Schnorr proof of knowledge of an ElGamal private key.
*
* Proves that the submitter knows the secret key corresponding to the
* provided public key, without revealing the secret key itself.
*
* @param pubKeySlice The ElGamal public key (size=xrpl::ecPubKeyLength).
* @param proofSlice The Schnorr proof (size=xrpl::ecSchnorrProofLength).
* @param contextHash The 256-bit context hash binding the proof.
* @return tesSUCCESS if valid, or an error code otherwise.
*/
TER
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash);
/**
* @brief Validates the format of encrypted amount fields in a transaction.
*
* Checks that all ciphertext fields in the transaction object have the
* correct length and contain valid EC points. This function is only used
* by ConfidentialMPTConvert and ConfidentialMPTConvertBack transactions.
*
* @param object The transaction object containing encrypted amount fields.
* @return tesSUCCESS if all formats are valid, temMALFORMED if required fields
* are missing, or temBAD_CIPHERTEXT if format validation fails.
*/
NotTEC
checkEncryptedAmountFormat(STObject const& object);
/**
* @brief Verifies revealed amount encryptions for all recipients.
*
* Validates that the same amount was correctly encrypted for the holder,
* issuer, and optionally the auditor using their respective public keys.
*
* @param amount The revealed plaintext amount.
* @param blindingFactor The blinding factor used in all encryptions
* (size=xrpl::ecBlindingFactorLength).
* @param holder The holder's public key and encrypted amount.
* @param issuer The issuer's public key and encrypted amount.
* @param auditor Optional auditor's public key and encrypted amount.
* @return tesSUCCESS if all encryptions are valid, or an error code otherwise.
*/
TER
verifyRevealedAmount(
uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor);
/**
* @brief Returns the number of recipients in a confidential transfer.
*
* Returns 4 if an auditor is present (sender, destination, issuer, auditor),
* or 3 if no auditor (sender, destination, issuer).
*
* @param hasAuditor Whether the issuance has an auditor configured.
* @return The number of recipients (3 or 4).
*/
constexpr std::size_t
getConfidentialRecipientCount(bool hasAuditor)
{
return hasAuditor ? 4 : 3;
}
/**
* @brief Verifies a compact sigma clawback proof.
*
* Proves that the issuer knows the exact amount encrypted in the holder's
* balance ciphertext. Used in ConfidentialMPTClawback to verify the issuer
* can decrypt the balance using their private key.
*
* @param amount The revealed plaintext amount.
* @param proof The zero-knowledge proof bytes (ecCompactClawbackProofLength).
* @param pubKeySlice The issuer's ElGamal public key (ecPubKeyLength bytes).
* @param ciphertext The issuer's encrypted balance on the holder's account
* (ecGamalEncryptedTotalLength bytes).
* @param contextHash The 256-bit context hash binding the proof.
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
*/
TER
verifyClawbackProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash);
/**
* @brief Generates a cryptographically secure 32-byte blinding factor.
*
* Produces random bytes suitable for use as an ElGamal blinding factor
* or Pedersen commitment randomness.
*
* @return A 32-byte buffer containing the random blinding factor.
*/
Buffer
generateBlindingFactor();
/**
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTSend transaction.
*
* This function calls mpt_verify_send_proof API in the mpt-crypto utility lib, which verifies the
* equality proof, amount linkage, balance linkage, and range proof.
* Equality proof: Proves the same value is encrypted for the sender, receiver, issuer, and auditor.
* Amount linkage: Proves the send amount matches the amount Pedersen commitment.
* Balance linkage: Proves the sender's balance matches the balance Pedersen
* commitment.
* Range proof: Proves the amount and the remaining balance are within range [0, 2^64-1].
*
* @param proof The full proof blob.
* @param sender The sender's public key and encrypted amount.
* @param destination The destination's public key and encrypted amount.
* @param issuer The issuer's public key and encrypted amount.
* @param auditor The auditor's public key and encrypted amount if present.
* @param spendingBalance The sender's current spending balance ciphertext.
* @param amountCommitment The Pedersen commitment to the send amount.
* @param balanceCommitment The Pedersen commitment to the sender's balance.
* @param contextHash The context hash binding the proof.
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
*/
TER
verifySendProof(
Slice const& proof,
ConfidentialRecipient const& sender,
ConfidentialRecipient const& destination,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor,
Slice const& spendingBalance,
Slice const& amountCommitment,
Slice const& balanceCommitment,
uint256 const& contextHash);
/**
* @brief Verifies all zero-knowledge proofs for a ConfidentialMPTConvertBack transaction.
*
* This function calls mpt_verify_convert_back_proof API in the mpt-crypto utility lib, which
* verifies the balance linkage proof and range proof. Balance linkage proof: proves the balance
* commitment matches the spending ciphertext. Range proof: proves the remaining balance after
* convert back is within range [0, 2^64-1].
*
* @param proof The full proof blob.
* @param pubKeySlice The holder's public key.
* @param spendingBalance The holder's spending balance ciphertext.
* @param balanceCommitment The Pedersen commitment to the balance.
* @param amount The amount being converted back to public.
* @param contextHash The context hash binding the proof.
* @return tesSUCCESS if all proofs are valid, or an error code otherwise.
*/
TER
verifyConvertBackProof(
Slice const& proof,
Slice const& pubKeySlice,
Slice const& spendingBalance,
Slice const& balanceCommitment,
uint64_t amount,
uint256 const& contextHash);
} // namespace xrpl

View File

@@ -40,9 +40,23 @@ struct Fees
the reserve increment times the number of increments.
*/
XRPAmount
accountReserve(std::size_t ownerCount) const
accountReserve(
std::size_t ownerCount,
std::size_t sponsoredOwnerCount = 0,
std::size_t sponsoringOwnerCount = 0,
bool isAccountSponsored = false,
std::size_t sponsoringAccountCount = 0) const
{
return reserve + ownerCount * increment;
auto const accountReserveUnits = (isAccountSponsored ? 0 : 1) + sponsoringAccountCount;
XRPL_ASSERT(
ownerCount >= sponsoredOwnerCount,
"xrpl::Fees::accountReserve : OwnerCount must be greater than or equal to "
"SponsoredOwnerCount");
auto const ownerReserveUnits = (ownerCount - sponsoredOwnerCount) + sponsoringOwnerCount;
return (reserve * accountReserveUnits) + (increment * ownerReserveUnits);
}
};

View File

@@ -151,6 +151,10 @@ static ticket_t const ticket{};
Keylet
signers(AccountID const& account) noexcept;
/** A Sponsor */
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept;
/** A Check */
/** @{ */
Keylet

View File

@@ -135,7 +135,7 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsfDisallowIncomingPayChan, 0x10000000) /* True, reject new paychans */ \
LSF_FLAG(lsfDisallowIncomingTrustline, 0x20000000) /* True, reject new trustlines (only if no issued assets) */ \
LSF_FLAG(lsfAllowTrustLineLocking, 0x40000000) /* True, enable trustline locking */ \
LSF_FLAG(lsfAllowTrustLineClawback, 0x80000000)) /* True, enable clawback */ \
LSF_FLAG(lsfAllowTrustLineClawback, 0x80000000)) /* True, enable clawback */ \
\
LEDGER_OBJECT(Offer, \
LSF_FLAG(lsfPassive, 0x00010000) \
@@ -173,7 +173,8 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsfMPTCanEscrow, 0x00000008) \
LSF_FLAG(lsfMPTCanTrade, 0x00000010) \
LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \
LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
LSF_FLAG(lsfMPTCanConfidentialAmount, 0x00000080)) \
\
LEDGER_OBJECT(MPTokenIssuanceMutable, \
LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \
@@ -183,7 +184,8 @@ enum LedgerEntryType : std::uint16_t {
LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \
LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000) \
LSF_FLAG(lsmfMPTCannotMutateCanConfidentialAmount, 0x00040000)) \
\
LEDGER_OBJECT(MPToken, \
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
@@ -199,7 +201,11 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
\
LEDGER_OBJECT(Sponsorship, \
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000))
// clang-format on

View File

@@ -7,8 +7,12 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace xrpl {
class STTx;
/**
* We have both transaction type permissions and granular type permissions.
* Since we will reuse the TransactionFormats to parse the Transaction
@@ -17,15 +21,15 @@ namespace xrpl {
* greater than the maximum value of uint16.
*/
enum GranularPermissionType : std::uint32_t {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define PERMISSION(type, txType, value) type = value,
#define GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields) name = value,
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
enum Delegation { delegable, notDelegable };
@@ -36,15 +40,16 @@ private:
Permission();
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
std::unordered_map<std::uint16_t, Delegation> delegableTx_;
std::unordered_map<std::string, GranularPermissionType> granularPermissionMap_;
std::unordered_map<GranularPermissionType, std::string> granularNameMap_;
std::unordered_map<GranularPermissionType, TxType> granularTxTypeMap_;
std::unordered_map<GranularPermissionType, std::uint32_t> granularPermittedFlags_;
std::unordered_map<GranularPermissionType, SOTemplate> granularTemplates_;
std::unordered_set<TxType> granularTxTypes_;
public:
static Permission const&
getInstance();
@@ -71,6 +76,9 @@ public:
bool
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
bool
hasGranularPermissions(TxType txType) const;
// for tx level permission, permission value is equal to tx type plus one
static uint32_t
txToPermissionType(TxType const& type);
@@ -78,6 +86,23 @@ public:
// tx type value is permission value minus one
static TxType
permissionToTxType(uint32_t const& value);
/**
* @brief Verifies a delegated transaction against its granular permission template.
*
* @note WARNING: Do not move this check before standard transaction-level
* format checks, which is in preclaim. This function assumes the transaction's
* base structural integrity (fees, sequence, signatures) has already been
* validated.
*
* @param tx The transaction to verify.
* @param heldPermissions The granular permissions that the sender hold.
* @return true if the transaction fields and flags comply with the granular template.
*/
[[nodiscard]] bool
checkGranularSandbox(
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldPermissions) const;
};
} // namespace xrpl

View File

@@ -307,4 +307,51 @@ std::size_t constexpr permissionMaxSize = 10;
/** The maximum number of transactions that can be in a batch. */
std::size_t constexpr maxBatchTxCount = 8;
/** Length of one component of EC ElGamal ciphertext */
std::size_t constexpr ecGamalEncryptedLength = 33;
/** EC ElGamal ciphertext length: two 33-byte components concatenated */
std::size_t constexpr ecGamalEncryptedTotalLength = ecGamalEncryptedLength * 2;
/** Length of EC point (compressed) */
std::size_t constexpr compressedECPointLength = 33;
/** Length of EC public key (compressed) */
std::size_t constexpr ecPubKeyLength = compressedECPointLength;
/** Length of EC private key in bytes */
std::size_t constexpr ecPrivKeyLength = 32;
/** Length of the EC blinding factor in bytes */
std::size_t constexpr ecBlindingFactorLength = 32;
/** Length of Schnorr ZKProof for public key registration (compact form) in bytes */
std::size_t constexpr ecSchnorrProofLength = 64;
/** Length of Pedersen Commitment (compressed) */
std::size_t constexpr ecPedersenCommitmentLength = compressedECPointLength;
/** Length of single bulletproof (range proof for 1 commitment) in bytes */
std::size_t constexpr ecSingleBulletproofLength = 688;
/** Length of double bulletproof (range proof for 2 commitments) in bytes */
std::size_t constexpr ecDoubleBulletproofLength = 754;
/** Length of compact AND-composed sigma proof for ConfidentialMPTSend.
* 192 bytes compact sigma proof + 754 bytes double bulletproof. */
std::size_t constexpr ecCompactSendProofLength = 946;
/** Length of compact AND-composed sigma proof for ConfidentialMPTConvertBack.
* 128 bytes compact sigma proof + 688 bytes single bulletproof. */
std::size_t constexpr ecCompactConvertBackProofLength = 816;
/** Length of compact sigma proof for ConfidentialMPTClawback. */
std::size_t constexpr ecCompactClawbackProofLength = 64;
/** Compressed EC point prefix for even y-coordinate */
std::uint8_t constexpr ecCompressedPrefixEvenY = 0x02;
/** Compressed EC point prefix for odd y-coordinate */
std::uint8_t constexpr ecCompressedPrefixOddY = 0x03;
} // namespace xrpl

View File

@@ -153,13 +153,16 @@ private:
Expected<void, std::string>
checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const;
void
buildBatchTxnIds();
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
friend class detail::STVar;
mutable std::vector<uint256> batchTxnIds_;
std::vector<uint256> batchTxnIds_;
};
bool

View File

@@ -122,6 +122,7 @@ enum TEMcodes : TERUnderlyingType {
temBAD_TRANSFER_FEE,
temINVALID_INNER_BATCH,
temBAD_MPT,
temBAD_CIPHERTEXT,
};
//------------------------------------------------------------------------------
@@ -210,6 +211,7 @@ enum TERcodes : TERUnderlyingType {
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
terNO_SPONSORSHIP, // No sponsorship found
};
//------------------------------------------------------------------------------
@@ -344,6 +346,12 @@ enum TECcodes : TERUnderlyingType {
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
// DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for
// backward compatibility with historical data on non-prod networks, can be
// reclaimed after those networks reset.
tecNO_DELEGATE_PERMISSION = 198,
tecBAD_PROOF = 199,
tecNO_SPONSOR_PERMISSION = 200,
};
//------------------------------------------------------------------------------

View File

@@ -100,7 +100,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TRANSACTION(Payment, \
TF_FLAG(tfNoRippleDirect, 0x00010000) \
TF_FLAG(tfPartialPayment, 0x00020000) \
TF_FLAG(tfLimitQuality, 0x00040000), \
TF_FLAG(tfLimitQuality, 0x00040000) \
TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \
MASK_ADJ(0)) \
\
TRANSACTION(TrustSet, \
@@ -138,7 +139,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfMPTCanEscrow, lsfMPTCanEscrow) \
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback), \
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \
TF_FLAG(tfMPTCanConfidentialAmount, lsfMPTCanConfidentialAmount), \
MASK_ADJ(0)) \
\
TRANSACTION(MPTokenAuthorize, \
@@ -212,6 +214,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipSet, \
TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \
TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \
TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \
TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \
TF_FLAG(tfDeleteObject, 0x00100000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipTransfer, \
TF_FLAG(tfSponsorshipEnd, 0x00000001) \
TF_FLAG(tfSponsorshipCreate, 0x00000002) \
TF_FLAG(tfSponsorshipReassign, 0x00000004), \
MASK_ADJ(0))
// clang-format on
@@ -336,6 +352,9 @@ getAllTxFlags()
inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
inline constexpr FlagValue tfTrustSetPermissionMask =
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
inline constexpr FlagValue tfSponsorshipSetPermissionMask =
~(tfUniversal | tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve |
tfSponsorshipClearRequireSignForFee | tfSponsorshipClearRequireSignForReserve);
// MPTokenIssuanceCreate MutableFlags:
// Indicating specific fields or flags may be changed after issuance.
@@ -347,10 +366,12 @@ inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTrans
inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback;
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee);
inline constexpr FlagValue tmfMPTCannotMutateCanConfidentialAmount =
lsmfMPTCannotMutateCanConfidentialAmount;
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = ~(
tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee | tmfMPTCannotMutateCanConfidentialAmount);
// MPTokenIssuanceSet MutableFlags:
// Set or Clear flags.
@@ -367,10 +388,13 @@ inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100;
inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200;
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400;
inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~(
tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback);
inline constexpr FlagValue tmfMPTSetCanConfidentialAmount = 0x00001000;
inline constexpr FlagValue tmfMPTClearCanConfidentialAmount = 0x00002000;
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
~(tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback |
tmfMPTClearCanClawback | tmfMPTSetCanConfidentialAmount | tmfMPTClearCanConfidentialAmount);
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.
@@ -443,4 +467,33 @@ getAsfFlagMap()
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
#pragma pop_macro("ACCOUNTSET_FLAGS")
#pragma push_macro("SPONSOR_FLAGS")
#pragma push_macro("SPONSOR_FLAG_TO_VALUE")
#pragma push_macro("SPONSOR_FLAG_TO_MAP")
// Sponsor Flag values
#define SPONSOR_FLAGS(SPF_FLAG) \
SPF_FLAG(spfSponsorFee, 1) \
SPF_FLAG(spfSponsorReserve, 2)
#define SPONSOR_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value;
#define SPONSOR_FLAG_TO_MAP(name, value) {#name, value},
SPONSOR_FLAGS(SPONSOR_FLAG_TO_VALUE)
inline std::map<std::string, FlagValue> const&
getspfFlagMap()
{
static std::map<std::string, FlagValue> const flags = {SPONSOR_FLAGS(SPONSOR_FLAG_TO_MAP)};
return flags;
}
#undef SPONSOR_FLAG_TO_VALUE
#undef SPONSOR_FLAG_TO_MAP
#undef SPONSOR_FLAGS
#pragma pop_macro("SPONSOR_FLAG_TO_VALUE")
#pragma pop_macro("SPONSOR_FLAG_TO_MAP")
#pragma pop_macro("SPONSOR_FLAGS")
} // namespace xrpl

View File

@@ -15,16 +15,18 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConfidentialTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicMPT, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PriceOracleOrder, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (MPTDeliveredAmount, Supported::yes, VoteBehavior::DefaultNo)
@@ -33,7 +35,6 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultN
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions

View File

@@ -150,6 +150,9 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
{sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator
{sfSponsoredOwnerCount, soeDEFAULT},
{sfSponsoringOwnerCount, soeDEFAULT},
{sfSponsoringAccountCount,soeDEFAULT},
}))
/** A ledger object which contains a list of object identifiers.
@@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfHighNode, soeOPTIONAL},
{sfHighQualityIn, soeOPTIONAL},
{sfHighQualityOut, soeOPTIONAL},
{sfHighSponsor, soeOPTIONAL},
{sfLowSponsor, soeOPTIONAL},
}))
/** The ledger object which lists the network's fee settings.
@@ -387,32 +392,41 @@ LEDGER_ENTRY(ltAMM, 0x0079, AMM, amm, ({
\sa keylet::mptIssuance
*/
LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfIssuer, soeREQUIRED},
{sfSequence, soeREQUIRED},
{sfTransferFee, soeDEFAULT},
{sfOwnerNode, soeREQUIRED},
{sfAssetScale, soeDEFAULT},
{sfMaximumAmount, soeOPTIONAL},
{sfOutstandingAmount, soeREQUIRED},
{sfLockedAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
{sfIssuer, soeREQUIRED},
{sfSequence, soeREQUIRED},
{sfTransferFee, soeDEFAULT},
{sfOwnerNode, soeREQUIRED},
{sfAssetScale, soeDEFAULT},
{sfMaximumAmount, soeOPTIONAL},
{sfOutstandingAmount, soeREQUIRED},
{sfLockedAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfDomainID, soeOPTIONAL},
{sfMutableFlags, soeDEFAULT},
{sfIssuerEncryptionKey, soeOPTIONAL},
{sfAuditorEncryptionKey, soeOPTIONAL},
{sfConfidentialOutstandingAmount, soeDEFAULT},
}))
/** A ledger object which tracks MPToken
\sa keylet::mptoken
*/
LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfAccount, soeREQUIRED},
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeDEFAULT},
{sfLockedAmount, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfAccount, soeREQUIRED},
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeDEFAULT},
{sfLockedAmount, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfConfidentialBalanceInbox, soeOPTIONAL},
{sfConfidentialBalanceSpending, soeOPTIONAL},
{sfConfidentialBalanceVersion, soeDEFAULT},
{sfIssuerEncryptedBalance, soeOPTIONAL},
{sfAuditorEncryptedBalance, soeOPTIONAL},
{sfHolderEncryptionKey, soeOPTIONAL},
}))
/** A ledger object which tracks Oracle
@@ -466,6 +480,7 @@ LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({
{sfAuthorize, soeREQUIRED},
{sfPermissions, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfDestinationNode, soeOPTIONAL},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
@@ -605,5 +620,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfLoanScale, soeDEFAULT},
}))
/** A ledger object representing a sponsorship.
\sa keylet::sponsor
*/
LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
{sfOwner, soeREQUIRED},
{sfSponsee, soeREQUIRED},
{sfFeeAmount, soeOPTIONAL},
{sfMaxFee, soeOPTIONAL},
{sfReserveCount, soeDEFAULT},
{sfOwnerNode, soeREQUIRED},
{sfSponseeNode, soeREQUIRED},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -1,49 +1,95 @@
#if !defined(PERMISSION)
#error "undefined macro: PERMISSION"
#if !defined(GRANULAR_PERMISSION)
#error "undefined macro: GRANULAR_PERMISSION"
#endif
/**
* PERMISSION(name, type, txType, value)
* GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields)
*
* This macro defines a permission:
* name: the name of the permission.
* type: the GranularPermissionType enum.
* txType: the corresponding TxType for this permission.
* value: the uint32 numeric value for the enum type.
* Defines a granular permission:
* name: the granular permission name.
* txType: the corresponding TxType for this permission.
* value: the uint32 numeric value for the enum type.
* allowedFlags: transaction flags permitted under this permission.
* allowedFields: transaction fields permitted under this permission.
*/
/** This permission grants the delegated account the ability to authorize a trustline. */
PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537)
/** Grants the ability to authorize a trustline. */
GRANULAR_PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537, tfUniversal | tfSetfAuth,
({{sfLimitAmount, soeREQUIRED}}))
/** This permission grants the delegated account the ability to freeze a trustline. */
PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538)
/** Grants the ability to freeze a trustline. */
GRANULAR_PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538, tfUniversal | tfSetFreeze,
({{sfLimitAmount, soeREQUIRED}}))
/** This permission grants the delegated account the ability to unfreeze a trustline. */
PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539)
/** Grants the ability to unfreeze a trustline. */
GRANULAR_PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539, tfUniversal | tfClearFreeze,
({{sfLimitAmount, soeREQUIRED}}))
/** This permission grants the delegated account the ability to set Domain. */
PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540)
/** Grants the ability to set Domain. */
GRANULAR_PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540, tfUniversal,
({{sfDomain, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to set EmailHashSet. */
PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541)
/** Grants the ability to set EmailHash. */
GRANULAR_PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541, tfUniversal,
({{sfEmailHash, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to set MessageKey. */
PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542)
/** Grants the ability to set MessageKey. */
GRANULAR_PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542, tfUniversal,
({{sfMessageKey, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to set TransferRate. */
PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543)
/** Grants the ability to set TransferRate. */
GRANULAR_PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543, tfUniversal,
({{sfTransferRate, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to set TickSize. */
PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544)
/** Grants the ability to set TickSize. */
GRANULAR_PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544, tfUniversal,
({{sfTickSize, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */
PERMISSION(PaymentMint, ttPAYMENT, 65545)
/** Grants the ability to mint payment (sending account is the issuer). Cross-currency payments are disallowed. */
GRANULAR_PERMISSION(PaymentMint, ttPAYMENT, 65545,
tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect,
({{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfSendMax, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfDeliverMin, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */
PERMISSION(PaymentBurn, ttPAYMENT, 65546)
/** Grants the ability to burn payment (destination account is the issuer). Cross-currency payments are disallowed. */
GRANULAR_PERMISSION(PaymentBurn, ttPAYMENT, 65546,
tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect,
({{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfSendMax, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfDeliverMin, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to lock MPToken. */
PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
/** Grants the ability to lock an MPToken. */
GRANULAR_PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547, tfUniversal | tfMPTLock,
({{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}}))
/** This permission grants the delegated account the ability to unlock MPToken. */
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
/** Grants the ability to unlock an MPToken. */
GRANULAR_PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548, tfUniversal | tfMPTUnlock,
({{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}}))
/** Grants the ability to set SponsorFee. */
GRANULAR_PERMISSION(SponsorFee, ttSPONSORSHIP_SET, 65549,
tfUniversal | tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee,
({{sfFeeAmount, soeOPTIONAL},
{sfMaxFee, soeOPTIONAL},
{sfSponsee, soeOPTIONAL},
{sfCounterpartySponsor, soeOPTIONAL}}))
/** Grants the ability to set SponsorReserve. */
GRANULAR_PERMISSION(SponsorReserve, ttSPONSORSHIP_SET, 65550,
tfUniversal | tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForReserve,
({{sfReserveCount, soeOPTIONAL},
{sfSponsee, soeOPTIONAL},
{sfCounterpartySponsor, soeOPTIONAL}}))

View File

@@ -11,7 +11,10 @@ secp256k1Context()
struct holder
{
secp256k1_context* impl;
holder() : impl(secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
// SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY were
// deprecated. All contexts support both signing and verification, so
// SECP256K1_CONTEXT_NONE is the correct flag to use.
holder() : impl(secp256k1_context_create(SECP256K1_CONTEXT_NONE))
{
}

View File

@@ -113,6 +113,12 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70)
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
TYPED_SFIELD(sfReserveCount, UINT32, 73)
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -146,6 +152,8 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
TYPED_SFIELD(sfVaultNode, UINT64, 30)
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::sMD_BaseTen|SField::sMD_Default)
TYPED_SFIELD(sfSponseeNode, UINT64, 33)
// 128-bit
TYPED_SFIELD(sfEmailHash, UINT128, 1)
@@ -205,6 +213,8 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::sMD_PseudoAccount | SField::sMD_Default)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfBlindingFactor, UINT256, 39)
TYPED_SFIELD(sfObjectID, UINT256, 40)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -264,6 +274,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)
@@ -298,6 +310,21 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfConfidentialBalanceInbox, VL, 32)
TYPED_SFIELD(sfConfidentialBalanceSpending, VL, 33)
TYPED_SFIELD(sfIssuerEncryptedBalance, VL, 34)
TYPED_SFIELD(sfIssuerEncryptionKey, VL, 35)
TYPED_SFIELD(sfHolderEncryptionKey, VL, 36)
TYPED_SFIELD(sfZKProof, VL, 37)
TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38)
TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39)
TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40)
TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -324,6 +351,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
TYPED_SFIELD(sfSponsor, ACCOUNT, 27)
TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never)
@@ -388,6 +420,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::sMD_Default, SField::notSigning)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::sMD_Default, SField::notSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -734,6 +734,8 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
{sfMPTokenMetadata, soeOPTIONAL},
{sfTransferFee, soeOPTIONAL},
{sfMutableFlags, soeOPTIONAL},
{sfIssuerEncryptionKey, soeOPTIONAL},
{sfAuditorEncryptionKey, soeOPTIONAL},
}))
/** This transaction type authorizes a MPToken instance */
@@ -936,7 +938,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
#endif
TRANSACTION(ttBATCH, 71, Batch,
Delegation::notDelegable,
featureBatch,
featureBatchV1_1,
noPriv,
({
{sfRawTransactions, soeREQUIRED},
@@ -1076,6 +1078,119 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
/** This transaction type converts into confidential MPT balance. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfHolderEncryptionKey, soeOPTIONAL},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeOPTIONAL},
}))
/** This transaction type merges MPT inbox. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_MERGE_INBOX, 86, ConfidentialMPTMergeInbox,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
}))
/** This transaction type converts back into public MPT balance. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT_BACK, 87, ConfidentialMPTConvertBack,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfHolderEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfBlindingFactor, soeREQUIRED},
{sfZKProof, soeREQUIRED},
{sfBalanceCommitment, soeREQUIRED},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTSend.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_SEND, 88, ConfidentialMPTSend,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfSenderEncryptedAmount, soeREQUIRED},
{sfDestinationEncryptedAmount, soeREQUIRED},
{sfIssuerEncryptedAmount, soeREQUIRED},
{sfAuditorEncryptedAmount, soeOPTIONAL},
{sfZKProof, soeREQUIRED},
{sfAmountCommitment, soeREQUIRED},
{sfBalanceCommitment, soeREQUIRED},
{sfCredentialIDs, soeOPTIONAL},
}))
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/token/ConfidentialMPTClawback.h>
#endif
TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
Delegation::delegable,
featureConfidentialTransfer,
noPriv,
({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeREQUIRED},
{sfMPTAmount, soeREQUIRED},
{sfZKProof, soeREQUIRED},
}))
/** This transaction transfer sponsorship */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
#endif
TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer,
Delegation::delegable,
featureSponsor,
noPriv,
({
{sfObjectID, soeOPTIONAL},
{sfSponsee, soeOPTIONAL},
}))
/** This transaction create sponsorship object */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#endif
TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
Delegation::delegable,
featureSponsor,
noPriv,
({
{sfCounterpartySponsor, soeOPTIONAL},
{sfSponsee, soeOPTIONAL},
{sfFeeAmount, soeOPTIONAL},
{sfMaxFee, soeOPTIONAL},
{sfReserveCount, soeOPTIONAL},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -137,6 +137,7 @@ JSS(authorized_credentials); // in: ledger_entry DepositPreauth
JSS(auth_accounts); // out: amm_info
JSS(auth_change); // out: AccountInfo
JSS(auth_change_queued); // out: AccountInfo
JSS(auditor_encrypted_balance); // out: mpt_holders (confidential MPT)
JSS(available); // out: ValidatorList
JSS(avg_bps_recv); // out: Peers
JSS(avg_bps_sent); // out: Peers
@@ -161,9 +162,6 @@ JSS(build_path); // in: TransactionSign
JSS(build_version); // out: NetworkOPs
JSS(cancel_after); // out: AccountChannels
JSS(can_delete); // out: CanDelete
JSS(mpt_amount); // out: mpt_holders
JSS(mpt_issuance_id); // in: Payment, mpt_holders
JSS(mptoken_index); // out: mpt_holders
JSS(changes); // out: BookChanges
JSS(channel_id); // out: AccountChannels
JSS(channels); // out: AccountChannels
@@ -185,165 +183,170 @@ JSS(command); // in: RPCHandler
JSS(common); // out: RPC server_definitions
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(credentials); // in: deposit_authorized
JSS(credential_type); // in: LedgerEntry DepositPreauth
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount, AccountLines
JSS(current); // out: OwnerInfo
JSS(current_activities); //
JSS(current_ledger_size); // out: TxQ
JSS(current_queue_size); // out: TxQ
JSS(data); // out: LedgerData
JSS(date); // out: tx/Transaction, NetworkOPs
JSS(dbKBLedger); // out: getCounts
JSS(dbKBTotal); // out: getCounts
JSS(dbKBTransaction); // out: getCounts
JSS(debug_signing); // in: TransactionSign
JSS(deletion_blockers_only); // in: AccountObjects
JSS(delivered_amount); // out: insertDeliveredAmount
JSS(deposit_authorized); // out: deposit_authorized
JSS(deprecated); //
JSS(descending); // in: AccountTx*
JSS(description); // in/out: Reservations
JSS(destination); // in: nft_buy_offers, nft_sell_offers
JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines
// out: AccountChannels
JSS(destination_amount); // in: PathRequest, RipplePathFind
JSS(destination_currencies); // in: PathRequest, RipplePathFind
JSS(destination_tag); // in: PathRequest
// out: AccountChannels
JSS(details); // out: Manifest, server_info
JSS(dir_entry); // out: DirectoryEntryIterator
JSS(dir_index); // out: DirectoryEntryIterator
JSS(dir_root); // out: DirectoryEntryIterator
JSS(discounted_fee); // out: amm_info
JSS(domain); // out: ValidatorInfo, Manifest
JSS(drops); // out: TxQ
JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(enabled); // out: AmendmentTable
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
JSS(entire_set); // out: get_aggregate_price
JSS(ephemeral_key); // out: ValidatorInfo
// in/out: Manifest
JSS(error); // out: error
JSS(errored); //
JSS(error_code); // out: error
JSS(error_exception); // out: Submit
JSS(error_message); // out: error
JSS(expand); // in: handler/Ledger
JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
JSS(features); // out: Feature
JSS(fee_base); // out: NetworkOPs
JSS(fee_div_max); // in: TransactionSign
JSS(fee_level); // out: AccountInfo
JSS(fee_mult_max); // in: TransactionSign
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
JSS(fetch_pack); // out: NetworkOPs
JSS(FIELDS); // out: RPC server_definitions
// matches definitions.json format
JSS(first); // out: rpc/Version
JSS(finished); //
JSS(fix_txns); // in: LedgerCleaner
JSS(flags); // out: AccountOffers, NetworkOPs
JSS(forward); // in: AccountTx
JSS(freeze); // out: AccountLines
JSS(freeze_peer); // out: AccountLines
JSS(deep_freeze); // out: AccountLines
JSS(deep_freeze_peer); // out: AccountLines
JSS(frozen_balances); // out: GatewayBalances
JSS(full); // in: LedgerClearer, handlers/Ledger
JSS(full_reply); // out: PathFind
JSS(fullbelow_size); // out: GetCounts
JSS(git); // out: server_info
JSS(good); // out: RPCVersion
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
JSS(historical_perminute); // historical_perminute.
JSS(holders); // out: MPTHolders
JSS(hostid); // out: NetworkOPs
JSS(hotwallet); // in: GatewayBalances
JSS(id); // websocket.
JSS(ident); // in: AccountCurrencies, AccountInfo, OwnerInfo
JSS(ignore_default); // in: AccountLines
JSS(in); // out: OverlayImpl
JSS(inLedger); // out: tx/Transaction
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(initial_sync_duration_us); //
JSS(internal_command); // in: Internal
JSS(invalid_API_version); // out: Many, when a request has an invalid version
JSS(io_latency_ms); // out: NetworkOPs
JSS(ip); // in: Connect, out: OverlayImpl
JSS(is_burned); // out: nft_info (clio)
JSS(isSerialized); // out: RPC server_definitions
// matches definitions.json format
JSS(isSigningField); // out: RPC server_definitions
// matches definitions.json format
JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(issuer); // in: RipplePathFind, Subscribe, Unsubscribe, BookOffers
// out: STPathSet, STAmount
JSS(job); //
JSS(job_queue); //
JSS(jobs); //
JSS(jsonrpc); // json version
JSS(jq_trans_overflow); // JobQueue transaction limit overflow.
JSS(kept); // out: SubmitTransaction
JSS(key); // out
JSS(key_type); // in/out: WalletPropose, TransactionSign
JSS(latency); // out: PeerImp
JSS(last); // out: RPCVersion
JSS(last_close); // out: NetworkOPs
JSS(last_refresh_time); // out: ValidatorSite
JSS(last_refresh_status); // out: ValidatorSite
JSS(last_refresh_message); // out: ValidatorSite
JSS(ledger); // in: NetworkOPs, LedgerCleaner, RPCHelpers
// out: NetworkOPs, PeerImp
JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, LedgerCurrent, LedgerAccept,
// AccountLines
JSS(ledger_data); // out: LedgerHeader
JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, RipplePathFind,
// TransactionEntry, handlers/Ledger
// out: NetworkOPs, RPCHelpers, LedgerClosed, LedgerData,
// AccountLines
JSS(ledger_hit_rate); // out: GetCounts
JSS(ledger_index); // in/out: many
JSS(ledger_index_max); // in, out: AccountTx*
JSS(ledger_index_min); // in, out: AccountTx*
JSS(ledger_max); // in, out: AccountTx*
JSS(ledger_min); // in, out: AccountTx*
JSS(ledger_time); // out: NetworkOPs
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions
JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions
JSS(levels); // LogLevels
JSS(confidential_balance_inbox); // out: mpt_holders (confidential MPT)
JSS(confidential_balance_spending); // out: mpt_holders (confidential MPT)
JSS(confidential_balance_version); // out: mpt_holders (confidential MPT)
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(credentials); // in: deposit_authorized
JSS(credential_type); // in: LedgerEntry DepositPreauth
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount, AccountLines
JSS(current); // out: OwnerInfo
JSS(current_activities); //
JSS(current_ledger_size); // out: TxQ
JSS(current_queue_size); // out: TxQ
JSS(data); // out: LedgerData
JSS(date); // out: tx/Transaction, NetworkOPs
JSS(dbKBLedger); // out: getCounts
JSS(dbKBTotal); // out: getCounts
JSS(dbKBTransaction); // out: getCounts
JSS(debug_signing); // in: TransactionSign
JSS(deletion_blockers_only); // in: AccountObjects
JSS(delivered_amount); // out: insertDeliveredAmount
JSS(deposit_authorized); // out: deposit_authorized
JSS(deprecated); //
JSS(descending); // in: AccountTx*
JSS(description); // in/out: Reservations
JSS(destination); // in: nft_buy_offers, nft_sell_offers
JSS(destination_account); // in: PathRequest, RipplePathFind, account_lines
// out: AccountChannels
JSS(destination_amount); // in: PathRequest, RipplePathFind
JSS(destination_currencies); // in: PathRequest, RipplePathFind
JSS(destination_tag); // in: PathRequest
// out: AccountChannels
JSS(details); // out: Manifest, server_info
JSS(dir_entry); // out: DirectoryEntryIterator
JSS(dir_index); // out: DirectoryEntryIterator
JSS(dir_root); // out: DirectoryEntryIterator
JSS(discounted_fee); // out: amm_info
JSS(domain); // out: ValidatorInfo, Manifest
JSS(drops); // out: TxQ
JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(enabled); // out: AmendmentTable
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
JSS(entire_set); // out: get_aggregate_price
JSS(ephemeral_key); // out: ValidatorInfo
// in/out: Manifest
JSS(error); // out: error
JSS(errored); //
JSS(error_code); // out: error
JSS(error_exception); // out: Submit
JSS(error_message); // out: error
JSS(expand); // in: handler/Ledger
JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
JSS(features); // out: Feature
JSS(fee_base); // out: NetworkOPs
JSS(fee_div_max); // in: TransactionSign
JSS(fee_level); // out: AccountInfo
JSS(fee_mult_max); // in: TransactionSign
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
JSS(fetch_pack); // out: NetworkOPs
JSS(FIELDS); // out: RPC server_definitions
// matches definitions.json format
JSS(first); // out: rpc/Version
JSS(finished); //
JSS(fix_txns); // in: LedgerCleaner
JSS(flags); // out: AccountOffers, NetworkOPs
JSS(forward); // in: AccountTx
JSS(freeze); // out: AccountLines
JSS(freeze_peer); // out: AccountLines
JSS(deep_freeze); // out: AccountLines
JSS(deep_freeze_peer); // out: AccountLines
JSS(frozen_balances); // out: GatewayBalances
JSS(full); // in: LedgerClearer, handlers/Ledger
JSS(full_reply); // out: PathFind
JSS(fullbelow_size); // out: GetCounts
JSS(git); // out: server_info
JSS(good); // out: RPCVersion
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
JSS(historical_perminute); // historical_perminute.
JSS(holders); // out: MPTHolders
JSS(holder_encryption_key); // out: mpt_holders (confidential MPT)
JSS(hostid); // out: NetworkOPs
JSS(hotwallet); // in: GatewayBalances
JSS(id); // websocket.
JSS(ident); // in: AccountCurrencies, AccountInfo, OwnerInfo
JSS(ignore_default); // in: AccountLines
JSS(in); // out: OverlayImpl
JSS(inLedger); // out: tx/Transaction
JSS(inbound); // out: PeerImp
JSS(index); // in: LedgerEntry
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
JSS(initial_sync_duration_us); //
JSS(internal_command); // in: Internal
JSS(invalid_API_version); // out: Many, when a request has an invalid version
JSS(io_latency_ms); // out: NetworkOPs
JSS(ip); // in: Connect, out: OverlayImpl
JSS(is_burned); // out: nft_info (clio)
JSS(isSerialized); // out: RPC server_definitions
// matches definitions.json format
JSS(isSigningField); // out: RPC server_definitions
// matches definitions.json format
JSS(isVLEncoded); // out: RPC server_definitions
// matches definitions.json format
JSS(issuer); // in: RipplePathFind, Subscribe, Unsubscribe, BookOffers
// out: STPathSet, STAmount
JSS(issuer_encrypted_balance); // out: mpt_holders (confidential MPT)
JSS(job); //
JSS(job_queue); //
JSS(jobs); //
JSS(jsonrpc); // json version
JSS(jq_trans_overflow); // JobQueue transaction limit overflow.
JSS(kept); // out: SubmitTransaction
JSS(key); // out
JSS(key_type); // in/out: WalletPropose, TransactionSign
JSS(latency); // out: PeerImp
JSS(last); // out: RPCVersion
JSS(last_close); // out: NetworkOPs
JSS(last_refresh_time); // out: ValidatorSite
JSS(last_refresh_status); // out: ValidatorSite
JSS(last_refresh_message); // out: ValidatorSite
JSS(ledger); // in: NetworkOPs, LedgerCleaner, RPCHelpers
// out: NetworkOPs, PeerImp
JSS(ledger_current_index); // out: NetworkOPs, RPCHelpers, LedgerCurrent, LedgerAccept,
// AccountLines
JSS(ledger_data); // out: LedgerHeader
JSS(ledger_hash); // in: RPCHelpers, LedgerRequest, RipplePathFind,
// TransactionEntry, handlers/Ledger
// out: NetworkOPs, RPCHelpers, LedgerClosed, LedgerData,
// AccountLines
JSS(ledger_hit_rate); // out: GetCounts
JSS(ledger_index); // in/out: many
JSS(ledger_index_max); // in, out: AccountTx*
JSS(ledger_index_min); // in, out: AccountTx*
JSS(ledger_max); // in, out: AccountTx*
JSS(ledger_min); // in, out: AccountTx*
JSS(ledger_time); // out: NetworkOPs
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
// matches definitions.json format
JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions
JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions
JSS(levels); // LogLevels
JSS(limit); // in/out: AccountTx*, AccountOffers, AccountLines, AccountObjects
// in: LedgerData, BookOffers
JSS(limit_peer); // out: AccountLines
@@ -401,6 +404,9 @@ JSS(min_ledger); // in: LedgerCleaner
JSS(minimum_fee); // out: TxQ
JSS(minimum_level); // out: TxQ
JSS(missingCommand); // error
JSS(mpt_amount); // out: mpt_holders
JSS(mpt_issuance_id); // in: Payment, mpt_holders
JSS(mptoken_index); // out: mpt_holders
JSS(mpt_issuance_id_a); // out: BookChanges
JSS(mpt_issuance_id_b); // out: BookChanges
JSS(name); // out: AmendmentTableImpl, PeerImp
@@ -552,6 +558,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
JSS(source_currencies); // in: PathRequest, RipplePathFind
JSS(source_tag); // out: AccountChannels
JSS(sponsee); // in: LedgerEntry
JSS(sponsor); // in: LedgerEntry
JSS(sponsored); // in: AccountObjects
JSS(stand_alone); // out: NetworkOPs
JSS(standard_deviation); // out: get_aggregate_price
JSS(start); // in: TxHistory

View File

@@ -518,6 +518,78 @@ public:
{
return this->sle_->isFieldPresent(sfLoanBrokerID);
}
/**
* @brief Get sfSponsoredOwnerCount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoredOwnerCount() const
{
if (hasSponsoredOwnerCount())
return this->sle_->at(sfSponsoredOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoredOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoredOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoredOwnerCount);
}
/**
* @brief Get sfSponsoringOwnerCount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringOwnerCount() const
{
if (hasSponsoringOwnerCount())
return this->sle_->at(sfSponsoringOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoringOwnerCount);
}
/**
* @brief Get sfSponsoringAccountCount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringAccountCount() const
{
if (hasSponsoringAccountCount())
return this->sle_->at(sfSponsoringAccountCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringAccountCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringAccountCount() const
{
return this->sle_->isFieldPresent(sfSponsoringAccountCount);
}
};
/**
@@ -819,6 +891,39 @@ public:
return *this;
}
/**
* @brief Set sfSponsoredOwnerCount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoredOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringOwnerCount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringAccountCount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringAccountCount] = value;
return *this;
}
/**
* @brief Build and return the completed AccountRoot wrapper.
* @param index The ledger entry index.

View File

@@ -90,6 +90,30 @@ public:
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfDestinationNode (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getDestinationNode() const
{
if (hasDestinationNode())
return this->sle_->at(sfDestinationNode);
return std::nullopt;
}
/**
* @brief Check if sfDestinationNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDestinationNode() const
{
return this->sle_->isFieldPresent(sfDestinationNode);
}
/**
* @brief Get sfPreviousTxnID (soeREQUIRED)
* @return The field value.
@@ -203,6 +227,17 @@ public:
return *this;
}
/**
* @brief Set sfDestinationNode (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
DelegateBuilder&
setDestinationNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfDestinationNode] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnID (soeREQUIRED)
* @return Reference to this builder for method chaining.

View File

@@ -147,6 +147,150 @@ public:
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfConfidentialBalanceInbox (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceInbox() const
{
if (hasConfidentialBalanceInbox())
return this->sle_->at(sfConfidentialBalanceInbox);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceInbox is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceInbox() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceInbox);
}
/**
* @brief Get sfConfidentialBalanceSpending (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getConfidentialBalanceSpending() const
{
if (hasConfidentialBalanceSpending())
return this->sle_->at(sfConfidentialBalanceSpending);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceSpending is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceSpending() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceSpending);
}
/**
* @brief Get sfConfidentialBalanceVersion (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getConfidentialBalanceVersion() const
{
if (hasConfidentialBalanceVersion())
return this->sle_->at(sfConfidentialBalanceVersion);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialBalanceVersion is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialBalanceVersion() const
{
return this->sle_->isFieldPresent(sfConfidentialBalanceVersion);
}
/**
* @brief Get sfIssuerEncryptedBalance (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptedBalance() const
{
if (hasIssuerEncryptedBalance())
return this->sle_->at(sfIssuerEncryptedBalance);
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptedBalance is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptedBalance() const
{
return this->sle_->isFieldPresent(sfIssuerEncryptedBalance);
}
/**
* @brief Get sfAuditorEncryptedBalance (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedBalance() const
{
if (hasAuditorEncryptedBalance())
return this->sle_->at(sfAuditorEncryptedBalance);
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedBalance is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedBalance() const
{
return this->sle_->isFieldPresent(sfAuditorEncryptedBalance);
}
/**
* @brief Get sfHolderEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getHolderEncryptionKey() const
{
if (hasHolderEncryptionKey())
return this->sle_->at(sfHolderEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfHolderEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolderEncryptionKey() const
{
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
}
};
/**
@@ -270,6 +414,72 @@ public:
return *this;
}
/**
* @brief Set sfConfidentialBalanceInbox (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceInbox(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceInbox] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceSpending (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceSpending(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfConfidentialBalanceSpending] = value;
return *this;
}
/**
* @brief Set sfConfidentialBalanceVersion (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setConfidentialBalanceVersion(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfConfidentialBalanceVersion] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedBalance (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setIssuerEncryptedBalance(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedBalance] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedBalance (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setAuditorEncryptedBalance(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedBalance] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptionKey] = value;
return *this;
}
/**
* @brief Build and return the completed MPToken wrapper.
* @param index The ledger entry index.

View File

@@ -278,6 +278,78 @@ public:
{
return this->sle_->isFieldPresent(sfMutableFlags);
}
/**
* @brief Get sfIssuerEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptionKey() const
{
if (hasIssuerEncryptionKey())
return this->sle_->at(sfIssuerEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptionKey() const
{
return this->sle_->isFieldPresent(sfIssuerEncryptionKey);
}
/**
* @brief Get sfAuditorEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptionKey() const
{
if (hasAuditorEncryptionKey())
return this->sle_->at(sfAuditorEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptionKey() const
{
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
}
/**
* @brief Get sfConfidentialOutstandingAmount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getConfidentialOutstandingAmount() const
{
if (hasConfidentialOutstandingAmount())
return this->sle_->at(sfConfidentialOutstandingAmount);
return std::nullopt;
}
/**
* @brief Check if sfConfidentialOutstandingAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasConfidentialOutstandingAmount() const
{
return this->sle_->isFieldPresent(sfConfidentialOutstandingAmount);
}
};
/**
@@ -469,6 +541,39 @@ public:
return *this;
}
/**
* @brief Set sfIssuerEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setAuditorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfConfidentialOutstandingAmount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setConfidentialOutstandingAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfConfidentialOutstandingAmount] = value;
return *this;
}
/**
* @brief Build and return the completed MPTokenIssuance wrapper.
* @param index The ledger entry index.

View File

@@ -243,6 +243,54 @@ public:
{
return this->sle_->isFieldPresent(sfHighQualityOut);
}
/**
* @brief Get sfHighSponsor (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHighSponsor() const
{
if (hasHighSponsor())
return this->sle_->at(sfHighSponsor);
return std::nullopt;
}
/**
* @brief Check if sfHighSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHighSponsor() const
{
return this->sle_->isFieldPresent(sfHighSponsor);
}
/**
* @brief Get sfLowSponsor (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getLowSponsor() const
{
if (hasLowSponsor())
return this->sle_->at(sfLowSponsor);
return std::nullopt;
}
/**
* @brief Check if sfLowSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasLowSponsor() const
{
return this->sle_->isFieldPresent(sfLowSponsor);
}
};
/**
@@ -410,6 +458,28 @@ public:
return *this;
}
/**
* @brief Set sfHighSponsor (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHighSponsor] = value;
return *this;
}
/**
* @brief Set sfLowSponsor (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfLowSponsor] = value;
return *this;
}
/**
* @brief Build and return the completed RippleState wrapper.
* @param index The ledger entry index.

View File

@@ -0,0 +1,344 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class SponsorshipBuilder;
/**
* @brief Ledger Entry: Sponsorship
*
* Type: ltSPONSORSHIP (0x0090)
* RPC Name: sponsorship
*
* Immutable wrapper around SLE providing type-safe field access.
* Use SponsorshipBuilder to construct new ledger entries.
*/
class Sponsorship : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltSPONSORSHIP;
/**
* @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Sponsorship(std::shared_ptr<SLE const> sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwner (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfSponsee (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getSponsee() const
{
return this->sle_->at(sfSponsee);
}
/**
* @brief Get sfFeeAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
return this->sle_->at(sfFeeAmount);
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->sle_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
return this->sle_->at(sfMaxFee);
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->sle_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (soeDEFAULT)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
return this->sle_->at(sfReserveCount);
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->sle_->isFieldPresent(sfReserveCount);
}
/**
* @brief Get sfOwnerNode (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfSponseeNode (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getSponseeNode() const
{
return this->sle_->at(sfSponseeNode);
}
};
/**
* @brief Builder for Sponsorship ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses Json::Value internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class SponsorshipBuilder : public LedgerEntryBuilderBase<SponsorshipBuilder>
{
public:
/**
* @brief Construct a new SponsorshipBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param owner The sfOwner field value.
* @param sponsee The sfSponsee field value.
* @param ownerNode The sfOwnerNode field value.
* @param sponseeNode The sfSponseeNode field value.
*/
SponsorshipBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
: LedgerEntryBuilderBase<SponsorshipBuilder>(ltSPONSORSHIP)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwner(owner);
setSponsee(sponsee);
setOwnerNode(ownerNode);
setSponseeNode(sponseeNode);
}
/**
* @brief Construct a SponsorshipBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SponsorshipBuilder(std::shared_ptr<SLE const> sle)
{
if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwner (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfSponsee (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (soeDEFAULT)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfSponseeNode (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfSponseeNode] = value;
return *this;
}
/**
* @brief Build and return the completed Sponsorship wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Sponsorship
build(uint256 const& index)
{
return Sponsorship{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -20,7 +20,7 @@ class BatchBuilder;
*
* Type: ttBATCH (71)
* Delegable: Delegation::notDelegable
* Amendment: featureBatch
* Amendment: featureBatchV1_1
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.

View File

@@ -0,0 +1,201 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTClawbackBuilder;
/**
* @brief Transaction: ConfidentialMPTClawback
*
* Type: ttCONFIDENTIAL_MPT_CLAWBACK (89)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTClawbackBuilder to construct new transactions.
*/
class ConfidentialMPTClawback : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CLAWBACK;
/**
* @brief Construct a ConfidentialMPTClawback transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTClawback(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTClawback");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfHolder (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getHolder() const
{
return this->tx_->at(sfHolder);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTClawback transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTClawbackBuilder : public TransactionBuilderBase<ConfidentialMPTClawbackBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTClawbackBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param holder The sfHolder field value.
* @param mPTAmount The sfMPTAmount field value.
* @param zKProof The sfZKProof field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTClawbackBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_ACCOUNT::type::value_type> const& holder, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTClawbackBuilder>(ttCONFIDENTIAL_MPT_CLAWBACK, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setHolder(holder);
setMPTAmount(mPTAmount);
setZKProof(zKProof);
}
/**
* @brief Construct a ConfidentialMPTClawbackBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTClawbackBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CLAWBACK)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTClawbackBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfHolder (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHolder] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTClawbackBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTClawback wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTClawback
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTClawback{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,336 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTConvertBuilder;
/**
* @brief Transaction: ConfidentialMPTConvert
*
* Type: ttCONFIDENTIAL_MPT_CONVERT (85)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTConvertBuilder to construct new transactions.
*/
class ConfidentialMPTConvert : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CONVERT;
/**
* @brief Construct a ConfidentialMPTConvert transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTConvert(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvert");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfHolderEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getHolderEncryptionKey() const
{
if (hasHolderEncryptionKey())
{
return this->tx_->at(sfHolderEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfHolderEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHolderEncryptionKey() const
{
return this->tx_->isFieldPresent(sfHolderEncryptionKey);
}
/**
* @brief Get sfHolderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getHolderEncryptedAmount() const
{
return this->tx_->at(sfHolderEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfBlindingFactor (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getBlindingFactor() const
{
return this->tx_->at(sfBlindingFactor);
}
/**
* @brief Get sfZKProof (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getZKProof() const
{
if (hasZKProof())
{
return this->tx_->at(sfZKProof);
}
return std::nullopt;
}
/**
* @brief Check if sfZKProof is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasZKProof() const
{
return this->tx_->isFieldPresent(sfZKProof);
}
};
/**
* @brief Builder for ConfidentialMPTConvert transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTConvertBuilder : public TransactionBuilderBase<ConfidentialMPTConvertBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTConvertBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param mPTAmount The sfMPTAmount field value.
* @param holderEncryptedAmount The sfHolderEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param blindingFactor The sfBlindingFactor field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTConvertBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& holderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_UINT256::type::value_type> const& blindingFactor, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTConvertBuilder>(ttCONFIDENTIAL_MPT_CONVERT, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setMPTAmount(mPTAmount);
setHolderEncryptedAmount(holderEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setBlindingFactor(blindingFactor);
}
/**
* @brief Construct a ConfidentialMPTConvertBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTConvertBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CONVERT)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvertBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setHolderEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setHolderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfBlindingFactor (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setBlindingFactor(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfBlindingFactor] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTConvert wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTConvert
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTConvert{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,310 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTConvertBackBuilder;
/**
* @brief Transaction: ConfidentialMPTConvertBack
*
* Type: ttCONFIDENTIAL_MPT_CONVERT_BACK (87)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTConvertBackBuilder to construct new transactions.
*/
class ConfidentialMPTConvertBack : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_CONVERT_BACK;
/**
* @brief Construct a ConfidentialMPTConvertBack transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTConvertBack(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvertBack");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfMPTAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getMPTAmount() const
{
return this->tx_->at(sfMPTAmount);
}
/**
* @brief Get sfHolderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getHolderEncryptedAmount() const
{
return this->tx_->at(sfHolderEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfBlindingFactor (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getBlindingFactor() const
{
return this->tx_->at(sfBlindingFactor);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
/**
* @brief Get sfBalanceCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getBalanceCommitment() const
{
return this->tx_->at(sfBalanceCommitment);
}
};
/**
* @brief Builder for ConfidentialMPTConvertBack transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTConvertBackBuilder : public TransactionBuilderBase<ConfidentialMPTConvertBackBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTConvertBackBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param mPTAmount The sfMPTAmount field value.
* @param holderEncryptedAmount The sfHolderEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param blindingFactor The sfBlindingFactor field value.
* @param zKProof The sfZKProof field value.
* @param balanceCommitment The sfBalanceCommitment field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTConvertBackBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_UINT64::type::value_type> const& mPTAmount, std::decay_t<typename SF_VL::type::value_type> const& holderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_UINT256::type::value_type> const& blindingFactor, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::decay_t<typename SF_VL::type::value_type> const& balanceCommitment, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTConvertBackBuilder>(ttCONFIDENTIAL_MPT_CONVERT_BACK, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setMPTAmount(mPTAmount);
setHolderEncryptedAmount(holderEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setBlindingFactor(blindingFactor);
setZKProof(zKProof);
setBalanceCommitment(balanceCommitment);
}
/**
* @brief Construct a ConfidentialMPTConvertBackBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTConvertBackBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_CONVERT_BACK)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTConvertBackBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfMPTAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setMPTAmount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfMPTAmount] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setHolderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfHolderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfBlindingFactor (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setBlindingFactor(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfBlindingFactor] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Set sfBalanceCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTConvertBackBuilder&
setBalanceCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBalanceCommitment] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTConvertBack wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTConvertBack
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTConvertBack{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,129 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTMergeInboxBuilder;
/**
* @brief Transaction: ConfidentialMPTMergeInbox
*
* Type: ttCONFIDENTIAL_MPT_MERGE_INBOX (86)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTMergeInboxBuilder to construct new transactions.
*/
class ConfidentialMPTMergeInbox : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MERGE_INBOX;
/**
* @brief Construct a ConfidentialMPTMergeInbox transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTMergeInbox(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMergeInbox");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
};
/**
* @brief Builder for ConfidentialMPTMergeInbox transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTMergeInboxBuilder : public TransactionBuilderBase<ConfidentialMPTMergeInboxBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTMergeInboxBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTMergeInboxBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTMergeInboxBuilder>(ttCONFIDENTIAL_MPT_MERGE_INBOX, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
}
/**
* @brief Construct a ConfidentialMPTMergeInboxBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTMergeInboxBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MERGE_INBOX)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMergeInboxBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTMergeInboxBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTMergeInbox wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTMergeInbox
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTMergeInbox{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,371 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class ConfidentialMPTSendBuilder;
/**
* @brief Transaction: ConfidentialMPTSend
*
* Type: ttCONFIDENTIAL_MPT_SEND (88)
* Delegable: Delegation::delegable
* Amendment: featureConfidentialTransfer
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use ConfidentialMPTSendBuilder to construct new transactions.
*/
class ConfidentialMPTSend : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_SEND;
/**
* @brief Construct a ConfidentialMPTSend transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit ConfidentialMPTSend(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTSend");
}
}
// Transaction-specific field getters
/**
* @brief Get sfMPTokenIssuanceID (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_UINT192::type::value_type
getMPTokenIssuanceID() const
{
return this->tx_->at(sfMPTokenIssuanceID);
}
/**
* @brief Get sfDestination (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getDestination() const
{
return this->tx_->at(sfDestination);
}
/**
* @brief Get sfSenderEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getSenderEncryptedAmount() const
{
return this->tx_->at(sfSenderEncryptedAmount);
}
/**
* @brief Get sfDestinationEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getDestinationEncryptedAmount() const
{
return this->tx_->at(sfDestinationEncryptedAmount);
}
/**
* @brief Get sfIssuerEncryptedAmount (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getIssuerEncryptedAmount() const
{
return this->tx_->at(sfIssuerEncryptedAmount);
}
/**
* @brief Get sfAuditorEncryptedAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptedAmount() const
{
if (hasAuditorEncryptedAmount())
{
return this->tx_->at(sfAuditorEncryptedAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptedAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptedAmount() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
}
/**
* @brief Get sfZKProof (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getZKProof() const
{
return this->tx_->at(sfZKProof);
}
/**
* @brief Get sfAmountCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getAmountCommitment() const
{
return this->tx_->at(sfAmountCommitment);
}
/**
* @brief Get sfBalanceCommitment (soeREQUIRED)
* @return The field value.
*/
[[nodiscard]]
SF_VL::type::value_type
getBalanceCommitment() const
{
return this->tx_->at(sfBalanceCommitment);
}
/**
* @brief Get sfCredentialIDs (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VECTOR256::type::value_type>
getCredentialIDs() const
{
if (hasCredentialIDs())
{
return this->tx_->at(sfCredentialIDs);
}
return std::nullopt;
}
/**
* @brief Check if sfCredentialIDs is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCredentialIDs() const
{
return this->tx_->isFieldPresent(sfCredentialIDs);
}
};
/**
* @brief Builder for ConfidentialMPTSend transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class ConfidentialMPTSendBuilder : public TransactionBuilderBase<ConfidentialMPTSendBuilder>
{
public:
/**
* @brief Construct a new ConfidentialMPTSendBuilder with required fields.
* @param account The account initiating the transaction.
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
* @param destination The sfDestination field value.
* @param senderEncryptedAmount The sfSenderEncryptedAmount field value.
* @param destinationEncryptedAmount The sfDestinationEncryptedAmount field value.
* @param issuerEncryptedAmount The sfIssuerEncryptedAmount field value.
* @param zKProof The sfZKProof field value.
* @param amountCommitment The sfAmountCommitment field value.
* @param balanceCommitment The sfBalanceCommitment field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
ConfidentialMPTSendBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination, std::decay_t<typename SF_VL::type::value_type> const& senderEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& destinationEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& issuerEncryptedAmount, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::decay_t<typename SF_VL::type::value_type> const& amountCommitment, std::decay_t<typename SF_VL::type::value_type> const& balanceCommitment, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<ConfidentialMPTSendBuilder>(ttCONFIDENTIAL_MPT_SEND, account, sequence, fee)
{
setMPTokenIssuanceID(mPTokenIssuanceID);
setDestination(destination);
setSenderEncryptedAmount(senderEncryptedAmount);
setDestinationEncryptedAmount(destinationEncryptedAmount);
setIssuerEncryptedAmount(issuerEncryptedAmount);
setZKProof(zKProof);
setAmountCommitment(amountCommitment);
setBalanceCommitment(balanceCommitment);
}
/**
* @brief Construct a ConfidentialMPTSendBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
ConfidentialMPTSendBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_SEND)
{
throw std::runtime_error("Invalid transaction type for ConfidentialMPTSendBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfMPTokenIssuanceID (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
{
object_[sfMPTokenIssuanceID] = value;
return *this;
}
/**
* @brief Set sfDestination (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfDestination] = value;
return *this;
}
/**
* @brief Set sfSenderEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setSenderEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfSenderEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfDestinationEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setDestinationEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfDestinationEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfIssuerEncryptedAmount (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptedAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptedAmount] = value;
return *this;
}
/**
* @brief Set sfZKProof (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfZKProof] = value;
return *this;
}
/**
* @brief Set sfAmountCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setAmountCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAmountCommitment] = value;
return *this;
}
/**
* @brief Set sfBalanceCommitment (soeREQUIRED)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setBalanceCommitment(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfBalanceCommitment] = value;
return *this;
}
/**
* @brief Set sfCredentialIDs (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
ConfidentialMPTSendBuilder&
setCredentialIDs(std::decay_t<typename SF_VECTOR256::type::value_type> const& value)
{
object_[sfCredentialIDs] = value;
return *this;
}
/**
* @brief Build and return the ConfidentialMPTSend wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
ConfidentialMPTSend
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return ConfidentialMPTSend{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -187,6 +187,58 @@ public:
{
return this->tx_->isFieldPresent(sfMutableFlags);
}
/**
* @brief Get sfIssuerEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerEncryptionKey() const
{
if (hasIssuerEncryptionKey())
{
return this->tx_->at(sfIssuerEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfIssuerEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerEncryptionKey() const
{
return this->tx_->isFieldPresent(sfIssuerEncryptionKey);
}
/**
* @brief Get sfAuditorEncryptionKey (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getAuditorEncryptionKey() const
{
if (hasAuditorEncryptionKey())
{
return this->tx_->at(sfAuditorEncryptionKey);
}
return std::nullopt;
}
/**
* @brief Check if sfAuditorEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorEncryptionKey() const
{
return this->tx_->isFieldPresent(sfAuditorEncryptionKey);
}
};
/**
@@ -297,6 +349,28 @@ public:
return *this;
}
/**
* @brief Set sfIssuerEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceSetBuilder&
setIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfAuditorEncryptionKey (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceSetBuilder&
setAuditorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfAuditorEncryptionKey] = value;
return *this;
}
/**
* @brief Build and return the MPTokenIssuanceSet wrapper.
* @param publicKey The public key for signing.

View File

@@ -0,0 +1,290 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipSetBuilder;
/**
* @brief Transaction: SponsorshipSet
*
* Type: ttSPONSORSHIP_SET (91)
* Delegable: Delegation::delegable
* Amendment: featureSponsor
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipSetBuilder to construct new transactions.
*/
class SponsorshipSet : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET;
/**
* @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipSet(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSet");
}
}
// Transaction-specific field getters
/**
* @brief Get sfCounterpartySponsor (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getCounterpartySponsor() const
{
if (hasCounterpartySponsor())
{
return this->tx_->at(sfCounterpartySponsor);
}
return std::nullopt;
}
/**
* @brief Check if sfCounterpartySponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCounterpartySponsor() const
{
return this->tx_->isFieldPresent(sfCounterpartySponsor);
}
/**
* @brief Get sfSponsee (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
/**
* @brief Get sfFeeAmount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
{
return this->tx_->at(sfFeeAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->tx_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
{
return this->tx_->at(sfMaxFee);
}
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->tx_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
{
return this->tx_->at(sfReserveCount);
}
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->tx_->isFieldPresent(sfReserveCount);
}
};
/**
* @brief Builder for SponsorshipSet transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipSetBuilder : public TransactionBuilderBase<SponsorshipSetBuilder>
{
public:
/**
* @brief Construct a new SponsorshipSetBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipSetBuilder>(ttSPONSORSHIP_SET, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipSetBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipSetBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_SET)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfCounterpartySponsor (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setCounterpartySponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfCounterpartySponsor] = value;
return *this;
}
/**
* @brief Set sfSponsee (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipSet wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipSet
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipSet{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -0,0 +1,179 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipTransferBuilder;
/**
* @brief Transaction: SponsorshipTransfer
*
* Type: ttSPONSORSHIP_TRANSFER (90)
* Delegable: Delegation::delegable
* Amendment: featureSponsor
* Privileges: noPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipTransferBuilder to construct new transactions.
*/
class SponsorshipTransfer : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER;
/**
* @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipTransfer(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransfer");
}
}
// Transaction-specific field getters
/**
* @brief Get sfObjectID (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getObjectID() const
{
if (hasObjectID())
{
return this->tx_->at(sfObjectID);
}
return std::nullopt;
}
/**
* @brief Check if sfObjectID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasObjectID() const
{
return this->tx_->isFieldPresent(sfObjectID);
}
/**
* @brief Get sfSponsee (soeOPTIONAL)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
};
/**
* @brief Builder for SponsorshipTransfer transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses Json::Value internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipTransferBuilder : public TransactionBuilderBase<SponsorshipTransferBuilder>
{
public:
/**
* @brief Construct a new SponsorshipTransferBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipTransferBuilder>(ttSPONSORSHIP_TRANSFER, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipTransferBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipTransferBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfObjectID (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setObjectID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfObjectID] = value;
return *this;
}
/**
* @brief Set sfSponsee (soeOPTIONAL)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipTransfer wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipTransfer
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipTransfer{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -122,7 +122,7 @@ private:
ApplyFlags flags_;
std::optional<ApplyViewImpl> view_;
// The ID of the batch transaction we are executing under, if seated.
// The ID of the batch transaction we are executing under, if set.
std::optional<uint256 const> parentBatchId_;
};

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
@@ -106,6 +107,20 @@ struct PreflightResult;
// Needed for preflight specialization
class Change;
enum class FeePayerType {
Account,
Delegate,
SponsorCoSigned,
SponsorPreFunded,
};
struct FeePayer
{
Keylet entry;
SF_AMOUNT const& balanceField;
FeePayerType type{FeePayerType::Account};
};
class Transactor
{
protected:
@@ -162,9 +177,6 @@ public:
static NotTEC
checkSign(PreclaimContext const& ctx);
static NotTEC
checkBatchSign(PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load.
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
@@ -206,8 +218,50 @@ public:
return tesSUCCESS;
}
/**
* This function can be overridden to introduce additional semantic constraints beyond the
* granular template validation for granular permissions. It is called by the base
* checkPermission method only after the transaction has successfully passed
* checkGranularSandbox.
*/
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
{
return tesSUCCESS;
}
/**
* Checks whether the transaction is authorized to be executed by the delegated account.
* This function enforces the strict permission check hierarchy. It is explicitly
* designed NOT to be overridden. Derived transactors must instead implement
* checkGranularSemantics to add custom validation logic for granular permissions.
*
* The evaluation proceeds as follows:
* - If transaction-level permission is granted, the function immediately returns tesSUCCESS.
* - If transaction-level permission is not granted, the function checks whether the transaction
* matches the granular permission template defined in permissions.macro. If it does, it then
* calls checkGranularSemantics to perform any additional, fine-grained validation.
*
*/
template <class T>
static NotTEC
checkPermission(ReadView const& view, STTx const& tx)
{
std::unordered_set<GranularPermissionType> heldGranularPermissions;
if (NotTEC const result = checkPermissionImpl(view, tx, heldGranularPermissions);
!isTesSuccess(result) || heldGranularPermissions.empty())
{
return result;
}
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
}
static NotTEC
checkSponsor(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by AccountDelete
@@ -293,14 +347,7 @@ protected:
std::optional<T> value,
unit::ValueUnit<Unit, T> min = unit::ValueUnit<Unit, T>{});
private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER
payFee();
protected:
static NotTEC
checkSingleSign(
ReadView const& view,
@@ -316,6 +363,24 @@ private:
STObject const& sigObject,
beast::Journal const j);
private:
static NotTEC
checkPermissionImpl(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType>& heldGranularPermissions);
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER
payFee();
void trapTransaction(uint256) const;
/** Performs early sanity checks on the account and fee fields.

View File

@@ -13,6 +13,7 @@
#include <xrpl/tx/invariants/NFTInvariant.h>
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <cstdint>
@@ -399,7 +400,11 @@ using InvariantChecks = std::tuple<
ValidLoanBroker,
ValidLoan,
ValidVault,
ValidMPTPayment>;
ValidConfidentialMPToken,
ValidMPTPayment,
ValidMPTTransfer,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -56,4 +56,65 @@ public:
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Confidential MPToken consistency
*
* - Convert/ConvertBack symmetry:
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
* - Cannot delete MPToken with non-zero confidential state:
* Cannot delete if sfIssuerEncryptedBalance exists
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
* - Privacy flag consistency:
* MPToken can only have encrypted fields if lsfMPTCanConfidentialAmount is set on
* issuance.
* - Encrypted field existence consistency:
* If sfConfidentialBalanceSpending/sfConfidentialBalanceInbox exists, then
* sfIssuerEncryptedBalance must also exist (and vice versa).
* - COA <= OutstandingAmount:
* Confidential outstanding balance cannot exceed total outstanding.
* - Verifies sfConfidentialBalanceVersion is changed whenever sfConfidentialBalanceSpending is
* modified on an MPToken.
*/
class ValidConfidentialMPToken
{
struct Changes
{
std::int64_t mptAmountDelta = 0;
std::int64_t coaDelta = 0;
std::int64_t outstandingDelta = 0;
SLE::const_pointer issuance;
bool deletedWithEncrypted = false;
bool badConsistency = false;
bool badCOA = false;
bool requiresPrivacyFlag = false;
bool badVersion = false;
};
std::map<uint192, Changes> changes_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidMPTTransfer
{
struct Value
{
std::optional<std::uint64_t> amtBefore;
std::optional<std::uint64_t> amtAfter;
};
// MPTID: {holder: Value}
hash_map<uint192, hash_map<AccountID, Value>> amount_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,55 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
namespace xrpl {
/**
* @brief Invariant: Sponsored owner counts are balanced.
*
* The following check is made for every transaction:
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
* - Account OwnerCount must be greater than or equal to SponsoredOwnerCount.
*/
class SponsorshipOwnerCountsMatch
{
std::int64_t deltaSponsoredOwnerCount_ = 0;
std::int64_t deltaSponsoringOwnerCount_ = 0;
std::uint64_t invalidOwnerCountLessThanSponsoredOwnerCount_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariant: Sponsoring account relationships tracked consistently.
*
* The following check is made for every transaction:
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
* the net delta of the count of ltACCOUNT_ROOT entries having
* `sfSponsor` present (presence transitions only: add/remove).
*/
class SponsorshipAccountCountMatchesField
{
std::int64_t deltaSponsoringAccountCount_ = 0;
std::int64_t deltaSponsorFieldPresence_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -104,7 +104,10 @@ public:
send(Args&&... args)
{
return accountSend(
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
std::forward<Args>(args)...,
std::nullopt,
WaiveTransferFee::Yes,
AllowMPTOverflow::Yes);
}
bool

View File

@@ -213,7 +213,8 @@ template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
return accountSend(
std::forward<Args>(args)..., std::nullopt, WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <StepAmount TIn, StepAmount TOut>

View File

@@ -23,9 +23,6 @@ public:
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -24,11 +24,7 @@ public:
// Interface used by AccountDelete
static TER
deleteDelegate(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
AccountID const& account,
beast::Journal j);
deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
};
} // namespace xrpl

View File

@@ -89,6 +89,7 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
@@ -123,6 +124,7 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
@@ -166,6 +168,7 @@ private:
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -191,6 +194,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -216,6 +220,7 @@ private:
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -238,6 +243,7 @@ private:
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -259,6 +265,7 @@ private:
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -281,6 +288,7 @@ private:
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,

View File

@@ -22,6 +22,9 @@ public:
{
}
static uint32_t
calculateOracleReserve(std::size_t count);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -32,7 +32,10 @@ public:
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
static TER
preclaim(PreclaimContext const& ctx);

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
@@ -27,29 +25,31 @@ public:
static NotTEC
preflightSigValidated(PreflightContext const& ctx);
static NotTEC
checkBatchSign(PreclaimContext const& ctx);
static NotTEC
checkSign(PreclaimContext const& ctx);
TER
doApply() override;
static constexpr auto disabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY,
});
static constexpr auto disabledTxTypes = std::to_array<TxType>(
{ttVAULT_CREATE,
ttVAULT_SET,
ttVAULT_DELETE,
ttVAULT_DEPOSIT,
ttVAULT_WITHDRAW,
ttVAULT_CLAWBACK,
ttLOAN_BROKER_SET,
ttLOAN_BROKER_DELETE,
ttLOAN_BROKER_COVER_DEPOSIT,
ttLOAN_BROKER_COVER_WITHDRAW,
ttLOAN_BROKER_COVER_CLAWBACK,
ttLOAN_SET,
ttLOAN_DELETE,
ttLOAN_MANAGE,
ttLOAN_PAY});
};
} // namespace xrpl

View File

@@ -0,0 +1,42 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Allows an MPT issuer to clawback confidential balances from a holder.
*
* This transaction enables the issuer of an MPToken Issuance (with clawback
* enabled) to reclaim confidential tokens from a holder's account. Unlike
* regular clawback, the issuer cannot see the holder's balance directly.
* Instead, the issuer must provide a zero-knowledge proof that demonstrates
* they know the exact encrypted balance amount.
*
* @par Cryptographic Operations:
* - **Equality Proof Verification**: Verifies that the issuer's revealed
* amount matches the holder's encrypted balance using the issuer's
* ElGamal private key.
*
* @see ConfidentialMPTSend, ConfidentialMPTConvert
*/
class ConfidentialMPTClawback : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTClawback(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Converts public (plaintext) MPT balance to confidential (encrypted)
* balance.
*
* This transaction allows a token holder to convert their publicly visible
* MPToken balance into an encrypted confidential balance. Once converted,
* the balance can only be spent using ConfidentialMPTSend transactions and
* remains hidden from public view on the ledger.
*
* @par Cryptographic Operations:
* - **Schnorr Proof Verification**: When registering a new ElGamal public key,
* verifies proof of knowledge of the corresponding private key.
* - **Revealed Amount Verification**: Verifies that the provided encrypted
* amounts (for holder, issuer, and optionally auditor) all encrypt the
* same plaintext amount using the provided blinding factor.
*
* @see ConfidentialMPTConvertBack, ConfidentialMPTSend
*/
class ConfidentialMPTConvert : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTConvert(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,45 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Converts confidential (encrypted) MPT balance back to public
* (plaintext) balance.
*
* This transaction allows a token holder to convert their encrypted
* confidential balance back into a publicly visible MPToken balance. The
* holder must prove they have sufficient confidential balance without
* revealing the actual balance amount.
*
* @par Cryptographic Operations:
* - **Revealed Amount Verification**: Verifies that the provided encrypted
* amounts correctly encrypt the conversion amount.
* - **Pedersen Linkage Proof**: Verifies that the provided balance commitment
* correctly links to the holder's encrypted spending balance.
* - **Bulletproof Range Proof**: Verifies that the remaining balance (after
* conversion) is non-negative, ensuring the holder has sufficient funds.
*
* @see ConfidentialMPTConvert, ConfidentialMPTSend
*/
class ConfidentialMPTConvertBack : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTConvertBack(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Merges the confidential inbox balance into the spending balance.
*
* In the confidential transfer system, incoming funds are deposited into an
* "inbox" balance that the recipient cannot immediately spend. This prevents
* front-running attacks where an attacker could invalidate a pending
* transaction by sending funds to the sender. This transaction merges the
* inbox into the spending balance, making those funds available for spending.
*
* @par Cryptographic Operations:
* - **Homomorphic Addition**: Adds the encrypted inbox balance to the
* encrypted spending balance using ElGamal homomorphic properties.
* - **Zero Encryption**: Resets the inbox to an encryption of zero.
*
* @note This transaction requires no zero-knowledge proofs because it only
* combines encrypted values that the holder already owns. The
* homomorphic properties of ElGamal encryption ensure correctness.
*
* @see ConfidentialMPTSend, ConfidentialMPTConvert
*/
class ConfidentialMPTMergeInbox : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTMergeInbox(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
/**
* @brief Transfers confidential MPT tokens between holders privately.
*
* This transaction enables private token transfers where the transfer amount
* is hidden from public view. Both sender and recipient must have initialized
* confidential balances. The transaction provides encrypted amounts for all
* parties (sender, destination, issuer, and optionally auditor) along with
* zero-knowledge proofs that verify correctness without revealing the amount.
*
* @par Cryptographic Operations:
* - **Multi-Ciphertext Equality Proof**: Verifies that all encrypted amounts
* (sender, destination, issuer, auditor) encrypt the same plaintext value.
* - **Amount Pedersen Linkage Proof**: Verifies that the amount commitment
* correctly links to the sender's encrypted amount.
* - **Balance Pedersen Linkage Proof**: Verifies that the balance commitment
* correctly links to the sender's encrypted spending balance.
* - **Bulletproof Range Proof**: Verifies remaining balance and
* transfer amount are non-negative.
*
* @note Funds are deposited into the destination's inbox, not spending
* balance. The recipient must call ConfidentialMPTMergeInbox to make
* received funds spendable.
*
* @see ConfidentialMPTMergeInbox, ConfidentialMPTConvert,
* ConfidentialMPTConvertBack
*/
class ConfidentialMPTSend : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit ConfidentialMPTSend(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
} // namespace xrpl

View File

@@ -42,7 +42,7 @@ public:
doApply() override;
static Expected<MPTID, TER>
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
create(ApplyView& view, STTx const& tx, beast::Journal journal, MPTCreateArgs const& args);
};
} // namespace xrpl

View File

@@ -22,9 +22,6 @@ public:
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -21,7 +21,10 @@ public:
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
checkGranularSemantics(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldGranularPermissions);
static TER
preclaim(PreclaimContext const& ctx);

View File

@@ -59,7 +59,8 @@ Logs::File::open(boost::filesystem::path const& path)
bool wasOpened = false;
// VFALCO TODO Make this work with Unicode file paths
std::unique_ptr<std::ofstream> stream(new std::ofstream(path.c_str(), std::fstream::app));
std::unique_ptr<std::ofstream> stream =
std::make_unique<std::ofstream>(path.c_str(), std::fstream::app);
if (stream->good())
{

View File

@@ -185,7 +185,7 @@ OpenView::txsEnd() const -> std::unique_ptr<txs_type::iter_base>
bool
OpenView::txExists(key_type const& key) const
{
return txs_.contains(key);
return txs_.contains(key) || base_->txExists(key);
}
auto

View File

@@ -8,6 +8,7 @@
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -285,6 +286,93 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
return std::nullopt;
}
uint32_t
ownerCount(std::shared_ptr<SLE const> const& sponsorSle)
{
auto const ownerCount = sponsorSle->getFieldU32(sfOwnerCount);
auto const sponsoredOwnerCount = sponsorSle->getFieldU32(sfSponsoredOwnerCount);
auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount);
return ownerCount + sponsoringOwnerCount - sponsoredOwnerCount;
}
XRPAmount
calculateReserve(std::shared_ptr<SLE const> const& sle, Fees const& fees)
{
XRPL_ASSERT(sle->getType() == ltACCOUNT_ROOT, "xrpl::calculateReserve : valid sle type");
return fees.accountReserve(
sle->getFieldU32(sfOwnerCount),
sle->getFieldU32(sfSponsoredOwnerCount),
sle->getFieldU32(sfSponsoringOwnerCount),
sle->isFieldPresent(sfSponsor),
sle->getFieldU32(sfSponsoringAccountCount));
}
TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
std::shared_ptr<SLE const> accSle,
STAmount const& accBalance,
std::shared_ptr<SLE const> const& sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t accountCountDelta)
{
if (sponsorSle)
{
auto const isCoSigning = isSponsorReserveCoSigning(tx);
auto const sle = view.read(
keylet::sponsor(sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
if (isCoSigning)
{
if (sle)
{
auto const reserveCountAllowed = sle->getFieldU32(sfReserveCount);
if (reserveCountAllowed < ownerCountDelta)
return tecINSUFFICIENT_RESERVE;
return tesSUCCESS;
}
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance);
STAmount const sponsorReserve{view.fees().accountReserve(
sponsorSle->getFieldU32(sfOwnerCount),
sponsorSle->getFieldU32(sfSponsoredOwnerCount),
sponsorSle->getFieldU32(sfSponsoringOwnerCount) + ownerCountDelta,
sponsorSle->isFieldPresent(sfSponsor),
sponsorSle->getFieldU32(sfSponsoringAccountCount) + accountCountDelta)};
if (sponsorBalance < sponsorReserve)
return tecINSUFFICIENT_RESERVE;
}
else
{
// pre funded
if (!sle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const reserveCountAllowed = sle->getFieldU32(sfReserveCount);
if (reserveCountAllowed < ownerCountDelta)
return tecINSUFFICIENT_RESERVE;
}
}
else
{
STAmount const reserve{view.fees().accountReserve(
accSle->getFieldU32(sfOwnerCount) + ownerCountDelta,
accSle->getFieldU32(sfSponsoredOwnerCount),
accSle->getFieldU32(sfSponsoringOwnerCount),
accSle->isFieldPresent(sfSponsor),
accSle->getFieldU32(sfSponsoringAccountCount) + accountCountDelta)};
if (accBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Modifiers
@@ -408,7 +496,7 @@ doWithdraw(
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = addEmptyHolding(view, tx, senderAcct, priorBalance, amount.asset(), j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
@@ -434,9 +522,12 @@ doWithdraw(
// LCOV_EXCL_STOP
}
auto const sponsorAccountID = getTxReserveSponsorAccountID(tx);
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
return accountSend(
view, sourceAcct, dstAcct, amount, j, sponsorAccountID, WaiveTransferFee::Yes);
}
TER

View File

@@ -74,9 +74,19 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
std::uint32_t const sponsoredOwnerCount = sle->getFieldU32(sfSponsoredOwnerCount);
std::uint32_t const sponsoringOwnerCount = sle->getFieldU32(sfSponsoringOwnerCount);
bool const isAccountSponsored = sle->isFieldPresent(sfSponsor);
std::uint32_t const sponsoringAccountCount = sle->getFieldU32(sfSponsoringAccountCount);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
auto const reserve = isPseudoAccount(sle) ? XRPAmount{0}
: view.fees().accountReserve(
ownerCount,
sponsoredOwnerCount,
sponsoringOwnerCount,
isAccountSponsored,
sponsoringAccountCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
@@ -105,21 +115,69 @@ transferRate(ReadView const& view, AccountID const& issuer)
}
void
adjustOwnerCount(
adjustSponsorOwnerCountHlp(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
SField const& sfield,
std::int32_t amount,
beast::Journal j)
{
if (!sle)
auto const accID = sle->getAccountID(sfAccount);
std::uint32_t const current{(sle)->getFieldU32(sfield)};
std::uint32_t const adjusted = confineOwnerCount(current, amount, accID, j);
view.adjustOwnerCountHook(accID, current, adjusted);
if (adjusted == 0)
sle->makeFieldAbsent(sfield);
else
sle->setFieldU32(sfield, adjusted);
view.update(sle);
}
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& accountSle,
std::shared_ptr<SLE> const& sponsorSle,
std::int32_t amount,
beast::Journal j)
{
if (!accountSle)
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
AccountID const id = (*sle)[sfAccount];
if (sponsorSle)
{
adjustSponsorOwnerCountHlp(view, sponsorSle, sfSponsoringOwnerCount, amount, j);
adjustSponsorOwnerCountHlp(view, accountSle, sfSponsoredOwnerCount, amount, j);
auto const account = accountSle->getAccountID(sfAccount);
auto const sponsorAccountID = (sponsorSle)->getAccountID(sfAccount);
auto sponsorObjSle = view.peek(keylet::sponsor(sponsorAccountID, account));
if (sponsorObjSle)
{
// pre funded
// update the pre-funded ReserveCount on Sponsorship ledger object
std::uint32_t const currentReserveCount = sponsorObjSle->getFieldU32(sfReserveCount);
// Reserve count moves opposite to amount: +amount => consume reserve (-), -amount =>
// payback (+)
std::uint32_t const adjusted =
confineOwnerCount(currentReserveCount, -amount, sponsorAccountID, j);
if (adjusted == 0)
sponsorObjSle->makeFieldAbsent(sfReserveCount);
else
sponsorObjSle->setFieldU32(sfReserveCount, adjusted);
view.update(sponsorObjSle);
}
}
std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)};
AccountID const id = (*accountSle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
view.update(sle);
accountSle->at(sfOwnerCount) = adjusted;
view.update(accountSle);
}
AccountID

View File

@@ -2,6 +2,7 @@
//
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/digest.h>
@@ -71,7 +72,10 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
}
if (isOwner)
adjustOwnerCount(view, sleAccount, -1, j);
{
auto const sponsorSle = getLedgerEntryReserveSponsor(view, sleCredential);
adjustOwnerCount(view, sleAccount, sponsorSle, -1, j);
}
return tesSUCCESS;
};

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -94,6 +95,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -110,12 +112,13 @@ addEmptyHolding(
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -148,7 +151,8 @@ authorizeMPToken(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleAcct, -1, journal);
auto const sponsor = getLedgerEntryReserveSponsor(view, sleMpt);
adjustOwnerCount(view, sleAcct, sponsor, -1, journal);
view.erase(sleMpt);
return tesSUCCESS;
@@ -158,18 +162,24 @@ authorizeMPToken(
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
auto const sponsor = getTxReserveSponsor(view, tx);
auto const isSponsoredAndPreFunded = sponsor && !isSponsorReserveCoSigning(tx);
// 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;
// If PreFunded Sponsor, it must be checked whether sufficient
// ReserveCount exists.
if (ownerCount(sponsor ? sponsor : sleAcct) >= 2 || isSponsoredAndPreFunded)
{
if (auto const ret =
checkInsufficientReserve(view, tx, sleAcct, priorBalance, sponsor, 1);
!isTesSuccess(ret))
return ret;
}
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
@@ -193,7 +203,8 @@ authorizeMPToken(
view.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, 1, journal);
adjustOwnerCount(view, sleAcct, sponsor, 1, journal);
addSponsorToLedgerEntry(mptoken, sponsor);
return tesSUCCESS;
}
@@ -238,6 +249,7 @@ authorizeMPToken(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
@@ -258,8 +270,16 @@ removeEmptyHolding(
(view.rules().enabled(fixSecurity3_1_3) && (*mptoken)[~sfLockedAmount].value_or(0) != 0))
return tecHAS_OBLIGATIONS;
// Don't delete if the token still has confidential balances
if (mptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
mptoken->isFieldPresent(sfConfidentialBalanceSpending))
{
return tecHAS_OBLIGATIONS;
}
return authorizeMPToken(
view,
tx,
{}, // priorBalance
mptID,
accountID,
@@ -328,9 +348,24 @@ requireAuth(
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
if (maybeDomainID)
{
XRPL_ASSERT(
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
"xrpl::requireAuth : issuance requires authorization");
// Defensive check: An issuance with sfDomainID must strictly enforce lsfMPTRequireAuth.
// While preclaim checks prevent clearing this flag when sfDomainID is set, we verify here
// to guard against potential logic regressions in the future changes.
if (view.rules().enabled(fixSecurity3_1_3))
{
// Post-fixSecurity3_1_3: Return a proper error code instead of asserting.
if (!(sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth))
return tefINTERNAL; // LCOV_EXCL_LINE
}
else
{
// Pre-fixSecurity3_1_3: Fault via assertion in Debug mode;
// potentially fall through in Release mode.
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))
@@ -363,6 +398,7 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
@@ -444,6 +480,7 @@ enforceMPTokenAuthorization(
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
tx,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
@@ -495,6 +532,22 @@ canTrade(ReadView const& view, Asset const& asset)
});
}
TER
canMPTTradeAndTransfer(
ReadView const& view,
Asset const& asset,
AccountID const& from,
AccountID const& to)
{
if (!asset.holds<MPTIssue>())
return tesSUCCESS;
if (auto const ter = canTrade(view, asset); !isTesSuccess(ter))
return ter;
return canTransfer(view, asset, from, to);
}
TER
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
{
@@ -757,6 +810,7 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::uint32_t const flags)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
@@ -773,6 +827,14 @@ createMPToken(
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
if (sponsor)
{
auto const sponsorSle = view.peek(keylet::account(*sponsor));
if (!sponsorSle)
return tecINTERNAL;
addSponsorToLedgerEntry(mptoken, sponsorSle);
}
view.insert(mptoken);
return tesSUCCESS;
@@ -783,6 +845,7 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
std::optional<xrpl::AccountID> const& sponsor,
beast::Journal j)
{
if (mptIssue.getIssuer() == holder)
@@ -792,7 +855,7 @@ checkCreateMPT(
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsor, 0);
!isTesSuccess(err))
{
return err;
@@ -802,7 +865,9 @@ checkCreateMPT(
{
return tecINTERNAL;
}
adjustOwnerCount(view, sleAcct, 1, j);
auto const sleSponsor =
sponsor ? view.peek(keylet::account(*sponsor)) : std::shared_ptr<SLE>();
adjustOwnerCount(view, sleAcct, sleSponsor, 1, j);
}
return tesSUCCESS;
}
@@ -862,65 +927,4 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo
view.issuerSelfDebitHookMPT(issue, amount, available);
}
static TER
checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID)
{
if (!asset.holds<MPTIssue>())
return tesSUCCESS;
auto const& issuanceID = asset.get<MPTIssue>().getMptID();
auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT ||
txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE ||
txType == ttCHECK_CASH || txType == ttPAYMENT;
XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX");
if (!validTx)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const& issuer = asset.getIssuer();
if (!view.exists(keylet::account(issuer)))
return tecNO_ISSUER; // LCOV_EXCL_LINE
auto const issuanceKey = keylet::mptIssuance(issuanceID);
auto const issuanceSle = view.read(issuanceKey);
if (!issuanceSle)
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
auto const flags = issuanceSle->getFlags();
if ((flags & lsfMPTLocked) != 0u)
return tecLOCKED; // LCOV_EXCL_LINE
// Offer crossing and Payment
if ((flags & lsfMPTCanTrade) == 0)
return tecNO_PERMISSION;
if (accountID != issuer)
{
if ((flags & lsfMPTCanTransfer) == 0)
return tecNO_PERMISSION;
auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID));
// Allow to succeed since some tx create MPToken if it doesn't exist.
// Tx's have their own check for missing MPToken.
if (!mptSle)
return tesSUCCESS;
if (mptSle->isFlag(lsfMPTLocked))
return tecLOCKED;
}
return tesSUCCESS;
}
TER
checkMPTTxAllowed(
ReadView const& view,
TxType txType,
Asset const& asset,
AccountID const& accountID)
{
// use isDEXAllowed for payment/offer crossing
XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment");
return checkMPTAllowed(view, txType, asset, accountID);
}
} // namespace xrpl

View File

@@ -6,6 +6,7 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STArray.h>
@@ -45,12 +46,19 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id)
Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)));
}
static std::shared_ptr<SLE>
static Expected<std::shared_ptr<SLE>, TER>
getPageForToken(
ApplyView& view,
STTx const& tx,
AccountID const& owner,
std::optional<AccountID> const& sponsor,
uint256 const& id,
std::function<void(ApplyView&, AccountID const&)> const& createCallback)
std::function<
TER(ApplyView&,
STTx const&,
std::shared_ptr<SLE> const&,
AccountID const&,
std::optional<AccountID> const&)> const& createCallback)
{
auto const base = keylet::nftpage_min(owner);
auto const first = keylet::nftpage(base, id);
@@ -69,7 +77,9 @@ getPageForToken(
cp = std::make_shared<SLE>(last);
cp->setFieldArray(sfNFTokens, arr);
view.insert(cp);
createCallback(view, owner);
if (auto const ret = createCallback(view, tx, cp, owner, sponsor); !isTesSuccess(ret))
return Unexpected(ret);
return cp;
}
@@ -182,7 +192,8 @@ getPageForToken(
cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp);
createCallback(view, owner);
if (auto const ret = createCallback(view, tx, np, owner, sponsor); ret != tesSUCCESS)
return Unexpected(ret);
return (first.key < np->key()) ? np : cp;
}
@@ -239,37 +250,69 @@ changeTokenURI(
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft)
insertToken(
ApplyView& view,
STTx const& tx,
AccountID owner,
std::optional<AccountID> const& sponsor,
STObject&& nft)
{
XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID), "xrpl::nft::insertToken : has NFT token");
// First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert
// the NFT.
std::shared_ptr<SLE> const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
Expected<std::shared_ptr<SLE>, TER> const page = getPageForToken(
view,
tx,
owner,
sponsor,
nft[sfNFTokenID],
[](ApplyView& view,
STTx const& tx,
std::shared_ptr<SLE> const& newPage,
AccountID const& owner,
std::optional<AccountID> const& sponsor) -> TER {
std::shared_ptr<SLE> const sponsorSle =
sponsor ? view.peek(keylet::account(*sponsor)) : std::shared_ptr<SLE>();
if (isReserveSponsored(tx))
{
auto const ownerSle = view.read(keylet::account(owner));
auto const ownerBalance = ownerSle->getFieldAmount(sfBalance);
if (auto const ret =
checkInsufficientReserve(view, tx, ownerSle, ownerBalance, sponsorSle, 1);
!isTesSuccess(ret))
return ret;
}
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsorSle,
1,
beast::Journal{beast::Journal::getNullSink()});
addSponsorToLedgerEntry(newPage, sponsorSle);
return tesSUCCESS;
});
if (!page)
if (!page.has_value())
return page.error();
if (!(*page))
return tecNO_SUITABLE_NFTOKEN_PAGE;
{
auto arr = page->getFieldArray(sfNFTokens);
auto arr = (*page)->getFieldArray(sfNFTokens);
arr.push_back(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
return compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
page->setFieldArray(sfNFTokens, arr);
(*page)->setFieldArray(sfNFTokens, arr);
}
view.update(page);
view.update((*page));
return tesSUCCESS;
}
@@ -400,20 +443,25 @@ removeToken(
curr->setFieldArray(sfNFTokens, arr);
view.update(curr);
int cnt = 0;
if (prev && mergePages(view, prev, curr))
cnt--;
if (next && mergePages(view, curr, next))
cnt--;
if (cnt != 0)
{
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
cnt,
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
}
if (next && mergePages(view, curr, next))
{
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
}
@@ -449,9 +497,11 @@ removeToken(
curr->makeFieldAbsent(sfPreviousPageMin);
}
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
@@ -489,9 +539,15 @@ removeToken(
view.update(next);
}
view.erase(curr);
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
int cnt = 1;
view.erase(curr);
// Since we're here, try to consolidate the previous and current pages
// of the page we removed (if any) into one. mergePages() _should_
@@ -506,13 +562,14 @@ removeToken(
view,
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
cnt++;
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
{
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
getLedgerEntryReserveSponsor(view, prev),
-1,
beast::Journal{beast::Journal::getNullSink()});
}
return tesSUCCESS;
}
@@ -655,8 +712,13 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
false))
return false;
auto const sponsor = getLedgerEntryReserveSponsor(view, offer);
adjustOwnerCount(
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
view,
view.peek(keylet::account(owner)),
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
view.erase(offer);
return true;
@@ -935,6 +997,7 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,
@@ -946,9 +1009,11 @@ tokenOfferCreateApply(
std::uint32_t txFlags)
{
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet);
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
return tecINSUFFICIENT_RESERVE;
auto const acct = view.read(acctKeylet);
auto const sponsor = getTxReserveSponsor(view, tx);
if (auto const ret = checkInsufficientReserve(view, tx, acct, priorBalance, sponsor, 1);
!isTesSuccess(ret))
return ret;
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
@@ -995,11 +1060,13 @@ tokenOfferCreateApply(
if (dest)
(*offer)[sfDestination] = *dest;
addSponsorToLedgerEntry(offer, sponsor);
view.insert(offer);
}
// Update owner count.
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
adjustOwnerCount(view, view.peek(acctKeylet), sponsor, 1, j);
return tesSUCCESS;
}

View File

@@ -1,6 +1,7 @@
#include <xrpl/ledger/helpers/OfferHelpers.h>
//
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/st.h>
@@ -48,7 +49,8 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
}
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
auto const sponsor = getLedgerEntryReserveSponsor(view, sle);
adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsor, -1, j);
view.erase(sle);

View File

@@ -2,6 +2,7 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Indexes.h>
namespace xrpl {
@@ -47,7 +48,8 @@ closeChannel(
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCount(view, sle, -1, j);
auto const sponsor = getLedgerEntryReserveSponsor(view, slep);
adjustOwnerCount(view, sle, sponsor, -1, j);
view.update(sle);
// Remove PayChan from ledger

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AmountConversions.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -176,6 +177,7 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
std::optional<AccountID> const& sponsorAccountID,
beast::Journal j)
{
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
@@ -261,8 +263,14 @@ trustCreate(
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
}
std::shared_ptr<SLE> sponsorSle = {};
if (sponsorAccountID)
sponsorSle = view.peek(keylet::account(*sponsorAccountID));
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, 1, j);
adjustOwnerCount(view, sleAccount, sponsorSle, 1, j);
addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -298,6 +306,9 @@ trustDelete(
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
view.erase(sleRippleState);
@@ -347,11 +358,15 @@ updateTrustLine(
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, sle, -1, j);
auto const currentSponsor =
getLedgerEntryReserveSponsor(view, state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, sle, currentSponsor, -1, j);
// Clear reserve flag.
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Balance is zero, receiver reserve is clear.
if (!after // Balance is zero.
&& ((flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)) == 0u))
@@ -451,6 +466,7 @@ issueIOU(
limit,
0,
0,
{},
j);
}
@@ -601,6 +617,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
@@ -628,10 +645,19 @@ addEmptyHolding(
// If the line already exists, don't create it again.
if (view.read(index))
return tecDUPLICATE;
auto const& sponsorAccountID =
!isPseudoAccount(sleDst) ? getTxReserveSponsorAccountID(tx) : std::nullopt;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
if (auto const ret = checkInsufficientReserve(
view,
tx,
sleDst,
priorBalance,
sponsorAccountID ? view.read(keylet::account(*sponsorAccountID))
: std::shared_ptr<SLE>(),
1);
!isTesSuccess(ret))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
@@ -649,6 +675,7 @@ addEmptyHolding(
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
sponsorAccountID,
journal);
}
@@ -690,11 +717,14 @@ removeEmptyHolding(
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleLowAccount, -1, journal);
auto const currentLowSponsor = getLedgerEntryReserveSponsor(view, line, sfLowSponsor);
adjustOwnerCount(view, sleLowAccount, currentLowSponsor, -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);
removeSponsorFromLedgerEntry(line, sfLowSponsor);
}
if (line->isFlag(lsfHighReserve))
@@ -704,11 +734,14 @@ removeEmptyHolding(
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleHighAccount, -1, journal);
auto const currentHighSponsor = getLedgerEntryReserveSponsor(view, line, sfHighSponsor);
adjustOwnerCount(view, sleHighAccount, currentHighSponsor, -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);
removeSponsorFromLedgerEntry(line, sfHighSponsor);
}
return trustDelete(
@@ -748,6 +781,9 @@ deleteAMMTrustLine(
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
return terNO_AMM;
auto const sponsorSle =
getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor);
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
{
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
@@ -758,7 +794,7 @@ deleteAMMTrustLine(
if ((sleState->getFlags() & uFlags) == 0u)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, -1, j);
return tesSUCCESS;
}

View File

@@ -5,6 +5,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -40,6 +41,14 @@ isGlobalFrozen(ReadView const& view, Asset const& asset)
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
}
TER
checkGlobalFrozen(ReadView const& view, Asset const& asset)
{
if (isGlobalFrozen(view, asset))
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
return tesSUCCESS;
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
@@ -47,6 +56,14 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
}
TER
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
if (isIndividualFrozen(view, account, asset))
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
return tesSUCCESS;
}
bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
{
@@ -313,7 +330,8 @@ accountHolds(
// Only if auth check is needed, as it needs to do an additional read
// operation. Note featureSingleAssetVault will affect error codes.
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
view.rules().enabled(featureSingleAssetVault))
(view.rules().enabled(featureSingleAssetVault) ||
view.rules().enabled(featureConfidentialTransfer)))
{
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
!isTesSuccess(err))
@@ -436,6 +454,7 @@ canAddHolding(ReadView const& view, Asset const& asset)
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -443,7 +462,7 @@ addEmptyHolding(
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
return addEmptyHolding(view, tx, accountID, priorBalance, issue, journal);
},
asset.value());
}
@@ -451,13 +470,17 @@ addEmptyHolding(
TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
if constexpr (std::is_same_v<TIss, Issue>)
return removeEmptyHolding(view, accountID, issue, journal);
else
return removeEmptyHolding(view, tx, accountID, issue, journal);
},
asset.value());
}
@@ -505,6 +528,7 @@ directSendNoFeeIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
std::optional<AccountID> const& sponsorAccountID,
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
@@ -572,7 +596,12 @@ directSendNoFeeIOU(
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
auto const currentSponsor = getLedgerEntryReserveSponsor(
view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, -1, j);
removeSponsorFromLedgerEntry(
sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Clear reserve flag.
sleRippleState->setFieldU32(
@@ -636,6 +665,7 @@ directSendNoFeeIOU(
saReceiverLimit,
0,
0,
sponsorAccountID,
j);
}
@@ -650,6 +680,7 @@ directSendNoLimitIOU(
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee)
{
auto const& issuer = saAmount.getIssuer();
@@ -662,7 +693,8 @@ directSendNoLimitIOU(
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j);
auto const ter =
directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorAccountID, j);
if (!isTesSuccess(ter))
return ter;
saActual = saAmount;
@@ -680,10 +712,12 @@ directSendNoLimitIOU(
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j);
TER terResult =
directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorAccountID, j);
if (tesSUCCESS == terResult)
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j);
terResult =
directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorAccountID, j);
return terResult;
}
@@ -699,6 +733,7 @@ directSendNoLimitMultiIOU(
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee)
{
auto const& issuer = issue.getIssuer();
@@ -726,7 +761,8 @@ directSendNoLimitMultiIOU(
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j))
if (auto const ter = directSendNoFeeIOU(
view, senderID, receiverID, amount, false, sponsorAccountID, j))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because directSendNoFeeIOU took
@@ -749,14 +785,15 @@ directSendNoLimitMultiIOU(
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
<< " cost=" << actual.getFullText();
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j))
if (TER const terResult =
directSendNoFeeIOU(view, issuer, receiverID, amount, true, sponsorAccountID, j))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult =
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j))
if (TER const terResult = directSendNoFeeIOU(
view, senderID, issuer, takeFromSender, true, sponsorAccountID, j))
return terResult;
}
@@ -770,6 +807,7 @@ accountSendIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee)
{
if (view.rules().enabled(fixAMMv1_1))
@@ -801,7 +839,8 @@ accountSendIOU(
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
return directSendNoLimitIOU(
view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorAccountID, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -889,6 +928,7 @@ accountSendMultiIOU(
Issue const& issue,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
@@ -900,7 +940,8 @@ accountSendMultiIOU(
JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
<< receivers.size() << " IOUs";
return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
return directSendNoLimitMultiIOU(
view, senderID, issue, receivers, actual, j, sponsorAccountID, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -1325,7 +1366,8 @@ directSendNoFee(
{
return saAmount.asset().visit(
[&](Issue const&) {
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
return directSendNoFeeIOU(
view, uSenderID, uReceiverID, saAmount, bCheckIssuer, std::nullopt, j);
},
[&](MPTIssue const&) {
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
@@ -1340,12 +1382,14 @@ accountSend(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
return saAmount.asset().visit(
[&](Issue const&) {
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
return accountSendIOU(
view, uSenderID, uReceiverID, saAmount, j, sponsorAccountID, waiveFee);
},
[&](MPTIssue const&) {
return accountSendMPT(
@@ -1360,13 +1404,15 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
std::optional<AccountID> const& sponsorAccountID,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
return asset.visit(
[&](Issue const& issue) {
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
return accountSendMultiIOU(
view, senderID, issue, receivers, j, sponsorAccountID, waiveFee);
},
[&](MPTIssue const& issue) {
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);

View File

@@ -0,0 +1,444 @@
#include <xrpl/protocol/ConfidentialTransfer.h>
#include <xrpl/protocol/Protocol.h>
#include <openssl/rand.h>
#include <utility/mpt_utility.h>
#include <secp256k1_mpt.h>
namespace xrpl {
/**
* @brief Converts an XRPL AccountID to mpt-crypto lib C struct.
*
* @param account The AccountID.
* @return The equivalent mpt-crypto lib account_id struct.
*/
account_id
toAccountId(AccountID const& account)
{
account_id res;
std::memcpy(res.bytes, account.data(), kMPT_ACCOUNT_ID_SIZE);
return res;
}
/**
* @brief Converts an XRPL uint192 to mpt-crypto lib C struct.
*
* @param i The XRPL MPTokenIssuance ID.
* @return The equivalent mpt-crypto lib mpt_issuance_id struct.
*/
mpt_issuance_id
toIssuanceId(uint192 const& issuance)
{
mpt_issuance_id res;
std::memcpy(res.bytes, issuance.data(), kMPT_ISSUANCE_ID_SIZE);
return res;
}
uint256
getSendContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& destination,
std::uint32_t version)
{
uint256 result;
mpt_get_send_context_hash(
toAccountId(account),
toIssuanceId(issuanceID),
sequence,
toAccountId(destination),
version,
result.data());
return result;
}
uint256
getClawbackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
AccountID const& holder)
{
uint256 result;
mpt_get_clawback_context_hash(
toAccountId(account),
toIssuanceId(issuanceID),
sequence,
toAccountId(holder),
result.data());
return result;
}
uint256
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
{
uint256 result;
mpt_get_convert_context_hash(
toAccountId(account), toIssuanceId(issuanceID), sequence, result.data());
return result;
}
uint256
getConvertBackContextHash(
AccountID const& account,
uint192 const& issuanceID,
std::uint32_t sequence,
std::uint32_t version)
{
uint256 result;
mpt_get_convert_back_context_hash(
toAccountId(account), toIssuanceId(issuanceID), sequence, version, result.data());
return result;
}
std::optional<EcPair>
makeEcPair(Slice const& buffer)
{
if (buffer.length() != 2 * ecGamalEncryptedLength)
return std::nullopt; // LCOV_EXCL_LINE
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
return secp256k1_ec_pubkey_parse(
secp256k1Context(),
&out,
reinterpret_cast<unsigned char const*>(slice.data()),
slice.length());
};
Slice const s1{buffer.data(), ecGamalEncryptedLength};
Slice const s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
EcPair pair{};
if (parsePubKey(s1, pair.c1) != 1 || parsePubKey(s2, pair.c2) != 1)
return std::nullopt;
return pair;
}
std::optional<Buffer>
serializeEcPair(EcPair const& pair)
{
auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
size_t outLen = ecGamalEncryptedLength; // 33 bytes
int const ret = secp256k1_ec_pubkey_serialize(
secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
return ret == 1 && outLen == ecGamalEncryptedLength;
};
Buffer buffer(ecGamalEncryptedTotalLength);
unsigned char* ptr = buffer.data();
bool const res1 = serializePubKey(pair.c1, ptr);
bool const res2 = serializePubKey(pair.c2, ptr + ecGamalEncryptedLength);
if (!res1 || !res2)
return std::nullopt;
return buffer;
}
bool
isValidCiphertext(Slice const& buffer)
{
return makeEcPair(buffer).has_value();
}
bool
isValidCompressedECPoint(Slice const& buffer)
{
if (buffer.size() != compressedECPointLength)
return false;
// Compressed EC points must start with 0x02 or 0x03
if (buffer[0] != ecCompressedPrefixEvenY && buffer[0] != ecCompressedPrefixOddY)
return false;
secp256k1_pubkey point;
return secp256k1_ec_pubkey_parse(secp256k1Context(), &point, buffer.data(), buffer.size()) == 1;
}
std::optional<Buffer>
homomorphicAdd(Slice const& a, Slice const& b)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return std::nullopt;
auto const pairA = makeEcPair(a);
auto const pairB = makeEcPair(b);
if (!pairA || !pairB)
return std::nullopt;
EcPair sum{};
if (auto res = secp256k1_elgamal_add(
secp256k1Context(), &sum.c1, &sum.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
res != 1)
{
return std::nullopt;
}
return serializeEcPair(sum);
}
std::optional<Buffer>
homomorphicSubtract(Slice const& a, Slice const& b)
{
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
return std::nullopt;
auto const pairA = makeEcPair(a);
auto const pairB = makeEcPair(b);
if (!pairA || !pairB)
return std::nullopt;
EcPair diff{};
if (auto res = secp256k1_elgamal_subtract(
secp256k1Context(), &diff.c1, &diff.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
res != 1)
{
return std::nullopt;
}
return serializeEcPair(diff);
}
Buffer
generateBlindingFactor()
{
unsigned char blindingFactor[ecBlindingFactorLength];
// todo: might need to be updated using another RNG
if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1)
Throw<std::runtime_error>("Failed to generate random number");
return Buffer(blindingFactor, ecBlindingFactorLength);
}
std::optional<Buffer>
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
{
if (blindingFactor.size() != ecBlindingFactorLength || pubKeySlice.size() != ecPubKeyLength)
return std::nullopt;
Buffer out(ecGamalEncryptedTotalLength);
if (mpt_encrypt_amount(amt, pubKeySlice.data(), blindingFactor.data(), out.data()) != 0)
return std::nullopt;
return out;
}
std::optional<Buffer>
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
{
if (pubKeySlice.size() != ecPubKeyLength)
return std::nullopt; // LCOV_EXCL_LINE
EcPair pair{};
secp256k1_pubkey pubKey;
if (auto res = secp256k1_ec_pubkey_parse(
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
res != 1)
{
return std::nullopt; // LCOV_EXCL_LINE
}
if (auto res = generate_canonical_encrypted_zero(
secp256k1Context(), &pair.c1, &pair.c2, &pubKey, account.data(), mptId.data());
res != 1)
{
return std::nullopt; // LCOV_EXCL_LINE
}
return serializeEcPair(pair);
}
TER
verifyRevealedAmount(
uint64_t const amount,
Slice const& blindingFactor,
ConfidentialRecipient const& holder,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor)
{
if (blindingFactor.size() != ecBlindingFactorLength ||
holder.publicKey.size() != ecPubKeyLength ||
holder.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
issuer.publicKey.size() != ecPubKeyLength ||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL; // LCOV_EXCL_LINE
secp256k1_context* ctx = mpt_secp256k1_context();
if (!ctx)
return tecINTERNAL; // LCOV_EXCL_LINE
// All calls are evaluated before combining results to prevent timing attacks.
auto verifyCiphertext = [&](ConfidentialRecipient const& r) -> bool {
secp256k1_pubkey pk, c1, c2;
if (secp256k1_ec_pubkey_parse(ctx, &pk, r.publicKey.data(), r.publicKey.size()) != 1)
return false;
if (!mpt_make_ec_pair(r.encryptedAmount.data(), &c1, &c2))
return false;
return secp256k1_elgamal_verify_encryption(
ctx, &c1, &c2, &pk, amount, blindingFactor.data()) == 1;
};
bool const holderOk = verifyCiphertext(holder);
bool const issuerOk = verifyCiphertext(issuer);
bool valid = holderOk && issuerOk;
if (auditor)
{
if (auditor->publicKey.size() != ecPubKeyLength ||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL; // LCOV_EXCL_LINE
bool const auditorOk = verifyCiphertext(*auditor);
valid = valid && auditorOk;
}
return valid ? TER{tesSUCCESS} : TER{tecBAD_PROOF};
}
NotTEC
checkEncryptedAmountFormat(STObject const& object)
{
// Current usage of this function is only for ConfidentialMPTConvert and
// ConfidentialMPTConvertBack transactions, which already enforce that these fields
// are present.
if (!object.isFieldPresent(sfHolderEncryptedAmount) ||
!object.isFieldPresent(sfIssuerEncryptedAmount))
return temMALFORMED; // LCOV_EXCL_LINE
if (object[sfHolderEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
if (hasAuditor && object[sfAuditorEncryptedAmount].length() != ecGamalEncryptedTotalLength)
return temBAD_CIPHERTEXT;
if (!isValidCiphertext(object[sfHolderEncryptedAmount]) ||
!isValidCiphertext(object[sfIssuerEncryptedAmount]))
return temBAD_CIPHERTEXT;
if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
return temBAD_CIPHERTEXT;
return tesSUCCESS;
}
TER
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
{
if (proofSlice.size() != ecSchnorrProofLength || pubKeySlice.size() != ecPubKeyLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_convert_proof(proofSlice.data(), pubKeySlice.data(), contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyClawbackProof(
uint64_t const amount,
Slice const& proof,
Slice const& pubKeySlice,
Slice const& ciphertext,
uint256 const& contextHash)
{
if (ciphertext.size() != ecGamalEncryptedTotalLength || pubKeySlice.size() != ecPubKeyLength ||
proof.size() != ecCompactClawbackProofLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_clawback_proof(
proof.data(), amount, pubKeySlice.data(), ciphertext.data(), contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifySendProof(
Slice const& proof,
ConfidentialRecipient const& sender,
ConfidentialRecipient const& destination,
ConfidentialRecipient const& issuer,
std::optional<ConfidentialRecipient> const& auditor,
Slice const& spendingBalance,
Slice const& amountCommitment,
Slice const& balanceCommitment,
uint256 const& contextHash)
{
auto const recipientCount = getConfidentialRecipientCount(auditor.has_value());
if (proof.size() != ecCompactSendProofLength || sender.publicKey.size() != ecPubKeyLength ||
sender.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
destination.publicKey.size() != ecPubKeyLength ||
destination.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
issuer.publicKey.size() != ecPubKeyLength ||
issuer.encryptedAmount.size() != ecGamalEncryptedTotalLength ||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
amountCommitment.size() != ecPedersenCommitmentLength ||
balanceCommitment.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
auto makeParticipant = [](ConfidentialRecipient const& r) {
mpt_confidential_participant p;
std::memcpy(p.pubkey, r.publicKey.data(), kMPT_PUBKEY_SIZE);
std::memcpy(p.ciphertext, r.encryptedAmount.data(), kMPT_ELGAMAL_TOTAL_SIZE);
return p;
};
std::vector<mpt_confidential_participant> participants(recipientCount);
participants[0] = makeParticipant(sender);
participants[1] = makeParticipant(destination);
participants[2] = makeParticipant(issuer);
if (auditor)
{
if (auditor->publicKey.size() != ecPubKeyLength ||
auditor->encryptedAmount.size() != ecGamalEncryptedTotalLength)
return tecINTERNAL;
participants[3] = makeParticipant(*auditor);
}
if (mpt_verify_send_proof(
proof.data(),
participants.data(),
static_cast<uint8_t>(recipientCount),
spendingBalance.data(),
amountCommitment.data(),
balanceCommitment.data(),
contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
TER
verifyConvertBackProof(
Slice const& proof,
Slice const& pubKeySlice,
Slice const& spendingBalance,
Slice const& balanceCommitment,
uint64_t amount,
uint256 const& contextHash)
{
if (proof.size() != ecCompactConvertBackProofLength || pubKeySlice.size() != ecPubKeyLength ||
spendingBalance.size() != ecGamalEncryptedTotalLength ||
balanceCommitment.size() != ecPedersenCommitmentLength)
return tecINTERNAL; // LCOV_EXCL_LINE
if (mpt_verify_convert_back_proof(
proof.data(),
pubKeySlice.data(),
spendingBalance.data(),
balanceCommitment.data(),
amount,
contextHash.data()) != 0)
return tecBAD_PROOF;
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -79,6 +79,7 @@ enum class LedgerNameSpace : std::uint16_t {
VAULT = 'V',
LOAN_BROKER = 'l', // lower-case L
LOAN = 'L',
SPONSORSHIP = '>',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -314,6 +315,12 @@ signers(AccountID const& account) noexcept
return signers(account, 0);
}
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept
{
return {ltSPONSORSHIP, indexHash(LedgerNameSpace::SPONSORSHIP, sponsor, sponsee)};
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
{

View File

@@ -159,6 +159,14 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, soeOPTIONAL},
{sfSigners, soeOPTIONAL},
});
add(sfSponsorSignature.jsonName.c_str(),
sfSponsorSignature.getCode(),
{
{sfSigningPubKey, soeOPTIONAL},
{sfTxnSignature, soeOPTIONAL},
{sfSigners, soeOPTIONAL},
});
}
InnerObjectFormats const&

View File

@@ -14,6 +14,7 @@ LedgerFormats::getCommonFields()
{sfLedgerIndex, soeOPTIONAL},
{sfLedgerEntryType, soeREQUIRED},
{sfFlags, soeREQUIRED},
{sfSponsor, soeOPTIONAL},
};
return commonFields;
}

View File

@@ -1,6 +1,8 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
@@ -32,41 +34,70 @@ Permission::Permission()
};
granularPermissionMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define PERMISSION(type, txType, value) {#type, type},
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {#type, type},
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
granularNameMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define PERMISSION(type, txType, value) {type, #type},
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {type, #type},
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
granularTxTypeMap_ = {
#pragma push_macro("PERMISSION")
#undef PERMISSION
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define PERMISSION(type, txType, value) {type, txType},
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) {type, txType},
#include <xrpl/protocol/detail/permissions.macro>
#undef PERMISSION
#pragma pop_macro("PERMISSION")
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
};
{
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
granularPermittedFlags_[type] = (flags);
#include <xrpl/protocol/detail/permissions.macro>
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
}
{
#pragma push_macro("GRANULAR_PERMISSION")
#undef GRANULAR_PERMISSION
#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \
granularTemplates_.emplace( \
std::piecewise_construct, \
std::forward_as_tuple(type), \
std::forward_as_tuple(std::vector<SOElement> fields, TxFormats::getCommonFields()));
#include <xrpl/protocol/detail/permissions.macro>
#undef GRANULAR_PERMISSION
#pragma pop_macro("GRANULAR_PERMISSION")
}
XRPL_ASSERT(
txFeatureMap_.size() == delegableTx_.size(),
"xrpl::Permission : txFeatureMap_ and delegableTx_ must have same "
@@ -79,6 +110,9 @@ Permission::Permission()
"xrpl::Permission::granularPermissionMap_ : granular permission "
"value must not exceed the maximum uint16_t value.");
}
for (auto const& [gp, txType] : granularTxTypeMap_)
granularTxTypes_.insert(txType);
}
Permission const&
@@ -133,6 +167,12 @@ Permission::getGranularTxType(GranularPermissionType const& gpType) const
return std::nullopt;
}
bool
Permission::hasGranularPermissions(TxType txType) const
{
return granularTxTypes_.contains(txType);
}
std::optional<std::reference_wrapper<uint256 const>>
Permission::getTxFeature(TxType txType) const
{
@@ -192,4 +232,48 @@ Permission::permissionToTxType(uint32_t const& value)
return static_cast<TxType>(value - 1);
}
bool
Permission::checkGranularSandbox(
STTx const& tx,
std::unordered_set<GranularPermissionType> const& heldPermissions) const
{
auto const txFlags = tx.getFlags();
// Build union of flags and templates across all held permissions.
std::uint32_t unionFlags = 0;
for (auto const& gp : heldPermissions)
{
auto const it = granularPermittedFlags_.find(gp);
if (it != granularPermittedFlags_.end())
unionFlags |= it->second;
}
// Check if flags are permitted
if ((txFlags & ~unionFlags) != 0)
return false;
// Check if fields are permitted. Every present field must appear in at least one held
// permission's template. The common fields are included in the constructor.
for (auto const& field : tx)
{
if (field.getSType() == STI_NOTPRESENT)
continue;
bool fieldAllowed = false;
for (auto const& gp : heldPermissions)
{
auto const it = granularTemplates_.find(gp);
if (it != granularTemplates_.end() && it->second.getIndex(field.getFName()) != -1)
{
fieldAllowed = true;
break;
}
}
if (!fieldAllowed)
return false;
}
return true;
}
} // namespace xrpl

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/contract.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/detail/secp256k1.h>
@@ -208,7 +209,7 @@ publicKeyType(Slice const& slice)
if (slice[0] == 0xED)
return KeyType::ed25519;
if (slice[0] == 0x02 || slice[0] == 0x03)
if (slice[0] == ecCompressedPrefixEvenY || slice[0] == ecCompressedPrefixOddY)
return KeyType::secp256k1;
}

View File

@@ -73,6 +73,7 @@ STTx::STTx(STObject&& object) : STObject(std::move(object))
tx_type_ = safe_cast<TxType>(getFieldU16(sfTransactionType));
applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // may throw
tid_ = getHash(HashPrefix::transactionID);
buildBatchTxnIds();
}
STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
@@ -89,6 +90,7 @@ STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // May throw
tid_ = getHash(HashPrefix::transactionID);
buildBatchTxnIds();
}
STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfTransaction)
@@ -106,6 +108,7 @@ STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfT
LogicError("Transaction type was mutated during assembly");
tid_ = getHash(HashPrefix::transactionID);
buildBatchTxnIds();
}
STBase*
@@ -280,6 +283,14 @@ STTx::checkSign(Rules const& rules) const
if (auto const ret = checkSign(rules, counterSig); !ret)
return Unexpected("Counterparty: " + ret.error());
}
if (isFieldPresent(sfSponsorSignature))
{
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
return Unexpected("Sponsor: " + ret.error());
}
return {};
}
@@ -294,6 +305,8 @@ STTx::checkBatchSign(Rules const& rules) const
JLOG(debugLog().fatal()) << "not a batch transaction";
return Unexpected("Not a batch transaction.");
}
if (!isFieldPresent(sfBatchSigners))
return Unexpected("Missing BatchSigners field.");
STArray const& signers{getFieldArray(sfBatchSigners)};
for (auto const& signer : signers)
{
@@ -308,9 +321,8 @@ STTx::checkBatchSign(Rules const& rules) const
}
catch (std::exception const& e)
{
JLOG(debugLog().error()) << "Batch signature check failed: " << e.what();
return Unexpected(std::string("Internal batch signature check failure: ") + e.what());
}
return Unexpected("Internal batch signature check failure.");
}
Json::Value
@@ -432,6 +444,7 @@ STTx::checkBatchSingleSign(STObject const& batchSigner) const
{
Serializer msg;
serializeBatch(msg, getFlags(), getBatchTransactionIDs());
finishMultiSigningData(batchSigner.getAccountID(sfAccount), msg);
return singleSignHelper(batchSigner, msg.slice());
}
@@ -505,7 +518,7 @@ multiSignHelper(
{
return Unexpected(
std::string("Invalid signature on account ") + toBase58(accountID) +
errorWhat.value_or("") + ".");
(errorWhat ? ": " + *errorWhat : "") + ".");
}
}
// All signatures verified.
@@ -554,41 +567,24 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
rules);
}
/**
* @brief Retrieves a batch of transaction IDs from the STTx.
*
* This function returns a vector of transaction IDs by extracting them from
* the field array `sfRawTransactions` within the STTx. If the batch
* transaction IDs have already been computed and cached in `batchTxnIds_`,
* it returns the cached vector. Otherwise, it computes the transaction IDs,
* caches them, and then returns the vector.
*
* @return A vector of `uint256` containing the batch transaction IDs.
*
* @note The function asserts that the `sfRawTransactions` field array is not
* empty and that the size of the computed batch transaction IDs matches the
* size of the `sfRawTransactions` field array.
*/
void
STTx::buildBatchTxnIds()
{
if (tx_type_ != ttBATCH || !isFieldPresent(sfRawTransactions))
return;
auto const& raw = getFieldArray(sfRawTransactions);
batchTxnIds_.reserve(raw.size());
for (STObject const& rb : raw)
batchTxnIds_.push_back(rb.getHash(HashPrefix::transactionID));
}
std::vector<uint256> const&
STTx::getBatchTransactionIDs() const
{
XRPL_ASSERT(getTxnType() == ttBATCH, "STTx::getBatchTransactionIDs : not a batch transaction");
XRPL_ASSERT(
!getFieldArray(sfRawTransactions).empty(),
"STTx::getBatchTransactionIDs : empty raw transactions");
// The list of inner ids is built once, then reused on subsequent calls.
// After the list is built, it must always have the same size as the array
// `sfRawTransactions`. The assert below verifies that.
if (batchTxnIds_.empty())
{
for (STObject const& rb : getFieldArray(sfRawTransactions))
batchTxnIds_.push_back(rb.getHash(HashPrefix::transactionID));
}
XRPL_ASSERT(
batchTxnIds_.size() == getFieldArray(sfRawTransactions).size(),
"STTx::getBatchTransactionIDs : batch transaction IDs size mismatch");
!batchTxnIds_.empty(), "STTx::getBatchTransactionIDs : batch transaction IDs not built");
return batchTxnIds_;
}
@@ -756,6 +752,9 @@ isRawTransactionOkay(STObject const& st, std::string& reason)
}
raw.applyTemplate(getTxFormat(tt)->getSOTemplate());
if (!passesLocalChecks(raw, reason))
return false;
}
catch (std::exception const& e)
{

View File

@@ -106,6 +106,8 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -199,6 +201,7 @@ transResults()
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
MAKE_ERROR(terRETRY, "Retry transaction."),
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
@@ -216,6 +219,7 @@ transResults()
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(terLOCKED, "Fund is locked."),
MAKE_ERROR(terNO_SPONSORSHIP, "No sponsorship found."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};

View File

@@ -28,6 +28,9 @@ TxFormats::getCommonFields()
{sfSigners, soeOPTIONAL}, // submit_multisigned
{sfNetworkID, soeOPTIONAL},
{sfDelegate, soeOPTIONAL},
{sfSponsor, soeOPTIONAL},
{sfSponsorFlags, soeOPTIONAL},
{sfSponsorSignature, soeOPTIONAL},
};
return commonFields;
}

View File

@@ -11,6 +11,7 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UintTypes.h>
@@ -147,6 +148,49 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
return temBAD_SIGNER;
}
bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
return temDISABLED;
if (hasSponsorFlags &&
ctx.tx.getFieldU32(sfSponsorFlags) & ~(spfSponsorFee | spfSponsorReserve))
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
if (!hasSponsor)
{
if (hasSponsorFlags)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor flags without sponsor definition";
return temINVALID_FLAG;
}
if (hasSponsorSig)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
return temMALFORMED;
}
}
else if (hasSponsorFlags)
{
auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
if ((sponsorFlags & ~(spfSponsorFee | spfSponsorReserve)) || sponsorFlags == 0)
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
}
else
{
JLOG(ctx.j.debug()) << "preflight1: no sponsor flags";
return temINVALID_FLAG;
}
if (auto const ret = preflight0(ctx, flagMask))
return ret;
@@ -177,13 +221,23 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
if (ctx.tx.getSeqProxy().isTicket() && ctx.tx.isFieldPresent(sfAccountTxnID))
return temINVALID;
if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatch))
if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatchV1_1))
return temINVALID_FLAG;
XRPL_ASSERT(
ctx.tx.isFlag(tfInnerBatchTxn) == ctx.parentBatchId.has_value() ||
!ctx.rules.enabled(featureBatch),
"Inner batch transaction must have a parent batch ID.");
// Reject if the inner batch flag and parentBatchId are inconsistent.
// A standalone tx with tfInnerBatchTxn but no parentBatchId is an
// attack attempt. A tx with parentBatchId but without tfInnerBatchTxn
// is a programming error.
if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value())
return temINVALID_INNER_BATCH;
// Sponsor checks
if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == id)
{
JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the "
"same as the transaction originator";
return temMALFORMED;
}
return tesSUCCESS;
}
@@ -199,15 +253,18 @@ Transactor::preflight2(PreflightContext const& ctx)
return *ret;
}
// It should be impossible for the InnerBatchTxn flag to be set without
// featureBatch being enabled
XRPL_ASSERT_PARTS(
!ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatch),
"xrpl::Transactor::preflight2",
"InnerBatch flag only set if feature enabled");
// Skip signature check on batch inner transactions
if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch))
// Skip signature check on batch inner transactions, but only if
// this tx was actually submitted through the batch apply path
// (parentBatchId is set). Without this check, a standalone tx
// with tfInnerBatchTxn could bypass signature verification.
if (ctx.tx.isFlag(tfInnerBatchTxn))
{
if (!ctx.rules.enabled(featureBatchV1_1))
return temINVALID_FLAG;
if (!ctx.parentBatchId.has_value())
return temINVALID_INNER_BATCH;
return tesSUCCESS;
}
// Do not add any checks after this point that are relevant for
// batch inner transactions. They will be skipped.
@@ -256,19 +313,62 @@ Transactor::preflightSigValidated(PreflightContext const& ctx)
}
NotTEC
Transactor::checkPermission(ReadView const& view, STTx const& tx)
Transactor::checkPermissionImpl(
ReadView const& view,
STTx const& tx,
std::unordered_set<GranularPermissionType>& heldGranularPermissions)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
if (!sle)
return terNO_DELEGATE_PERMISSION;
return checkTxPermission(sle, tx);
if (isTesSuccess(checkTxPermission(sle, tx)))
return tesSUCCESS;
if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
return terNO_DELEGATE_PERMISSION;
heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
if (heldGranularPermissions.empty())
return terNO_DELEGATE_PERMISSION;
if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
return terNO_DELEGATE_PERMISSION;
return tesSUCCESS;
}
NotTEC
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsor))
return tesSUCCESS;
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
if (hasSponsorSignature)
return tesSUCCESS;
auto const sponsorSle =
view.read(keylet::sponsor(tx.getAccountID(sfSponsor), tx.getAccountID(sfAccount)));
// sponsorship object missing for pre-funded tx
if (!sponsorSle)
return terNO_SPONSORSHIP;
auto const sponsorFlags = tx.getFieldU32(sfSponsorFlags);
if (sponsorFlags & spfSponsorFee && sponsorSle->isFlag(lsfSponsorshipRequireSignForFee))
return terNO_SPONSORSHIP;
if (sponsorFlags & spfSponsorReserve && sponsorSle->isFlag(lsfSponsorshipRequireSignForReserve))
return terNO_SPONSORSHIP;
return tesSUCCESS;
}
XRPAmount
@@ -279,6 +379,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// The computation has two parts:
// * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction.
// * The additional cost of each multisignature on the sponsor.
XRPAmount const baseFee = view.fees().base;
// Each signer adds one more baseFee to the minimum required fee
@@ -286,7 +387,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
std::size_t const signerCount =
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
return baseFee + (signerCount * baseFee);
std::size_t sponsorSignerCount = 0;
if (tx.isFieldPresent(sfSponsorSignature))
{
auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
sponsorSignerCount +=
sponsorObj.isFieldPresent(sfSigners) ? sponsorObj.getFieldArray(sfSigners).size() : 0;
}
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
}
// Returns the fee in fee units, not scaled for load.
@@ -356,12 +465,41 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
if (feePaid == beast::zero)
return tesSUCCESS;
auto const id = ctx.tx.getFeePayer();
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
auto const payer = getFeePayer(ctx.view, ctx.tx);
auto const payerSle = ctx.view.read(payer.entry);
auto const balance = (*sle)[sfBalance].xrp();
if (!payerSle)
{
if (payer.type == FeePayerType::SponsorPreFunded)
// Sanity check: already checked in checkSponsor
return tefINTERNAL; // LCOV_EXCL_LINE
return terNO_ACCOUNT;
}
XRPAmount maxSpendable = beast::zero;
if (payer.type == FeePayerType::SponsorPreFunded)
{
if (payerSle->getType() != ltSPONSORSHIP)
return tefINTERNAL; // LCOV_EXCL_LINE
if (payerSle->isFieldPresent(payer.balanceField))
maxSpendable = payerSle->getFieldAmount(payer.balanceField).xrp();
if (payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
maxSpendable = std::min(maxSpendable, cap);
}
}
else
{
if (payerSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
maxSpendable = payerSle->getFieldAmount(payer.balanceField).xrp();
}
// NOTE: Because preclaim evaluates against a static readview, it
// does not reflect fee deductions from other transactions paid by
@@ -370,12 +508,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
// transactions, this check may pass optimistically.
// The fee shortfall will be handled by the Transactor::reset mechanism,
// which caps the fee to the remaining actual balance.
if (balance < feePaid)
if (maxSpendable < feePaid)
{
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
<< " paid=" << to_string(feePaid);
if ((balance > beast::zero) && !ctx.view.open())
if ((maxSpendable > beast::zero) && !ctx.view.open())
{
// Closed ledger, non-zero balance, less than fee
return tecINSUFF_FEE;
@@ -392,16 +530,23 @@ Transactor::payFee()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const feePayer = ctx_.tx.getFeePayer();
auto const sle = view().peek(keylet::account(feePayer));
auto const payer = getFeePayer(view(), ctx_.tx);
auto const sle = view().peek(payer.entry);
JLOG(j_.trace()) << "Fee payer: " + to_string(payer.entry.key);
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back if the transaction succeeds.
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
if (feePayer != account_)
view().update(sle); // done in `apply()` for the account
auto const feeAmountAfter = sle->getFieldAmount(payer.balanceField) - feePaid;
if (feeAmountAfter == beast::zero && payer.balanceField == sfFeeAmount)
// Because ltSponsorship.sfFeeAmount is soeOptional
sle->makeFieldAbsent(payer.balanceField);
else
sle->setFieldAmount(payer.balanceField, feeAmountAfter);
view().update(sle);
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
@@ -575,7 +720,8 @@ Transactor::ticketDelete(
}
// Update the Ticket owner's reserve.
adjustOwnerCount(view, sleAccount, -1, j);
auto const sponsor = getLedgerEntryReserveSponsor(view, sleTicket);
adjustOwnerCount(view, sleAccount, sponsor, -1, j);
// Remove Ticket from ledger.
view.erase(sleTicket);
@@ -648,7 +794,7 @@ Transactor::checkSign(
auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey);
// Ignore signature check on batch inner transactions
if (parentBatchId && view.rules().enabled(featureBatch))
if (parentBatchId && view.rules().enabled(featureBatchV1_1))
{
// Defensive Check: These values are also checked in Batch::preflight
if (sigObject.isFieldPresent(sfTxnSignature) || !pkSigner.empty() ||
@@ -666,6 +812,22 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (sigObject.isFieldPresent(sfSponsorSignature))
{
// Co-signed sponsorship
// Sanity check: already checked in preflight1
if (!sigObject.isFieldPresent(sfSponsor))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorAccountID = sigObject.getAccountID(sfSponsor);
auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
if (auto const ret =
checkSign(view, flags, std::nullopt, sponsorAccountID, sponsorSignature, j);
!isTesSuccess(ret))
return ret;
}
// If the pk is empty and not simulate or simulate and signers,
// then we must be multi-signing.
if (sigObject.isFieldPresent(sfSigners))
@@ -699,50 +861,6 @@ Transactor::checkSign(PreclaimContext const& ctx)
return checkSign(ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
}
NotTEC
Transactor::checkBatchSign(PreclaimContext const& ctx)
{
NotTEC ret = tesSUCCESS;
STArray const& signers{ctx.tx.getFieldArray(sfBatchSigners)};
for (auto const& signer : signers)
{
auto const idAccount = signer.getAccountID(sfAccount);
Blob const& pkSigner = signer.getFieldVL(sfSigningPubKey);
if (pkSigner.empty())
{
if (ret = checkMultiSign(ctx.view, ctx.flags, idAccount, signer, ctx.j);
!isTesSuccess(ret))
return ret;
}
else
{
// LCOV_EXCL_START
if (!publicKeyType(makeSlice(pkSigner)))
return tefBAD_AUTH;
// LCOV_EXCL_STOP
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
// A batch can include transactions from an un-created account ONLY
// when the account master key is the signer
if (!sleAccount)
{
if (idAccount != idSigner)
return tefBAD_AUTH;
return tesSUCCESS;
}
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
!isTesSuccess(ret))
return ret;
}
}
return ret;
}
NotTEC
Transactor::checkSingleSign(
ReadView const& view,
@@ -1043,15 +1161,17 @@ Transactor::reset(XRPAmount fee)
if (!txnAcct)
return {tefINTERNAL, beast::zero};
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
auto const payer = getFeePayer(view(), ctx_.tx);
auto const payerSle = view().peek(payer.entry);
if (!payerSle)
return {tefINTERNAL, beast::zero}; // LCOV_EXCL_LINE
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
auto const balance = payerSle->getFieldAmount(payer.balanceField).xrp();
// balance should have already been checked in checkFee / preFlight.
XRPL_ASSERT(
balance != beast::zero && (!view().open() || balance >= fee),
(fee == beast::zero || balance != beast::zero) && (!view().open() || balance >= fee),
"xrpl::Transactor::reset : valid balance");
// We retry/reject the transaction if the account balance is zero or
@@ -1066,7 +1186,13 @@ Transactor::reset(XRPAmount fee)
// If for some reason we are unable to consume the ticket or sequence
// then the ledger is corrupted. Rather than make things worse we
// reject the transaction.
payerSle->setFieldAmount(sfBalance, balance - fee);
auto const feeAmountAfter = balance - fee;
if (feeAmountAfter == beast::zero && payer.balanceField == sfFeeAmount)
// Because ltSponsorship.sfFeeAmount is soeOptional
payerSle->makeFieldAbsent(payer.balanceField);
else
payerSle->setFieldAmount(payer.balanceField, feeAmountAfter);
TER const ter{consumeSeqProxy(txnAcct)};
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
@@ -1080,6 +1206,33 @@ Transactor::reset(XRPAmount fee)
return {ter, fee};
}
FeePayer
Transactor::getFeePayer(ReadView const& view, STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && (tx.getFieldU32(sfSponsorFlags) & spfSponsorFee))
{
auto const sponsorAccountID = tx.getAccountID(sfSponsor);
auto const sponseeAccountID = tx.getAccountID(sfAccount);
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
auto const sponsorshipKeylet = keylet::sponsor(sponsorAccountID, sponseeAccountID);
// if pre-funded sponsorship exists, prefer it
if (hasSponsorSignature && !view.exists(sponsorshipKeylet))
// co-signed
return FeePayer{
keylet::account(sponsorAccountID), sfBalance, FeePayerType::SponsorCoSigned};
// pre funded
return FeePayer{sponsorshipKeylet, sfFeeAmount, FeePayerType::SponsorPreFunded};
}
auto const payerAccountKeylet = keylet::account(tx.getFeePayer());
auto const payerType =
tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account;
return FeePayer{payerAccountKeylet, sfBalance, payerType};
}
// The sole purpose of this function is to provide a convenient, named
// location to set a breakpoint, to be used when replaying transactions.
void

View File

@@ -24,29 +24,12 @@ checkValidity(HashRouter& router, STTx const& tx, Rules const& rules)
auto const flags = router.getFlags(id);
// Ignore signature check on batch inner transactions
if (tx.isFlag(tfInnerBatchTxn) && rules.enabled(featureBatch))
if (tx.isFlag(tfInnerBatchTxn) && rules.enabled(featureBatchV1_1))
{
// Defensive Check: These values are also checked in Batch::preflight
if (tx.isFieldPresent(sfTxnSignature) || !tx.getSigningPubKey().empty() ||
tx.isFieldPresent(sfSigners))
return {Validity::SigBad, "Malformed: Invalid inner batch transaction."};
// This block should probably have never been included in the
// original `Batch` implementation. An inner transaction never
// has a valid signature.
bool const neverValid = rules.enabled(fixBatchInnerSigs);
if (!neverValid)
{
std::string reason;
if (!passesLocalChecks(tx, reason))
{
router.setFlags(id, SF_LOCALBAD);
return {Validity::SigGoodOnly, reason};
}
router.setFlags(id, SF_SIGGOOD);
return {Validity::Valid, ""};
}
}
if (any(flags & SF_SIGBAD))

View File

@@ -174,7 +174,10 @@ invoke_preclaim(PreclaimContext const& ctx)
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx))
return result;
if (NotTEC const result = Transactor::checkPermission<T>(ctx.view, ctx.tx))
return result;
if (NotTEC const result = T::checkSign(ctx))

View File

@@ -117,6 +117,10 @@ XRPNotCreated::visitEntry(
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (before->isFieldPresent(sfFeeAmount))
drops_ -= (*before)[sfFeeAmount].xrp().drops();
break;
default:
break;
}
@@ -137,6 +141,10 @@ XRPNotCreated::visitEntry(
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += (*after)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (!isDelete && after->isFieldPresent(sfFeeAmount))
drops_ += (*after)[sfFeeAmount].xrp().drops();
break;
default:
break;
}
@@ -495,6 +503,20 @@ AccountRootsDeletedClean::finalize(
if (enforce)
return false;
}
// An account should not be deleted with sponsorship fields
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) ||
after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor))
{
JLOG(j.fatal()) << "Invariant failed: account deletion left "
"behind a sponsorship field";
XRPL_ASSERT(
enforce,
"xrpl::AccountRootsDeletedClean::finalize : "
"deleted account has no sponsorship fields");
if (enforce)
return false;
}
// Simple types
for (auto const& [keyletfunc, _1, _2] : directAccountKeylets)
{
@@ -853,8 +875,10 @@ ValidPseudoAccounts::visitEntry(
// 1. Exactly one of the pseudo-account fields is set.
// 2. The sequence number is not changed.
// 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
// flags are set.
// flags are set.
// 4. The RegularKey is not set.
// 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor
// fields are not set.
{
std::vector<SField const*> const& fields = getPseudoAccountFields();
@@ -881,6 +905,12 @@ ValidPseudoAccounts::visitEntry(
{
errors_.emplace_back("pseudo-account has a regular key");
}
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) ||
after->isFieldPresent(sfSponsoringAccountCount))
{
errors_.emplace_back("pseudo-account has a sponsorship field");
}
}
}
}

View File

@@ -9,8 +9,19 @@
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <algorithm>
#include <array>
namespace xrpl {
static constexpr auto confidentialMPTTxTypes = std::to_array<TxType>({
ttCONFIDENTIAL_MPT_SEND,
ttCONFIDENTIAL_MPT_CONVERT,
ttCONFIDENTIAL_MPT_CONVERT_BACK,
ttCONFIDENTIAL_MPT_MERGE_INBOX,
ttCONFIDENTIAL_MPT_CLAWBACK,
});
void
ValidMPTIssuance::visitEntry(
bool isDelete,
@@ -341,6 +352,14 @@ ValidMPTPayment::finalize(
{
if (isTesSuccess(result))
{
// Confidential transactions are validated by ValidConfidentialMPToken.
// They modify encrypted fields and sfConfidentialOutstandingAmount
// rather than sfMPTAmount/sfOutstandingAmount in the standard way,
// so ValidMPTPayment's accounting does not apply to them.
if (std::ranges::find(confidentialMPTTxTypes, tx.getTxnType()) !=
confidentialMPTTxTypes.end())
return true;
bool const enforce = view.rules().enabled(featureMPTokensV2);
if (overflow_)
{
@@ -369,4 +388,324 @@ ValidMPTPayment::finalize(
return true;
}
void
ValidConfidentialMPToken::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
// Helper to get MPToken Issuance ID safely
auto const getMptID = [](std::shared_ptr<SLE const> const& sle) -> uint192 {
if (!sle)
return beast::zero;
if (sle->getType() == ltMPTOKEN)
return sle->getFieldH192(sfMPTokenIssuanceID);
if (sle->getType() == ltMPTOKEN_ISSUANCE)
return makeMptID(sle->getFieldU32(sfSequence), sle->getAccountID(sfIssuer));
return beast::zero;
};
if (before && before->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(before);
changes_[id].mptAmountDelta -= before->getFieldU64(sfMPTAmount);
// Cannot delete MPToken with non-zero confidential state or non-zero public amount
if (isDelete)
{
bool const hasPublicBalance = before->getFieldU64(sfMPTAmount) > 0;
bool const hasEncryptedFields = before->isFieldPresent(sfConfidentialBalanceSpending) ||
before->isFieldPresent(sfConfidentialBalanceInbox) ||
before->isFieldPresent(sfIssuerEncryptedBalance);
if (hasPublicBalance || hasEncryptedFields)
changes_[id].deletedWithEncrypted = true;
}
}
if (after && after->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(after);
changes_[id].mptAmountDelta += after->getFieldU64(sfMPTAmount);
// Encrypted field existence consistency
bool const hasIssuerBalance = after->isFieldPresent(sfIssuerEncryptedBalance);
bool const hasHolderInbox = after->isFieldPresent(sfConfidentialBalanceInbox);
bool const hasHolderSpending = after->isFieldPresent(sfConfidentialBalanceSpending);
bool const hasAnyHolder = hasHolderInbox || hasHolderSpending;
if (hasAnyHolder != hasIssuerBalance)
{
changes_[id].badConsistency = true;
}
// Privacy flag consistency
bool const hasEncrypted = hasAnyHolder || hasIssuerBalance;
if (hasEncrypted)
changes_[id].requiresPrivacyFlag = true;
}
if (before && before->getType() == ltMPTOKEN_ISSUANCE)
{
uint192 const id = getMptID(before);
if (before->isFieldPresent(sfConfidentialOutstandingAmount))
changes_[id].coaDelta -= before->getFieldU64(sfConfidentialOutstandingAmount);
changes_[id].outstandingDelta -= before->getFieldU64(sfOutstandingAmount);
}
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
{
uint192 const id = getMptID(after);
auto& change = changes_[id];
bool const hasCOA = after->isFieldPresent(sfConfidentialOutstandingAmount);
std::uint64_t const coa = (*after)[~sfConfidentialOutstandingAmount].value_or(0);
std::uint64_t const oa = after->getFieldU64(sfOutstandingAmount);
if (hasCOA)
change.coaDelta += coa;
change.outstandingDelta += oa;
change.issuance = after;
// COA <= OutstandingAmount
if (coa > oa)
change.badCOA = true;
}
if (before && after && before->getType() == ltMPTOKEN && after->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(after);
// sfConfidentialBalanceVersion must change when spending changes
auto const spendingBefore = (*before)[~sfConfidentialBalanceSpending];
auto const spendingAfter = (*after)[~sfConfidentialBalanceSpending];
auto const versionBefore = (*before)[~sfConfidentialBalanceVersion];
auto const versionAfter = (*after)[~sfConfidentialBalanceVersion];
if (spendingBefore.has_value() && spendingBefore != spendingAfter)
{
if (versionBefore == versionAfter)
{
changes_[id].badVersion = true;
}
}
}
}
bool
ValidConfidentialMPToken::finalize(
STTx const& tx,
TER const result,
XRPAmount const,
ReadView const& view,
beast::Journal const& j)
{
if (result != tesSUCCESS)
return true;
for (auto const& [id, checks] : changes_)
{
// Find the MPTokenIssuance
auto const issuance = [&]() -> std::shared_ptr<SLE const> {
if (checks.issuance)
return checks.issuance;
return view.read(keylet::mptIssuance(id));
}();
// Skip all invariance checks if issuance doesn't exist because that means the MPT has been
// deleted
if (!issuance)
continue;
// Cannot delete MPToken with non-zero confidential state
if (checks.deletedWithEncrypted)
{
if ((*issuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
{
JLOG(j.fatal())
<< "Invariant failed: MPToken deleted with encrypted fields while COA > 0";
return false;
}
}
// Encrypted field existence consistency
if (checks.badConsistency)
{
JLOG(j.fatal()) << "Invariant failed: MPToken encrypted field "
"existence inconsistency";
return false;
}
// COA <= OutstandingAmount
if (checks.badCOA)
{
JLOG(j.fatal()) << "Invariant failed: Confidential outstanding amount "
"exceeds total outstanding amount";
return false;
}
// Privacy flag consistency
if (checks.requiresPrivacyFlag)
{
if (!issuance->isFlag(lsfMPTCanConfidentialAmount))
{
JLOG(j.fatal()) << "Invariant failed: MPToken has encrypted "
"fields but Issuance does not have "
"lsfMPTCanConfidentialAmount set";
return false;
}
}
// We only enforce this when Confidential Outstanding Amount changes (Convert, ConvertBack,
// ConfidentialClawback). This avoids falsely failing on Escrow or AMM operations that lock
// public tokens outside of ltMPTOKEN. Convert / ConvertBack:
// - COA and MPTAmount must have opposite deltas, which cancel each other out to zero.
// - OA remains unchanged.
// - Therefore, the net delta on both sides of the equation is zero.
//
// Clawback:
// - MPTAmount remains unchanged.
// - COA and OA must have identical deltas (mirrored on each side).
// - The equation remains balanced as both sides have equal offsets.
if (checks.coaDelta != 0)
{
if (checks.mptAmountDelta + checks.coaDelta != checks.outstandingDelta)
{
JLOG(j.fatal()) << "Invariant failed: Token conservation "
"violation for MPT "
<< to_string(id);
return false;
}
}
if (checks.badVersion)
{
JLOG(j.fatal())
<< "Invariant failed: MPToken sfConfidentialBalanceVersion not updated when "
"sfConfidentialBalanceSpending changed";
return false;
}
}
return true;
}
void
ValidMPTTransfer::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
// Record the before/after MPTAmount for each (issuanceID, account) pair
// so finalize() can determine whether a transfer actually occurred.
auto update = [&](SLE const& sle, bool isBefore) {
if (sle.getType() == ltMPTOKEN)
{
auto const issuanceID = sle[sfMPTokenIssuanceID];
auto const account = sle[sfAccount];
auto const amount = sle[sfMPTAmount];
if (isBefore)
{
amount_[issuanceID][account].amtBefore = amount;
}
else
{
amount_[issuanceID][account].amtAfter = amount;
}
}
};
if (before)
update(*before, true);
if (after)
update(*after, false);
}
bool
ValidMPTTransfer::finalize(
STTx const& tx,
TER const,
XRPAmount const,
ReadView const& view,
beast::Journal const& j)
{
// AMMClawback is called by the issuer, so freeze restrictions do not apply.
auto const txnType = tx.getTxnType();
if (txnType == ttAMM_CLAWBACK)
return true;
// DEX transactions (AMM[Create,Deposit,Withdraw], cross-currency payments, offer creates) are
// subject to the MPTCanTrade flag in addition to the standard transfer rules.
// A payment is only DEX if it is a cross-currency payment.
auto const isDEX = [&tx, &txnType] {
if (txnType == ttPAYMENT)
{
// A payment is cross-currency (and thus DEX) only if SendMax is present
// and its asset differs from the destination asset.
auto const amount = tx[sfAmount];
return tx[~sfSendMax].value_or(amount).asset() != amount.asset();
}
return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttAMM_WITHDRAW ||
txnType == ttOFFER_CREATE;
}();
// Only enforce once MPTokensV2 is enabled to preserve consensus with non-V2 nodes.
// Log invariant failure error even if MPTokensV2 is disabled.
auto const enforce = !view.rules().enabled(featureMPTokensV2);
for (auto const& [mptID, values] : amount_)
{
std::uint16_t senders = 0;
std::uint16_t receivers = 0;
bool frozen = false;
auto const sleIssuance = view.read(keylet::mptIssuance(mptID));
if (!sleIssuance)
{
continue;
}
auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer);
auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade);
for (auto const& [account, value] : values)
{
// Classify each account as a sender or receiver based on whether their MPTAmount
// decreased or increased. Count new MPToken holders (no amtBefore) as receivers.
// Skip deleted MPToken holders (amtAfter is nullopt); deletion requires zero balance.
if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
{
if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore)
{
++receivers;
}
else
{
++senders;
}
// Check once: if any involved account is frozen, the whole
// issuance transfer is considered frozen. Only need to check for
// frozen if there is a transfer of funds.
if (!frozen && isFrozen(view, account, MPTIssue{mptID}))
{
frozen = true;
}
}
}
// A transfer between holders has occurred (senders > 0 && receivers > 0).
// Fail if the issuance is frozen, does not permit transfers, or — for
// DEX transactions — does not permit trading.
if ((frozen || !canTransfer || (isDEX && !canTrade)) && senders > 0 && receivers > 0)
{
JLOG(j.fatal()) << "Invariant failed: invalid MPToken transfer between holders";
return enforce;
}
}
return true;
}
} // namespace xrpl

View File

@@ -138,6 +138,18 @@ ValidPermissionedDomain::finalize(
}
return true;
}
case ttSPONSORSHIP_TRANSFER: {
if (sleStatus_.empty())
return true;
if (sleStatus_[0].isDelete_)
{
JLOG(j.fatal()) << "Invariant failed: domain object "
"deleted by SponsorshipTransfer";
return false;
}
return true;
}
default: {
if (!sleStatus_.empty())
{

View File

@@ -0,0 +1,116 @@
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
//
#include <xrpl/basics/Log.h>
namespace xrpl {
// Add new sponsorship-related invariants implementations
void
SponsorshipOwnerCountsMatch::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsored = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
return 0;
};
auto getSponsoring = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringOwnerCount);
return 0;
};
auto getOwnerCount = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfOwnerCount);
return 0;
};
auto getSponsoredOwnerCount = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
return 0;
};
std::int64_t const beforeSponsored = getSponsored(before);
std::int64_t const afterSponsored = getSponsored(after);
std::int64_t const beforeSponsoring = getSponsoring(before);
std::int64_t const afterSponsoring = getSponsoring(after);
deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored);
deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring);
if (getOwnerCount(after) < getSponsoredOwnerCount(after))
invalidOwnerCountLessThanSponsoredOwnerCount_ += 1;
}
bool
SponsorshipOwnerCountsMatch::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j)
{
if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not "
"equal SponsoringOwnerCount delta.";
return false;
}
if (invalidOwnerCountLessThanSponsoredOwnerCount_ > 0)
{
JLOG(j.fatal())
<< "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount.";
return false;
}
return true;
}
void
SponsorshipAccountCountMatchesField::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsoringAccountCount = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringAccountCount);
return 0;
};
auto hasSponsorField = [](std::shared_ptr<SLE const> const& sle) -> bool {
return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor);
};
std::int64_t const beforeCount = getSponsoringAccountCount(before);
std::int64_t const afterCount = getSponsoringAccountCount(after);
deltaSponsoringAccountCount_ += (afterCount - beforeCount);
int const beforePresent = hasSponsorField(before) ? 1 : 0;
int const afterPresent = hasSponsorField(after) ? 1 : 0;
deltaSponsorFieldPresence_ += (afterPresent - beforePresent);
}
bool
SponsorshipAccountCountMatchesField::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j)
{
if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_)
{
JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not "
"match net delta of sfSponsor presence.";
return false;
}
return true;
}
} // namespace xrpl

View File

@@ -698,7 +698,8 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
// Create MPToken for the offer's owner. No need to check
// for the reserve since the offer is removed if it is consumed.
// Therefore, the owner count remains the same.
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
if (auto const err =
checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, std::nullopt, j_);
!isTesSuccess(err))
{
return true;

View File

@@ -390,7 +390,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio
// for the reserve since the offer doesn't go on the books
// if crossed. Insufficient reserve is allowed if the offer
// crossed. See CreateOffer::applyGuts() for reserve check.
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, std::nullopt, j_);
!isTesSuccess(err))
{
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
resetCache(srcDebtDir);

View File

@@ -0,0 +1,298 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
namespace xrpl {
std::uint32_t
SponsorshipSet::getFlagsMask(PreflightContext const& ctx)
{
return tfSponsorshipSetMask;
}
NotTEC
SponsorshipSet::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
if ((flags & tfSponsorshipSetRequireSignForFee) &&
(flags & tfSponsorshipClearRequireSignForFee))
return temINVALID_FLAG;
if ((flags & tfSponsorshipSetRequireSignForReserve) &&
(flags & tfSponsorshipClearRequireSignForReserve))
return temINVALID_FLAG;
auto const account = ctx.tx.getAccountID(sfAccount);
bool const hasSponsor = ctx.tx.isFieldPresent(sfCounterpartySponsor);
bool const hasSponsee = ctx.tx.isFieldPresent(sfSponsee);
// The transaction must specify either Sponsor or Sponsee, but not both.
if (hasSponsor == hasSponsee)
return temMALFORMED;
auto const sponsorAccountID = ctx.tx[~sfCounterpartySponsor].value_or(account);
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(account);
if (sponsorAccountID == sponseeAccountID)
return temMALFORMED;
if ((flags & tfDeleteObject) != 0u)
{
// can not combine with any modification flags when deleting
constexpr std::uint32_t modifyFlags = tfSponsorshipSetRequireSignForFee |
tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForFee |
tfSponsorshipClearRequireSignForReserve;
if (flags & modifyFlags)
return temINVALID_FLAG;
// can not include these fields when deleting
if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfReserveCount) ||
ctx.tx.isFieldPresent(sfMaxFee))
return temMALFORMED;
}
else
{
// although both Sponsor and Sponsee can delete,
// only the Sponsor can create or update sponsorship.
if (account != sponsorAccountID)
return temMALFORMED;
// Check FeeAmount and MaxFee
auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC {
if (!ctx.tx.isFieldPresent(field))
return tesSUCCESS;
auto const amount = ctx.tx.getFieldAmount(field);
if (!isXRP(amount))
return temBAD_AMOUNT;
if (amount.xrp() < XRPAmount{0})
return temBAD_AMOUNT;
return tesSUCCESS;
};
if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret))
return ret;
if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret))
return ret;
}
return tesSUCCESS;
}
TER
SponsorshipSet::preclaim(PreclaimContext const& ctx)
{
auto const sponsorAccountID = ctx.tx[~sfCounterpartySponsor].value_or(ctx.tx[sfAccount]);
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(ctx.tx[sfAccount]);
if (sponseeAccountID == sponsorAccountID)
return tecINTERNAL; // LCOV_EXCL_LINE
// check Sponsor
auto const sponsorAccSle = ctx.view.read(keylet::account(sponsorAccountID));
if (!sponsorAccSle)
return tecNO_DST;
// check Sponsee
auto const sponseeSle = ctx.view.read(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tecNO_DST;
// Pseudo accounts cannot be sponsors or sponsees
if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle))
return tecNO_PERMISSION;
// check if object exists
auto const sponsorObjSle = ctx.view.read(keylet::sponsor(sponsorAccountID, sponseeAccountID));
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorObjSle)
return tecNO_ENTRY;
return tesSUCCESS;
}
TER
SponsorshipSet::doApply()
{
auto const sponsorAccountID = ctx_.tx[~sfCounterpartySponsor].value_or(account_);
auto const sponseeAccountID = ctx_.tx[~sfSponsee].value_or(account_);
if (sponseeAccountID == sponsorAccountID)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorAccSle = ctx_.view().peek(keylet::account(sponsorAccountID));
if (!sponsorAccSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!ctx_.view().exists(keylet::account(sponseeAccountID)))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorKeylet = keylet::sponsor(sponsorAccountID, sponseeAccountID);
auto const sponsorObjSle = ctx_.view().peek(sponsorKeylet);
if (ctx_.tx.isFlag(tfDeleteObject))
{
// Delete
if (!sponsorObjSle)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsor = getLedgerEntryReserveSponsor(ctx_.view(), sponsorObjSle);
adjustOwnerCount(ctx_.view(), sponsorAccSle, sponsor, -1, ctx_.journal);
ctx_.view().dirRemove(
keylet::ownerDir(sponsorAccountID),
(*sponsorObjSle)[sfOwnerNode],
sponsorObjSle->key(),
false);
ctx_.view().dirRemove(
keylet::ownerDir(sponseeAccountID),
(*sponsorObjSle)[sfSponseeNode],
sponsorObjSle->key(),
false);
// transfer feeAmount from ledger entry
if (sponsorObjSle->isFieldPresent(sfFeeAmount))
{
auto const feeAmount = sponsorObjSle->getFieldAmount(sfFeeAmount);
(*sponsorAccSle)[sfBalance] += feeAmount;
}
ctx_.view().erase(sponsorObjSle);
return tesSUCCESS;
}
auto const feeAmount = ctx_.tx[~sfFeeAmount];
auto const maxFee = ctx_.tx[~sfMaxFee];
auto const reserveCount = ctx_.tx[~sfReserveCount];
auto reserveSponsorAccSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorObjSle)
{
// Create
auto newSle = std::make_shared<SLE>(sponsorKeylet);
(*newSle)[sfOwner] = sponsorAccountID;
(*newSle)[sfSponsee] = sponseeAccountID;
if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
if (feeAmount && *feeAmount > XRPAmount(0))
{
(*sponsorAccSle)[sfBalance] -= *feeAmount;
(*newSle)[sfFeeAmount] = *feeAmount;
}
if (auto const ret = checkInsufficientReserve(
ctx_.view(),
ctx_.tx,
sponsorAccSle,
STAmount{(*sponsorAccSle)[sfBalance]}.xrp(),
reserveSponsorAccSle,
1);
!isTesSuccess(ret))
return tecUNFUNDED;
if (maxFee && *maxFee > XRPAmount(0))
(*newSle)[sfMaxFee] = *maxFee;
if (reserveCount && *reserveCount > 0)
(*newSle)[sfReserveCount] = *reserveCount;
auto flags = 0;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
(*newSle)[sfFlags] = flags;
auto const sponsorPage = view().dirInsert(
keylet::ownerDir(sponsorAccountID), sponsorKeylet, describeOwnerDir(sponsorAccountID));
if (!sponsorPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfOwnerNode] = *sponsorPage;
auto const sponseePage = view().dirInsert(
keylet::ownerDir(sponseeAccountID), sponsorKeylet, describeOwnerDir(sponseeAccountID));
if (!sponseePage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfSponseeNode] = *sponseePage;
adjustOwnerCount(view(), sponsorAccSle, reserveSponsorAccSle, 1, ctx_.journal);
addSponsorToLedgerEntry(newSle, reserveSponsorAccSle);
ctx_.view().insert(newSle);
return tesSUCCESS;
}
// Update
if (feeAmount)
{
auto const currentFeeAmount = (*sponsorObjSle)[~sfFeeAmount].value_or(XRPAmount(0));
auto feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount);
if (feeAmountDelta > beast::zero && feeAmountDelta > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
// transfer feeAmount to ledger entry
if (feeAmountDelta != beast::zero)
{
(*sponsorAccSle)[sfBalance] -= feeAmountDelta;
if (*feeAmount == XRPAmount(0))
(*sponsorObjSle).makeFieldAbsent(sfFeeAmount);
else
(*sponsorObjSle).setFieldAmount(sfFeeAmount, *feeAmount);
}
}
if (maxFee)
{
if (*maxFee == XRPAmount(0))
(*sponsorObjSle).makeFieldAbsent(sfMaxFee);
else
(*sponsorObjSle)[sfMaxFee] = *maxFee;
}
if (reserveCount)
{
if (*reserveCount == 0)
(*sponsorObjSle).makeFieldAbsent(sfReserveCount);
else
(*sponsorObjSle)[sfReserveCount] = *reserveCount;
}
// update Flags
auto flags = sponsorObjSle->getFieldU32(sfFlags);
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForFee))
flags &= ~lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
flags &= ~lsfSponsorshipRequireSignForReserve;
if (flags != (*sponsorObjSle)[sfFlags])
(*sponsorObjSle)[sfFlags] = flags;
view().update(sponsorObjSle);
return tesSUCCESS;
}
} // namespace xrpl

View File

@@ -0,0 +1,594 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/tx/transactors/oracle/OracleSet.h>
#include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
namespace xrpl {
std::uint32_t
SponsorshipTransfer::getFlagsMask(PreflightContext const& ctx)
{
return tfSponsorshipTransferMask;
}
NotTEC
SponsorshipTransfer::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
auto const flagsSet = flags & ~(tfSponsorshipTransferMask | tfUniversal);
if (std::popcount(flagsSet) != 1)
{
JLOG(ctx.j.debug()) << "preflight: Only one SponsorshipTransfer flag can be set per tx.";
return temINVALID_FLAG;
}
if (flags & tfSponsorshipCreate)
{
if (!isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should be set when creating sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
JLOG(ctx.j.debug())
<< "preflight: sfSponsee should be available only when ending sponsorship";
return temMALFORMED;
}
}
if (flags & tfSponsorshipReassign)
{
if (!isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should be set when reassigning sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
JLOG(ctx.j.debug())
<< "preflight: sfSponsee should not be set when reassigning sponsorship";
return temMALFORMED;
}
}
if (flags & tfSponsorshipEnd)
{
if (isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should not be set when ending sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
if (ctx.tx.getAccountID(sfSponsee) == ctx.tx.getAccountID(sfAccount))
{
JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be the same as the account";
return temMALFORMED;
}
}
}
// When an account sponsoring, sfSponsorSignature must be provided
auto const newSponsor = getTxReserveSponsorAccountID(ctx.tx);
bool const isObjectSponsor = ctx.tx.isFieldPresent(sfObjectID);
// both sfSponsor and sfObjectID are provided
bool const isNewAccountSponsor = newSponsor && !isObjectSponsor;
if (isNewAccountSponsor && !ctx.tx.isFieldPresent(sfSponsorSignature))
{
JLOG(ctx.j.debug()) << "preflight: sponsoring an account needs co-signing sponsor";
return temMALFORMED;
}
return tesSUCCESS;
}
template <typename T>
inline std::optional<AccountID>
getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account)
{
switch (sle->getType())
{
case ltNFTOKEN_OFFER:
case ltORACLE:
case ltPERMISSIONED_DOMAIN:
case ltVAULT:
return sle->getAccountID(sfOwner);
case ltCHECK:
case ltDID:
case ltTICKET:
case ltOFFER:
case ltXCHAIN_OWNED_CLAIM_ID:
case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
case ltESCROW:
case ltPAYCHAN:
case ltMPTOKEN:
case ltDELEGATE:
case ltBRIDGE:
case ltDEPOSIT_PREAUTH:
return sle->getAccountID(sfAccount);
case ltMPTOKEN_ISSUANCE:
return sle->getAccountID(sfIssuer);
case ltSIGNER_LIST: {
auto const signerList = view.read(keylet::signers(account));
if (!signerList)
return std::nullopt;
if (signerList->key() == sle->key())
return account;
return std::nullopt;
}
case ltCREDENTIAL: {
if (sle->isFlag(lsfAccepted))
return sle->getAccountID(sfSubject);
return sle->getAccountID(sfIssuer);
}
case ltNFTOKEN_PAGE: {
// the upper 20 bytes of the index of ltNFTokenPage are the Owner's
// AccountID
uint256 const& key = sle->key();
return AccountID::fromVoid(key.data());
}
case ltRIPPLE_STATE: {
if (sle->isFlag(lsfHighReserve))
{
auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == account)
return highAccount;
}
if (sle->isFlag(lsfLowReserve))
{
auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == account)
return lowAccount;
}
return std::nullopt;
}
case ltACCOUNT_ROOT: {
// AccountRoot is not supported for object sponsorship
return std::nullopt;
}
case ltNEGATIVE_UNL:
case ltDIR_NODE:
case ltAMENDMENTS:
case ltLEDGER_HASHES:
case ltFEE_SETTINGS:
case ltAMM:
return std::nullopt;
default:
return std::nullopt;
};
}
template <typename T>
inline std::uint32_t
getLedgerEntryOwnerCount(T const& sle)
{
switch (sle->getType())
{
case ltORACLE: {
return OracleSet::calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size());
}
default:
return 1;
}
};
template <typename T>
inline SF_ACCOUNT const&
getLedgerEntrySponsorField(T const& sle, AccountID const& owner)
{
switch (sle->getType())
{
case ltRIPPLE_STATE: {
if (sle->isFlag(lsfHighReserve))
{
auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == owner)
return sfHighSponsor;
}
if (sle->isFlag(lsfLowReserve))
{
auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == owner)
return sfLowSponsor;
}
// LCOV_EXCL_START
UNREACHABLE("Should not happen. Owner should be checked before calling this function.");
// LCOV_EXCL_STOP
}
default:
return sfSponsor;
}
};
TER
SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
{
auto const index = ctx.tx[~sfObjectID];
auto const flags = ctx.tx.getFlags();
auto const newSponsor = getTxReserveSponsor(ctx.view, ctx.tx);
bool const isObjectSponsor = index != std::nullopt;
auto const account = ctx.tx[sfAccount];
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(account);
auto const sponseeSle = ctx.view.read(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (isObjectSponsor)
{
auto const sle = ctx.view.read(keylet::unchecked(*index));
if (!sle)
return tecNO_ENTRY;
auto const ownerCountDelta = getLedgerEntryOwnerCount(sle);
auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeAccountID);
if (!owner || owner != sponseeAccountID)
return tecNO_PERMISSION;
auto const& sponsorField = getLedgerEntrySponsorField(sle, *owner);
if (flags & tfSponsorshipCreate)
{
if (!newSponsor)
return tecNO_PERMISSION;
// check object is not sponsored yet
if (sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
}
else if (flags & tfSponsorshipReassign)
{
if (!newSponsor)
return tecNO_PERMISSION;
// check object is already sponsored
if (!sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
}
else if (flags & tfSponsorshipEnd)
{
if (newSponsor)
return tecNO_PERMISSION;
// check object is sponsored
if (!sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
// only the sponsor or sponsee can end sponsorship
auto const sponsor = sle->getAccountID(sponsorField);
if (account != sponsor && account != sponseeAccountID)
return tecNO_PERMISSION;
}
// check new sponsor have sufficient balance
if (auto const ter = checkInsufficientReserve(
ctx.view,
ctx.tx,
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsor,
ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
else
{
if (flags & tfSponsorshipCreate)
{
if (!newSponsor)
return tecNO_PERMISSION;
// check account is not sponsored yet
if (sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
}
else if (flags & tfSponsorshipReassign)
{
if (!newSponsor)
return tecNO_PERMISSION;
// check account is already sponsored
if (!sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
}
else if (flags & tfSponsorshipEnd)
{
if (newSponsor)
return tecNO_PERMISSION;
// check account is sponsored
if (!sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
// only the sponsor or sponsee can end sponsorship
auto const sponsor = sponseeSle->getAccountID(sfSponsor);
if (account != sponsor && account != sponseeAccountID)
return tecNO_PERMISSION;
}
// check account have sufficient balance
// In the case of removing an account sponsor, accSle should have no sfSponsor set
// (AccountReserve = 0). However, by setting accountCountDelta = 1 here, we are able to
// calculate the actual required Account Reserve.
if (auto const ter = checkInsufficientReserve(
ctx.view,
ctx.tx,
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsor,
0,
1);
!isTesSuccess(ter))
return ter;
}
return tesSUCCESS;
}
TER
adjustReserveCount(
ApplyView& view,
AccountID const& account,
AccountID const& sponsor,
int32_t delta)
{
if (delta == 0)
return tesSUCCESS;
auto const sponsorKeylet = keylet::sponsor(sponsor, account);
auto const sponsorSle = view.peek(sponsorKeylet);
if (!sponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const reserveCount = sponsorSle->getFieldU32(sfReserveCount);
int32_t const afterReserveCount = reserveCount + delta;
if (afterReserveCount < 0)
{
// already checked in preclaim()
return tefINTERNAL; // LCOV_EXCL_LINE
}
if (afterReserveCount == 0)
{
sponsorSle->makeFieldAbsent(sfReserveCount);
}
else
{
sponsorSle->setFieldU32(sfReserveCount, afterReserveCount);
}
view.update(sponsorSle);
return tesSUCCESS;
}
TER
SponsorshipTransfer::doApply()
{
auto const& tx = ctx_.tx;
auto const index = tx[~sfObjectID];
auto const flags = tx.getFlags();
bool const isObjectSponsor = index != std::nullopt;
auto const sponseeAccountID = tx[~sfSponsee].value_or(account_);
auto const sponseeSle = view().peek(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const setSponsorFieldU32 = [](auto const& sle, auto const& field, auto const& delta) {
int32_t const newValue = static_cast<int32_t>(sle->getFieldU32(field)) + delta;
if (newValue <= 0)
{
sle->makeFieldAbsent(field);
}
else
{
sle->setFieldU32(field, static_cast<std::uint32_t>(newValue));
}
};
if (isObjectSponsor)
{
auto const hasSignature = tx.isFieldPresent(sfSponsorSignature);
// transfer object sponsor
auto const objSle = view().peek(keylet::unchecked(*index));
if (!objSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerAccountID = getLedgerEntryOwner(view(), objSle, sponseeAccountID);
if (!ownerAccountID)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerSle = view().peek(keylet::account(*ownerAccountID));
if (!ownerSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerCountDelta = getLedgerEntryOwnerCount(objSle);
auto const& sponsorField = getLedgerEntrySponsorField(objSle, *ownerAccountID);
if (flags & tfSponsorshipCreate)
{
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
XRPL_ASSERT(!!newSponsorAccountID, "New sponsor is required when creating sponsorship");
// update owner's sponsored count
setSponsorFieldU32(ownerSle, sfSponsoredOwnerCount, ownerCountDelta);
view().update(ownerSle);
// increment new sponsor's sponsoring count
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
view().update(newSponsorSle);
// set new sponsor to object
objSle->setAccountID(sponsorField, newSponsorAccountID);
view().update(objSle);
if (!hasSignature)
{
// use ReserveCount for pre-funded sponsoring
if (auto const ter = adjustReserveCount(
view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
}
else if (flags & tfSponsorshipReassign)
{
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
XRPL_ASSERT(
!!newSponsorAccountID, "New sponsor is required when reassigning sponsorship");
auto const oldSponsorAccountID = objSle->getAccountID(sponsorField);
XRPL_ASSERT(
!!oldSponsorAccountID, "Old sponsor is required when reassigning sponsorship");
// decrement old sponsor's sponsoring count
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta);
view().update(oldSponsorSle);
// increment new sponsor's sponsoring count
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
view().update(newSponsorSle);
// set new sponsor to object
objSle->setAccountID(sponsorField, newSponsorAccountID);
view().update(objSle);
if (!hasSignature)
{
// use ReserveCount for pre-funded sponsoring
if (auto const ter = adjustReserveCount(
view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
// payback the reserve count if ltSponsorship exists
if (auto const sponsorSle =
view().exists(keylet::sponsor(oldSponsorAccountID, sponseeAccountID));
sponsorSle)
{
if (auto const ter = adjustReserveCount(
view(), sponseeAccountID, oldSponsorAccountID, ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
}
else if (flags & tfSponsorshipEnd)
{
auto const oldSponsorAccountID = objSle->getAccountID(sponsorField);
XRPL_ASSERT(!!oldSponsorAccountID, "Old sponsor is required when ending sponsorship");
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
// decrement sponsored count
setSponsorFieldU32(sponseeSle, sfSponsoredOwnerCount, -ownerCountDelta);
view().update(sponseeSle);
// decrement old sponsoring count
setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta);
view().update(oldSponsorSle);
// payback the reserve count if ltSponsorship exists
if (auto const sponsorSle =
view().exists(keylet::sponsor(oldSponsorAccountID, sponseeAccountID));
sponsorSle)
{
if (auto const ter = adjustReserveCount(
view(), sponseeAccountID, oldSponsorAccountID, ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
// remove sponsor from object
objSle->makeFieldAbsent(sponsorField);
view().update(objSle);
}
}
else
{
if (flags & tfSponsorshipCreate)
{
// create account sponsor
// increment new sponsoring count
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
view().update(newSponsorSle);
// set new sponsor to account
sponseeSle->setAccountID(sfSponsor, newSponsorAccountID);
view().update(sponseeSle);
}
else if (flags & tfSponsorshipReassign)
{
// reassign account sponsor
// increment new sponsoring count
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
view().update(newSponsorSle);
// decrement old sponsoring count
auto const oldSponsor = sponseeSle->getAccountID(sfSponsor);
auto const oldSponsorSle = view().peek(keylet::account(oldSponsor));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringAccountCount, -1);
view().update(oldSponsorSle);
// set new sponsor to account
sponseeSle->setAccountID(sfSponsor, newSponsorAccountID);
view().update(sponseeSle);
}
else if (flags & tfSponsorshipEnd)
{
// dissolve account sponsor
auto const oldSponsorAccountID = sponseeSle->getAccountID(sfSponsor);
sponseeSle->makeFieldAbsent(sfSponsor);
view().update(sponseeSle);
// decrement account sponsoring count
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringAccountCount, -1);
view().update(oldSponsorSle);
}
}
return tesSUCCESS;
}
} // namespace xrpl

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