Compare commits

...

25 Commits

Author SHA1 Message Date
Mayukha Vadari
646b3aa269 do an audit of fields 2026-05-29 10:50:44 -04:00
Mayukha Vadari
4dc61deeb4 fix build 2026-05-29 10:23:21 -04:00
Mayukha Vadari
e2103b1bef remove inner object mutabilities 2026-05-29 09:46:19 -04:00
Mayukha Vadari
9dfb0cf981 NoModifiedUnmodifiableFields -> NoModifiedImmutableFields 2026-05-29 09:44:43 -04:00
Mayukha Vadari
84908d56c1 rename constant -> mutability 2026-05-29 09:43:45 -04:00
Mayukha Vadari
076d923cc1 fix tests 2026-05-28 13:31:26 -04:00
Mayukha Vadari
fd86ca9205 Merge branch 'develop' of https://github.com/XRPLF/rippled into mvadari/constant-field-invariant 2026-05-28 12:32:57 -04:00
Mayukha Vadari
effec43851 ignore clang-tidy 2026-05-21 14:52:21 -04:00
Mayukha Vadari
08d5e1f6db Merge branch 'develop' into mvadari/constant-field-invariant 2026-05-20 17:16:01 -04:00
Mayukha Vadari
ceba7c6e34 fix typo 2026-05-20 17:12:28 -04:00
Mayukha Vadari
18994a31b1 Merge branch 'develop' of https://github.com/XRPLF/rippled into mvadari/constant-field-invariant 2026-05-20 16:47:06 -04:00
Mayukha Vadari
0a671b7d0b fix more issues 2026-05-20 16:37:55 -04:00
Mayukha Vadari
9cf2c0c3ae more fixes 2026-05-20 15:23:09 -04:00
Mayukha Vadari
bf68edb541 Merge branch 'develop' of https://github.com/XRPLF/rippled into mvadari/constant-field-invariant 2026-05-19 16:22:16 -04:00
Mayukha Vadari
67a199fcb5 fix build issue 2026-05-06 17:18:19 -04:00
Mayukha Vadari
02fba4c998 remove bad assert 2026-05-06 15:42:42 -04:00
Mayukha Vadari
1e51b61747 fix straggling issues 2026-05-06 11:39:05 -04:00
Mayukha Vadari
7b9a98692d fix clang-tidy issues 2026-05-06 11:36:45 -04:00
Mayukha Vadari
98ed477ca7 rename enum values 2026-05-06 11:35:38 -04:00
Mayukha Vadari
654734dcf9 fix clang-tidy issues 2026-05-06 11:17:21 -04:00
Mayukha Vadari
d5ca8ef77d Merge branch 'develop' into mvadari/constant-field-invariant 2026-05-06 11:16:19 -04:00
Mayukha Vadari
7de9560048 Update src/libxrpl/protocol/InnerObjectFormats.cpp
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-27 09:33:25 -04:00
Mayukha Vadari
568623f699 fix clang-tidy issues 2026-04-27 09:30:42 -04:00
Mayukha Vadari
cf33298abd add tests, fix issues 2026-04-27 08:52:52 -04:00
Mayukha Vadari
b46ca16f7d feature: Add invariant checks to ensure only fields that are expected to be modified are modified 2026-04-25 19:59:47 -04:00
14 changed files with 603 additions and 353 deletions

View File

@@ -27,6 +27,13 @@ enum SOEStyle {
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum SOETxMPTIssue { SoeMptNone, SoeMptSupported, SoeMptNotSupported };
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum SOEMutability {
SoeImmutable,
SoeMutable,
SoeImmutableSetOnce,
};
//------------------------------------------------------------------------------
/** An element in a SOTemplate. */
@@ -35,6 +42,7 @@ class SOElement
// Use std::reference_wrapper so SOElement can be stored in a std::vector.
std::reference_wrapper<SField const> sField_;
SOEStyle style_;
SOEMutability mutability_ = SoeImmutable;
SOETxMPTIssue supportMpt_ = SoeMptNone;
private:
@@ -48,6 +56,14 @@ private:
nm += ": '" + fieldName.getName() + "'";
Throw<std::runtime_error>("SField (" + nm + ") in SOElement must be useful.");
}
XRPL_ASSERT(
mutability_ != SoeImmutableSetOnce || style_ == SoeOptional,
"xrpl::SOElement::init : set-once fields must be optional");
XRPL_ASSERT(
mutability_ != SoeMutable || style_ == SoeRequired || style_ == SoeOptional ||
style_ == SoeDefault,
"xrpl::SOElement::init : mutable fields must have a valid style");
}
public:
@@ -56,6 +72,12 @@ public:
init(fieldName);
}
SOElement(SField const& fieldName, SOEStyle style, SOEMutability mutability)
: sField_(fieldName), style_(style), mutability_(mutability)
{
init(fieldName);
}
template <typename T>
requires(std::is_same_v<T, STAmount> || std::is_same_v<T, STIssue>)
SOElement(
@@ -67,6 +89,18 @@ public:
init(fieldName);
}
template <typename T>
requires(std::is_same_v<T, STAmount> || std::is_same_v<T, STIssue>)
SOElement(
TypedField<T> const& fieldName,
SOEStyle style,
SOEMutability mutability,
SOETxMPTIssue supportMpt = SoeMptNotSupported)
: sField_(fieldName), style_(style), mutability_(mutability), supportMpt_(supportMpt)
{
init(fieldName);
}
[[nodiscard]] SField const&
sField() const
{
@@ -79,6 +113,12 @@ public:
return style_;
}
[[nodiscard]] SOEMutability
mutability() const
{
return mutability_;
}
[[nodiscard]] SOETxMPTIssue
supportMPT() const
{

View File

@@ -587,7 +587,7 @@ public:
/** Returns `true` if the field is set.
Fields with soeDEFAULT and set to the
Fields with SoeDefault and set to the
default value will return `true`
*/
explicit

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (ImmutableInvariant, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)

View File

@@ -24,15 +24,15 @@
\sa keylet::nftoffer
*/
LEDGER_ENTRY(ltNFTOKEN_OFFER, 0x0037, NFTokenOffer, nft_offer, ({
{sfOwner, SoeRequired},
{sfNFTokenID, SoeRequired},
{sfAmount, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfNFTokenOfferNode, SoeRequired},
{sfDestination, SoeOptional},
{sfExpiration, SoeOptional},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired, SoeImmutable},
{sfNFTokenID, SoeRequired, SoeImmutable},
{sfAmount, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfNFTokenOfferNode, SoeRequired, SoeImmutable},
{sfDestination, SoeOptional, SoeImmutable},
{sfExpiration, SoeOptional, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which describes a check.
@@ -40,18 +40,18 @@ LEDGER_ENTRY(ltNFTOKEN_OFFER, 0x0037, NFTokenOffer, nft_offer, ({
\sa keylet::check
*/
LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({
{sfAccount, SoeRequired},
{sfDestination, SoeRequired},
{sfSendMax, SoeRequired},
{sfSequence, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfDestinationNode, SoeRequired},
{sfExpiration, SoeOptional},
{sfInvoiceID, SoeOptional},
{sfSourceTag, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfDestination, SoeRequired, SoeImmutable},
{sfSendMax, SoeRequired, SoeImmutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfDestinationNode, SoeRequired, SoeImmutable},
{sfExpiration, SoeOptional, SoeImmutable},
{sfInvoiceID, SoeOptional, SoeImmutable},
{sfSourceTag, SoeOptional, SoeImmutable},
{sfDestinationTag, SoeOptional, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** The ledger object which tracks the DID.
@@ -59,13 +59,13 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({
\sa keylet::did
*/
LEDGER_ENTRY(ltDID, 0x0049, DID, did, ({
{sfAccount, SoeRequired},
{sfDIDDocument, SoeOptional},
{sfURI, SoeOptional},
{sfData, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfDIDDocument, SoeOptional, SoeMutable},
{sfURI, SoeOptional, SoeMutable},
{sfData, SoeOptional, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** The ledger object which tracks the current negative UNL state.
@@ -75,11 +75,11 @@ LEDGER_ENTRY(ltDID, 0x0049, DID, did, ({
\sa keylet::negativeUNL
*/
LEDGER_ENTRY(ltNEGATIVE_UNL, 0x004e, NegativeUNL, nunl, ({
{sfDisabledValidators, SoeOptional},
{sfValidatorToDisable, SoeOptional},
{sfValidatorToReEnable, SoeOptional},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
{sfDisabledValidators, SoeOptional, SoeMutable},
{sfValidatorToDisable, SoeOptional, SoeMutable},
{sfValidatorToReEnable, SoeOptional, SoeMutable},
{sfPreviousTxnID, SoeOptional, SoeMutable},
{sfPreviousTxnLgrSeq, SoeOptional, SoeMutable},
}))
/** A ledger object which contains a list of NFTs
@@ -87,11 +87,11 @@ LEDGER_ENTRY(ltNEGATIVE_UNL, 0x004e, NegativeUNL, nunl, ({
\sa keylet::nftpageMin, keylet::nftpageMax, keylet::nftpage
*/
LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({
{sfPreviousPageMin, SoeOptional},
{sfNextPageMin, SoeOptional},
{sfNFTokens, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfPreviousPageMin, SoeOptional, SoeMutable},
{sfNextPageMin, SoeOptional, SoeMutable},
{sfNFTokens, SoeRequired, SoeMutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which contains a signer list for an account.
@@ -101,13 +101,13 @@ LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({
// All fields are SoeRequired because there is always a SignerEntries.
// If there are no SignerEntries the node is deleted.
LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
{sfOwner, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfSignerQuorum, SoeRequired},
{sfSignerEntries, SoeRequired},
{sfSignerListID, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeOptional, SoeImmutableSetOnce},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfSignerQuorum, SoeRequired, SoeMutable},
{sfSignerEntries, SoeRequired, SoeMutable},
{sfSignerListID, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which describes a ticket.
@@ -115,11 +115,11 @@ LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
\sa keylet::kTicket
*/
LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
{sfAccount, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfTicketSequence, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfTicketSequence, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which describes an account.
@@ -127,29 +127,29 @@ LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
\sa keylet::account
*/
LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAccount, SoeRequired},
{sfSequence, SoeRequired},
{sfBalance, SoeRequired},
{sfOwnerCount, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccountTxnID, SoeOptional},
{sfRegularKey, SoeOptional},
{sfEmailHash, SoeOptional},
{sfWalletLocator, SoeOptional},
{sfWalletSize, SoeOptional},
{sfMessageKey, SoeOptional},
{sfTransferRate, SoeOptional},
{sfDomain, SoeOptional},
{sfTickSize, SoeOptional},
{sfTicketCount, SoeOptional},
{sfNFTokenMinter, SoeOptional},
{sfMintedNFTokens, SoeDefault},
{sfBurnedNFTokens, SoeDefault},
{sfFirstNFTokenSequence, SoeOptional},
{sfAMMID, SoeOptional}, // pseudo-account designator
{sfVaultID, SoeOptional}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
{sfAccount, SoeRequired, SoeImmutable},
{sfSequence, SoeRequired, SoeMutable},
{sfBalance, SoeRequired, SoeMutable},
{sfOwnerCount, SoeRequired, SoeMutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfAccountTxnID, SoeOptional, SoeMutable},
{sfRegularKey, SoeOptional, SoeMutable},
{sfEmailHash, SoeOptional, SoeMutable},
{sfWalletLocator, SoeOptional, SoeMutable},
{sfWalletSize, SoeOptional, SoeMutable},
{sfMessageKey, SoeOptional, SoeMutable},
{sfTransferRate, SoeOptional, SoeMutable},
{sfDomain, SoeOptional, SoeMutable},
{sfTickSize, SoeOptional, SoeMutable},
{sfTicketCount, SoeOptional, SoeMutable},
{sfNFTokenMinter, SoeOptional, SoeMutable},
{sfMintedNFTokens, SoeDefault, SoeMutable},
{sfBurnedNFTokens, SoeDefault, SoeMutable},
{sfFirstNFTokenSequence, SoeOptional, SoeImmutableSetOnce},
{sfAMMID, SoeOptional, SoeImmutable}, // pseudo-account designator
{sfVaultID, SoeOptional, SoeImmutable}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional, SoeImmutable}, // pseudo-account designator
}))
/** A ledger object which contains a list of object identifiers.
@@ -158,22 +158,22 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
keylet::ownerDir
*/
LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
{sfOwner, SoeOptional}, // for owner directories
{sfTakerPaysCurrency, SoeOptional}, // order book directories
{sfTakerPaysIssuer, SoeOptional}, // order book directories
{sfTakerPaysMPT, SoeOptional}, // order book directories
{sfTakerGetsCurrency, SoeOptional}, // order book directories
{sfTakerGetsIssuer, SoeOptional}, // order book directories
{sfTakerGetsMPT, SoeOptional}, // order book directories
{sfExchangeRate, SoeOptional}, // order book directories
{sfIndexes, SoeRequired},
{sfRootIndex, SoeRequired},
{sfIndexNext, SoeOptional},
{sfIndexPrevious, SoeOptional},
{sfNFTokenID, SoeOptional},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
{sfDomainID, SoeOptional} // order book directories
{sfOwner, SoeOptional, SoeImmutable}, // for owner directories
{sfTakerPaysCurrency, SoeOptional, SoeImmutable}, // order book directories
{sfTakerPaysIssuer, SoeOptional, SoeImmutable}, // order book directories
{sfTakerPaysMPT, SoeOptional, SoeImmutable}, // order book directories
{sfTakerGetsCurrency, SoeOptional, SoeImmutable}, // order book directories
{sfTakerGetsIssuer, SoeOptional, SoeImmutable}, // order book directories
{sfTakerGetsMPT, SoeOptional, SoeImmutable}, // order book directories
{sfExchangeRate, SoeOptional, SoeImmutable}, // order book directories
{sfIndexes, SoeRequired, SoeMutable},
{sfRootIndex, SoeRequired, SoeImmutable},
{sfIndexNext, SoeOptional, SoeMutable},
{sfIndexPrevious, SoeOptional, SoeMutable},
{sfNFTokenID, SoeOptional, SoeImmutable},
{sfPreviousTxnID, SoeOptional, SoeMutable},
{sfPreviousTxnLgrSeq, SoeOptional, SoeMutable},
{sfDomainID, SoeOptional, SoeImmutable}, // order book directories
}))
/** The ledger object which lists details about amendments on the network.
@@ -183,10 +183,10 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
\sa keylet::amendments
*/
LEDGER_ENTRY(ltAMENDMENTS, 0x0066, Amendments, amendments, ({
{sfAmendments, SoeOptional}, // Enabled
{sfMajorities, SoeOptional},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
{sfAmendments, SoeOptional, SoeMutable}, // Enabled
{sfMajorities, SoeOptional, SoeMutable},
{sfPreviousTxnID, SoeOptional, SoeMutable},
{sfPreviousTxnLgrSeq, SoeOptional, SoeMutable},
}))
/** A ledger object that contains a list of ledger hashes.
@@ -198,9 +198,9 @@ LEDGER_ENTRY(ltAMENDMENTS, 0x0066, Amendments, amendments, ({
\sa keylet::skip
*/
LEDGER_ENTRY(ltLEDGER_HASHES, 0x0068, LedgerHashes, hashes, ({
{sfFirstLedgerSequence, SoeOptional},
{sfLastLedgerSequence, SoeOptional},
{sfHashes, SoeRequired},
{sfFirstLedgerSequence, SoeOptional, SoeImmutable},
{sfLastLedgerSequence, SoeOptional, SoeMutable},
{sfHashes, SoeRequired, SoeMutable},
}))
/** The ledger object which lists details about sidechains.
@@ -208,16 +208,16 @@ LEDGER_ENTRY(ltLEDGER_HASHES, 0x0068, LedgerHashes, hashes, ({
\sa keylet::bridge
*/
LEDGER_ENTRY(ltBRIDGE, 0x0069, Bridge, bridge, ({
{sfAccount, SoeRequired},
{sfSignatureReward, SoeRequired},
{sfMinAccountCreateAmount, SoeOptional},
{sfXChainBridge, SoeRequired},
{sfXChainClaimID, SoeRequired},
{sfXChainAccountCreateCount, SoeRequired},
{sfXChainAccountClaimCount, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfSignatureReward, SoeRequired, SoeMutable},
{sfMinAccountCreateAmount, SoeOptional, SoeMutable},
{sfXChainBridge, SoeRequired, SoeImmutable},
{sfXChainClaimID, SoeRequired, SoeMutable},
{sfXChainAccountCreateCount, SoeRequired, SoeMutable},
{sfXChainAccountClaimCount, SoeRequired, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which describes an offer on the DEX.
@@ -225,18 +225,18 @@ LEDGER_ENTRY(ltBRIDGE, 0x0069, Bridge, bridge, ({
\sa keylet::offer
*/
LEDGER_ENTRY(ltOFFER, 0x006f, Offer, offer, ({
{sfAccount, SoeRequired},
{sfSequence, SoeRequired},
{sfTakerPays, SoeRequired},
{sfTakerGets, SoeRequired},
{sfBookDirectory, SoeRequired},
{sfBookNode, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfExpiration, SoeOptional},
{sfDomainID, SoeOptional},
{sfAdditionalBooks, SoeOptional},
{sfAccount, SoeRequired, SoeImmutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfTakerPays, SoeRequired, SoeMutable},
{sfTakerGets, SoeRequired, SoeMutable},
{sfBookDirectory, SoeRequired, SoeImmutable},
{sfBookNode, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfExpiration, SoeOptional, SoeImmutable},
{sfDomainID, SoeOptional, SoeImmutable},
{sfAdditionalBooks, SoeOptional, SoeImmutable},
}))
/** A ledger object which describes a deposit pre-authorization.
@@ -244,12 +244,12 @@ LEDGER_ENTRY(ltOFFER, 0x006f, Offer, offer, ({
\sa keylet::depositPreauth
*/
LEDGER_ENTRY_DUPLICATE(ltDEPOSIT_PREAUTH, 0x0070, DepositPreauth, deposit_preauth, ({
{sfAccount, SoeRequired},
{sfAuthorize, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAuthorizeCredentials, SoeOptional},
{sfAccount, SoeRequired, SoeImmutable},
{sfAuthorize, SoeOptional, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfAuthorizeCredentials, SoeOptional, SoeImmutable},
}))
/** A claim id for a cross chain transaction.
@@ -257,15 +257,15 @@ LEDGER_ENTRY_DUPLICATE(ltDEPOSIT_PREAUTH, 0x0070, DepositPreauth, deposit_preaut
\sa keylet::xChainClaimID
*/
LEDGER_ENTRY(ltXCHAIN_OWNED_CLAIM_ID, 0x0071, XChainOwnedClaimID, xchain_owned_claim_id, ({
{sfAccount, SoeRequired},
{sfXChainBridge, SoeRequired},
{sfXChainClaimID, SoeRequired},
{sfOtherChainSource, SoeRequired},
{sfXChainClaimAttestations, SoeRequired},
{sfSignatureReward, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfXChainBridge, SoeRequired, SoeImmutable},
{sfXChainClaimID, SoeRequired, SoeImmutable},
{sfOtherChainSource, SoeRequired, SoeImmutable},
{sfXChainClaimAttestations, SoeRequired, SoeMutable},
{sfSignatureReward, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which describes a bidirectional trust line.
@@ -275,17 +275,17 @@ LEDGER_ENTRY(ltXCHAIN_OWNED_CLAIM_ID, 0x0071, XChainOwnedClaimID, xchain_owned_c
\sa keylet::line
*/
LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfBalance, SoeRequired},
{sfLowLimit, SoeRequired},
{sfHighLimit, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfLowNode, SoeOptional},
{sfLowQualityIn, SoeOptional},
{sfLowQualityOut, SoeOptional},
{sfHighNode, SoeOptional},
{sfHighQualityIn, SoeOptional},
{sfHighQualityOut, SoeOptional},
{sfBalance, SoeRequired, SoeMutable},
{sfLowLimit, SoeRequired, SoeMutable},
{sfHighLimit, SoeRequired, SoeMutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfLowNode, SoeOptional, SoeImmutable},
{sfLowQualityIn, SoeOptional, SoeMutable},
{sfLowQualityOut, SoeOptional, SoeMutable},
{sfHighNode, SoeOptional, SoeImmutable},
{sfHighQualityIn, SoeOptional, SoeMutable},
{sfHighQualityOut, SoeOptional, SoeMutable},
}))
/** The ledger object which lists the network's fee settings.
@@ -296,16 +296,16 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
*/
LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
// Old version uses raw numbers
{sfBaseFee, SoeOptional},
{sfReferenceFeeUnits, SoeOptional},
{sfReserveBase, SoeOptional},
{sfReserveIncrement, SoeOptional},
{sfBaseFee, SoeOptional, SoeMutable},
{sfReferenceFeeUnits, SoeOptional, SoeMutable},
{sfReserveBase, SoeOptional, SoeMutable},
{sfReserveIncrement, SoeOptional, SoeMutable},
// New version uses Amounts
{sfBaseFeeDrops, SoeOptional},
{sfReserveBaseDrops, SoeOptional},
{sfReserveIncrementDrops, SoeOptional},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
{sfBaseFeeDrops, SoeOptional, SoeMutable},
{sfReserveBaseDrops, SoeOptional, SoeMutable},
{sfReserveIncrementDrops, SoeOptional, SoeMutable},
{sfPreviousTxnID, SoeOptional, SoeMutable},
{sfPreviousTxnLgrSeq, SoeOptional, SoeMutable},
}))
/** A claim id for a cross chain create account transaction.
@@ -313,13 +313,13 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
\sa keylet::xChainCreateAccountClaimID
*/
LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAccountClaimID, xchain_owned_create_account_claim_id, ({
{sfAccount, SoeRequired},
{sfXChainBridge, SoeRequired},
{sfXChainAccountCreateCount, SoeRequired},
{sfXChainCreateAccountAttestations, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfXChainBridge, SoeRequired, SoeImmutable},
{sfXChainAccountCreateCount, SoeRequired, SoeImmutable},
{sfXChainCreateAccountAttestations, SoeRequired, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object describing a single escrow.
@@ -327,21 +327,21 @@ LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAc
\sa keylet::escrow
*/
LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfAccount, SoeRequired},
{sfSequence, SoeOptional},
{sfDestination, SoeRequired},
{sfAmount, SoeRequired},
{sfCondition, SoeOptional},
{sfCancelAfter, SoeOptional},
{sfFinishAfter, SoeOptional},
{sfSourceTag, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfTransferRate, SoeOptional},
{sfIssuerNode, SoeOptional},
{sfAccount, SoeRequired, SoeImmutable},
{sfSequence, SoeOptional, SoeImmutable},
{sfDestination, SoeRequired, SoeImmutable},
{sfAmount, SoeRequired, SoeImmutable},
{sfCondition, SoeOptional, SoeImmutable},
{sfCancelAfter, SoeOptional, SoeImmutable},
{sfFinishAfter, SoeOptional, SoeImmutable},
{sfSourceTag, SoeOptional, SoeImmutable},
{sfDestinationTag, SoeOptional, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfDestinationNode, SoeOptional, SoeImmutable},
{sfTransferRate, SoeOptional, SoeImmutable},
{sfIssuerNode, SoeOptional, SoeImmutable},
}))
/** A ledger object describing a single unidirectional XRP payment channel.
@@ -349,21 +349,21 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
\sa keylet::payChan
*/
LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfAccount, SoeRequired},
{sfDestination, SoeRequired},
{sfSequence, SoeOptional},
{sfAmount, SoeRequired},
{sfBalance, SoeRequired},
{sfPublicKey, SoeRequired},
{sfSettleDelay, SoeRequired},
{sfExpiration, SoeOptional},
{sfCancelAfter, SoeOptional},
{sfSourceTag, SoeOptional},
{sfDestinationTag, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfAccount, SoeRequired, SoeImmutable},
{sfDestination, SoeRequired, SoeImmutable},
{sfSequence, SoeOptional, SoeImmutable},
{sfAmount, SoeRequired, SoeMutable},
{sfBalance, SoeRequired, SoeMutable},
{sfPublicKey, SoeRequired, SoeImmutable},
{sfSettleDelay, SoeRequired, SoeImmutable},
{sfExpiration, SoeOptional, SoeMutable},
{sfCancelAfter, SoeOptional, SoeImmutable},
{sfSourceTag, SoeOptional, SoeImmutable},
{sfDestinationTag, SoeOptional, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfDestinationNode, SoeOptional, SoeImmutable},
}))
/** The ledger object which tracks the AMM.
@@ -371,126 +371,126 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
\sa keylet::amm
*/
LEDGER_ENTRY(ltAMM, 0x0079, AMM, amm, ({
{sfAccount, SoeRequired},
{sfTradingFee, SoeDefault},
{sfVoteSlots, SoeOptional},
{sfAuctionSlot, SoeOptional},
{sfLPTokenBalance, SoeRequired},
{sfAsset, SoeRequired},
{sfAsset2, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeOptional},
{sfPreviousTxnLgrSeq, SoeOptional},
{sfAccount, SoeRequired, SoeImmutable},
{sfTradingFee, SoeDefault, SoeMutable},
{sfVoteSlots, SoeOptional, SoeMutable},
{sfAuctionSlot, SoeOptional, SoeMutable},
{sfLPTokenBalance, SoeRequired, SoeMutable},
{sfAsset, SoeRequired, SoeImmutable},
{sfAsset2, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeOptional, SoeMutable},
{sfPreviousTxnLgrSeq, SoeOptional, SoeMutable},
}))
/** A ledger object which tracks MPTokenIssuance
\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},
{sfReferenceHolding, SoeOptional},
{sfIssuer, SoeRequired, SoeImmutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfTransferFee, SoeDefault, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfAssetScale, SoeDefault, SoeImmutable},
{sfMaximumAmount, SoeOptional, SoeImmutable},
{sfOutstandingAmount, SoeRequired, SoeMutable},
{sfLockedAmount, SoeOptional, SoeMutable},
{sfMPTokenMetadata, SoeOptional, SoeMutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfDomainID, SoeOptional, SoeMutable},
{sfMutableFlags, SoeDefault, SoeMutable},
{sfReferenceHolding, SoeOptional, SoeImmutable},
}))
/** 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, SoeImmutable},
{sfMPTokenIssuanceID, SoeRequired, SoeImmutable},
{sfMPTAmount, SoeDefault, SoeMutable},
{sfLockedAmount, SoeOptional, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which tracks Oracle
\sa keylet::oracle
*/
LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, oracle, ({
{sfOwner, SoeRequired},
{sfOracleDocumentID, SoeOptional},
{sfProvider, SoeRequired},
{sfPriceDataSeries, SoeRequired},
{sfAssetClass, SoeRequired},
{sfLastUpdateTime, SoeRequired},
{sfURI, SoeOptional},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired, SoeImmutable},
{sfOracleDocumentID, SoeOptional, SoeImmutableSetOnce},
{sfProvider, SoeRequired, SoeImmutable},
{sfPriceDataSeries, SoeRequired, SoeMutable},
{sfAssetClass, SoeRequired, SoeImmutable},
{sfLastUpdateTime, SoeRequired, SoeMutable},
{sfURI, SoeOptional, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which tracks Credential
\sa keylet::credential
*/
LEDGER_ENTRY(ltCREDENTIAL, 0x0081, Credential, credential, ({
{sfSubject, SoeRequired},
{sfIssuer, SoeRequired},
{sfCredentialType, SoeRequired},
{sfExpiration, SoeOptional},
{sfURI, SoeOptional},
{sfIssuerNode, SoeRequired},
{sfSubjectNode, SoeOptional},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfSubject, SoeRequired, SoeImmutable},
{sfIssuer, SoeRequired, SoeImmutable},
{sfCredentialType, SoeRequired, SoeImmutable},
{sfExpiration, SoeOptional, SoeImmutable},
{sfURI, SoeOptional, SoeImmutable},
{sfIssuerNode, SoeRequired, SoeImmutable},
{sfSubjectNode, SoeOptional, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object which tracks PermissionedDomain
\sa keylet::permissionedDomain
*/
LEDGER_ENTRY(ltPERMISSIONED_DOMAIN, 0x0082, PermissionedDomain, permissioned_domain, ({
{sfOwner, SoeRequired},
{sfSequence, SoeRequired},
{sfAcceptedCredentials, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired, SoeImmutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfAcceptedCredentials, SoeRequired, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object representing permissions an account has delegated to another account.
\sa keylet::delegate
*/
LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({
{sfAccount, SoeRequired},
{sfAuthorize, SoeRequired},
{sfPermissions, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfAccount, SoeRequired, SoeImmutable},
{sfAuthorize, SoeRequired, SoeImmutable},
{sfPermissions, SoeRequired, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfDestinationNode, SoeOptional, SoeImmutable},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
}))
/** A ledger object representing a single asset vault.
\sa keylet::vault
*/
LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfSequence, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfOwner, SoeRequired},
{sfAccount, SoeRequired},
{sfData, SoeOptional},
{sfAsset, SoeRequired},
{sfAssetsTotal, SoeDefault},
{sfAssetsAvailable, SoeDefault},
{sfAssetsMaximum, SoeDefault},
{sfLossUnrealized, SoeDefault},
{sfShareMPTID, SoeRequired},
{sfWithdrawalPolicy, SoeRequired},
{sfScale, SoeDefault},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfOwner, SoeRequired, SoeImmutable},
{sfAccount, SoeRequired, SoeImmutable},
{sfData, SoeOptional, SoeMutable},
{sfAsset, SoeRequired, SoeImmutable},
{sfAssetsTotal, SoeDefault, SoeMutable},
{sfAssetsAvailable, SoeDefault, SoeMutable},
{sfAssetsMaximum, SoeDefault, SoeMutable},
{sfLossUnrealized, SoeDefault, SoeMutable},
{sfShareMPTID, SoeRequired, SoeImmutable},
{sfWithdrawalPolicy, SoeRequired, SoeImmutable},
{sfScale, SoeDefault, SoeImmutable},
// no SharesTotal ever (use MPTIssuance.sfOutstandingAmount)
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
}))
@@ -502,23 +502,23 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
\sa keylet::loanbroker
*/
LEDGER_ENTRY(ltLOAN_BROKER, 0x0088, LoanBroker, loan_broker, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfSequence, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfVaultNode, SoeRequired},
{sfVaultID, SoeRequired},
{sfAccount, SoeRequired},
{sfOwner, SoeRequired},
{sfLoanSequence, SoeRequired},
{sfData, SoeDefault},
{sfManagementFeeRate, SoeDefault},
{sfOwnerCount, SoeDefault},
{sfDebtTotal, SoeDefault},
{sfDebtMaximum, SoeDefault},
{sfCoverAvailable, SoeDefault},
{sfCoverRateMinimum, SoeDefault},
{sfCoverRateLiquidation, SoeDefault},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfSequence, SoeRequired, SoeImmutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfVaultNode, SoeRequired, SoeImmutable},
{sfVaultID, SoeRequired, SoeImmutable},
{sfAccount, SoeRequired, SoeImmutable},
{sfOwner, SoeRequired, SoeImmutable},
{sfLoanSequence, SoeRequired, SoeMutable},
{sfData, SoeDefault, SoeMutable},
{sfManagementFeeRate, SoeDefault, SoeImmutable},
{sfOwnerCount, SoeDefault, SoeMutable},
{sfDebtTotal, SoeDefault, SoeMutable},
{sfDebtMaximum, SoeDefault, SoeMutable},
{sfCoverAvailable, SoeDefault, SoeMutable},
{sfCoverRateMinimum, SoeDefault, SoeImmutable},
{sfCoverRateLiquidation, SoeDefault, SoeImmutable},
}))
/** A ledger object representing a loan between a Borrower and a Loan Broker
@@ -526,27 +526,27 @@ LEDGER_ENTRY(ltLOAN_BROKER, 0x0088, LoanBroker, loan_broker, ({
\sa keylet::loan
*/
LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfLoanBrokerNode, SoeRequired},
{sfLoanBrokerID, SoeRequired},
{sfLoanSequence, SoeRequired},
{sfBorrower, SoeRequired},
{sfLoanOriginationFee, SoeDefault},
{sfLoanServiceFee, SoeDefault},
{sfLatePaymentFee, SoeDefault},
{sfClosePaymentFee, SoeDefault},
{sfOverpaymentFee, SoeDefault},
{sfInterestRate, SoeDefault},
{sfLateInterestRate, SoeDefault},
{sfCloseInterestRate, SoeDefault},
{sfOverpaymentInterestRate, SoeDefault},
{sfStartDate, SoeRequired},
{sfPaymentInterval, SoeRequired},
{sfGracePeriod, SoeDefault},
{sfPreviousPaymentDueDate, SoeDefault},
{sfNextPaymentDueDate, SoeDefault},
{sfPreviousTxnID, SoeRequired, SoeMutable},
{sfPreviousTxnLgrSeq, SoeRequired, SoeMutable},
{sfOwnerNode, SoeRequired, SoeImmutable},
{sfLoanBrokerNode, SoeRequired, SoeImmutable},
{sfLoanBrokerID, SoeRequired, SoeImmutable},
{sfLoanSequence, SoeRequired, SoeImmutable},
{sfBorrower, SoeRequired, SoeImmutable},
{sfLoanOriginationFee, SoeDefault, SoeImmutable},
{sfLoanServiceFee, SoeDefault, SoeImmutable},
{sfLatePaymentFee, SoeDefault, SoeImmutable},
{sfClosePaymentFee, SoeDefault, SoeImmutable},
{sfOverpaymentFee, SoeDefault, SoeImmutable},
{sfInterestRate, SoeDefault, SoeImmutable},
{sfLateInterestRate, SoeDefault, SoeImmutable},
{sfCloseInterestRate, SoeDefault, SoeImmutable},
{sfOverpaymentInterestRate, SoeDefault, SoeImmutable},
{sfStartDate, SoeRequired, SoeImmutable},
{sfPaymentInterval, SoeRequired, SoeImmutable},
{sfGracePeriod, SoeDefault, SoeImmutable},
{sfPreviousPaymentDueDate, SoeDefault, SoeMutable},
{sfNextPaymentDueDate, SoeDefault, SoeMutable},
// The loan object tracks these values:
//
// - PaymentRemaining: The number of payments left in the loan. When it
@@ -594,17 +594,17 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
//
// Note the "True" values may differ significantly from the tracked
// rounded values.
{sfPaymentRemaining, SoeDefault},
{sfPeriodicPayment, SoeRequired},
{sfPrincipalOutstanding, SoeDefault},
{sfTotalValueOutstanding, SoeDefault},
{sfManagementFeeOutstanding, SoeDefault},
{sfPaymentRemaining, SoeDefault, SoeMutable},
{sfPeriodicPayment, SoeRequired, SoeMutable},
{sfPrincipalOutstanding, SoeDefault, SoeMutable},
{sfTotalValueOutstanding, SoeDefault, SoeMutable},
{sfManagementFeeOutstanding, SoeDefault, SoeMutable},
// Based on the computed total value at creation, used for
// rounding calculated values so they are all on a
// consistent scale - that is, they all have the same
// number of digits after the decimal point (excluding
// trailing zeros).
{sfLoanScale, SoeDefault},
{sfLoanScale, SoeDefault, SoeImmutable},
}))
#undef EXPAND

View File

@@ -27,7 +27,7 @@ public:
SF_UINT32::type::value_type flags = 0)
{
// Don't call object_.set(soTemplate) - keep object_ as a free object.
// This avoids creating STBase placeholders for soeDEFAULT fields,
// This avoids creating STBase placeholders for SoeDefault fields,
// which would cause applyTemplate() to throw "may not be explicitly
// set to default" when building the SLE.
// The SLE constructor will call applyTemplate() which properly

View File

@@ -35,7 +35,7 @@ public:
std::optional<SF_AMOUNT::type::value_type> fee)
{
// Don't call object_.set(soTemplate) - keep object_ as a free object.
// This avoids creating STBase placeholders for soeDEFAULT fields,
// This avoids creating STBase placeholders for SoeDefault fields,
// which would cause applyTemplate() to throw "may not be explicitly
// set to default" when building the STTx.
// The STTx constructor will call applyTemplate() which properly

View File

@@ -360,7 +360,7 @@ public:
* object is modified. Creation and deletion are ignored.
*
*/
class NoModifiedUnmodifiableFields
class NoModifiedImmutableFields
{
// Pair is <before, after>.
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
@@ -411,7 +411,7 @@ using InvariantChecks = std::tuple<
ValidPermissionedDEX,
ValidBookDirectory,
ValidAMM,
NoModifiedUnmodifiableFields,
NoModifiedImmutableFields,
ValidPseudoAccounts,
ValidLoanBroker,
ValidLoan,

View File

@@ -137,9 +137,9 @@ InnerObjectFormats::InnerObjectFormats()
{sfCredentialType, SoeRequired},
});
add(sfPermission.jsonName.cStr(), sfPermission.getCode(), {{sfPermissionValue, SoeRequired}});
add(sfPermission.jsonName, sfPermission.getCode(), {{sfPermissionValue, SoeRequired}});
add(sfBatchSigner.jsonName.cStr(),
add(sfBatchSigner.jsonName,
sfBatchSigner.getCode(),
{{sfAccount, SoeRequired},
{sfSigningPubKey, SoeOptional},

View File

@@ -12,9 +12,9 @@ std::vector<SOElement> const&
LedgerFormats::getCommonFields()
{
static auto const kCommonFields = std::vector<SOElement>{
{sfLedgerIndex, SoeOptional},
{sfLedgerEntryType, SoeRequired},
{sfFlags, SoeRequired},
{sfLedgerIndex, SoeOptional, SoeImmutable},
{sfLedgerEntryType, SoeRequired, SoeImmutable},
{sfFlags, SoeRequired, SoeMutable},
};
return kCommonFields;
}

View File

@@ -39,7 +39,7 @@ associateAsset(SLE& sle, Asset const& asset)
// associateAsset in derived classes may change the underlying
// value, but it won't know anything about how the value relates to
// the SLE. If the template element is soeDEFAULT, and the value
// the SLE. If the template element is SoeDefault, and the value
// changed to the default value, remove the field.
if (style == SoeDefault && ta.isDefault())
sle.makeFieldAbsent(field);

View File

@@ -18,6 +18,7 @@
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
@@ -968,7 +969,7 @@ ValidPseudoAccounts::finalize(
//------------------------------------------------------------------------------
void
NoModifiedUnmodifiableFields::visitEntry(
NoModifiedImmutableFields::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
@@ -982,26 +983,105 @@ NoModifiedUnmodifiableFields::visitEntry(
changedEntries_.emplace(before, after);
}
// Check whether any immutable (or unannotated) fields in the given template
// have been modified between before and after.
// TODO(future): recurse into STObject and STArray fields using InnerObjectFormats
static bool
hasImmutableFieldChanged(STObject const& before, STObject const& after, SOTemplate const& tmpl)
{
for (auto const& elem : tmpl)
{
auto const& sf = elem.sField();
auto const mutability = elem.mutability();
auto const* bField = before.peekAtPField(sf);
auto const* aField = after.peekAtPField(sf);
bool const bPresent = (bField != nullptr) && bField->getSType() != STI_NOTPRESENT;
bool const aPresent = (aField != nullptr) && aField->getSType() != STI_NOTPRESENT;
if (mutability == SoeImmutable)
{
// Field must not change at all, including transitions between
// default (not-present) and explicit values.
if (bPresent != aPresent || (bPresent && aPresent && *bField != *aField))
return true;
}
else if (mutability == SoeImmutableSetOnce)
{
XRPL_ASSERT(
elem.style() == SoeOptional,
"xrpl::hasImmutableFieldChanged : set-once fields must be optional");
// Field may be set once, but cannot be removed or changed after
// it is present.
if (bPresent && (!aPresent || *bField != *aField))
return true;
}
// SoeMutable fields may change freely — no recursion
// into inner objects/arrays is needed because the parent
// field explicitly allows changes to its entire contents.
}
return false;
}
bool
NoModifiedUnmodifiableFields::finalize(
NoModifiedImmutableFields::finalize(
STTx const& tx,
TER const,
XRPAmount const,
ReadView const& view,
beast::Journal const& j)
{
// LedgerStateFix repairs ledger invariants, so it may need to modify
// fields that are otherwise immutable.
if (tx.getTxnType() == ttLEDGER_STATE_FIX)
return true;
static auto const kFieldChanged = [](auto const& before, auto const& after, auto const& field) {
bool const beforeField = before->isFieldPresent(field);
bool const afterField = after->isFieldPresent(field);
return beforeField != afterField || (afterField && before->at(field) != after->at(field));
};
bool const useTemplate = view.rules().enabled(fixImmutableInvariant);
for (auto const& slePair : changedEntries_)
{
auto const& before = slePair.first;
auto const& after = slePair.second;
auto const type = after->getType();
bool bad = false;
[[maybe_unused]] bool enforce = false;
// New template-based check. This is a superset of the old hardcoded
// path below: the common template includes sfLedgerEntryType, the
// explicit check below covers sfLedgerIndex, and the loan/loan-broker
// fields from the old switch are marked immutable in their ledger
// templates.
if (useTemplate)
{
bool bad = false;
auto const* format = LedgerFormats::getInstance().findByType(type);
if (format != nullptr)
bad = hasImmutableFieldChanged(*before, *after, format->getSOTemplate());
// sfLedgerIndex is a non-serialized (discardable) field
// that is not reliably present via peekAtPField, so we
// check it explicitly.
if (!bad)
bad = kFieldChanged(before, after, sfLedgerIndex);
if (bad)
{
JLOG(j.fatal()) << "Invariant failed: changed an unchangeable field for "
<< tx.getTransactionID();
return false;
}
continue;
}
// Old hardcoded check
bool badOld = false;
bool const enforceOld = view.rules().enabled(featureLendingProtocol);
switch (type)
{
case ltLOAN_BROKER:
@@ -1010,8 +1090,7 @@ NoModifiedUnmodifiableFields::finalize(
* amendment status, allowing for detection and logging of
* potential issues even when the amendment is disabled.
*/
enforce = view.rules().enabled(featureLendingProtocol);
bad = kFieldChanged(before, after, sfLedgerEntryType) ||
badOld = kFieldChanged(before, after, sfLedgerEntryType) ||
kFieldChanged(before, after, sfLedgerIndex) ||
kFieldChanged(before, after, sfSequence) ||
kFieldChanged(before, after, sfOwnerNode) ||
@@ -1029,10 +1108,9 @@ NoModifiedUnmodifiableFields::finalize(
* amendment status, allowing for detection and logging of
* potential issues even when the amendment is disabled.
*/
enforce = view.rules().enabled(featureLendingProtocol);
bad = kFieldChanged(before, after, sfLedgerEntryType) ||
badOld = kFieldChanged(before, after, sfLedgerEntryType) ||
kFieldChanged(before, after, sfLedgerIndex) ||
kFieldChanged(before, after, sfSequence) ||
kFieldChanged(before, after, sfLoanSequence) ||
kFieldChanged(before, after, sfOwnerNode) ||
kFieldChanged(before, after, sfLoanBrokerNode) ||
kFieldChanged(before, after, sfLoanBrokerID) ||
@@ -1061,19 +1139,19 @@ NoModifiedUnmodifiableFields::finalize(
* all transactions are affected because that's when it
* was added.
*/
enforce = view.rules().enabled(featureLendingProtocol);
bad = kFieldChanged(before, after, sfLedgerEntryType) ||
badOld = kFieldChanged(before, after, sfLedgerEntryType) ||
kFieldChanged(before, after, sfLedgerIndex);
}
XRPL_ASSERT(
!bad || enforce,
"xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
!badOld || enforceOld,
"xrpl::NoModifiedImmutableFields::finalize : no bad "
"changes or enforce invariant");
if (bad)
if (badOld)
{
JLOG(j.fatal()) << "Invariant failed: changed an unchangeable field for "
<< tx.getTransactionID();
if (enforce)
if (enforceOld)
return false;
}
}

View File

@@ -222,7 +222,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
}
}
// sfMutableFlags is soeDEFAULT, defaulting to 0 if not specified on
// sfMutableFlags is SoeDefault, defaulting to 0 if not specified on
// the ledger.
auto const currentMutableFlags = sleMptIssuance->getFieldU32(sfMutableFlags);
@@ -323,7 +323,7 @@ MPTokenIssuanceSet::doApply()
if (auto const transferFee = ctx_.tx[~sfTransferFee])
{
// TransferFee uses soeDEFAULT style:
// TransferFee uses SoeDefault style:
// - If the field is absent, it is interpreted as 0.
// - If the field is present, it must be non-zero.
// Therefore, when TransferFee is 0, the field should be removed.

View File

@@ -1,6 +1,7 @@
#include <test/jtx/AMM.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/Oracle.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/amount.h>
#include <test/jtx/fee.h>
@@ -2228,7 +2229,7 @@ class Invariants_test : public beast::unit_test::Suite
};
void
testNoModifiedUnmodifiableFields()
testNoModifiedImmutableFields()
{
testcase("no modified unmodifiable fields");
using namespace jtx;
@@ -2278,10 +2279,12 @@ class Invariants_test : public beast::unit_test::Suite
// TODO: Loan Object
// Template-based checks: common constant fields on AccountRoot
{
auto const mods = std::to_array<std::function<void(SLE::pointer&)>>({
[](SLE::pointer& sle) { sle->at(sfLedgerEntryType) += 1; },
[](SLE::pointer& sle) { sle->at(sfLedgerIndex) = uint256(1u); },
[](SLE::pointer& sle) { sle->at(sfAccount) = Account("other").id(); },
});
for (auto const& mod : mods)
@@ -2298,6 +2301,133 @@ class Invariants_test : public beast::unit_test::Suite
});
}
}
// Template-based checks: SoeMutable field
// (sfPreviousTxnID) on AccountRoot should NOT fail when
// modified — no invariant checks this field.
{
doInvariantCheck(
{},
[&](Account const& a1, Account const&, ApplyContext& ac) {
auto sle = ac.view().peek(keylet::account(a1.id()));
if (!sle)
return false;
sle->at(sfPreviousTxnID) = uint256(42u);
ac.view().update(sle);
return true;
},
XRPAmount{},
STTx{ttACCOUNT_SET, [](STObject&) {}},
{tesSUCCESS, tesSUCCESS});
}
// Without fixImmutableInvariant, old hardcoded path should
// still catch sfLedgerEntryType/sfLedgerIndex changes
{
auto const mods = std::to_array<std::function<void(SLE::pointer&)>>({
[](SLE::pointer& sle) { sle->at(sfLedgerEntryType) += 1; },
[](SLE::pointer& sle) { sle->at(sfLedgerIndex) = uint256(1u); },
});
for (auto const& mod : mods)
{
doInvariantCheck(
Env(*this, defaultAmendments() - fixImmutableInvariant),
{{"changed an unchangeable field"}},
[&](Account const& a1, Account const&, ApplyContext& ac) {
auto sle = ac.view().peek(keylet::account(a1.id()));
if (!sle)
return false;
mod(sle);
ac.view().update(sle);
return true;
});
}
}
// Without fixImmutableInvariant, modifying a SoeImmutable field
// that is NOT sfLedgerEntryType/sfLedgerIndex on a non-loan
// type should NOT fail (old code doesn't check it)
{
doInvariantCheck(
Env(*this, defaultAmendments() - fixImmutableInvariant),
{},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto sle = ac.view().peek(keylet::account(a1.id()));
if (!sle)
return false;
sle->at(sfAccount) = a2.id();
ac.view().update(sle);
return true;
},
XRPAmount{},
STTx{ttACCOUNT_SET, [](STObject&) {}},
{tesSUCCESS, tesSUCCESS});
}
// Template-based checks: SoeImmutableSetOnce field on Oracle.
{
Keylet oracleKeylet = keylet::amendments();
Preclose const createLegacyOracle = [this, &oracleKeylet](
Account const& a, Account const&, Env& env) {
jtx::oracle::Oracle const oracle{env, {.owner = a.id()}};
oracleKeylet = keylet::oracle(a.id(), oracle.documentID());
auto const sle = env.le(oracleKeylet);
return BEAST_EXPECT(sle && !sle->isFieldPresent(sfOracleDocumentID));
};
doInvariantCheck(
Env(*this, defaultAmendments() - fixIncludeKeyletFields),
{},
[&](Account const&, Account const&, ApplyContext& ac) {
auto sle = ac.view().peek(oracleKeylet);
if (!sle)
return false;
sle->at(sfOracleDocumentID) = std::uint32_t{1};
ac.view().update(sle);
return true;
},
XRPAmount{},
STTx{ttACCOUNT_SET, [](STObject&) {}},
{tesSUCCESS, tesSUCCESS},
createLegacyOracle);
}
{
Keylet oracleKeylet = keylet::amendments();
Preclose const createOracle = [this, &oracleKeylet](
Account const& a, Account const&, Env& env) {
jtx::oracle::Oracle const oracle{env, {.owner = a.id()}};
oracleKeylet = keylet::oracle(a.id(), oracle.documentID());
auto const sle = env.le(oracleKeylet);
return BEAST_EXPECT(sle && sle->isFieldPresent(sfOracleDocumentID));
};
auto const mods = std::to_array<std::function<void(SLE::pointer&)>>({
[](SLE::pointer& sle) { sle->at(sfOracleDocumentID) = std::uint32_t{2}; },
[](SLE::pointer& sle) { sle->makeFieldAbsent(sfOracleDocumentID); },
});
for (auto const& mod : mods)
{
doInvariantCheck(
{{"changed an unchangeable field"}},
[&](Account const&, Account const&, ApplyContext& ac) {
auto sle = ac.view().peek(oracleKeylet);
if (!sle)
return false;
mod(sle);
ac.view().update(sle);
return true;
},
XRPAmount{},
STTx{ttACCOUNT_SET, [](STObject&) {}},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
createOracle);
}
}
}
void
@@ -3094,7 +3224,7 @@ class Invariants_test : public beast::unit_test::Suite
TxAccount::A2);
doInvariantCheck(
{"violation of vault immutable data"},
{"violation of vault immutable data", "changed an unchangeable field"},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
auto sleVault = ac.view().peek(keylet);
@@ -3106,11 +3236,11 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
precloseXrp);
doInvariantCheck(
{"violation of vault immutable data"},
{"violation of vault immutable data", "changed an unchangeable field"},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
auto sleVault = ac.view().peek(keylet);
@@ -3122,11 +3252,11 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
precloseXrp);
doInvariantCheck(
{"violation of vault immutable data"},
{"violation of vault immutable data", "changed an unchangeable field"},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
auto sleVault = ac.view().peek(keylet);
@@ -3138,7 +3268,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_SET, [](STObject& tx) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
precloseXrp);
doInvariantCheck(
@@ -3226,7 +3356,7 @@ class Invariants_test : public beast::unit_test::Suite
TxAccount::A2);
doInvariantCheck(
{"updated shares must not exceed maximum"},
{"updated shares must not exceed maximum", "changed an unchangeable field"},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
auto sleVault = ac.view().peek(keylet);
@@ -3242,7 +3372,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
precloseXrp,
TxAccount::A2);
@@ -3403,7 +3533,8 @@ class Invariants_test : public beast::unit_test::Suite
{"create operation must not have updated a vault",
"shares issuer and vault pseudo-account must be the same",
"shares issuer must be a pseudo-account",
"shares issuer pseudo-account must point back to the vault"},
"shares issuer pseudo-account must point back to the vault",
"changed an unchangeable field"},
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
auto sleVault = ac.view().peek(keylet);
@@ -3419,7 +3550,7 @@ class Invariants_test : public beast::unit_test::Suite
},
XRPAmount{},
STTx{ttVAULT_CREATE, [](STObject&) {}},
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
[&](Account const& a1, Account const& a2, Env& env) {
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
@@ -4906,7 +5037,7 @@ public:
testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3);
testPermissionedDEX(defaultAmendments() - fixCleanup3_1_3);
testBookDirectoryExchangeRate();
testNoModifiedUnmodifiableFields();
testNoModifiedImmutableFields();
testValidPseudoAccounts();
testValidLoanBroker();
testVault();

View File

@@ -3673,7 +3673,7 @@ protected:
auto const loan = objects[0u];
BEAST_EXPECT(loan[sfBorrower] == lender.human());
// soeDEFAULT fields are not returned if they're in the default
// SoeDefault fields are not returned if they're in the default
// state
BEAST_EXPECT(!loan.isMember(sfCloseInterestRate));
BEAST_EXPECT(!loan.isMember(sfClosePaymentFee));