diff --git a/docs/README.md b/docs/README.md index 88c618a..797eab3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,5 @@ > [!NOTE] -> This documentation reflects the XRP Ledger release [3.2.0](https://github.com/XRPLF/rippled/tree/3.2.0). Source references throughout link to that tag. +> This documentation reflects the XRP Ledger release [3.3.0](https://github.com/XRPLF/rippled/tree/3.3.0). > [!WARNING] > 🚧 This documentation is **work in progress** diff --git a/docs/amms/README.md b/docs/amms/README.md index 934f761..e1e3e41 100644 --- a/docs/amms/README.md +++ b/docs/amms/README.md @@ -491,7 +491,7 @@ When an AMM pool contains MPT assets, the AMM pseudo-account holds `MPToken` ent [^mptoken-amm-flag]: MPToken created with lsfMPTAMM flag: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L311) [^mptoken-authorized-flag]: MPToken implicitly authorized (lsfMPTAuthorized set unconditionally): [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L311) -[^mptoken-creation]: MPToken creation for AMM pseudo-account: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L318) +[^mptoken-creation]: MPToken creation for AMM pseudo-account: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L335-L336) [^mptoken-no-owner-count]: AMM owner count not adjusted for MPToken: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L320-L321) See [MPTokens Documentation](../mpts/README.md) for complete details on `MPToken` ledger entries. @@ -523,12 +523,12 @@ Several AMM transactions (`AMMCreate`, `AMMDeposit`, `AMMWithdraw`, `AMMBid`) us - `tecNO_TARGET`: Peer account doesn't exist when creating trust line (from `trustCreate()`)[^iou-no-target] - `tefBAD_LEDGER`: Directory removal failed when deleting trust line (from `trustDelete()`)[^iou-bad-ledger] -[^iou-ripple-send]: directSendNoLimitIOU function: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L694-L743) -[^iou-ripple-credit]: directSendNoFeeIOU function: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L550-L690) -[^iou-issue]: issueIOU function: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L385-L476) -[^iou-redeem]: redeemIOU function: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L479-L547) +[^iou-ripple-send]: directSendNoLimitIOU function: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L794-L847) +[^iou-ripple-credit]: directSendNoFeeIOU function: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L643-L789) +[^iou-issue]: issueIOU function: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L397-L489) +[^iou-redeem]: redeemIOU function: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L493-L561) [^iou-dir-full]: Owner directory full check: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L218-L227) -[^iou-insuf-reserve]: Insufficient reserve to create trust line: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L654-L655) +[^iou-insuf-reserve]: Insufficient reserve to create trust line: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L674-L681) [^iou-no-line]: Trust line doesn't exist after attempting redeem: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L538-L547) [^iou-null-account]: Receiver account SLE null check: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L668-L670), [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L233-L234) [^iou-no-target]: Peer account doesn't exist check: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L239-L241) @@ -544,7 +544,7 @@ Several AMM transactions (`AMMCreate`, `AMMDeposit`, `AMMWithdraw`, `AMMBid`) us - Receiver's MPToken ledger entry doesn't exist (not authorized to hold the MPT)[^mpt-receiver-no-auth] - `tecINTERNAL`: Outstanding amount is less than the amount being redeemed when receiver is issuer[^mpt-internal] -[^mpt-object-not-found]: MPT issuance not found: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1161-L1163) +[^mpt-object-not-found]: MPT issuance not found: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1269-L1271) [^mpt-path-dry-send]: MPT transfer exceeds MaximumAmount (directSendNoLimitMPT): [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1178-L1179) [^mpt-path-dry-credit]: MPT transfer exceeds MaximumAmount (directSendNoFeeMPT): [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1084-L1085) [^mpt-insufficient-funds]: Sender MPToken balance insufficient: [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1095-L1097) @@ -595,7 +595,7 @@ The two amounts can be in any order - the AMM will automatically order them as ` **Validation against the ledger view**[^ammcreate-preclaim-validation] -[^ammcreate-preclaim-validation]: Validation against ledger view (preclaim): [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L95-L240) +[^ammcreate-preclaim-validation]: Validation against ledger view (preclaim): [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L95-L242) - `tecDUPLICATE`: an AMM already exists for this token pair - `tecNO_LINE`: `Amount` or `Amount2` issuer has `lsfRequireAuth` flag set, but account has no trust line with the issuer @@ -609,6 +609,7 @@ The two amounts can be in any order - the AMM will automatically order them as ` - `tecAMM_INVALID_TOKENS`: either `Amount` or `Amount2` is an LP token from another AMM. The code does not explicitly check for *another* AMM, but at this point, LP token from this AMM should not exist - With [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault): - `terADDRESS_COLLISION`: generated AMM account ID already exists + - `tecWRONG_ASSET`: either amount is an MPT issued by a pseudo-account (vault share tokens cannot back an AMM) - Without [AMMClawback](https://xrpl.org/resources/known-amendments#ammclawback): - `tecINTERNAL`: `Amount` or `Amount2` issuer account does not exist in the ledger - `tecNO_PERMISSION`: @@ -618,7 +619,7 @@ The two amounts can be in any order - the AMM will automatically order them as ` **Validation during doApply**[^ammcreate-doapply-validation] -[^ammcreate-doapply-validation]: Validation during doApply: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L251-L329) +[^ammcreate-doapply-validation]: Validation during doApply: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L268-L359) - `tecDUPLICATE`: - AMM pseudo-account ID generation failed (no valid account ID found after 256 attempts) @@ -628,7 +629,7 @@ The two amounts can be in any order - the AMM will automatically order them as ` ### 3.1.2. State Changes[^ammcreate-state-changes] -[^ammcreate-state-changes]: State changes (doApply): [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L251-L372) +[^ammcreate-state-changes]: State changes (doApply): [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L268-L403) - `AccountRoot` object is **created** for AMM pseudo-account: - `Account`: Generated pseudo-account ID (from collision-avoidance algorithm) @@ -742,7 +743,7 @@ The deposit mode is determined by exactly one of these flags (enforced by checki **Validation against the ledger view**[^ammdeposit-preclaim-validation] -[^ammdeposit-preclaim-validation]: Validation against ledger view (preclaim): [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L178-L380) +[^ammdeposit-preclaim-validation]: Validation against ledger view (preclaim): [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L177-L361) - `terNO_AMM`: AMM ledger entry does not exist for specified asset pair - `tecINTERNAL`: @@ -753,7 +754,10 @@ The deposit mode is determined by exactly one of these flags (enforced by checki - Authorization/freeze checks (applied unconditionally to the deposited `Amount`/`Amount2` for non-`tfLPToken` modes, and with [AMMClawback](https://xrpl.org/resources/known-amendments#ammclawback) also to the pool `Asset`/`Asset2`): - `tecNO_LINE`: the asset's issuer has `lsfRequireAuth` set, but the account has no trust line with the issuer - `tecNO_AUTH`: the asset's issuer has `lsfRequireAuth` set, and the trust line exists but lacks authorization (missing `lsfLowAuth` or `lsfHighAuth` flag) - - `tecFROZEN` (IOU/XRP) or `tecLOCKED` (MPT): the asset is frozen/locked (AMM account, currency/issuance, or depositor account) + - `tecFROZEN` (IOU/XRP) or `tecLOCKED` (MPT): the asset is frozen/locked (AMM account, currency/issuance, or depositor account). Under the `fixCleanup3_3_0` amendment, both pool assets are checked whether or not they are deposited, so a deposit now also fails when the AMM pseudo-account's holding of the non-deposited pool asset is individually frozen (the deposited funds could not later be withdrawn). Without the amendment such a deposit succeeds. The conditions with the amendment: + - the asset is globally frozen or locked + - the AMM pseudo-account's holding of either pool asset is individually frozen + - the depositor's holding of the asset is individually frozen, unless the depositor is that asset's issuer - `tecUNFUNDED_AMM`: - account has insufficient token balance to deposit - account has insufficient XRP to deposit (and LP token trust line already exists) @@ -765,7 +769,7 @@ The deposit mode is determined by exactly one of these flags (enforced by checki **Validation during doApply**[^ammdeposit-doapply-validation] -[^ammdeposit-doapply-validation]: Validation during doApply: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L383-L1014) +[^ammdeposit-doapply-validation]: Validation during doApply: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L412-L1046) - `tecINTERNAL`: AMM ledger entry does not exist (should not happen if preclaim succeeded) - `temBAD_AMOUNT`: Deposit amount after adjustment/calculation is zero or negative. Deposit amounts are adjusted based on the deposit mode (e.g., proportional calculations for tfLPToken, pool ratio adjustments for tfTwoAsset, or LP token precision adjustments). @@ -843,7 +847,7 @@ The withdrawal mode is determined by exactly one of these flags (enforced by che **Static validation**[^ammwithdraw-static-validation] -[^ammwithdraw-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L43-L53), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L57-L60), [`preflight`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L63-L173) +[^ammwithdraw-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L43-L53), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L57-L60), [`preflight`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L63-L168) - `temDISABLED`: - AMM amendment not enabled @@ -870,7 +874,7 @@ The withdrawal mode is determined by exactly one of these flags (enforced by che **Validation against the ledger view**[^ammwithdraw-preclaim-validation] -[^ammwithdraw-preclaim-validation]: Validation against ledger view (preclaim): [`AMMWithdraw.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L187-L304) +[^ammwithdraw-preclaim-validation]: Validation against ledger view (preclaim): [`AMMWithdraw.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L182-L314) - `terNO_AMM`: AMM ledger entry does not exist for specified asset pair - `tecINTERNAL`: @@ -881,7 +885,10 @@ The withdrawal mode is determined by exactly one of these flags (enforced by che - Account has zero LP tokens - `tecNO_LINE`: `Asset` or `Asset2` issuer has `lsfRequireAuth` flag set, but account has no trust line with the issuer - `tecNO_AUTH`: `Asset` or `Asset2` issuer has `lsfRequireAuth` flag set, and the trust line exists but lacks authorization (missing `lsfLowAuth` or `lsfHighAuth` flag) -- `tecFROZEN` (IOU/XRP) or `tecLOCKED` (MPT): `Asset` or `Asset2` is frozen/locked (AMM account, currency/issuance, or withdrawer account) +- `tecFROZEN` (IOU/XRP) or `tecLOCKED` (MPT): `Asset` or `Asset2` is frozen/locked (AMM account, currency/issuance, or withdrawer account). Under the `fixCleanup3_3_0` amendment, the conditions producing these codes change: + - withdrawal is always allowed when the withdrawer is the asset's issuer + - a regular individual freeze on the withdrawer's own holding no longer blocks it, only a deep freeze does + - an issuer withdrawing its own frozen token reads the pool balance ignoring the freeze - `temBAD_AMM_TOKENS`: - `LPTokenIn` issue (currency code + issuer) does not match the AMM's LP token issue - `EPrice` issue does not match the AMM's LP token issue @@ -890,7 +897,7 @@ The withdrawal mode is determined by exactly one of these flags (enforced by che **Validation during doApply**[^ammwithdraw-doapply-validation] -[^ammwithdraw-doapply-validation]: Validation during doApply: [`AMMWithdraw.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L307-L422) +[^ammwithdraw-doapply-validation]: Validation during doApply: [`AMMWithdraw.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L336-L462) - With [fixAMMv1_1](https://xrpl.org/resources/known-amendments#fixammv1_1): `tecAMM_INVALID_TOKENS`: LP token balance adjustment failed. When the withdrawer is the only remaining LP, if their LP token balance differs from the AMM's `LPTokenBalance` by more than 0.1%, the withdrawal fails. If the difference is within 0.1%, the AMM's `LPTokenBalance` is adjusted to match the account's balance to allow full withdrawal despite rounding errors. - `tecINTERNAL`: AMM ledger entry does not exist (should not happen if preclaim succeeded) @@ -898,7 +905,8 @@ The withdrawal mode is determined by exactly one of these flags (enforced by che - Withdrawing one side of the pool (one asset amount equals pool balance but the other doesn't) - Withdrawing all LP tokens but not all assets - Withdrawal amount exceeds current pool balance -- `tecAMM_FAILED`: Withdrawal constraints not satisfied (calculated withdrawal amounts don't meet minimum requirements specified in transaction fields) +- `tecAMM_FAILED`: Withdrawal constraints not satisfied (calculated withdrawal amounts don't meet minimum requirements specified in transaction fields). Under `fixCleanup3_3_0`, the `singleWithdrawEPrice` mode also fails with this code when its formula's denominator is exactly zero. Without the amendment that division throws and the transaction fails with `tefEXCEPTION` +- `tecPRECISION_LOSS`: (with both `fixCleanup3_3_0` and [fixAMMv1_3](https://xrpl.org/resources/known-amendments#fixammv1_3)) the pool product invariant fails after computing the new LP token balance. Without `fixCleanup3_3_0` the same situations are rejected by the `ValidAMM` invariant checker with `tecINVARIANT_FAILED` - `tecAMM_INVALID_TOKENS`: Calculated LP tokens or withdrawal amounts are zero or invalid - `tecINSUFFICIENT_RESERVE`: (With [fixAMMv1_2](https://xrpl.org/resources/known-amendments#fixammv1_2)) Insufficient XRP reserve to create trust line for withdrawn token that the account doesn't currently hold - `tecINCOMPLETE`: Withdrawal empties the pool (all LP tokens redeemed) but AMM account deletion is incomplete due to too many trust lines to delete in a single transaction. The withdrawal succeeds, but the AMM account cleanup must be completed with subsequent AMMDelete transactions. Limited to deleting `kMaxDeletableAmmTrustLines` trust lines per transaction. @@ -969,7 +977,7 @@ The `AMMVote` transaction allows LP token holders to vote on the AMM's trading f **Validation during doApply**[^ammvote-doapply-validation] -[^ammvote-doapply-validation]: Validation during doApply: [`AMMVote.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMVote.cpp#L82-L235) +[^ammvote-doapply-validation]: Validation during doApply: [`AMMVote.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMVote.cpp#L81-L232) - `tecINTERNAL`: AMM ledger entry does not exist (should not happen if preclaim succeeded) @@ -1045,7 +1053,7 @@ See [Bidding documentation](bidding.md) for more details. **Validation during doApply**[^ammbid-doapply-validation] -[^ammbid-doapply-validation]: Validation during doApply: [`AMMBid.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMBid.cpp#L180-L363) +[^ammbid-doapply-validation]: Validation during doApply: [`AMMBid.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMBid.cpp#L179-L355) - `tecAMM_FAILED`: Computed price exceeds `BidMax` - `tecAMM_INVALID_TOKENS`: Pay price exceeds LP token holdings @@ -1259,7 +1267,7 @@ The transaction uses AMM withdrawal logic internally: **Validation against the ledger view**[^ammclawback-preclaim-validation] -[^ammclawback-preclaim-validation]: Validation against ledger view (preclaim): [`AMMClawback.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMClawback.cpp#L102-L154) +[^ammclawback-preclaim-validation]: Validation against ledger view (preclaim): [`AMMClawback.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMClawback.cpp#L101-L153) - `terNO_ACCOUNT`: Issuer account or holder account does not exist - `terNO_AMM`: AMM pool does not exist for the specified asset pair @@ -1274,7 +1282,7 @@ The transaction uses AMM withdrawal logic internally: **Validation during doApply**[^ammclawback-doapply-validation] -[^ammclawback-doapply-validation]: Validation during doApply: [`AMMClawback.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMClawback.cpp#L169-L291) +[^ammclawback-doapply-validation]: Validation during doApply: [`AMMClawback.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMClawback.cpp#L168-L300) - `tecINTERNAL`: - AMM ledger entry does not exist @@ -1285,6 +1293,7 @@ The transaction uses AMM withdrawal logic internally: - With [fixAMMClawbackRounding](https://xrpl.org/resources/known-amendments#fixammclawbackrounding): Holder is the only remaining LP and their LP token balance differs from the AMM's `LPTokenBalance` by more than 0.1% - Calculated LP token amount during withdrawal is zero or invalid - LP token balance adjustment failed during withdrawal +- `tecPRECISION_LOSS`: (with both `fixCleanup3_3_0` and [fixAMMv1_3](https://xrpl.org/resources/known-amendments#fixammv1_3)) the pool product invariant fails after computing the new LP token balance, the same check as in AMMWithdraw - Propagate errors from withdrawal logic (uses `AMMWithdraw::equalWithdrawTokens` or `equalWithdrawMatchingOneAmount`): - `tecAMM_FAILED`: Withdrawal constraints not satisfied - Other withdrawal-related errors (see [AMMWithdraw Failure Conditions](#332-failure-conditions)) diff --git a/docs/amms/deposit.md b/docs/amms/deposit.md index 694d4cd..e7dd96c 100644 --- a/docs/amms/deposit.md +++ b/docs/amms/deposit.md @@ -39,7 +39,9 @@ The `applyGuts` function[^apply-guts] is the main entry point for processing AMM It retrieves the AMM ledger entry and current pool balances, then determines which trading fee applies to the depositor (regular or discounted for [auction slot holders](#3-gettradingfee)). Based on the transaction flags and provided fields, it dispatches to one of deposit mode handlers: three [multi-asset modes](#4-multi-asset-deposit-modes) that maintain proportional deposits or reinitialize empty pools, and three [single-asset modes](#5-single-asset-deposit-modes) that perform single-sided deposits. Each mode handler calculates the deposit amounts and LP tokens to issue, then calls the [common deposit function](#6-common-deposit-function) to execute the actual asset transfers and update the pool state. -[^apply-guts]: `AMMDeposit::applyGuts`: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L383-L484) +Under the `fixCleanup3_3_0` amendment, `applyGuts` runs the freeze checks for both pool assets whether or not they are deposited. See the [failure conditions](README.md#322-failure-conditions) for the resulting behavior change. + +[^apply-guts]: `AMMDeposit::applyGuts`: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L412-L526) ## 2.1. applyGuts Pseudo-Code @@ -188,7 +190,7 @@ The `getTradingFee` function[^get-trading-fee] is called by [`applyGuts`](#2-app It checks if the depositor holds the [auction slot](README.md#121-auction-slot) or is listed in the slot's authorized accounts and if the auction slot has not expired. If so, it returns the discounted fee (1/10th of the regular fee). Otherwise, it returns the AMM's standard trading fee. -[^get-trading-fee]: `getTradingFee`: [`AMMHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/AMMHelpers.cpp#L566-L593) +[^get-trading-fee]: `getTradingFee`: [`AMMHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/AMMHelpers.cpp#L602-L627) ## 3.1. getTradingFee Pseudo-Code @@ -706,7 +708,7 @@ def singleDepositEPrice( The `deposit()` function[^deposit] is called by all deposit modes to perform the actual asset transfers. This function validates minimum constraints for slippage protection, checks the depositor has sufficient funds for the deposit, transfers the assets from the depositor to the AMM account, and issues LP tokens to the depositor (creating a trust line if needed). -[^deposit]: `AMMDeposit::deposit`: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L501-L620) +[^deposit]: `AMMDeposit::deposit`: [`AMMDeposit.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp#L543-L670) ## 6.1. deposit Pseudo-Code diff --git a/docs/amms/helpers.md b/docs/amms/helpers.md index e1535a3..fb1b140 100644 --- a/docs/amms/helpers.md +++ b/docs/amms/helpers.md @@ -48,6 +48,10 @@ AMM helpers use **token** as a subject in many function names. This refers to an Functions for handling precision and rounding with the [fixAMMv1_3](https://xrpl.org/resources/known-amendments#fixammv1_3) amendment. These functions ensure that floating-point calculations do not introduce precision errors that could be exploited or cause inconsistencies. +The `checkAMMPrecisionLoss` helper (requires `fixCleanup3_3_0` together with `fixAMMv1_3`) verifies the pool product invariant, comparing `sqrt(asset1 * asset2)` against the new LP token balance. A failed check returns `tecPRECISION_LOSS`.[^check-precision-loss] + +[^check-precision-loss]: [`AMMHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/AMMHelpers.cpp#L436-L471) + ## 2.1. getRoundedLPTokens Calculate LP tokens with proper rounding and precision adjustment. diff --git a/docs/amms/withdraw.md b/docs/amms/withdraw.md index 3d17ca5..d4bb874 100644 --- a/docs/amms/withdraw.md +++ b/docs/amms/withdraw.md @@ -34,7 +34,9 @@ AMM helpers use **token** as a subject in many function names. This refers to an The `applyGuts` function[^applyGuts] is the main entry point for processing AMMWithdraw transactions. It retrieves the AMM ledger entry and the withdrawer's LP token balance, determines how many LP tokens to redeem (all tokens for `tfWithdrawAll`/`tfOneAssetWithdrawAll`, or the specified amount from `LPTokenIn`), then adjusts the LP token balance for precision if needed. The function gets the current pool balances and determines which trading fee applies to the withdrawer (regular or discounted for [auction slot holders](#3-gettradingfee)). Based on the transaction flags and provided fields, it dispatches to one of five withdrawal mode handlers (implementing seven total modes): two [multi-asset modes](#4-multi-asset-withdrawal-modes) that maintain proportional withdrawals, and three [single-asset modes](#5-single-asset-withdrawal-modes) that perform single-sided withdrawals. Each mode handler calculates the withdrawal amounts and LP tokens to burn, then calls the [common withdraw function](#6-common-withdraw-function) to execute the actual asset transfers and update the pool state. After the withdrawal, if the pool is empty (zero LP tokens), the function attempts to delete the AMM account - if successful, the AMM is fully removed; if incomplete due to remaining trust lines, the AMM remains in an empty state with the LP token balance set to zero. -[^applyGuts]: AMMWithdraw::applyGuts: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L307-L422) +Under the `fixCleanup3_3_0` amendment, the freeze rules relax as described in the [failure conditions](README.md#332-failure-conditions). In this path, an issuer withdrawing its own frozen token reads the pool balances with `IgnoreFreeze` instead of the `ZeroIfFrozen` shown in the pseudo-code below. + +[^applyGuts]: AMMWithdraw::applyGuts: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L336-L462) ## 2.1. applyGuts Pseudo-Code @@ -522,9 +524,9 @@ Withdraw a single asset with an effective price constraint.[^singleWithdrawEPric This mode allows users to control the effective price when redeeming LP tokens, where effective price is defined as the ratio of LP tokens redeemed to asset withdrawn. The user provides `EPrice` (maximum effective price) and optionally `Amount` (minimum withdrawal amount). As with deposits, `EPrice` is an upper bound: the trade is sized so the effective price does not exceed `EPrice`. A lower effective price means a better deal for the withdrawer (fewer LP tokens per asset withdrawn). -The function solves a derived formula from Equation 8 to calculate the LP tokens that achieve exactly the specified effective price. It then calculates the withdrawal amount as `tokensAdj / ePrice`. If the calculated amount is less than the user's optional `Amount` constraint, the transaction fails with `tecAMM_FAILED`. +The function solves a derived formula from Equation 8 to calculate the LP tokens that achieve exactly the specified effective price. It then calculates the withdrawal amount as `tokensAdj / ePrice`. If the calculated amount is less than the user's optional `Amount` constraint, the transaction fails with `tecAMM_FAILED`. Under the `fixCleanup3_3_0` amendment, a denominator (`T*f - B*E`) of exactly zero also fails with `tecAMM_FAILED`. Without the amendment that division throws and the transaction fails with `tefEXCEPTION`. -[^singleWithdrawEPrice]: AMMWithdraw::singleWithdrawEPrice: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L1073-L1130) +[^singleWithdrawEPrice]: AMMWithdraw::singleWithdrawEPrice: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L1119-L1179) ### 5.3.1. singleWithdrawEPrice Pseudo-Code @@ -592,7 +594,9 @@ def singleWithdrawEPrice( The `withdraw()` function[^withdraw] serves as the final common pathway for all withdrawal modes, executing the actual asset transfers after mode-specific handlers determine the withdrawal amounts. -[^withdraw]: AMMWithdraw::withdraw: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L472-L709) +Under `fixCleanup3_3_0` together with `fixAMMv1_3`, the common path also runs the pool product check described in [Precision and Rounding](helpers.md#2-precision-and-rounding). See the [failure conditions](README.md#332-failure-conditions) for the resulting `tecPRECISION_LOSS`. + +[^withdraw]: AMMWithdraw::withdraw: [AMMWithdraw.cpp](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp#L479-L749) This function orchestrates a sequenced validation and execution flow. It begins by verifying the withdrawer holds sufficient LP tokens to redeem, then enforces pool integrity constraints that prevent malformed states. diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 3faf1c8..0000000 --- a/docs/changelog.md +++ /dev/null @@ -1,71 +0,0 @@ -Credentials - -PermissionedDomains - -MPT: - -#7077 - ReferenceHolding field -#7040 - Implicitly authorize Vault, LoanBroker, and AMM pseudo-accounts (make sure we covered everything) -#6712 - Disallow MPTClearRequireAuth if is set -#7037 - fixCleanup3_2_0 (only enables amendment, but leaving here for reference) -#7117 - Fix non-canonical MPT amount -#5285 - Add MPT support to DEX (integrated before, there was a couple of commits since our last edits) - - -Offers: - -#7087 - Quality on hybrid offers -#5935 - Fix directory limit -#5285 - Also covered offers, as well as MPTs -#7362 - Invariant check, but the underlying code was already covered -#6716 - fixCleanup3_1_3 (empty AdditionalBooks check in hybrid offer invariant; was fixSecurity3_1_3) - - - - - -Payments: - -#7117 - Fix non-canonical MPT amount -#5978 - DepositPreauth retired -#6568 - Decouple reserve from fee in delegate payment -#6056 - Retire deletable accounts -#7040 - Pseudo-account implicit-authorization - -Trust lines: - -#5989 - Retire fixTRustLinesToSelf -#6045 - Retire DisallowIncoming amendment -#5270 - Lending Protocol (and subsequent PRs) -#5935 - Remove directory limit size - - -Path finding: - -#6571 - Enable clang-tidy readability-identifier-naming check -#6226 - Modularise HashRouter, Conditions, and OrderBookDB - - -Flow: - -#5285 - Add MPT support to DEX (mostly covered already, missing latest edits) -#6571 - Enable clang-tidy readability-identifier-naming check -#6580 - Rename transactor files/classes to tx name -#6676 - Called rename non-functional uses of ripple(d) to xrpl(d), but hides deeper refactoring -#7040 - Add unconditional canTrade on both book assets and stricter MPT transfer-rate parity -#7120 - Rename static constants -#7284 - Rename account_ to accountID_ - -AMMS: - -README.md - -#5285 - Add MPT support to DEX (Mostly covered already, latest changes) -#7040 - Frozen-check refactor -#6453 - Refactoring - TokenHelpers/RippleStateHelpers/AccountRootHelpers -#6571 - Style refactoring -#6676 - rippleCredit -> directSendNoFee -#7120 - Rename static constants -#6138 - Rename info() to header() -#6733 - Combine AMMHelpers and AMMUtils -#7284 - Rename account_ to accountID_ diff --git a/docs/credentials/README.md b/docs/credentials/README.md index b74c649..0f9071a 100644 --- a/docs/credentials/README.md +++ b/docs/credentials/README.md @@ -133,6 +133,7 @@ This ensures each credential is uniquely identified by its (subject, issuer, typ | `URI` | Blob | Optional | Reference URI for credential metadata (max 256 bytes) | | `IssuerNode` | UInt64 | Yes | Index of the issuer's owner directory page | | `SubjectNode` | UInt64 | Optional | Index of the subject's owner directory page (only present when issuer != subject)[^2] | +| `Sponsor` | AccountID | Optional | Account currently covering this credential's owner reserve. Present only while the reserve is sponsored (`Sponsor` amendment)[^6] | | `Flags` | UInt32 | Yes | Credential flags (see below); always present, 0 until `lsfAccepted` is set | | `PreviousTxnID` | Hash256 | Yes | Hash of the previous transaction that modified this entry | | `PreviousTxnLgrSeq` | UInt32 | Yes | Ledger sequence of the previous transaction | @@ -149,8 +150,9 @@ The `Flags` field can contain the following values: - When `lsfAccepted` is not set: The credential exists but has not been accepted by the subject. It cannot be used for authorization. It appears in both the issuer's and subject's owner directories, but only the issuer's owner count is incremented (the issuer pays the reserve).[^3] - When `lsfAccepted` is set: The credential has been accepted and is active. It appears in both the issuer's and subject's owner directories and can be used for authorization. -[^3]: Credential added to both directories during creation: [`Credentials.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp#L147-L174) +[^3]: Credential added to both directories during creation: [`CredentialCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp#L154-L183) [^4]: Deletion authorization: [`Credentials.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp#L89-L94) +[^6]: [`LedgerFormats.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/protocol/LedgerFormats.cpp#L13-L21), [`SponsorHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/SponsorHelpers.cpp#L267-L285) - Self-issued credentials (issuer == subject) automatically have `lsfAccepted` set during creation. ### 2.1.3. Pseudo-accounts @@ -185,11 +187,15 @@ Credentials follow the standard XRP Ledger reserve requirements: The owner reserve is calculated as `incrementalReserve` (the per-object owner reserve increment set by the network). When a credential is deleted, the reserve is freed and the owner count decreases. +Under the `Sponsor` amendment (XLS-68), a credential's reserve can be covered by a reserve sponsor recorded in the credential's `Sponsor` field. The sponsor then bears the reserve in place of the issuer or subject. Sponsorship does not carry over automatically when the subject accepts. Deletion releases the reserve against the recorded sponsor. The sponsorship mechanism is described in the [transactions documentation](../transactions/README.md).[^7] + +[^7]: [`SponsorHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/SponsorHelpers.cpp#L28-L61), [`CredentialCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp#L141-L170), [`CredentialAccept.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp#L96-L134), [`CredentialHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L97-L101) + # 3. Transactions ## 3.1. CredentialCreate Transaction -The `CredentialCreate` transaction creates a new credential from an issuer to a subject. +The `CredentialCreate` transaction creates a new credential from an issuer to a subject. Under the `fixCleanup3_3_0` amendment, the subject cannot be a pseudo-account (an AMM, Vault, or LoanBroker account). | Field Name | Required? | JSON Type | Internal Type | Description | |-------------------|:------------------:|:---------:|:-------------:|:------------------------------------------------------------| @@ -215,12 +221,15 @@ The `CredentialCreate` transaction creates a new credential from an issuer to a - `tecNO_TARGET`: Subject account does not exist - `tecDUPLICATE`: A credential with this (subject, issuer, credentialType) triple already exists +- `tecPSEUDO_ACCOUNT`: `Subject` is a pseudo-account, such as an AMM, Vault, or LoanBroker account (requires the `fixCleanup3_3_0` amendment)[^8] + +[^8]: [`CredentialCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp#L101-L105) **Validation during doApply** - `tefINTERNAL`: Failed to create credential ledger entry or issuer account not found - `tecEXPIRED`: `Expiration` field is set to a time in the past (before ledger close time) -- `tecINSUFFICIENT_RESERVE`: Issuer has insufficient XRP to pay the owner reserve +- `tecINSUFFICIENT_RESERVE`: Issuer has insufficient XRP to pay the owner reserve. For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance - `tecDIR_FULL`: Owner directory is full and cannot add new entry ### 3.1.2. State Changes @@ -233,6 +242,7 @@ The `CredentialCreate` transaction creates a new credential from an issuer to a - `URI`: Set to specified URI (if provided) - `SubjectNode`: Index in subject's owner directory - `IssuerNode`: Index in issuer's owner directory + - `Sponsor`: Set to the reserve sponsor (only when the transaction's reserve is sponsored) - `Flags`: - If issuer == subject: `lsfAccepted` is set immediately - If issuer != subject: No flags set (credential awaits acceptance) @@ -240,6 +250,8 @@ The `CredentialCreate` transaction creates a new credential from an issuer to a - Issuer's `AccountRoot` is **modified**: - `OwnerCount`: Incremented by 1 +- For a reserve-sponsored transaction, the sponsorship accounting fields on the issuer, the sponsor, and any pre-funded `Sponsorship` entry are also updated, as described in the [transactions documentation](../transactions/README.md). + - `DirectoryNode` entries are **created/modified**: - Credential added to issuer's owner directory (always) - If issuer == subject: Same directory entry (counted once) @@ -274,7 +286,7 @@ The `CredentialAccept` transaction allows a subject to accept a credential that **Validation during doApply** - `tefINTERNAL`: Subject or issuer account not found -- `tecINSUFFICIENT_RESERVE`: Subject has insufficient XRP to pay the owner reserve +- `tecINSUFFICIENT_RESERVE`: Subject has insufficient XRP to pay the owner reserve. For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance - `tecEXPIRED`: Credential has expired (current ledger time > credential's `Expiration`) ### 3.2.2. State Changes @@ -283,6 +295,7 @@ The `CredentialAccept` transaction allows a subject to accept a credential that - `Credential` object is **modified**: - `Flags`: `lsfAccepted` flag is set + - `Sponsor`: The pre-acceptance sponsor, if any, is removed. The accept transaction's reserve sponsor, if any, is recorded - Issuer's `AccountRoot` is **modified**: - `OwnerCount`: Decremented by 1 @@ -290,6 +303,8 @@ The `CredentialAccept` transaction allows a subject to accept a credential that - Subject's `AccountRoot` is **modified**: - `OwnerCount`: Incremented by 1 +- For sponsored reserves, the issuer-side release is applied against the pre-acceptance sponsor and the subject's new reserve is accounted against the accept transaction's sponsor, as described in the [transactions documentation](../transactions/README.md). + **If credential is expired:** - `Credential` object is **deleted** (removed from ledger) @@ -353,6 +368,8 @@ The `CredentialDelete` transaction removes a credential from the ledger. - Subject's `AccountRoot` is **modified** (if credential was accepted): - `OwnerCount`: Decremented by 1 +- If the credential carries a `Sponsor` field, the reserve release is accounted against that sponsor, as described in the [transactions documentation](../transactions/README.md). + - `DirectoryNode` entries are **updated**: - Credential entry removed from issuer's owner directory (always) - Credential entry removed from subject's owner directory (if subject != issuer) @@ -392,7 +409,7 @@ The sender includes the hashes of credentials they hold. During transaction proc Supplying `CredentialIDs` is itself constrained: if any listed credential is expired, the transaction fails with `tecEXPIRED` before the deposit-authorization checks run, and this applies to any transaction that carries `CredentialIDs` (Payment, EscrowFinish, etc.), even when the destination does not require deposit authorization. The expired credential is also deleted as part of this (recovering its reserve), even though the transaction fails. Under the `fixCleanup3_1_3` amendment, if that deletion itself fails, the transaction halts and returns the deletion's error (e.g. `tecINTERNAL`) instead of `tecEXPIRED`.[^5] -[^5]: [`CredentialHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L62-L65) +[^5]: [`CredentialHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L61-L64) **Example Flow**: diff --git a/docs/flow/README.md b/docs/flow/README.md index 33cfc6f..2a1c181 100644 --- a/docs/flow/README.md +++ b/docs/flow/README.md @@ -257,7 +257,7 @@ flowchart LR [^strandflow-entrypoint]: Strand Flow implementation: [`StrandFlow.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/detail/StrandFlow.h#L82) [^tostrands]: toStrands implementation: [`PaySteps.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/PaySteps.cpp#L574) [^quality-rate]: Quality stored as normalize(input / output) via `getRate`: [`STAmount.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/STAmount.cpp#L460-L481), [`Quality.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Quality.cpp#L17-L18) -[^quality-comparison]: Inverted comparison operators (lower stored value = higher quality): [`Quality.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/protocol/Quality.h#L216-L230) +[^quality-comparison]: Inverted comparison operators (lower stored value = higher quality): [`Quality.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/Quality.h#L239-L254) [^quality-increment]: Increment decreases stored value (higher quality), decrement increases it (lower quality): [`Quality.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Quality.cpp#L21-L53) [^quality-no-improvement]: Quality anti-improvement check in `qualitiesSrcRedeems`: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L738-L749) [^composed-quality]: `composedQuality` multiplies step rates: [`Quality.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Quality.cpp#L114-L131) @@ -328,10 +328,10 @@ Each step type calculates quality differently: See the [steps documentation](steps.md) for detailed quality calculations. [^xrp-quality]: XRPEndpointStep always returns `Quality{STAmount::kURateOne}`: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L253-L257) -[^xrp-quality-oc]: `qualityUpperBound` is in the base template `XRPEndpointStep` with no override in the offer crossing variant `XRPEndpointOfferCrossingStep`: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L190-L241) +[^xrp-quality-oc]: `qualityUpperBound` is in the base template `XRPEndpointStep` with no override in the offer crossing variant `XRPEndpointOfferCrossingStep`: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L189-L240) [^mpt-quality-issues]: MPTEndpointStep applies transfer rate in `qualitiesSrcIssues` only when `redeems(prevStepDebtDirection)`: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L750-L768) [^mpt-oc-prev-issues]: MPTEndpointOfferCrossingStep asserts previous step always issues: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L299-L310) -[^direct-quality-payment]: DirectIPaymentStep reads QualityIn/QualityOut from trust line fields: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L341-L380) +[^direct-quality-payment]: DirectIPaymentStep reads QualityIn/QualityOut from trust line fields: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L341-L380) [^direct-quality-issues]: DirectStepI applies transfer rate in `qualitiesSrcIssues` only when `redeems(prevStepDebtDirection)`: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L752-L771) [^direct-quality-oc]: DirectIOfferCrossingStep ignores trust line quality fields, always returns `QUALITY_ONE`: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L382-L388) [^direct-oc-prev-issues]: DirectIOfferCrossingStep asserts previous step always issues: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L296-L307) @@ -481,7 +481,9 @@ Once domain access is verified and Flow begins execution, it has two key implica **Order Book Isolation**: BookSteps are constructed with the domain ID, which affects order book directory lookup. The book directory hash includes the domain ID: `hash(BOOK_NAMESPACE, asset_in, asset_out, domainID)`. This ensures that only offers within the specified domain can be discovered and consumed. -Domain payments and offer crossing cannot consume AMM liquidity. The BookStep still builds its AMM liquidity object regardless of domain; what is suppressed is AMM *consumption*, short-circuited in `tryAMM`, which returns early when the book is domain-scoped (`if (book_.domain)`). An AMM offer can still contribute to a strand's quality *estimate* (`qualityUpperBound`/`tip` do not check the domain); only consumption is blocked. +Domain payments and offer crossing cannot consume AMM liquidity. The BookStep still builds its AMM liquidity object regardless of domain. Consumption is short-circuited in `tryAMM`, which returns early when the book is domain-scoped (`if (book_.domain)`). Under the `fixCleanup3_3_0` amendment, the quality estimate is corrected the same way: `getAMMOffer` returns no offer for a domain book, so `qualityUpperBound` and `tip` no longer include AMM liquidity that crossing cannot consume.[^domain-amm-quality] Without the amendment, an AMM offer can still contribute to a domain strand's quality estimate even though it can never be consumed. + +[^domain-amm-quality]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L904-L917), [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L820-L823) For example: @@ -489,7 +491,7 @@ For example: - Flow engine creates BookSteps with domainID - BookSteps look up domain-specific order book directories - Only domain offers and hybrid offers (in domain book) can be consumed -- AMM liquidity is not consumed (suppressed in `tryAMM`; it may still factor into quality estimation) +- AMM liquidity is not consumed (suppressed in `tryAMM`) and is excluded from quality estimation 2. Open Payment or Offer Crossing (domainID not set): - Flow engine creates BookSteps without domainID diff --git a/docs/flow/steps.md b/docs/flow/steps.md index f96b45e..cf8430b 100644 --- a/docs/flow/steps.md +++ b/docs/flow/steps.md @@ -128,14 +128,14 @@ The context affects step behavior throughout the flow process. Steps receive the | MPTEndpoint (payment) | `MPTEndpointPaymentStep`[^mptendpointpaymentstep] | `MPTAmount` | `MPTAmount` | `make_MPTEndpointStep`[^make-mptendpointstep] | | MPTEndpoint (offer crossing) | `MPTEndpointOfferCrossingStep`[^mptendpointoffercrossingstep] | `MPTAmount` | `MPTAmount` | `make_MPTEndpointStep` | -[^directipaymentstep]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L231-L279) -[^directioffercrossingstep]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L282-L337) +[^directipaymentstep]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L231-L279) +[^directioffercrossingstep]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L282-L337) [^bookpaymentstep]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L282-L369) [^bookoffercrossingstep]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L373-L557) [^xrpendpointpaymentstep]: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L167-L187) -[^xrpendpointoffercrossingstep]: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L190-L241) -[^mptendpointpaymentstep]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L242-L281) -[^mptendpointoffercrossingstep]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L284-L326) +[^xrpendpointoffercrossingstep]: [`XRPEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/XRPEndpointStep.cpp#L189-L240) +[^mptendpointpaymentstep]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L242-L281) +[^mptendpointoffercrossingstep]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L284-L326) [^iouamount]: [`IOUAmount.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/protocol/IOUAmount.h#L24-L91) [^xrpamount]: [`XRPAmount.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/protocol/XRPAmount.h#L19-L237) [^mptamount]: [`MPTAmount.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/protocol/MPTAmount.h#L16-L83) @@ -487,7 +487,7 @@ def quality(sb, qDir): return q ``` -[^directipaymentstep-quality]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L342-L380) +[^directipaymentstep-quality]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L342-L380) ### 2.2.2. `maxFlow` Implementation @@ -531,7 +531,7 @@ The `check` method[^directipaymentstep-check] validates payment-specific constra [^check-payment-no-auth]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L437) [^check-payment-no-ripple]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L445) [^check-payment-path-dry]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L458) -[^directipaymentstep-check]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L418-L463) +[^directipaymentstep-check]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L418-L463) ## 2.3. DirectIOfferCrossingStep (Offer Crossing-Specific Implementation) @@ -553,7 +553,7 @@ Otherwise, it returns using the same logic as DirectIPaymentStep. ### 2.3.3. `check` Implementation The function[^directioffercrossingstep-check] has no additional failure conditions beyond the [base class checks](#215-check-implementation). Offer crossing does not require a pre-existing trust line for `takerPays`, but placing the offer would fail if `takerGets` trust line did not exist for the holder. -[^directioffercrossingstep-check]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/DirectStep.cpp#L466-L472) +[^directioffercrossingstep-check]: [`DirectStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/DirectStep.cpp#L466-L472) # 3. XRPEndpointStep @@ -856,7 +856,7 @@ def qualitiesSrcIssues(prevDebtDir): **Payment-specific (`MPTEndpointPaymentStep`):**[^mptendpointstep-check-payment] -[^mptendpointstep-check-payment]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L331-L393) +[^mptendpointstep-check-payment]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L331-L392) [^mptendpointstep-check-payment-requireauth]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L340-L350) [^mptendpointstep-check-payment-frozen]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L361-L366) [^mptendpointstep-check-payment-dex]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L374-L376) @@ -878,7 +878,7 @@ def qualitiesSrcIssues(prevDebtDir): **Offer crossing-specific (`MPTEndpointOfferCrossingStep`):**[^mptendpointstep-check-offer] -[^mptendpointstep-check-offer]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L396-L402) +[^mptendpointstep-check-offer]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L395-L401) - No additional MPT checks: offer crossing doesn't require a pre-existing MPToken, so it relies on the standard `MPTEndpointStep` checks (above) and returns `tesSUCCESS`. @@ -980,14 +980,14 @@ The order book traversal in `forEachOffer` is implemented through `FlowOfferStre [^flowofferstream-class]: [`OfferStream.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/OfferStream.h#L129-L149) [^tofferstreambase-class]: [`OfferStream.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/OfferStream.h#L15-L109) -[^offerstream-implementation]: `TOfferStreamBase` implementation with `BookTip` delegation: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/OfferStream.cpp#L205-L232) +[^offerstream-implementation]: `TOfferStreamBase` implementation with `BookTip` delegation: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/OfferStream.cpp#L205-L232) `FlowOfferStream` adds the `permToRemove` collection, which tracks offers that should be permanently removed even if the strand is not applied. This is used by `forEachOffer` to track self-crossed offers and other invalid offers that need removal regardless of transaction outcome. -During iteration, `TOfferStreamBase::step()` determines which offers to remove from the order book.[^offerstream-step] It marks offers for permanent removal when the ledger entry is missing, the offer has expired, either amount is zero, the asset is deep frozen, the offer owner's account is no longer in the offer's domain (for domain-restricted offers), or the owner has zero balance. For unfunded offers and tiny offers with reduced quality under `fixReducedOffersV1`, it distinguishes between offers that were already in that state versus offers that became that way during the current transaction by comparing balances in the current view against the pristine `cancelView`. +During iteration, `TOfferStreamBase::step()` determines which offers to remove from the order book.[^offerstream-step] It marks offers for permanent removal when the ledger entry is missing, the offer has expired, either amount is zero, the asset is deep frozen, the offer owner's account is no longer in the offer's domain (for offers carrying a `DomainID`), or the owner has zero balance. Under the `fixCleanup3_3_0` amendment, the domain membership check runs only when the book being walked is a domain book. This keeps a hybrid offer's open-book entry consumable when its owner loses domain access, for example through credential expiry. Without the amendment the check runs during any book walk, so losing domain access also removes a hybrid offer from open-book processing. For unfunded offers and tiny offers with reduced quality under `fixReducedOffersV1`, it distinguishes between offers that were already in that state versus offers that became that way during the current transaction by comparing balances in the current view against the pristine `cancelView`. Only offers that were already unfunded or tiny are permanently removed from the ledger; offers that became unfunded or tiny during execution are simply skipped for this transaction but remain in the order book. -[^offerstream-step]: Offer removal logic in `TOfferStreamBase::step()`: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/OfferStream.cpp#L214-L327) +[^offerstream-step]: Offer removal logic in `TOfferStreamBase::step()`: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/OfferStream.cpp#L214-L333) For each valid offer that passes these checks, `TOfferStreamBase` verifies whether the offer owner has sufficient balance to cover their takerGets obligation.[^offerstream-funds-helper] For IOU issuers, this returns the full requested amount directly since they can issue unlimited amounts. @@ -1008,7 +1008,7 @@ BookStep handles three asset types, each with different authorization requiremen - **Tokens (IOUs)**: Checked via `requireAuth`, which verifies the offer owner either has a trust line to the token issuer, or the issuer doesn't require authorization (`lsfRequireAuth` flag). If the issuer requires auth and the owner lacks the appropriate auth flag on their trust line, the offer is marked for removal. - **MPTs**: Require `requireAuth` (which checks the [`lsfMPTAuthorized`](../mpts/README.md#2221-flags) flag on the holder's MPToken) and `checkMPTDEX`, the MPT DEX permission check (it runs [`canTrade`](../mpts/README.md#361-cantrade) on both book assets and, where the owner is not the issuer, [`canTransfer`](../mpts/README.md#362-cantransfer) for the [`lsfMPTCanTransfer`](../mpts/README.md#2121-flags) flag). When crossing offers where the owner will receive an MPT, if the owner doesn't have an MPToken entry, BookStep automatically creates it via `checkCreateMPT`. -[^bookstep-auth]: Asset authorization checks and MPToken creation in BookStep: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L729-L760) +[^bookstep-auth]: Asset authorization checks and MPToken creation in BookStep: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L730-L761) **Pseudocode notes:** @@ -1169,7 +1169,7 @@ def fwdImp(sb, in): ## 5.3. `forEachOffer` -[^bookstep-foreachoffer]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L686-L853) +[^bookstep-foreachoffer]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L687-L854) `BookStep::forEachOffer`[^bookstep-foreachoffer] iterates through available liquidity sources (order book offers and AMM offers) in quality order, calling a provided callback for each valid offer until the payment requirements are satisfied. @@ -1198,7 +1198,7 @@ If self-cross removal occurs before any non-self offer has been attempted, the e [^bookstep-self-cross]: Self-cross detection and permanent removal in `BookOfferCrossingStep::limitSelfCrossQuality`: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L399-L454) -[^booktip-step]: `TOfferStreamBase` stores a `BookTip` and delegates advancement to it: [`OfferStream.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/OfferStream.h#L49-L59), [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/OfferStream.cpp#L190-L206). `BookTip::step()` deletes the current offer before finding the next one: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookTip.cpp#L20-L67) +[^booktip-step]: `TOfferStreamBase` stores a `BookTip` and delegates advancement to it: [`OfferStream.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/OfferStream.h#L49-L59), [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/OfferStream.cpp#L190-L206). `BookTip::step()` deletes the current offer before finding the next one: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookTip.cpp#L18-L65) [^booktip-permanent-removal]: `OfferCreate` deletes each offer reported in `removableOffers` from both sandboxes: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L459-L465) @@ -1542,17 +1542,17 @@ def qualityUpperBound(prevStepDir): ### 5.5.2. `tipOfferQuality` Helper Function -The `tipOfferQuality` method returns the best quality available at the tip of the order book along with its source type (AMM or CLOB). The method calls the `tip()` helper function, which compares both CLOB and AMM offer qualities and returns whichever provides a better exchange rate. +The `tipOfferQuality` method returns the best quality available at the tip of the order book along with its source type (AMM or CLOB). The method calls the `tip()` helper function, which compares both CLOB and AMM offer qualities and returns whichever provides a better exchange rate. Under the `fixCleanup3_3_0` amendment, `getAMMOffer` returns no AMM offer for a domain book, so `tip()` reports the CLOB quality alone. Without the amendment, AMM quality can win the comparison even for a domain book, where crossing can never consume it (see [section 3.3 of the flow documentation](README.md#33-domain-payments)). For CLOB offers, the quality is retrieved using the BookTip iterator class[^booktip-class]. BookTip traverses offers in an order book from the highest quality to lowest quality by navigating the directory structure where qualities are encoded in the 8 rightmost bytes of directory index keys. The BookTip constructor[^booktip-constructor] takes a `Book` parameter, which contains the asset pair (in/out currencies and issuers) and an optional `domain` field. When a domain is specified, BookTip looks up the domain-specific order book directory[^booktip-domain] computed as `hash(BOOK_NAMESPACE, asset_in, asset_out, domainID)`, ensuring only domain offers are traversed. The `step()` method[^booktip-step] searches the directory for the first offer page, extracts the quality from the index[^booktip-extract-quality], and retrieves the corresponding offer ledger entry. The `quality()` method[^booktip-quality-method] then returns this extracted quality value. -[^booktip-class]: [`BookTip.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/paths/BookTip.h#L15-L61) +[^booktip-class]: [`BookTip.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/tx/paths/BookTip.h#L19-L68) [^booktip-constructor]: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookTip.cpp#L15-L18) [^booktip-domain]: [`Indexes.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Indexes.cpp#L107-L109) -[^booktip-step]: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookTip.cpp#L20-L67) +[^booktip-step]: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookTip.cpp#L18-L65) [^booktip-extract-quality]: [`BookTip.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookTip.cpp#L49) diff --git a/docs/mpts/README.md b/docs/mpts/README.md index 707a48e..9e424e3 100644 --- a/docs/mpts/README.md +++ b/docs/mpts/README.md @@ -42,6 +42,7 @@ - [4.1.1. Issuer Minting (Issuer -> Holder)](#411-issuer-minting-issuer---holder) - [4.1.2. Holder Burning (Holder -> Issuer)](#412-holder-burning-holder---issuer) - [4.1.3. Holder-to-Holder Transfer (with Transfer Fee)](#413-holder-to-holder-transfer-with-transfer-fee) +- [5. Confidential MPT Balances (ConfidentialTransfer Amendment)](#5-confidential-mpt-balances-confidentialtransfer-amendment) # 1. Introduction @@ -81,7 +82,7 @@ classDiagram +uint64 LockedAmount +Blob MPTokenMetadata +uint32 Flags - +uint32 MutableFlags + +uint32 ImmutableFlags +uint256 DomainID } @@ -153,7 +154,10 @@ Where `sequence` is the issuer's sequence number at creation time and `issuer` i | `OutstandingAmount` | UInt64 | Yes | Total amount in circulation. Equals the sum of every holder's balance, including escrow-locked amounts.[^outstanding-balance] | | `LockedAmount` | UInt64 | Optional | Amount currently locked in escrows | | `MPTokenMetadata` | Blob | Optional | Arbitrary metadata (1-1024 bytes) | -| `MutableFlags` | UInt32 | Default | Mutability permissions for capability flags (see [Flags](#2121-flags)) | +| `ImmutableFlags` | UInt32 | Default | Capability flags and fields declared immutable (see [Flags](#2121-flags), requires the DynamicMPT amendment) | +| `IssuerEncryptionKey` | Blob | Optional | Issuer's ElGamal public key for confidential balances (ConfidentialTransfer amendment, see [section 5](#5-confidential-mpt-balances-confidentialtransfer-amendment)) | +| `AuditorEncryptionKey` | Blob | Optional | Auditor's ElGamal public key, installed together with the issuer key. Requires an `AuditorEncryptedAmount` on Convert, ConvertBack, and Send (ConfidentialTransfer amendment) | +| `ConfidentialOutstandingAmount` | UInt64 | Default | Supply currently held in confidential form. Counted within `OutstandingAmount` and never exceeds it (ConfidentialTransfer amendment) | | `DomainID` | UInt256 | Optional | Permissioned domain identifier for MPT authorization (see [DomainID and Authorization](#11-domainid-and-authorization)) | | `ReferenceHolding` | UInt256 | Optional | Vault-share issuances only. Points to the vault pseudo-account's holding of the underlying asset (an `MPToken` or `RippleState`). Set internally by `VaultCreate`. `canTrade` and `canTransfer` follow it so the share inherits the underlying asset's tradability and transferability. | | `PreviousTxnID` | UInt256 | Yes | Transaction hash that most recently modified this entry | @@ -162,7 +166,7 @@ Where `sequence` is the issuer's sequence number at creation time and `issuer` i [^outstanding-balance]: [`MPTInvariant.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/invariants/MPTInvariant.cpp#L398-L418), [`finalize`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/invariants/MPTInvariant.cpp#L454-L470) **Field constraints**: -- `TransferFee`: If non-zero, requires `lsfMPTCanTransfer` flag +- `TransferFee`: If non-zero, requires `lsfMPTCanTransfer` flag and is incompatible with `lsfMPTCanHoldConfidentialBalance` - `MaximumAmount`: If set, `OutstandingAmount` cannot exceed it - `MPTokenMetadata`: Maximum 1024 bytes - `DomainID`: If set, requires `lsfMPTRequireAuth` and both `featurePermissionedDomains` and `featureSingleAssetVault` amendments @@ -178,12 +182,13 @@ Where `sequence` is the issuer's sequence number at creation time and `issuer` i | `lsfMPTCanTrade` | `0x00000010` | MPT can be traded on the decentralized exchange | | `lsfMPTCanTransfer` | `0x00000020` | MPT can be transferred between accounts | | `lsfMPTCanClawback` | `0x00000040` | Issuer can claw back MPTs from holders | +| `lsfMPTCanHoldConfidentialBalance` | `0x00000080` | Holders can keep part of their balance in encrypted form (ConfidentialTransfer amendment, see [section 5](#5-confidential-mpt-balances-confidentialtransfer-amendment)) | **Flag Mutability**: - The `lsfMPTLocked` flag is always mutable and can be set/cleared by the issuer via the `MPTokenIssuanceSet` transaction. -- All capability flags (`lsfMPTCanLock`, `lsfMPTRequireAuth`, `lsfMPTCanEscrow`, `lsfMPTCanTrade`, `lsfMPTCanTransfer`, `lsfMPTCanClawback`) are immutable by default but can be made mutable at creation time. When creating an issuance via `MPTokenIssuanceCreate`, the issuer can set mutability flags (`tmfMPTCanMutateCanLock`, `tmfMPTCanMutateRequireAuth`, `tmfMPTCanMutateCanEscrow`, `tmfMPTCanMutateCanTrade`, `tmfMPTCanMutateCanTransfer`, `tmfMPTCanMutateCanClawback`) that allow the corresponding capability flag to be set or cleared later via `MPTokenIssuanceSet`. These mutability permissions are stored in the `MutableFlags` field on the `MPTokenIssuance` ledger entry and cannot be changed after creation. +- All other capability flags, `MPTokenMetadata`, and `TransferFee` cannot be changed after creation without the [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) amendment: any `MPTokenIssuanceSet` carrying such a change fails with `temDISABLED`. With the amendment enabled, previously unset capability flags can be enabled later (never cleared), and the metadata and transfer fee can be changed, unless declared immutable. The issuer declares immutability per flag or field through the `ImmutableFlags` field, at creation or later. Immutability bits can only be added, never removed. The checks read the bits already stored on the ledger entry, so a single transaction can enable a capability and declare it immutable at the same time. **`lsfMPTCanTrade` and `lsfMPTCanTransfer` distinction**: @@ -221,11 +226,15 @@ Where `0x004F` is the OwnerDirectory space key (uppercase 'O'). Creating an `MPTokenIssuance` always requires one owner reserve. The issuer's `OwnerCount` is incremented when the issuance is created and decremented when it is destroyed. +Under the `Sponsor` amendment, the issuance's reserve can be covered by a reserve sponsor recorded in the entry's `Sponsor` field. The sponsor, rather than the issuer, then bears the reserve, and destroying the issuance releases it against the recorded sponsor. The sponsorship mechanism is described in the [transactions documentation](../transactions/README.md).[^mpt-sponsor] + +[^mpt-sponsor]: [`SponsorHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/SponsorHelpers.cpp#L28-L61), [`SponsorHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/SponsorHelpers.cpp#L267-L285), [`MPTokenIssuanceCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L123-L229) + ## 2.2. MPToken Ledger Entry The `MPToken` ledger entry (type `ltMPTOKEN = 0x007f`)[^mpt-ledger-layout] tracks an individual holder's balance and settings for a specific MPT issuance. -[^mpt-ledger-layout]: [`ledger_entries.macro`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/protocol/detail/ledger_entries.macro#L409-L417) +[^mpt-ledger-layout]: [`ledger_entries.macro`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/detail/ledger_entries.macro#L417-L431) ### 2.2.1. Object Identifier @@ -239,7 +248,7 @@ concatenated in order: The `MPTokenIssuance` key is calculated as `SHA512-Half(0x007E, MPTID)` where `0x007E` is the `MPTokenIssuance` space key and `MPTID` is the 192-bit issuance identifier.[^mpt-keylet] -[^mpt-keylet]: [`Indexes.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Indexes.cpp#L533-L541) +[^mpt-keylet]: [`Indexes.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/protocol/Indexes.cpp#L567-L575) ### 2.2.2. Fields @@ -249,6 +258,12 @@ The `MPTokenIssuance` key is calculated as `SHA512-Half(0x007E, MPTID)` where `0 | `MPTokenIssuanceID` | UInt192 | Yes | Reference to the MPT issuance (MPTID) | | `MPTAmount` | UInt64 | Default | Available (spendable) balance held by this account (max `0x7FFFFFFFFFFFFFFF`) | | `LockedAmount` | UInt64 | Optional | Portion of the holder's balance locked in MPT escrows, held separately from `MPTAmount` | +| `HolderEncryptionKey` | Blob | Optional | Holder's ElGamal public key, registered on first conversion (ConfidentialTransfer amendment) | +| `ConfidentialBalanceSpending` | Blob | Optional | Spendable confidential balance, encrypted to the holder's key (ConfidentialTransfer amendment) | +| `ConfidentialBalanceInbox` | Blob | Optional | Received confidential funds awaiting merge (ConfidentialTransfer amendment) | +| `IssuerEncryptedBalance` | Blob | Optional | Mirror of the confidential balance encrypted to the issuer's key (ConfidentialTransfer amendment) | +| `AuditorEncryptedBalance` | Blob | Optional | Mirror encrypted to the auditor's key, when the issuance has one (ConfidentialTransfer amendment) | +| `ConfidentialBalanceVersion` | UInt32 | Default | Zero on first conversion, incremented whenever the confidential spending state is operated on, protecting proofs against replay (ConfidentialTransfer amendment) | | `OwnerNode` | UInt64 | Yes | Index of the owner directory page for this holder | | `PreviousTxnID` | UInt256 | Yes | Transaction hash that most recently modified this entry | | `PreviousTxnLgrSeq` | UInt32 | Yes | Ledger sequence of the transaction that most recently modified this entry | @@ -272,9 +287,10 @@ The `lsfMPTLocked` flag can only be set if the issuance has `lsfMPTCanLock` flag The `lsfMPTAuthorized` flag is only relevant when the issuance has `lsfMPTRequireAuth` flag set. -The `lsfMPTAMM` flag is automatically set when an AMM creates an `MPToken` for an MPT in its pool (which requires the MPTokensV2 amendment). The AMM's `MPToken` is created with both `lsfMPTAMM` and `lsfMPTAuthorized`, even when the issuance has `lsfMPTRequireAuth`. AMM pseudo-accounts, like Vault and LoanBroker pseudo-accounts, are implicitly authorized, so `requireAuth` succeeds for them and they can hold a require-auth MPT. This implicit authorization cannot be revoked. An `MPTokenAuthorize` transaction naming an AMM, Vault, or LoanBroker pseudo-account as `Holder` fails with `tecNO_PERMISSION`. See [AMM documentation](../amms/README.md) for AMM pseudo-account behavior.[^mpt-amm-auth] +The `lsfMPTAMM` flag is automatically set when an AMM creates an `MPToken` for an MPT in its pool (which requires the MPTokensV2 amendment). The AMM's `MPToken` is created with both `lsfMPTAMM` and `lsfMPTAuthorized`, even when the issuance has `lsfMPTRequireAuth`. AMM pseudo-accounts, like Vault and LoanBroker pseudo-accounts, are implicitly authorized, so `requireAuth` succeeds for them and they can hold a require-auth MPT. This implicit authorization cannot be revoked. Under the `fixCleanup3_3_0` amendment, the exemption applies at the start of the authorization check: a pseudo-account passes `requireAuth` even without an existing `MPToken` entry and skips the vault-share and permissioned-domain checks. Without the amendment, the exemption only bypasses the final `lsfMPTAuthorized` flag test, so a pseudo-account with no `MPToken` entry fails strong authorization with `tecNO_AUTH`.[^mpt-pseudo-auth] An `MPTokenAuthorize` transaction naming an AMM, Vault, or LoanBroker pseudo-account as `Holder` fails with `tecNO_PERMISSION`. See [AMM documentation](../amms/README.md) for AMM pseudo-account behavior.[^mpt-amm-auth] -[^mpt-amm-auth]: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L310-L319), [`requireAuth`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L385-L390), [`MPTokenAuthorize.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp#L134-L139) +[^mpt-amm-auth]: [`AMMCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/AMMCreate.cpp#L327-L337), [`requireAuth`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L329-L348), [`MPTokenAuthorize.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp#L134-L139) +[^mpt-pseudo-auth]: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L319-L425) ### 2.2.3. Pseudo-accounts @@ -293,7 +309,11 @@ MPToken reserves are determined by the account's total `OwnerCount`: The holder's `OwnerCount` is always incremented when an `MPToken` is created and decremented when deleted. This differs from trust lines, which only increment `OwnerCount` when in non-default state. The reserve grace for the first two owned items, however, mirrors trust-line reserve behavior: no incremental reserve is enforced while `OwnerCount` is below 2.[^mpt-reserve] -[^mpt-reserve]: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L193-L204) +[^mpt-reserve]: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L191-L212) + +Under the `Sponsor` amendment, an `MPToken` created by a sponsored `MPTokenAuthorize` records the sponsor in its `Sponsor` field, and the sponsor bears the reserve. The reserve waiver for fewer than two owned objects does not apply to a sponsored transaction. `MPToken` entries auto-created during payment execution and offer crossing are never sponsored.[^mptoken-sponsor] + +[^mptoken-sponsor]: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L167-L232), [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L730-L740), [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L404-L420) # 3. Transactions @@ -311,9 +331,9 @@ The `MPTokenIssuanceCreate` transaction creates a new MPT issuance with specifie | `MPTokenMetadata` | | `Conditional` | `String - Hexadecimal` | `Blob` | | Arbitrary metadata (1-1024 bytes). By convention, should decode to JSON describing what the MPT represents. | | `DomainID` | | `No` | `String - Hexadecimal` | `UInt256` | | Permissioned domain identifier (requires amendments) | | `Flags` | | `Conditional` | `Number` | `UInt32` | `0` | Capability flags | -| `MutableFlags` | | `No` | `Number` | `UInt32` | `0` | Mutability flags. Requires [DynamicMPT](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0094-dynamic-MPT) amendment. | +| `ImmutableFlags` | | `No` | `Number` | `UInt32` | `0` | Capability flags and fields declared immutable. Requires [DynamicMPT](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0094-dynamic-MPT) amendment. | -`Conditional` modifiability (in the table above) means the field or its capability flags can be changed after creation via `MPTokenIssuanceSet`, but only if the matching `tmfMPTCanMutate*` flag was set at creation (requires the DynamicMPT amendment). +`Conditional` modifiability (in the table above) means the field or its capability flags can be changed after creation via `MPTokenIssuanceSet` under the DynamicMPT amendment, unless the matching `tif*` bit is declared in `ImmutableFlags`. Capability flags can only be enabled later, never cleared. **Transaction Flags (Capability Flags)**: @@ -325,38 +345,41 @@ The `MPTokenIssuanceCreate` transaction creates a new MPT issuance with specifie | `tfMPTCanTrade` | `0x00000010` | Enable trading on DEX | | `tfMPTCanTransfer` | `0x00000020` | Enable transfers between accounts | | `tfMPTCanClawback` | `0x00000040` | Enable issuer clawback | +| `tfMPTCanHoldConfidentialBalance` | `0x00000080` | Enable confidential balances (ConfidentialTransfer amendment) | There is no flag to lock the issuance at creation. `lsfMPTLocked` is intentionally not settable here; an issuance is always created unlocked and can be locked later via `MPTokenIssuanceSet`. -**MutableFlags (Mutability Flags)**: +**ImmutableFlags (Immutability Declarations)**: -These flags control whether the corresponding capability flags can be changed after creation via `MPTokenIssuanceSet`: +These bits declare a capability flag or field immutable. Anything not declared immutable can be changed later via `MPTokenIssuanceSet` under the DynamicMPT amendment (capability flags can only be enabled, never cleared): -| Flag Name | Hex Value | Description | -|------------------------------|--------------|-----------------------------------------------| -| `tmfMPTCanMutateCanLock` | `0x00000002` | Allow changing `lsfMPTCanLock` flag later | -| `tmfMPTCanMutateRequireAuth` | `0x00000004` | Allow changing `lsfMPTRequireAuth` flag later | -| `tmfMPTCanMutateCanEscrow` | `0x00000008` | Allow changing `lsfMPTCanEscrow` flag later | -| `tmfMPTCanMutateCanTrade` | `0x00000010` | Allow changing `lsfMPTCanTrade` flag later | -| `tmfMPTCanMutateCanTransfer` | `0x00000020` | Allow changing `lsfMPTCanTransfer` flag later | -| `tmfMPTCanMutateCanClawback` | `0x00000040` | Allow changing `lsfMPTCanClawback` flag later | -| `tmfMPTCanMutateMetadata` | `0x00010000` | Allow changing `MPTokenMetadata` field later | -| `tmfMPTCanMutateTransferFee` | `0x00020000` | Allow changing `TransferFee` field later | +| Flag Name | Hex Value | Description | +|-----------------------|--------------|------------------------------------------------| +| `tifMPTCanLock` | `0x00000002` | `lsfMPTCanLock` can never be enabled later | +| `tifMPTRequireAuth` | `0x00000004` | `lsfMPTRequireAuth` can never be enabled later | +| `tifMPTCanEscrow` | `0x00000008` | `lsfMPTCanEscrow` can never be enabled later | +| `tifMPTCanTrade` | `0x00000010` | `lsfMPTCanTrade` can never be enabled later | +| `tifMPTCanTransfer` | `0x00000020` | `lsfMPTCanTransfer` can never be enabled later | +| `tifMPTCanClawback` | `0x00000040` | `lsfMPTCanClawback` can never be enabled later | +| `tifMPTCanHoldConfidentialBalance` | `0x00000080` | `lsfMPTCanHoldConfidentialBalance` can never be enabled later (also requires the ConfidentialTransfer amendment) | +| `tifMPTMetadata` | `0x00010000` | `MPTokenMetadata` can never be changed | +| `tifMPTTransferFee` | `0x00020000` | `TransferFee` can never be changed | ### 3.1.1. Failure Conditions **Static validation**[^mptissuancecreate-static-validation] -[^mptissuancecreate-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L30-L41), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L44-L48), [`preflight`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L51-L101) +[^mptissuancecreate-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L30-L56), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L58-L63), [`preflight`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L65-L120) - `temDISABLED`: - [MPTokensV1](https://xrpl.org/resources/known-amendments#mptokensv1) amendment is not enabled - `DomainID` is specified but amendments not enabled (requires both [PermissionedDomains](https://xrpl.org/resources/known-amendments#permissioneddomains) and [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault)) - - `MutableFlags` is specified but [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) amendment is not enabled + - `ImmutableFlags` is specified but [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) amendment is not enabled + - `tfMPTCanHoldConfidentialBalance` or `tifMPTCanHoldConfidentialBalance` is specified but the ConfidentialTransfer amendment is not enabled - `temINVALID_FLAG`: - `Flags` contains a bit outside the allowed capability-flag set - - `MutableFlags` is present but is zero, or contains a bit outside the allowed mutability-flag set (when `MutableFlags` is included, at least one valid mutability bit must be set) -- `temBAD_TRANSFER_FEE`: `TransferFee` exceeds 50000 (50%) + - `ImmutableFlags` is present but is zero, or contains a bit outside the allowed immutability-flag set (when `ImmutableFlags` is included, at least one valid bit must be set) +- `temBAD_TRANSFER_FEE`: `TransferFee` exceeds 50000 (50%), or is non-zero with `tfMPTCanHoldConfidentialBalance` set - `temMALFORMED`: - `TransferFee` is non-zero but `tfMPTCanTransfer` is not set - `DomainID` is specified but is zero (must omit field if not using domains) @@ -367,9 +390,9 @@ These flags control whether the corresponding capability flags can be changed af **Validation during doApply**[^mptissuancecreate-doapply-validation] -[^mptissuancecreate-doapply-validation]: Validation during doApply (reserve, directory, and internal checks in `create`): [`MPTokenIssuanceCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L104-L172) +[^mptissuancecreate-doapply-validation]: Validation during doApply (reserve, directory, and internal checks in `create`): [`MPTokenIssuanceCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp#L123-L229) -- `tecINSUFFICIENT_RESERVE`: the account's pre-fee balance is below the reserve required for one additional owned object (base reserve plus per-owner increments) +- `tecINSUFFICIENT_RESERVE`: the account's pre-fee balance is below the reserve required for one additional owned object (base reserve plus per-owner increments). For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance - `tecDIR_FULL`: Owner directory is full and cannot accommodate the new issuance - `tecINTERNAL`: Signing account does not exist @@ -381,12 +404,13 @@ These flags control whether the corresponding capability flags can be changed af - `OutstandingAmount`: Set to 0 - `OwnerNode`: Set to directory page index - `Flags`: Set to transaction flags (excluding universal flags) - - `MutableFlags`: Set if provided + - `ImmutableFlags`: Set if provided - `AssetScale`: Set if provided - `TransferFee`: Set if provided - `MaximumAmount`: Set if provided - `MPTokenMetadata`: Set if provided - `DomainID`: Set if provided + - `Sponsor`: Set to the reserve sponsor (only when the transaction's reserve is sponsored) - Issuer's `AccountRoot` is **modified**: - `OwnerCount`: Incremented by 1 @@ -417,7 +441,7 @@ The `MPTokenIssuanceDestroy` transaction deletes an MPT issuance. This can only **Validation against the ledger view**[^mptissuancedestroy-preclaim-validation] -[^mptissuancedestroy-preclaim-validation]: Validation against ledger view (preclaim): [`MPTokenIssuanceDestroy.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp#L23-L42) +[^mptissuancedestroy-preclaim-validation]: Validation against ledger view (preclaim): [`MPTokenIssuanceDestroy.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp#L21-L40) - `tecOBJECT_NOT_FOUND`: `MPTokenIssuance` with specified MPTID does not exist - `tecNO_PERMISSION`: Signing account is not the issuer @@ -429,7 +453,7 @@ The `LockedAmount` check is a defensive guard: escrow-locked tokens remain count **Validation during doApply**[^mptissuancedestroy-doapply-validation] -[^mptissuancedestroy-doapply-validation]: Validation during doApply: [`MPTokenIssuanceDestroy.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp#L45-L59) +[^mptissuancedestroy-doapply-validation]: Validation during doApply: [`MPTokenIssuanceDestroy.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp#L43-L56) - `tecINTERNAL`: Signing account is not the issuer - `tefBAD_LEDGER`: Failed to remove issuance from owner directory (indicates ledger corruption) @@ -454,7 +478,7 @@ The `MPTokenIssuanceSet` transaction is **sent by the issuer only** to modify mu - Lock/unlock the entire issuance (global lock) - Lock/unlock individual holders - Set/clear the `DomainID` field -- Mutate the metadata, transfer fee, and capability flags (requires the DynamicMPT amendment and the corresponding mutability permissions granted at creation) +- Enable capability flags, change the metadata and transfer fee, and extend the immutability declarations (requires the DynamicMPT amendment; anything declared immutable in `ImmutableFlags` cannot be changed) | Field Name | Required? | Modifiable? | JSON Type | Internal Type | Default Value | Description | |---------------------|:------------------:|:-----------:|:----------------------:|:-------------:|:-------------:|:-----------------------------------------------------------------------------------------------------------------| @@ -463,40 +487,32 @@ The `MPTokenIssuanceSet` transaction is **sent by the issuer only** to modify mu | `MPTokenIssuanceID` | :heavy_check_mark: | `No` | `String` | `UInt192` | | The MPTID of the issuance to modify | | `Holder` | | `No` | `String` | `AccountID` | | If present, modifies holder's `MPToken`; otherwise modifies `MPTokenIssuance` | | `DomainID` | | `Yes` | `String` | `UInt256` | | Set/clear domain (only when `Holder` not present, set to zero to clear) | -| `MPTokenMetadata` | | `Yes` | `String - Hexadecimal` | `Blob` | | Change metadata (requires `tmfMPTCanMutateMetadata` permission) | -| `TransferFee` | | `Yes` | `Number` | `UInt16` | | Change transfer fee (requires `tmfMPTCanMutateTransferFee` permission, requires `lsfMPTCanTransfer` if non-zero) | -| `MutableFlags` | | `No` | `Number` | `UInt32` | `0` | Set/clear capability flags (see below, requires corresponding mutability permissions) | -| `Flags` | | `No` | `Number` | `UInt32` | `0` | Lock/unlock flags (see below) | +| `MPTokenMetadata` | | `Yes` | `String - Hexadecimal` | `Blob` | | Change metadata (fails if `tifMPTMetadata` is declared) | +| `TransferFee` | | `Yes` | `Number` | `UInt16` | | Change transfer fee (fails if `tifMPTTransferFee` is declared; a non-zero fee requires `lsfMPTCanTransfer`, already set or enabled by this transaction) | +| `ImmutableFlags` | | `No` | `Number` | `UInt32` | `0` | Declare additional capability flags or fields immutable (bits are added, never removed) | +| `IssuerEncryptionKey` | | `No` | `String - Hexadecimal` | `Blob` | | Install the issuer's ElGamal key, once (ConfidentialTransfer amendment) | +| `AuditorEncryptionKey` | | `No` | `String - Hexadecimal` | `Blob` | | Install the auditor's ElGamal key, only together with `IssuerEncryptionKey` (ConfidentialTransfer amendment) | +| `Flags` | | `No` | `Number` | `UInt32` | `0` | Lock/unlock flags and set-only capability flags (see below) | **Transaction Flags**: -| Flag Name | Hex Value | Description | -|---------------|--------------|-------------------------------| -| `tfMPTLock` | `0x00000001` | Lock the issuance or holder | -| `tfMPTUnlock` | `0x00000002` | Unlock the issuance or holder | - -**MutableFlags (Set/Clear Capability Flags)**: - -These flags are used in the `MutableFlags` field to set or clear capability flags. Each capability requires the corresponding mutability permission (set during issuance creation): - -| Flag Name | Hex Value | Description | -|--------------------------|--------------|-----------------------------------------------------------------------------------| -| `tmfMPTSetCanLock` | `0x00000001` | Set `lsfMPTCanLock` flag (requires `tmfMPTCanMutateCanLock` permission) | -| `tmfMPTClearCanLock` | `0x00000002` | Clear `lsfMPTCanLock` flag (requires `tmfMPTCanMutateCanLock` permission) | -| `tmfMPTSetRequireAuth` | `0x00000004` | Set `lsfMPTRequireAuth` flag (requires `tmfMPTCanMutateRequireAuth` permission) | -| `tmfMPTClearRequireAuth` | `0x00000008` | Clear `lsfMPTRequireAuth` flag (requires `tmfMPTCanMutateRequireAuth` permission) | -| `tmfMPTSetCanEscrow` | `0x00000010` | Set `lsfMPTCanEscrow` flag (requires `tmfMPTCanMutateCanEscrow` permission) | -| `tmfMPTClearCanEscrow` | `0x00000020` | Clear `lsfMPTCanEscrow` flag (requires `tmfMPTCanMutateCanEscrow` permission) | -| `tmfMPTSetCanTrade` | `0x00000040` | Set `lsfMPTCanTrade` flag (requires `tmfMPTCanMutateCanTrade` permission) | -| `tmfMPTClearCanTrade` | `0x00000080` | Clear `lsfMPTCanTrade` flag (requires `tmfMPTCanMutateCanTrade` permission) | -| `tmfMPTSetCanTransfer` | `0x00000100` | Set `lsfMPTCanTransfer` flag (requires `tmfMPTCanMutateCanTransfer` permission) | -| `tmfMPTClearCanTransfer` | `0x00000200` | Clear `lsfMPTCanTransfer` flag (requires `tmfMPTCanMutateCanTransfer` permission) | -| `tmfMPTSetCanClawback` | `0x00000400` | Set `lsfMPTCanClawback` flag (requires `tmfMPTCanMutateCanClawback` permission) | -| `tmfMPTClearCanClawback` | `0x00000800` | Clear `lsfMPTCanClawback` flag (requires `tmfMPTCanMutateCanClawback` permission) | +| Flag Name | Hex Value | Description | +|-----------------------|--------------|------------------------------------------| +| `tfMPTLock` | `0x00000001` | Lock the issuance or holder | +| `tfMPTUnlock` | `0x00000002` | Unlock the issuance or holder | +| `tfMPTSetCanLock` | `0x00000004` | Enable `lsfMPTCanLock` (DynamicMPT) | +| `tfMPTSetRequireAuth` | `0x00000008` | Enable `lsfMPTRequireAuth` (DynamicMPT) | +| `tfMPTSetCanEscrow` | `0x00000010` | Enable `lsfMPTCanEscrow` (DynamicMPT) | +| `tfMPTSetCanTrade` | `0x00000020` | Enable `lsfMPTCanTrade` (DynamicMPT) | +| `tfMPTSetCanTransfer` | `0x00000040` | Enable `lsfMPTCanTransfer` (DynamicMPT) | +| `tfMPTSetCanClawback` | `0x00000080` | Enable `lsfMPTCanClawback` (DynamicMPT) | +| `tfMPTSetCanHoldConfidentialBalance` | `0x00000100` | Enable `lsfMPTCanHoldConfidentialBalance` (DynamicMPT and ConfidentialTransfer) | + +The `tfMPTSet*` flags only enable capability flags. A capability flag can never be cleared. Enabling a flag whose `tif*` bit is declared in `ImmutableFlags` fails with `tecNO_PERMISSION`. **Behavior**: -- **When `Holder` is NOT specified**: Modifies the `MPTokenIssuance` (global lock/unlock, `DomainID`, or DynamicMPT mutations: metadata, transfer fee, capability flags) +- **When `Holder` is NOT specified**: Modifies the `MPTokenIssuance` (global lock/unlock, `DomainID`, or DynamicMPT mutations: enabling capability flags, changing metadata or transfer fee, extending `ImmutableFlags`) - **When `Holder` is specified**: Modifies the holder's `MPToken` (individual lock/unlock only) **Lock requirements**: @@ -507,30 +523,31 @@ These flags are used in the `MutableFlags` field to set or clear capability flag **Static validation**[^mptissuanceset-static-validation] -[^mptissuanceset-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L32-L37), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L40-L43), [`preflight`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L76-L140) +[^mptissuanceset-static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L27-L34), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L36-L40), [`preflight`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L42-L133) - `temDISABLED`: - [MPTokensV1](https://xrpl.org/resources/known-amendments#mptokensv1) amendment is not enabled - `DomainID` specified but amendments not enabled (requires [PermissionedDomains](https://xrpl.org/resources/known-amendments#permissioneddomains) and [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault)) - - Mutation fields (`MutableFlags`, `MPTokenMetadata`, or `TransferFee`) specified but [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) amendment is not enabled + - Mutations (any `tfMPTSet*` flag, `ImmutableFlags`, `MPTokenMetadata`, or `TransferFee`) specified but [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) amendment is not enabled + - `IssuerEncryptionKey`, `AuditorEncryptionKey`, `tfMPTSetCanHoldConfidentialBalance`, or `tifMPTCanHoldConfidentialBalance` specified but the ConfidentialTransfer amendment is not enabled - `temMALFORMED`: - Both `DomainID` and `Holder` specified (mutually exclusive) - `Account` equals `Holder` (cannot lock own MPToken) - - With [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault) or [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt), must specify at least one of: `tfMPTLock`, `tfMPTUnlock`, `DomainID`, or mutation fields (transaction must change something) - - `Holder` field present with mutation fields (mutually exclusive) - - Transaction flags set with mutation fields (cannot lock/unlock while mutating) + - With [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault), [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt), or ConfidentialTransfer, must specify at least one of: `tfMPTLock`, `tfMPTUnlock`, `DomainID`, encryption keys, or mutations (transaction must change something) + - `Holder` field present with mutations or encryption keys (mutually exclusive) + - `tfMPTLock` or `tfMPTUnlock` combined with mutations (cannot lock/unlock while mutating) - `MPTokenMetadata` exceeds 1024 bytes - - Non-zero `TransferFee` with `tmfMPTClearCanTransfer` in the same transaction + - `AuditorEncryptionKey` without `IssuerEncryptionKey` in the same transaction + - An encryption key that is not a valid compressed EC point - `temINVALID_FLAG`: - Both `tfMPTLock` and `tfMPTUnlock` specified - Invalid flags specified - - `MutableFlags` is zero or contains invalid flags - - `MutableFlags` sets and clears the same capability flag -- `temBAD_TRANSFER_FEE`: `TransferFee` exceeds 50000 (50%) + - `ImmutableFlags` is zero or contains invalid flags +- `temBAD_TRANSFER_FEE`: `TransferFee` exceeds 50000 (50%), or a non-zero `TransferFee` is combined with `tfMPTSetCanHoldConfidentialBalance` **Validation against the ledger view**[^mptissuanceset-preclaim-validation] -[^mptissuanceset-preclaim-validation]: Validation against ledger view (preclaim): [`checkPermission`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L143-L173), [`preclaim`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L176-L265) +[^mptissuanceset-preclaim-validation]: Validation against ledger view (preclaim): [`preclaim`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L135-L265) - `terNO_ACCOUNT`: Signing account does not exist (enforced by the base transactor, before `MPTokenIssuanceSet` preclaim) - `tecOBJECT_NOT_FOUND`: @@ -541,16 +558,19 @@ These flags are used in the `MutableFlags` field to set or clear capability flag - Signing account is not the issuer - Attempting to lock/unlock without `lsfMPTCanLock` (when [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault) or [DynamicMPT](https://xrpl.org/resources/known-amendments#dynamicmpt) is enabled). When neither amendment is enabled, an issuance without `lsfMPTCanLock` rejects any `MPTokenIssuanceSet` with `tecNO_PERMISSION` - `DomainID` field is present (to set, or to clear with `DomainID` = 0) but the issuance does not have `lsfMPTRequireAuth` - - Clearing `lsfMPTRequireAuth` (via `tmfMPTClearRequireAuth` in `MutableFlags`) while the issuance still has a `DomainID` set. A `DomainID` requires `lsfMPTRequireAuth` to remain active, so the issuer must clear the `DomainID` before clearing `RequireAuth`. - - Attempting to change a capability flag (via `MutableFlags`) without the corresponding mutability permission set during issuance creation - - Attempting to change `MPTokenMetadata` without `tmfMPTCanMutateMetadata` permission - - Attempting to change `TransferFee` without `tmfMPTCanMutateTransferFee` permission - - Setting non-zero `TransferFee` when `lsfMPTCanTransfer` flag is not set + - Enabling a capability flag (via a `tfMPTSet*` flag) whose `tif*` bit is declared in the issuance's `ImmutableFlags` + - Changing `MPTokenMetadata` when `tifMPTMetadata` is declared + - Changing `TransferFee` when `tifMPTTransferFee` is declared + - Setting non-zero `TransferFee` when `lsfMPTCanTransfer` is not set and not being enabled by the same transaction + - Setting non-zero `TransferFee` on an issuance with `lsfMPTCanHoldConfidentialBalance`, or enabling that capability while a non-zero `TransferFee` is stored + - Resubmitting an already-installed encryption key + - Installing an encryption key without the confidential capability enabled or being enabled by the same transaction + - Installing an encryption key, or enabling the confidential capability, while confidential supply is outstanding - `tecNO_DST`: `Holder` account does not exist **Validation during doApply**[^mptissuanceset-doapply-validation] -[^mptissuanceset-doapply-validation]: Validation during doApply: [`MPTokenIssuanceSet.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L268-L373) +[^mptissuanceset-doapply-validation]: Validation during doApply: [`MPTokenIssuanceSet.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L268-L400) - `tecINTERNAL`: `MPTokenIssuance` does not exist @@ -565,10 +585,9 @@ These flags are used in the `MutableFlags` field to set or clear capability flag - If `DomainID` zero: Clear `DomainID` field (remove from entry) - If `MPTokenMetadata` present: update the field, or clear it (remove from the entry) when the value is empty - If `TransferFee` present: update the field, or clear it (remove from the entry) when the value is 0 (`TransferFee` is `soeDEFAULT`, so absent means 0) - - If `MutableFlags` present with set flags: Set corresponding capability flags (e.g., `tmfMPTSetCanTrade` sets `lsfMPTCanTrade`) - - If `MutableFlags` present with clear flags: Clear corresponding capability flags (e.g., `tmfMPTClearCanTrade` clears `lsfMPTCanTrade`). Clearing `lsfMPTCanTransfer` via `tmfMPTClearCanTransfer` also clears the `TransferFee` field.[^clear-cantransfer-clears-transferfee] - -[^clear-cantransfer-clears-transferfee]: Clearing `lsfMPTCanTransfer` clears `TransferFee`: [`MPTokenIssuanceSet.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp#L313-L318) + - If any `tfMPTSet*` flag present: Set the corresponding capability flags (e.g., `tfMPTSetCanTrade` sets `lsfMPTCanTrade`) + - If `ImmutableFlags` present: OR the bits into the entry's `ImmutableFlags` (immutability extends, never shrinks) + - If `IssuerEncryptionKey` or `AuditorEncryptionKey` present: Install the key (each can be installed only once) **When `Holder` is specified** (modifying `MPToken`): @@ -625,7 +644,7 @@ Issuer-initiated authorize/unauthorize only applies when the issuance has `lsfMP **Validation against the ledger view**[^mptokenauthorize-preclaim-validation] -[^mptokenauthorize-preclaim-validation]: Validation against ledger view (preclaim): [`MPTokenAuthorize.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp#L37-L142) +[^mptokenauthorize-preclaim-validation]: Validation against ledger view (preclaim): [`MPTokenAuthorize.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp#L35-L160) **When Holder NOT specified (holder-initiated)**: @@ -654,13 +673,13 @@ Issuer-initiated authorize/unauthorize only applies when the issuance has `lsfMP **Validation during doApply**[^mptokenauthorize-doapply-validation] -[^mptokenauthorize-doapply-validation]: Validation during doApply: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L148-L268) +[^mptokenauthorize-doapply-validation]: Validation during doApply: [`MPTokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L145-L275) - `tecINTERNAL`: Signing account does not exist **When creating MPToken (holder-initiated, no flags)**: -- `tecINSUFFICIENT_RESERVE`: Account has insufficient XRP balance to cover reserve for creating `MPToken` (waived if OwnerCount < 2) +- `tecINSUFFICIENT_RESERVE`: Account has insufficient XRP balance to cover reserve for creating `MPToken` (waived if OwnerCount < 2, but never waived for a sponsored transaction). For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance - `tecDIR_FULL`: Owner directory is full and cannot accommodate the new `MPToken` **When deleting MPToken (holder-initiated, `tfMPTUnauthorize`)**: @@ -679,6 +698,7 @@ Issuer-initiated authorize/unauthorize only applies when the issuance has `lsfMP - `MPTAmount`: Set to 0 - `OwnerNode`: Set to directory page index - `Flags`: Set to 0 (not authorized initially) + - `Sponsor`: Set to the reserve sponsor (only when the transaction's reserve is sponsored) - Holder's `AccountRoot` is **modified**: - `OwnerCount`: Incremented by 1 @@ -744,7 +764,7 @@ Transaction fields are described in [Clawback Fields](https://xrpl.org/docs/refe - Issuance does not have `lsfMPTCanClawback` flag - `tecINSUFFICIENT_FUNDS`: Holder's `MPTAmount` is zero (nothing to claw back) -[^clawback-preclaim-validation]: Validation against the ledger view (preclaim): [`preclaim`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/Clawback.cpp#L184-L211), [`preclaimHelper`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/Clawback.cpp#L150-L182) +[^clawback-preclaim-validation]: Validation against the ledger view (preclaim): [`preclaim`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/Clawback.cpp#L184-L211), [`preclaimHelper`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/Clawback.cpp#L149-L181) ### 3.5.2. State Changes @@ -773,7 +793,7 @@ These checks cover *tradability* and *transferability* only. Two related concern `canTrade(view, asset)`: checks whether an asset may be traded on the DEX.[^mpt-cantrade] -[^mpt-cantrade]: [`canTrade`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L581-L619) +[^mpt-cantrade]: [`canTrade`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L607-L645) **Used by**: OfferCreate (`OfferCreate::preclaim`) and cross-currency payment book steps (`BookStep`, `MPTEndpointStep`). AMM transactions reach it indirectly via `canMPTTradeAndTransfer`. @@ -794,7 +814,7 @@ These checks cover *tradability* and *transferability* only. Two related concern `canTransfer(view, mptIssue, from, to, waive = No)` checks whether `to` may receive the MPT from `from`.[^mpt-cantransfer] -[^mpt-cantransfer]: [`canTransfer`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L524-L579) +[^mpt-cantransfer]: [`canTransfer`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp#L550-L605) **Used by**: holder-to-holder MPT payments (`MPTEndpointStep`, `Payment`), `CheckCash`/`CheckCreate`, `EscrowCreate`, Vault and Lending transactions, and offer-owner validation in `BookStep`. @@ -934,3 +954,23 @@ A `ValidAmounts` transaction invariant provides defense in depth: it rejects any When MPTokensV2 is enabled, MPT payments run through the Flow engine and lock/freeze is enforced per Flow step in `MPTEndpointStep`. A global lock (`lsfMPTLocked` on the `MPTokenIssuance`) is checked on the first step, and an individual lock (`lsfMPTLocked` on a holder's `MPToken`) on each step; a locked step returns `terLOCKED`. A pure issuer-side transfer (minting or burning) is a single Flow step (both first and last) and is exempt from the freeze check, so it succeeds even when the MPT is globally locked.[^mpt-flow-lock] [^mpt-flow-lock]: [`MPTEndpointStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/MPTEndpointStep.cpp#L843-L856) + +# 5. Confidential MPT Balances (ConfidentialTransfer Amendment) + +The `ConfidentialTransfer` amendment lets holders of an issuance with `lsfMPTCanHoldConfidentialBalance` keep part of their balance on the ledger in encrypted form (ElGamal ciphertexts) and move it without revealing amounts. Zero-knowledge proofs validate every operation: malformed ciphertexts fail with `temBAD_CIPHERTEXT`, and cryptographically invalid proofs fail with `tecBAD_PROOF`. Every confidential transaction charges nine extra base fees on top of the normal fee.[^ct-overview] + +- `ConfidentialMPTConvert` moves public balance into encrypted form: the holder's `MPTAmount` decreases, `ConfidentialOutstandingAmount` increases, and the amount lands in the holder's inbox. The first conversion registers the holder's encryption key and initializes the confidential fields. The issuer cannot convert.[^ct-convert] +- `ConfidentialMPTMergeInbox` folds the inbox into the spending balance. Received confidential funds cannot be spent or converted back until merged.[^ct-merge] +- `ConfidentialMPTSend` transfers between holders fully encrypted, without changing either holder's public `MPTAmount` or the issuance's `OutstandingAmount`. The issuance must have `lsfMPTCanTransfer`, and deposit authorization applies, including `CredentialIDs`.[^ct-send] +- `ConfidentialMPTConvertBack` reveals an amount and returns it to the public `MPTAmount`, with a range proof that the remaining confidential balance is non-negative.[^ct-convertback] +- `ConfidentialMPTClawback` lets the issuer of a `lsfMPTCanClawback` issuance claw back a holder's entire confidential balance.[^ct-clawback] + +Confidential holdings stay counted in `OutstandingAmount`, so `MPTokenIssuanceDestroy` keeps failing with `tecHAS_OBLIGATIONS` while confidential supply exists. `MPTokenAuthorize` with `tfMPTUnauthorize` fails with `tecHAS_OBLIGATIONS` while the issuance has confidential supply outstanding and the `MPToken` carries confidential fields. For non-issuer holders, spendable-balance computations count only the public `MPTAmount`, so confidential funds must be converted back before a regular Payment or DEX operation can move them.[^ct-interactions] + +[^ct-overview]: [`features.macro`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/detail/features.macro#L21), [`Protocol.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/Protocol.h#L489-L492), [`TER.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/TER.h#L133), [`TER.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/TER.h#L371) +[^ct-convert]: [`ConfidentialMPTConvert.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/ConfidentialMPTConvert.cpp#L72-L326) +[^ct-merge]: [`ConfidentialMPTMergeInbox.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/ConfidentialMPTMergeInbox.cpp#L40-L126) +[^ct-send]: [`ConfidentialMPTSend.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp#L153-L422) +[^ct-convertback]: [`ConfidentialMPTConvertBack.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/ConfidentialMPTConvertBack.cpp#L141-L295) +[^ct-clawback]: [`ConfidentialMPTClawback.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/ConfidentialMPTClawback.cpp#L53-L186) +[^ct-interactions]: [`MPTokenIssuanceDestroy.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp#L21-L40), [`MPTokenAuthorize.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp#L86-L104), [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L430-L450) diff --git a/docs/offers/README.md b/docs/offers/README.md index 31303f7..7634057 100644 --- a/docs/offers/README.md +++ b/docs/offers/README.md @@ -102,7 +102,7 @@ flowchart LR The fee and sequence number are applied to the base ledger view by the transactor before offer crossing begins. Both sandboxes below are built over that base view, so the fee is recorded outside of them and persists regardless of which one is applied: - `sb`: the crossing results, the deletions of offers consumed or removed during crossing, and the new resting offer -- `sbCancel`: the deletion of offers marked for permanent removal during crossing, such as expired, already-unfunded, invalid, or directly self-crossable offers +- `sbCancel`: the deletion of offers marked for permanent removal during crossing, such as expired, already-unfunded, invalid, no-longer-in-domain, or directly self-crossable offers When the offer will not be placed (a `tfFillOrKill` offer that cannot fully cross, or a `tfImmediateOrCancel` offer that crosses nothing), `sbCancel` is applied instead of `sb`. This discards the crossing and placement work while keeping the fee and permanent offer cleanup. See [Ledger Views and Sandboxes](../transactions/README.md#5-ledger-views-and-sandboxes) for how sandboxes provide atomic state changes. @@ -218,7 +218,7 @@ Both calculations preserve the original offer's quality, the `takerGets : takerP [^buy-offer-residual]: Buy offer residual calculation: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L527-L533) [^sell-offer-residual]: Sell offer residual calculation: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L501-L520) [^no-balance-no-offer]: No balance check after crossing: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L480-L486) -[^offer-reserve]: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L834-L844) +[^offer-reserve]: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L835-L847) ## 1.3. Rate Calculation @@ -286,8 +286,11 @@ A **domain offer** is an offer created with the `DomainID` field set. Domain off A **hybrid offer** is an offer created with both the `DomainID` field set AND the `tfHybrid` flag enabled. Hybrid offers exist simultaneously in both the domain order book and the open order book, with a primary entry in the domain book and a secondary entry (via the `AdditionalBooks` field) in the open book. When a hybrid offer is created, it only crosses with offers in the domain book, since the `DomainID` is passed to the flow engine which uses that domain's order book. Once the hybrid offer is resting on the books, it can be consumed by both domain payments/offers (via the domain book entry) and open payments/offers (via the open book entry).[^hybrid-books] +Under the `fixCleanup3_3_0` amendment, a resting hybrid offer's domain membership is re-validated only while the domain book is being walked. Losing domain access, for example through credential expiry, removes the offer during domain-book processing but leaves the open-book entry consumable. Without the amendment, the membership check ran during any book walk, so losing domain access also removed the hybrid offer during open-book processing.[^hybrid-eviction] + [^domain-book-segregation]: [`Indexes.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Indexes.cpp#L102-L110) -[^hybrid-books]: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L560-L602) +[^hybrid-books]: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L561-L603) +[^hybrid-eviction]: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/OfferStream.cpp#L253-L267) # 2. Ledger Entries @@ -442,7 +445,7 @@ Pages form a doubly-linked list structure: [^dir-page-limit]: [`ApplyView.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/ApplyView.cpp#L124-L129) [^page-keylet]: [`Indexes.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/protocol/Indexes.cpp#L362-L369) -[^dir-append-insert]: [`ApplyView.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/ledger/ApplyView.h#L301-L354) +[^dir-append-insert]: [`ApplyView.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/ledger/ApplyView.h#L326-L380) ### 2.2.3. Fields diff --git a/docs/path_finding/README.md b/docs/path_finding/README.md index dc1e49b..abf7b9c 100644 --- a/docs/path_finding/README.md +++ b/docs/path_finding/README.md @@ -397,7 +397,7 @@ The path finding algorithm searches through these types based on the requested s For the complete list of path types for each payment type, see `Pathfinder::initPathTable()`[^init-path-table]. -[^init-path-table]: Path table initialization: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L1388-L1453) +[^init-path-table]: Path table initialization: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L1365-L1430) **Configuration** @@ -859,7 +859,7 @@ def addLinks(currentPaths, incompletePaths, addFlags, continueCallback): `addLink`[^add-link] is where the actual path expansion happens - it's the function that queries the ledger and creates new path branches. -[^add-link]: Core path expansion function: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L995-L1324) +[^add-link]: Core path expansion function: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L972-L1301) **Parameters:** @@ -929,21 +929,21 @@ Each asset connection undergoes these checks in order: [^get-ripple-lines-direction]: LineDirection::incoming excludes trust lines where the account has NoRipple set: [`TrustLine.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/TrustLine.cpp#L61) [^noripple-candidate-check]: Per-candidate NoRipple check in addLink: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L1101) [^asset-cache-superset]: AssetCache returns the outgoing superset when incoming is requested but outgoing is already cached: [`AssetCache.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/AssetCache.cpp#L78-L87) -[^getpathsout]: getPathsOut computes the paths out score for an account: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L749-L854) +[^getpathsout]: getPathsOut computes the paths out score for an account: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L748-L835) [^getpathsout-auth]: getPathsOut checks lsfRequireAuth on the candidate account: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L771-L775) [^getpathsout-booksize]: Score starts with order book size: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L786) -[^getpathsout-destination-bonus]: Destination bonus of +10000: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L802-L806) +[^getpathsout-destination-bonus]: Destination bonus of +10000: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L800-L804) [^getpathsout-frozen]: Global freeze check in getPathsOut: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L776-L784) -[^getpathsout-iou-loop]: IOU trust line scoring loop: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L789-L820) -[^getpathsout-noripple]: getPathsOut skips trust lines where the peer has NoRipple set: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L808-L810) -[^getpathsout-freeze]: getPathsOut skips trust lines where the peer has frozen the line: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L812-L814) -[^getpathsout-mpt-loop]: MPT scoring loop: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L823-L849) -[^getpathsout-mpt-match]: MPT ID match check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L828-L829) -[^getpathsout-mpt-balance]: MPT zero balance or maxed out check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L831-L832) -[^getpathsout-mpt-auth]: MPT authorization check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L834-L835) -[^getpathsout-mpt-destination]: MPT destination bonus of +10000: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L837-L840) -[^getpathsout-mpt-frozen]: MPT frozen check (redundant with outer freeze check): [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L841) -[^getpathsout-mpt-count]: MPT count increment: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L845-L846) +[^getpathsout-iou-loop]: IOU trust line scoring loop: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L788-L830) +[^getpathsout-noripple]: getPathsOut skips trust lines where the peer has NoRipple set: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L805-L806) +[^getpathsout-freeze]: getPathsOut skips trust lines where the peer has frozen the line: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L807-L808) +[^getpathsout-mpt-loop]: MPT scoring loop: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L813-L831) +[^getpathsout-mpt-match]: MPT ID match check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L818-L820) +[^getpathsout-mpt-balance]: MPT zero balance or maxed out check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L818-L820) +[^getpathsout-mpt-auth]: MPT authorization check: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L818-L820) +[^getpathsout-mpt-destination]: MPT destination bonus of +10000: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L821-L825) +[^getpathsout-mpt-frozen]: MPT frozen check (redundant with outer freeze check): [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L826-L827) +[^getpathsout-mpt-count]: MPT count increment: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/xrpld/rpc/detail/Pathfinder.cpp#L827-L828) [^compare-account-candidate]: compareAccountCandidate sorts by priority descending, then account ID descending: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L108-L124) [^dest-complete-path]: Destination account with matching asset completes the path: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L1121-L1130) [^dest-high-priority]: Destination account with non-matching asset receives high priority directly: [`Pathfinder.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/xrpld/rpc/detail/Pathfinder.cpp#L1132-L1136) diff --git a/docs/payments/README.md b/docs/payments/README.md index 23739bc..bf27e9a 100644 --- a/docs/payments/README.md +++ b/docs/payments/README.md @@ -87,6 +87,8 @@ When a Payment transaction creates a new destination account (destination does n least the base reserve amount in XRP. If the XRP amount is below the base reserve, the payment fails with `tecNO_DST_INSUF_XRP`. +Under the `Sponsor` amendment, a payment carrying the `tfSponsorCreatedAccount` flag can create the destination account with any positive XRP amount, as small as one drop. The source account then sponsors the new account's base reserve (see the [transactions documentation](../transactions/README.md)). + ## 2.2. RippleState Ledger Entry See [Trust Lines Documentation](../trust_lines/README.md#21-ripplestate-ledger-entry) for complete details on @@ -148,7 +150,8 @@ When `build_path` is `true`: **Static validation**[^static-validation] -[^static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L86-L93), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L97-L109), [`preflight`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L113-L272) +[^static-validation]: Static validation (preflight): [`checkExtraFeatures`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L86-L93), [`getFlagsMask`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L97-L109), [`preflight`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L113-L287) +[^sponsor-created-account]: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L125-L138), [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L399-L423), [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L495-L521) The following preflight failure conditions apply. Cases that depend on a specific amendment are noted inline: @@ -156,12 +159,17 @@ The following preflight failure conditions apply. Cases that depend on a specifi - transaction contains `sfCredentialIDs` and the [Credentials](https://xrpl.org/resources/known-amendments#credentials) amendment is not enabled. - transaction contains `sfDomainID` and the [PermissionedDEX](https://xrpl.org/resources/known-amendments#permissioneddex) amendment is not enabled. - `Amount` is an MPT and the [MPTokensV1](https://xrpl.org/resources/known-amendments#mptokensv1) amendment is not enabled. -- `temINVALID_FLAG`: transaction flags contain invalid flags for the payment type. + - transaction contains `tfSponsorCreatedAccount` and the `Sponsor` amendment is not enabled. +- `temINVALID_FLAG`: + - transaction flags contain invalid flags for the payment type. + - `tfSponsorCreatedAccount` is combined with `tfNoRippleDirect`, `tfPartialPayment`, or `tfLimitQuality`. +- `temINVALID`: `tfSponsorCreatedAccount` with a `SendMax` or `Paths` field.[^sponsor-created-account] - `temMALFORMED`: - `sfCredentialIDs` array is empty or exceeds maximum size of 8. To leave credential IDs out, leave out the entire field. - `sfCredentialIDs` array contains duplicate credential IDs - `sfDomainID` is present but is all zeros. To omit the domain, leave out the entire field. Enforced under the `fixCleanup3_2_0` amendment.[^domainid-zero] - `temBAD_AMOUNT`: + - `Amount` is not XRP and the `tfSponsorCreatedAccount` flag is set. - `Amount` is XRP and mantissa is bigger than `100000000000000000ull`. - `SendMax` is XRP and mantissa is bigger than `100000000000000000ull`.[^isLegalNet-sendmax] - `SendMax` is specified but is negative or zero. @@ -182,7 +190,7 @@ The following preflight failure conditions apply. Cases that depend on a specifi **Validation against the ledger view**[^preclaim-validation] -[^preclaim-validation]: Validation against ledger view (preclaim): [`checkPermission`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L276-L312), [`preclaim`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L315-L402) +[^preclaim-validation]: Validation against ledger view (preclaim): [`checkGranularSemantics`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L290-L356), [`preclaim`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L359-L469) [^isLegalNet-sendmax]: Both Amount and SendMax checked via isLegalNet: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L160) [^delivermin-checks]: DeliverMin checked for legal amount and positive value: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L247-L266) [^domainid-zero]: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L128-L132) @@ -190,12 +198,14 @@ The following preflight failure conditions apply. Cases that depend on a specifi - Destination account does not exist: - `tecNO_DST`: payment is not XRP - - `telNO_DST_PARTIAL`: `tfPartialPayment` flag is set. User cannot fund a new account with a partial payment. - - `tecNO_DST_INSUF_XRP`: XRP amount is below reserve. + - `telNO_DST_PARTIAL`: `tfPartialPayment` flag is set (XRP payments, since a non-XRP payment fails with `tecNO_DST` first). User cannot fund a new account with a partial payment. Inside a batch (parent batch ID present with `BatchV1_1` enabled), this returns `tefNO_DST_PARTIAL` instead. + - `tecNO_DST_INSUF_XRP`: XRP amount is below reserve (waived when `tfSponsorCreatedAccount` is set: any positive amount funds the account and the source sponsors its base reserve). + - `tecNO_SPONSOR_PERMISSION`: `tfSponsorCreatedAccount` is set but the destination account already exists. - `tecDST_TAG_NEEDED`: destination account has `lsfRequireDestTag` flag set and transaction did not specify `DestinationTag` field. - `telBAD_PATH_COUNT`: - the `Paths` field contains more than 6 paths. - any `Path` in `Paths` has more than 8 elements. + - Inside a batch (parent batch ID present with `BatchV1_1` enabled), these cases return `tefBAD_PATH_COUNT` instead. - `tecBAD_CREDENTIALS`: Credential validation failed: - Any credential ID in `sfCredentialIDs` doesn't exist in the ledger - Any credential doesn't belong to the source account @@ -204,17 +214,18 @@ The following preflight failure conditions apply. Cases that depend on a specifi - `terNO_DELEGATE_PERMISSION`: Transaction specifies a delegate but: - The delegate authorization doesn't exist in the ledger - The delegate doesn't have transaction-level permission for Payment - - For granular permissions: the payment is not a direct payment (has `Paths` or `SendMax` with different asset), OR - - For granular permissions: neither PaymentMint (when source is issuer) nor PaymentBurn (when destination is issuer) permission is granted + - For granular permissions (`PaymentMint`/`PaymentBurn`, `PermissionDelegationV1_1` amendment): the transaction carries a field or flag outside the granular templates (for example `DeliverMin`, `DomainID`, `Paths`, or any non-universal flag), or `SendMax` names a different asset than `Amount`, or `Amount` is XRP + - For granular permissions with an IOU `Amount`: the issuer is not one of the two endpoints, or the trust line between source and destination does not exist, or `PaymentMint` is held but the payment redeems (the destination's trust limit is not positive or the source currently holds the destination's IOUs), or `PaymentBurn` is held but the source is not currently the holder + - For granular permissions with an MPT `Amount`: `PaymentMint` requires the source to be the MPT issuer and `PaymentBurn` requires the destination to be the MPT issuer **Validation during doApply** **Direct XRP Payments:**[^direct-xrp-payment] -[^direct-xrp-payment]: Direct XRP payment execution: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L594-L679) +[^direct-xrp-payment]: Direct XRP payment execution: [`Payment.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/payment/Payment.cpp#L682-L763) - `tefINTERNAL`: Source account does not exist. -- `tecUNFUNDED_PAYMENT`: sending the payment would leave the source account below its required reserve. When the source account is the fee payer, it must also be able to cover the fee, which may be drawn from the reserve; in a delegated payment the delegate pays the fee, so it is not charged against the source. +- `tecUNFUNDED_PAYMENT`: sending the payment would leave the source account below its required reserve. Under the `Sponsor` amendment the reserve is sponsorship-aware: objects covered by a sponsor stop counting, and objects or accounts the source sponsors are added. When the source is the fee payer, it must cover `Amount` plus the larger of the reserve and the fee. When the fee payer is a delegate or a fee sponsor, the source covers only `Amount` plus the reserve. - `tecNO_PERMISSION`: Destination is a pseudo-account. - If the destination has the `lsfDepositAuth` flag set: - Payment succeeds if source == destination (paying yourself) @@ -247,6 +258,7 @@ The following preflight failure conditions apply. Cases that depend on a specifi - `Account`: Destination account ID - `Balance`: Payment amount - `Sequence`: the sequence of the ledger in which the account is created + - `Sponsor`: Set to the source account (only with `tfSponsorCreatedAccount`; the source's `SponsoringAccountCount` is incremented)[^sponsor-created-account] **Cross-Currency Payments:** @@ -314,7 +326,7 @@ Direct XRP payments are the simplest payment type, transferring XRP directly fro **When the destination account exists**: The payment decreases the source account's `Balance` by the payment amount and increases the destination account's `Balance` by the same amount. If the destination account has the `lsfPasswordSpent` flag set, it is cleared to allow another free `SetRegularKey` transaction. -**When the destination account does not exist**: A new `AccountRoot` entry is created for the destination with the payment amount as its initial balance. The account's `Sequence` is set to the current ledger sequence. The payment must meet the base reserve requirement (see [Reserves](#213-reserves)), or it fails with `tecNO_DST_INSUF_XRP`. +**When the destination account does not exist**: A new `AccountRoot` entry is created for the destination with the payment amount as its initial balance. The account's `Sequence` is set to the current ledger sequence. The payment must meet the base reserve requirement (see [Reserves](#213-reserves)), or it fails with `tecNO_DST_INSUF_XRP`. With `tfSponsorCreatedAccount` (`Sponsor` amendment), the base reserve requirement is waived: any positive amount creates the account, the source is recorded as its sponsor, and the source's reserve requirement grows by one base reserve. All validation checks are performed before execution, including reserve requirements, deposit authorization, and destination tags. See [Failure Conditions](#311-failure-conditions) for complete validation rules. diff --git a/docs/permissioned_domains/README.md b/docs/permissioned_domains/README.md index d9e1bd8..6558442 100644 --- a/docs/permissioned_domains/README.md +++ b/docs/permissioned_domains/README.md @@ -28,7 +28,7 @@ PermissionedDomains enable credential-based access control for decentralized exchange activity on the XRP Ledger. A domain owner creates a PermissionedDomain specifying which credentials are required, and only accounts holding those credentials can place offers within that domain. This creates segregated order books where trading activity is restricted to authorized participants. Domain restrictions also apply to cross-currency payments that carry a `DomainID`, both the sender and receiver must be in the domain (see [§4.1 Domain Membership](#41-domain-membership)). -Domain offers support all asset types available on the XRP Ledger: XRP, tokens (issued currencies), and MPTs (Multi-Purpose Tokens, which require the `MPTokensV2` amendment). Any trading pair can be restricted to a permissioned domain. Note that domain offers cross only against the permissioned limit order book; automated market maker (AMM) pools are not consulted for domain crossing.[^amm-no-domain] +Domain offers support all asset types available on the XRP Ledger: XRP, tokens (issued currencies), and MPTs (Multi-Purpose Tokens, which require the `MPTokensV2` amendment). Any trading pair can be restricted to a permissioned domain. Note that domain offers cross only against the permissioned limit order book; automated market maker (AMM) pools are not consulted for domain crossing.[^amm-no-domain] Under the `fixCleanup3_3_0` amendment, AMM liquidity is also excluded from a domain book's quality estimate, so path ranking matches what domain crossing can deliver.[^amm-no-domain-estimate] For example, a securities exchange creates a PermissionedDomain requiring "accredited_investor" credentials from a regulatory authority. When Alice wants to trade: 1. Domain Setup: ExchangeAccountID submits PermissionedDomainSet with: `AcceptedCredentials=[{Issuer: RegulatorAccountID, CredentialType: "accredited_investor"}]` @@ -40,6 +40,7 @@ For example, a securities exchange creates a PermissionedDomain requiring "accre The domain owner always has access to their own domain. All other participants must hold valid credentials. Credentials can be revoked (via expiration or deletion), automatically removing access without the domain owner's involvement. [^amm-no-domain]: AMM pools are not consulted when a book has a domain: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/paths/BookStep.cpp#L820-L822) +[^amm-no-domain-estimate]: [`BookStep.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/BookStep.cpp#L904-L917) ## 1.1. Terminology and Concepts @@ -122,6 +123,10 @@ Under the `fixCleanup3_2_0` amendment, when a hybrid offer partially crosses on [^pd-hybrid-rate]: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L944-L953) +Under the `fixCleanup3_3_0` amendment, a resting hybrid offer's domain membership is re-validated only while the domain book is being walked. Losing domain access, for example through credential expiry, removes the offer during domain-book processing but leaves the open-book entry consumable. Without the amendment, the membership check ran during any book walk, so losing domain access also removed the hybrid offer during open-book processing.[^pd-hybrid-eviction] + +[^pd-hybrid-eviction]: [`OfferStream.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/paths/OfferStream.cpp#L253-L267) + # 3. Transactions ## 3.1. PermissionedDomainSet Transaction @@ -247,7 +252,7 @@ Function: accountInDomain(view, account, domainID) **Expiration Check**: Credential expiration is compared against the ledger's `parentCloseTime`. Expired credentials are treated as if they don't exist for domain access purposes. During transaction apply, an expired credential encountered while verifying domain membership is also deleted to reclaim its reserve; under the `fixCleanup3_1_3` amendment, if that deletion fails the transaction halts and returns the propagated error (e.g. `tecINTERNAL`) instead of continuing the membership check.[^pd-expiry-delete] -[^pd-expiry-delete]: [`removeExpired`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L62-L65), [`verifyValidDomain`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L332-L334) +[^pd-expiry-delete]: [`removeExpired`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L61-L64), [`verifyValidDomain`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/CredentialHelpers.cpp#L332-L334) **Performance**: Verification iterates through the domain's AcceptedCredentials array (max 10 entries), performing one ledger lookup per credential until a valid match is found. diff --git a/docs/transactions/README.md b/docs/transactions/README.md index 4c20bf1..b6c21ce 100644 --- a/docs/transactions/README.md +++ b/docs/transactions/README.md @@ -12,6 +12,9 @@ - [5. Ledger Views and Sandboxes](#5-ledger-views-and-sandboxes) - [5.1. Atomic Application](#51-atomic-application) - [5.1.1. Conditional Atomicity](#511-conditional-atomicity) +- [6. Fees and Reserves](#6-fees-and-reserves) +- [7. Sponsorship (Sponsor Amendment, XLS-68)](#7-sponsorship-sponsor-amendment-xls-68) +- [8. Batch Transactions (BatchV1_1 Amendment)](#8-batch-transactions-batchv1_1-amendment) # 1. Introduction @@ -222,7 +225,7 @@ classDiagram Transaction processing follows a three-phase pipeline: preflight (static validation), preclaim (ledger-based validation), and doApply (execution). Each phase can fail and return an error to the client. The `Transactor` base class coordinates this flow by calling into derived transaction classes at specific validation and execution points. -The table below shows all functions called during each phase. The "Implemented By" column indicates whether the function is implemented in `applySteps.cpp` (the top-level orchestrator for each phase), the `Transactor` base class (providing common behavior for all transactions), or the `Derived` transaction-specific class (e.g., `Payment`, `AMMCreate`). "Transactor (overridable)" means the base class provides a default implementation that derived classes may optionally override. +The table below shows the main functions called during each phase. The "Implemented By" column indicates whether the function is implemented in `applySteps.cpp` (the top-level orchestrator for each phase), the `Transactor` base class (providing common behavior for all transactions), or the `Derived` transaction-specific class (e.g., `Payment`, `AMMCreate`). "Transactor (overridable)" means the base class provides a default implementation that derived classes may optionally override. | Phase | Function | Implemented By | Description | |---------------|-------------------------------|---------------------------|--------------------------------------------------------------------------| @@ -236,16 +239,17 @@ The table below shows all functions called during each phase. The "Implemented B | **Preclaim** | `invokePreclaim()` | applySteps.cpp | Orchestrates preclaim phase | | | `checkSeqProxy()` | Transactor | Validate sequence number or ticket | | | `checkPriorTxAndLastLedger()` | Transactor | Check prior transaction and last ledger sequence | -| | `checkPermission()` | Transactor | Verify account permissions | +| | `checkSponsor()` | Transactor | Validate the sponsor account and any signatureless `Sponsorship` authorization (`Sponsor` amendment) | +| | `invokeCheckPermission()` | Transactor | Verify account permissions (delegate transaction-level and granular permissions) | | | `checkSign()` | Transactor | Verify signature authorization | -| | `checkFee()` | Transactor | Verify sufficient balance for fee | +| | `checkFee()` | Transactor | Verify the fee payer has sufficient balance for the fee | | | `preclaim()` | Derived | Transaction-specific ledger-based validation | | **Apply** | `doApply()` | applySteps.cpp | Orchestrates apply phase | | | `operator()()` | Transactor | Entry point, exception handling | | | `apply()` | Transactor | Orchestrates doApply flow | | | `preCompute()` | Transactor | Per-transaction setup (validates account) | | | `consumeSeqProxy()` | Transactor | Consume sequence or delete ticket | -| | `payFee()` | Transactor | Deduct transaction fee | +| | `payFee()` | Transactor | Deduct the transaction fee from the fee payer | | | `doApply()` | Derived | **Required override** - transaction-specific execution | @@ -284,8 +288,8 @@ Preflight validation is orchestrated by `Transactor::invokePreflight()` which - Returns `true` by default (base class implementation) 3. **preflight1()**: Account and fee field validation (Transactor base class method) - - Check `sfTicketSequence` field validity (requires `featureTicketBatch` amendment) - Check `sfDelegate` field validity (requires `featurePermissionDelegationV1_1` amendment) + - Validate the sponsor fields - Calls **preflight0()** internally for early sanity checks: - Verify transaction ID is not zero - Verify NetworkID matches (for networks > 1024) @@ -294,7 +298,7 @@ Preflight validation is orchestrated by `Transactor::invokePreflight()` which - Validate `Fee` field is XRP, non-negative, and within acceptable range - Check signing key validity via `preflightCheckSigningKey()` - Verify `AccountTxnID` and `TicketSequence` are not both present (incompatible) - - Check `tfInnerBatchTxn` flag validity (requires `featureBatch` amendment) + - Check `tfInnerBatchTxn` flag validity 4. **preflightUniversal()**: Cross-cutting amount validation (Transactor base class method) - Runs after `preflight1()` and before the derived class's `preflight()` @@ -353,13 +357,14 @@ Preclaim validation is divided into two phases: **Phase 1: Pre-signature validation** (must return NotTEC - no tec codes allowed) 1. `checkSeqProxy`: Verify sequence number or ticket exists 2. `checkPriorTxAndLastLedger`: Check PriorTxnID and LastLedgerSequence fields -3. `checkPermission`: Verify delegate permissions (if sfDelegate field present); can be overridden by specific transactions for additional permission checks -4. `checkSign`: Verify signature matches account authorization (master key, regular key, or multisig) +3. `checkSponsor`: Verify the sponsor account exists +4. `invokeCheckPermission`: Verify delegate permissions +5. `checkSign`: Verify signature matches account authorization All checks before and including signature verification must return NotTEC codes. Allowing tec results before signature verification would risk fee theft, as the fee would be charged before confirming the signature is valid. **Phase 2: Post-signature validation** (can return TER including tec codes) -1. `checkFee`: Verify account has sufficient balance for fee +1. `checkFee`: Verify the fee payer has sufficient balance for the fee 2. **Transaction-specific checks** (from derived class): - Implemented in derived class `preclaim()` method - Example: Payment checks if destination exists, validates paths, credentials, etc. @@ -371,7 +376,7 @@ All checks before and including signature verification must return NotTEC codes. Transactions that fail preclaim may or may not be added to the ledger depending on the error code. The `likelyToClaimFee` flag is set to true if the preclaim result is `tesSUCCESS`, or a `tec` error code (values >= 100) **when the transaction is not being applied as a retry** (i.e. the `TapRetry` flag is not set).[^likely-to-claim-fee] Transactions with `tec` errors are added to the ledger, consume the fee, and increment the account's sequence number, even though the transaction's intended operation fails. Other error codes (`tem`, `tef`, `ter`, `tel`) result in the transaction not being added to the ledger.[^doapply-check] This distinction ensures the network is protected from spam (by charging fees for transactions that pass basic validation) while not penalizing users for transactions that fail due to malformation or other non-chargeable issues. -[^likely-to-claim-fee]: likelyToClaimFee flag calculation: [`applySteps.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/applySteps.h#L216); the `tec`-and-not-retry rule lives in [`isTecClaimHardFail`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/applySteps.h#L28). +[^likely-to-claim-fee]: likelyToClaimFee flag calculation: [`applySteps.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/applySteps.h#L216). The `tec`-and-not-retry rule lives in [`isTecClaimHardFail`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/tx/applySteps.h#L28). [^doapply-check]: doApply checks likelyToClaimFee flag: [`applySteps.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/applySteps.cpp#L440-L441) ## 3.3. DoApply @@ -402,7 +407,7 @@ Transactions that fail preclaim may or may not be added to the ledger depending 3. **Transactor::apply()** (base class execution): - Calls `preCompute()` to perform per-transaction setup (e.g. validating the account) - Calls `consumeSeqProxy()` to consume sequence or delete ticket - - Calls `payFee()` to deduct transaction fee + - Calls `payFee()` to deduct the transaction fee from the fee payer - Updates AccountTxnID if present - Calls derived class `doApply()` for transaction-specific logic @@ -501,7 +506,7 @@ PaymentSandbox maintains two tracking systems: 2. **Deferred credits table (`tab_`)**: Tracks metadata for query purposes during transaction execution: - Credits, debits, self-debits, and original balances (for XRP, tokens, and MPTs) - - Maximum owner count seen per account + - Maximum owner counts seen per account **Hooks for Balance Management:** @@ -523,7 +528,7 @@ Accounts in a payment are not allowed to use assets acquired during that payment Accounts cannot use freed reserves acquired during the transaction's execution. PaymentSandbox enforces this through: -- `ownerCountHook(account, count)`: Returns the **maximum** `OwnerCount` the account has reached during the transaction's execution (tracked in `tab_`), not the current value. When calculating available balance (via `xrpLiquid`), this ensures freed reserves cannot be used mid-transaction. +- `ownerCountHook(account, count)`: Returns the **maximum** owner counts the account has reached during the transaction's execution (tracked in `tab_`), not the current values. When calculating available balance (via `xrpLiquid`), this ensures freed reserves cannot be used mid-transaction. With the `Sponsor` amendment, the owner, sponsored, and sponsoring counters are tracked together as a group. - `adjustOwnerCountHook(account, cur, next)`: Records owner count changes in `tab_` to maintain the maximum value across all nested payment sandboxes. @@ -580,3 +585,25 @@ else [^conditional-atomicity]: Conditional atomicity pattern in OfferCreate: [`OfferCreate.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/dex/OfferCreate.cpp#L969-L990) [^balanceHook]: Balance hook description from source comments: [`ReadView.h`](https://github.com/XRPLF/rippled/blob/3.2.0/include/xrpl/ledger/ReadView.h#L149-L153) + +# 6. Fees and Reserves + +Every transaction destroys a small amount of XRP as its fee. The minimum fee derives from the network's base fee and grows with load and with the number of signatures. The fee is checked in preclaim (`checkFee`) and deducted during apply (`payFee`), and it is charged even when the transaction fails with a `tec` code (see [section 4](#4-transaction-result-codes)).[^fees] + +Reserves are XRP an account must hold but cannot spend: a base reserve for the account itself plus one owner reserve increment for each object it owns. Reserves are not consumed. They gate creation: a transaction that would create an object fails unless the owner's balance covers the increased requirement. The per-object documents describe who bears each object's reserve.[^reserves] + +# 7. Sponsorship (Sponsor Amendment, XLS-68) + +The `Sponsor` amendment (XLS-68) lets a sponsor account pay another account's fees and cover its reserves. A transaction opts in with the common `Sponsor` and `SponsorFlags` fields, choosing fee sponsorship, reserve sponsorship, or both. The sponsor approves by co-signing the transaction (`SponsorSignature`) or in advance through a standing `Sponsorship` ledger entry, managed with the `SponsorshipSet` and `SponsorshipTransfer` transactions. The pipeline hooks are described in [section 3](#3-transaction-processing-pipeline).[^sponsorship] + +With fee sponsorship, the sponsor becomes the fee payer. With reserve sponsorship, a created object records its sponsor (the `Sponsor` field on most entry types, `HighSponsor` or `LowSponsor` per trust line side) and counts against the sponsor's reserve instead of the owner's: the owner count used for reserve calculations becomes `OwnerCount - SponsoredOwnerCount + SponsoringOwnerCount`. Deletion releases the reserve against the recorded sponsor.[^sponsor-reserve] + +# 8. Batch Transactions (BatchV1_1 Amendment) + +The `BatchV1_1` amendment adds the `Batch` transaction, which wraps several inner transactions that apply together on a closed ledger. Inner transactions carry the `tfInnerBatchTxn` flag, skip individual signature checks because the outer batch's signers authorize them, and return `tef` codes where an open-ledger submission would return `tel` codes. Preflight rejects a transaction whose flag disagrees with its batch context with `temINVALID_INNER_BATCH`.[^batch] + +[^fees]: [`Transactor.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/Transactor.cpp#L448-L473), [`Transactor.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/Transactor.cpp#L621-L695) +[^reserves]: [`Fees.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/Fees.h#L46-L56) +[^sponsorship]: [`Transactor.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/Transactor.cpp#L175-L225), [`transactions.macro`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/detail/transactions.macro#L1168-L1195), [`ledger_entries.macro`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/detail/ledger_entries.macro#L627-L637) +[^sponsor-reserve]: [`LedgerFormats.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/protocol/LedgerFormats.cpp#L11-L21), [`AccountRootHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp#L142-L200), [`AccountRootHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp#L229-L266), [`AccountRootHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp#L359-L378) +[^batch]: [`Transactor.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/Transactor.cpp#L282-L290), [`TER.h`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/TER.h#L180-L181) diff --git a/docs/trust_lines/README.md b/docs/trust_lines/README.md index 5c6a09e..9c76ebd 100644 --- a/docs/trust_lines/README.md +++ b/docs/trust_lines/README.md @@ -167,6 +167,10 @@ When a trust line side transitions from default to non-default state, the `lsfLo set and the account's `OwnerCount` is incremented. When all parameters return to default state, the reserve flag is cleared and `OwnerCount` is decremented. +Under the `Sponsor` amendment, the reserve for the source account's side of a trust line can be covered by a reserve sponsor. Each side records its own sponsor in the `LowSponsor` or `HighSponsor` field of the `RippleState` entry. Only the transaction's own account's side can be sponsored, never the counterparty's. The reserve waiver for accounts owning fewer than two objects does not apply to a sponsored transaction. When a sponsored side returns to default state, the reserve release is accounted against the recorded sponsor and the field is removed. Trust lines created implicitly during payment execution and offer crossing are never sponsored. The `SponsorshipTransfer` transaction can start, reassign, or end sponsorship of a side that currently holds a reserve. The sponsorship mechanism is described in the [transactions documentation](../transactions/README.md).[^tl-sponsor] + +[^tl-sponsor]: [`ledger_entries.macro`](https://github.com/XRPLF/rippled/blob/3.3.0/include/xrpl/protocol/detail/ledger_entries.macro#L280-L294), [`TrustSet.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/TrustSet.cpp#L319-L331), [`TokenHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/ledger/helpers/TokenHelpers.cpp#L1480-L1497), [`SponsorshipTransfer.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp#L238-L295) + # 3. Transactions ## 3.1. RippleState Transactions @@ -218,7 +222,7 @@ to [TrustSet Flags](https://xrpl.org/docs/references/protocol/transactions/types - `temDST_IS_SRC`: the source account and the destination account (`LimitAmount.issuer`) are the same. - `tecNO_DST`: the [AMM](https://xrpl.org/resources/known-amendments#amm) or [SingleAssetVault](https://xrpl.org/resources/known-amendments#singleassetvault) amendment is enabled and the destination (issuer) account does not exist. - `tecNO_PERMISSION`: the destination account has the `lsfDisallowIncomingTrustline` flag set: - - If the trust line was already created for a destination with `lsfDisallowIncomingTrustline` and amendment [fixDisallowIncomingV1](https://xrpl.org/resources/known-amendments#fixdisallowincomingv1) was enabled, do not fail. + - If the trust line already exists, do not fail (the [fixDisallowIncomingV1](https://xrpl.org/resources/known-amendments#fixdisallowincomingv1) amendment that introduced this exemption was retired, so it applies unconditionally). - If the destination account is a pseudo-account: - `sfAMMID`: destination is an AMM account (has `sfAMMID` field), but the trust line does not already exist between source and AMM. - `tecAMM_EMPTY`: AMM has zero LP IOUs - cannot create trust lines to empty AMMs. @@ -240,8 +244,8 @@ to [TrustSet Flags](https://xrpl.org/docs/references/protocol/transactions/types - `tecNO_DST`: destination account does not exist. - `tecNO_PERMISSION`: the user is trying to set `tfSetNoRipple` and the source account's balance on the trust line is negative. - `tecINSUF_RESERVE_LINE`: user does not have enough balance to cover the reserve and wants to modify an existing trust line, regardless - of whether they or the counterparty have created the original trust line. -- `tecNO_LINE_INSUF_RESERVE`: user does not have enough balance to cover the reserve and wants to create a new trust line. + of whether they or the counterparty have created the original trust line. For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance. +- `tecNO_LINE_INSUF_RESERVE`: user does not have enough balance to cover the reserve and wants to create a new trust line. For a sponsored reserve, the sponsor has insufficient XRP or the pre-funded sponsorship has no remaining owner-count allowance. - `tecNO_LINE_REDUNDANT`: trust line does not already exist, amount is `0`, and `QualityIn` and `QualityOut` are either not set, or set to their default value (`1,000,000,000`), and if `tfSetfAuth` flag is not set. #### 3.1.1.2. State Changes @@ -274,8 +278,10 @@ to [TrustSet Flags](https://xrpl.org/docs/references/protocol/transactions/types - If account's parameters in a trust line change to non-default values such that it requires reserve but did not before: - Set appropriate `lsfLowReserve` or `lsfHighReserve` flag + - If the transaction's reserve is sponsored, record the sponsor in the side's `LowSponsor` or `HighSponsor` field - If account no longer requires reserve because its values in a trust line are now default values: - Clear appropriate `lsfLowReserve` or `lsfHighReserve` flag + - The reserve release is accounted against the sponsor recorded on that side, if any, and the sponsor field is removed - Only the NoRipple, freeze, authorization, and reserve flag bits are individually set or cleared (as described above); all other stored flag bits are preserved, and `sfFlags` is rewritten only if it changed. @@ -289,7 +295,7 @@ to [TrustSet Flags](https://xrpl.org/docs/references/protocol/transactions/types - The source account's NoRipple flag (`lsfLowNoRipple` or `lsfHighNoRipple`) is set if the TrustSet transaction contains `tfSetNoRipple` and not `tfClearNoRipple`[^trustcreate-noripple-src]. - The destination account's NoRipple flag is set if the destination account does **not** have `lsfDefaultRipple` on their account[^trustcreate-noripple-dst]. `lsfDefaultRipple` is an account-level flag set via AccountSet (`asfDefaultRipple`). When an issuer sets `lsfDefaultRipple`, new trust lines are created without NoRipple on the issuer's side, allowing rippling by default. -[^modify-then-delete]: Default state check and deletion after modification: [`TrustSet.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/tx/transactors/token/TrustSet.cpp#L595-L600) +[^modify-then-delete]: Default state check and deletion after modification: [`TrustSet.cpp`](https://github.com/XRPLF/rippled/blob/3.3.0/src/libxrpl/tx/transactors/token/TrustSet.cpp#L617-L622) [^trustcreate-noripple]: NoRipple initialization in trustCreate: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L264-L281) [^trustcreate-noripple-src]: Source account NoRipple from transaction flags: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L264-L267) [^trustcreate-noripple-dst]: Destination account NoRipple from lsfDefaultRipple: [`RippleStateHelpers.cpp`](https://github.com/XRPLF/rippled/blob/3.2.0/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp#L277-L281)