-
Notifications
You must be signed in to change notification settings - Fork 2
[ALPHANET] Token PayChannel #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,9 @@ | |
|
|
||
| #include <xrpl/basics/Log.h> | ||
| #include <xrpl/beast/utility/Journal.h> | ||
| #include <xrpl/beast/utility/Zero.h> | ||
| #include <xrpl/ledger/ApplyView.h> | ||
| #include <xrpl/ledger/ReadView.h> | ||
| #include <xrpl/ledger/helpers/AccountRootHelpers.h> | ||
| #include <xrpl/ledger/helpers/MPTokenHelpers.h> | ||
| #include <xrpl/ledger/helpers/RippleStateHelpers.h> | ||
|
|
@@ -25,6 +27,290 @@ | |
|
|
||
| namespace xrpl { | ||
|
|
||
| /** Validate that @p account may lock @p amount of a token for later delivery | ||
| * to @p dest. | ||
| * | ||
| * The lock-side counterpart of escrowUnlockPreclaimHelper: every issuer | ||
| * control (locking opt-in, authorization, freeze/lock, transferability, | ||
| * spendable balance) that gates locking token value lives here, so any | ||
| * transactor that locks funds applies the same rules. The signature is | ||
| * view-based rather than PreclaimContext-based so it can also run from | ||
| * doApply. | ||
| */ | ||
| template <ValidIssueType T> | ||
| TER | ||
| escrowLockPreclaimHelper( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| AccountID const& dest, | ||
| STAmount const& amount, | ||
| beast::Journal j); | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowLockPreclaimHelper<Issue>( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| AccountID const& dest, | ||
| STAmount const& amount, | ||
| beast::Journal j) | ||
| { | ||
| auto const& issue = amount.get<Issue>(); | ||
| AccountID const& issuer = amount.getIssuer(); | ||
| // If the issuer is the same as the account, return tecNO_PERMISSION | ||
| if (issuer == account) | ||
| return tecNO_PERMISSION; | ||
|
|
||
| // If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION | ||
| auto const sleIssuer = view.read(keylet::account(issuer)); | ||
| if (!sleIssuer) | ||
| return tecNO_ISSUER; | ||
| if (!sleIssuer->isFlag(lsfAllowTrustLineLocking)) | ||
| return tecNO_PERMISSION; | ||
|
|
||
| // If the account does not have a trustline to the issuer, return tecNO_LINE | ||
| auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency)); | ||
| if (!sleRippleState) | ||
| return tecNO_LINE; | ||
|
|
||
| STAmount const balance = (*sleRippleState)[sfBalance]; | ||
|
|
||
| // If balance is positive, issuer must have higher address than account | ||
| if (balance > beast::kZero && issuer < account) | ||
| return tecNO_PERMISSION; // LCOV_EXCL_LINE | ||
|
|
||
| // If balance is negative, issuer must have lower address than account | ||
| if (balance < beast::kZero && issuer > account) | ||
| return tecNO_PERMISSION; // LCOV_EXCL_LINE | ||
|
|
||
| // If the issuer has requireAuth set, check if the account is authorized | ||
| if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has requireAuth set, check if the destination is authorized | ||
| if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has frozen the account, return tecFROZEN | ||
| if (isFrozen(view, account, issue)) | ||
| return tecFROZEN; | ||
|
|
||
| // If the issuer has frozen the destination, return tecFROZEN | ||
| if (isFrozen(view, dest, issue)) | ||
| return tecFROZEN; | ||
|
|
||
| STAmount const spendableAmount = | ||
| accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j); | ||
|
|
||
| // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS | ||
| if (spendableAmount <= beast::kZero) | ||
| return tecINSUFFICIENT_FUNDS; | ||
|
|
||
| // If the spendable amount is less than the amount, return | ||
| // tecINSUFFICIENT_FUNDS | ||
| if (spendableAmount < amount) | ||
| return tecINSUFFICIENT_FUNDS; | ||
|
|
||
| // If the amount is not addable to the balance, return tecPRECISION_LOSS | ||
| if (!canAdd(spendableAmount, amount)) | ||
| return tecPRECISION_LOSS; | ||
|
|
||
| return tesSUCCESS; | ||
| } | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowLockPreclaimHelper<MPTIssue>( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| AccountID const& dest, | ||
| STAmount const& amount, | ||
| beast::Journal j) | ||
| { | ||
| AccountID const issuer = amount.getIssuer(); | ||
| // If the issuer is the same as the account, return tecNO_PERMISSION | ||
| if (issuer == account) | ||
| return tecNO_PERMISSION; | ||
|
|
||
| // If the mpt does not exist, return tecOBJECT_NOT_FOUND | ||
| auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID()); | ||
| auto const sleIssuance = view.read(issuanceKey); | ||
| if (!sleIssuance) | ||
| return tecOBJECT_NOT_FOUND; | ||
|
|
||
| // If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION | ||
| if (!sleIssuance->isFlag(lsfMPTCanEscrow)) | ||
| return tecNO_PERMISSION; | ||
|
|
||
| // If the issuer is not the same as the issuer of the mpt, return | ||
| // tecNO_PERMISSION | ||
| if (sleIssuance->getAccountID(sfIssuer) != issuer) | ||
| return tecNO_PERMISSION; // LCOV_EXCL_LINE | ||
|
|
||
| // If the account does not have the mpt, return tecOBJECT_NOT_FOUND | ||
| if (!view.exists(keylet::mptoken(issuanceKey.key, account))) | ||
| return tecOBJECT_NOT_FOUND; | ||
|
|
||
| // If the issuer has requireAuth set, check if the account is | ||
| // authorized | ||
| auto const& mptIssue = amount.get<MPTIssue>(); | ||
| if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth); | ||
| !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has requireAuth set, check if the destination is | ||
| // authorized | ||
| if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has frozen the account, return tecLOCKED | ||
| if (isFrozen(view, account, mptIssue)) | ||
| return tecLOCKED; | ||
|
|
||
| // If the issuer has frozen the destination, return tecLOCKED | ||
| if (isFrozen(view, dest, mptIssue)) | ||
| return tecLOCKED; | ||
|
|
||
| // If the mpt cannot be transferred, return tecNO_AUTH | ||
| if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| STAmount const spendableAmount = accountHolds( | ||
| view, | ||
| account, | ||
| amount.get<MPTIssue>(), | ||
| FreezeHandling::IgnoreFreeze, | ||
| AuthHandling::IgnoreAuth, | ||
| j); | ||
|
|
||
| // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS | ||
| if (spendableAmount <= beast::kZero) | ||
| return tecINSUFFICIENT_FUNDS; | ||
|
|
||
| // If the spendable amount is less than the amount, return | ||
| // tecINSUFFICIENT_FUNDS | ||
| if (spendableAmount < amount) | ||
| return tecINSUFFICIENT_FUNDS; | ||
|
|
||
| return tesSUCCESS; | ||
| } | ||
|
|
||
| template <ValidIssueType T> | ||
| TER | ||
| escrowLockApplyHelper( | ||
| ApplyView& view, | ||
| AccountID const& issuer, | ||
| AccountID const& sender, | ||
| STAmount const& amount, | ||
| beast::Journal journal); | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowLockApplyHelper<Issue>( | ||
| ApplyView& view, | ||
| AccountID const& issuer, | ||
| AccountID const& sender, | ||
| STAmount const& amount, | ||
| beast::Journal journal) | ||
| { | ||
| // Defensive: Issuer cannot create an escrow | ||
| if (issuer == sender) | ||
| return tecINTERNAL; // LCOV_EXCL_LINE | ||
|
|
||
| auto const ter = | ||
| directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal); | ||
| if (!isTesSuccess(ter)) | ||
| return ter; // LCOV_EXCL_LINE | ||
| return tesSUCCESS; | ||
| } | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowLockApplyHelper<MPTIssue>( | ||
| ApplyView& view, | ||
| AccountID const& issuer, | ||
| AccountID const& sender, | ||
| STAmount const& amount, | ||
| beast::Journal journal) | ||
| { | ||
| // Defensive: Issuer cannot create an escrow | ||
| if (issuer == sender) | ||
| return tecINTERNAL; // LCOV_EXCL_LINE | ||
|
|
||
| auto const ter = lockEscrowMPT(view, sender, amount, journal); | ||
| if (!isTesSuccess(ter)) | ||
| return ter; // LCOV_EXCL_LINE | ||
| return tesSUCCESS; | ||
| } | ||
|
|
||
| template <ValidIssueType T> | ||
| TER | ||
| escrowUnlockPreclaimHelper( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| STAmount const& amount, | ||
| bool checkFreeze = true); | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowUnlockPreclaimHelper<Issue>( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| STAmount const& amount, | ||
| bool checkFreeze) | ||
| { | ||
| AccountID const& issuer = amount.getIssuer(); | ||
| // If the issuer is the same as the account, return tesSUCCESS | ||
| if (issuer == account) | ||
| return tesSUCCESS; | ||
|
|
||
| // If the issuer has requireAuth set, check if the destination is authorized | ||
| if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has deep frozen the destination, return tecFROZEN | ||
| if (checkFreeze && | ||
| isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer())) | ||
| return tecFROZEN; | ||
|
|
||
| return tesSUCCESS; | ||
| } | ||
|
|
||
| template <> | ||
| inline TER | ||
| escrowUnlockPreclaimHelper<MPTIssue>( | ||
| ReadView const& view, | ||
| AccountID const& account, | ||
| STAmount const& amount, | ||
| bool checkFreeze) | ||
| { | ||
| AccountID const& issuer = amount.getIssuer(); | ||
| // If the issuer is the same as the account, return tesSUCCESS | ||
| if (issuer == account) | ||
| return tesSUCCESS; | ||
|
|
||
| // If the mpt does not exist, return tecOBJECT_NOT_FOUND | ||
| auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID()); | ||
| auto const sleIssuance = view.read(issuanceKey); | ||
| if (!sleIssuance) | ||
| return tecOBJECT_NOT_FOUND; | ||
|
|
||
| // If the issuer has requireAuth set, check if the account is | ||
| // authorized | ||
| auto const& mptIssue = amount.get<MPTIssue>(); | ||
| if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth); | ||
| !isTesSuccess(ter)) | ||
| return ter; | ||
|
|
||
| // If the issuer has frozen the account, return tecLOCKED | ||
| if (checkFreeze && isFrozen(view, account, mptIssue)) | ||
| return tecLOCKED; | ||
|
|
||
| return tesSUCCESS; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The MPT unlock preclaim has no Suggested fixEither: (1) document the intentional omission, add an explicit |
||
| } | ||
|
|
||
| //------------------------------------------------------------------------------ | ||
|
|
||
| template <ValidIssueType T> | ||
| TER | ||
| escrowUnlockApplyHelper( | ||
|
|
@@ -58,14 +344,15 @@ escrowUnlockApplyHelper<Issue>( | |
| bool const recvLow = issuer > receiver; | ||
| bool const senderIssuer = issuer == sender; | ||
| bool const receiverIssuer = issuer == receiver; | ||
| bool const lineExisted = ctx.view.exists(trustLineKey); | ||
|
|
||
| if (senderIssuer) | ||
| return tecINTERNAL; // LCOV_EXCL_LINE | ||
|
|
||
| if (receiverIssuer) | ||
| return tesSUCCESS; | ||
|
|
||
| if (!ctx.view.exists(trustLineKey) && createAsset) | ||
| if (!lineExisted && createAsset) | ||
| { | ||
| // Can the account cover the trust line's reserve? | ||
| auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| #include <xrpl/basics/base_uint.h> | ||
| #include <xrpl/beast/utility/Journal.h> | ||
| #include <xrpl/ledger/ApplyView.h> | ||
| #include <xrpl/protocol/AccountID.h> | ||
| #include <xrpl/protocol/Rules.h> | ||
| #include <xrpl/protocol/STLedgerEntry.h> | ||
| #include <xrpl/protocol/TER.h> | ||
|
|
@@ -17,12 +18,19 @@ namespace xrpl { | |
| * @param slep The SLE for the PayChannel object to close. | ||
| * @param view The apply view in which ledger state modifications are made. | ||
| * @param key The ledger key identifying the PayChannel entry. | ||
| * @param txAccount The account submitting the transaction that closes the | ||
| * channel. | ||
| * @param j Journal used for fatal-level diagnostic messages. | ||
| * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal | ||
| * fails; tefINTERNAL if the source account SLE cannot be found. | ||
| */ | ||
| TER | ||
| closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j); | ||
| closeChannel( | ||
| SLE::ref slep, | ||
| ApplyView& view, | ||
| uint256 const& key, | ||
| AccountID const& txAccount, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the non-XRP (IOU/MPT) branch of Suggested fixRemove the dependency on |
||
| beast::Journal j); | ||
|
|
||
| /** Add two uint32_t values with saturation at UINT32_MAX. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IOU unlock path gates delivery on
isDeepFrozen(), which only checkslsfHighDeepFreeze/lsfLowDeepFreezeon the trust line — it ignores both the issuer's global freeze (lsfGlobalFreezeon their AccountRoot) and regular per-line freezes (lsfHighFreeze/lsfLowFreeze). This is directly inconsistent with the corresponding lock path inescrowLockPreclaimHelper<Issue>, which correctly usesisFrozen()covering all three freeze forms, and with the MPT unlock counterpart (escrowUnlockPreclaimHelper<MPTIssue>) which also usesisFrozen(). Since neitherEscrowFinish::doApplynorPaymentChannelClaim::doApplyperform any additional freeze check, this preclaim helper is the sole enforcement point. An issuer who applies a compliance freeze after an IOU escrow or payment channel is created cannot prevent the frozen destination from receiving the tokens. ReplaceisDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer())withisFrozen(view, account, amount.get<Issue>())to match the lock-side semantics and the MPT specialization.Suggested fix
Replace
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer())withisFrozen(view, account, amount.get<Issue>()). This matches the lock path (escrowLockPreclaimHelper<Issue>) and the MPT unlock path (escrowUnlockPreclaimHelper<MPTIssue>), both of which useisFrozen(). TheisFrozen()overload forIssueinternally checks global freeze on the issuer's AccountRoot and both per-line freeze flags on the trust line — all three mechanisms a compliant issuer would use.