From 866afeee0ce9a8c279e0db8cd2a9e29048fe5443 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 27 Jul 2026 17:04:51 -0700 Subject: [PATCH] refactor: extract escrow lock helpers and paychan test helpers --- include/xrpl/ledger/helpers/EscrowHelpers.h | 290 +++++++++++++++++- .../tx/transactors/escrow/EscrowCancel.cpp | 61 +--- .../tx/transactors/escrow/EscrowCreate.cpp | 210 +------------ .../tx/transactors/escrow/EscrowFinish.cpp | 69 +---- src/test/app/AMMMPT_test.cpp | 1 + src/test/app/AMM_test.cpp | 1 + src/test/app/EscrowToken_test.cpp | 49 +-- src/test/app/PayChan_test.cpp | 27 +- src/test/app/Sponsor_test.cpp | 1 + src/test/jtx/TestHelpers.h | 76 +---- src/test/jtx/impl/TestHelpers.cpp | 147 +++------ src/test/jtx/impl/paychan.cpp | 138 +++++++++ src/test/jtx/paychan.h | 88 ++++++ 13 files changed, 588 insertions(+), 570 deletions(-) create mode 100644 src/test/jtx/impl/paychan.cpp create mode 100644 src/test/jtx/paychan.h diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 9f54e537699..17e856a60ec 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include #include #include @@ -25,6 +27,291 @@ 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 +TER +escrowLockPreclaimHelper( + ReadView const& view, + AccountID const& account, + AccountID const& dest, + STAmount const& amount, + beast::Journal j); + +template <> +inline TER +escrowLockPreclaimHelper( + ReadView const& view, + AccountID const& account, + AccountID const& dest, + STAmount const& amount, + beast::Journal j) +{ + auto const& issue = amount.get(); + 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( + 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().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(); + 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(), + 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 +TER +escrowLockApplyHelper( + ApplyView& view, + AccountID const& issuer, + AccountID const& sender, + STAmount const& amount, + beast::Journal journal); + +template <> +inline TER +escrowLockApplyHelper( + 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(), journal); + if (!isTesSuccess(ter)) + return ter; // LCOV_EXCL_LINE + return tesSUCCESS; +} + +template <> +inline TER +escrowLockApplyHelper( + 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 +TER +escrowUnlockPreclaimHelper( + ReadView const& view, + AccountID const& account, + STAmount const& amount, + bool checkFreeze = true); + +template <> +inline TER +escrowUnlockPreclaimHelper( + 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(), account); !isTesSuccess(ter)) + return ter; + + // If the issuer has deep frozen the destination, return tecFROZEN + if (checkFreeze && + isDeepFrozen(view, account, amount.get().currency, amount.getIssuer())) + return tecFROZEN; + + return tesSUCCESS; +} + +template <> +inline TER +escrowUnlockPreclaimHelper( + 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().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(); + 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; +} + +//------------------------------------------------------------------------------ + template TER escrowUnlockApplyHelper( @@ -58,6 +345,7 @@ escrowUnlockApplyHelper( 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 @@ -65,7 +353,7 @@ escrowUnlockApplyHelper( 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); diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index feed43d410f..f30aee7749b 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -5,15 +5,10 @@ #include #include #include -#include -#include -#include #include #include #include #include -#include -#include #include #include #include @@ -33,60 +28,6 @@ EscrowCancel::preflight(PreflightContext const& ctx) return tesSUCCESS; } -template -static TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount); - -template <> -TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount) -{ - AccountID const& issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecINTERNAL - if (issuer == account) - return tecINTERNAL; // LCOV_EXCL_LINE - - // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.get(), account); !isTesSuccess(ter)) - return ter; - - return tesSUCCESS; -} - -template <> -TER -escrowCancelPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - STAmount const& amount) -{ - AccountID const issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tecINTERNAL - if (issuer == account) - return tecINTERNAL; // LCOV_EXCL_LINE - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = keylet::mptokenIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.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(); - if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); - !isTesSuccess(ter)) - return ter; - - return tesSUCCESS; -} - TER EscrowCancel::preclaim(PreclaimContext const& ctx) { @@ -104,7 +45,7 @@ EscrowCancel::preclaim(PreclaimContext const& ctx) { if (auto const ret = std::visit( [&](T const&) { - return escrowCancelPreclaimHelper(ctx, account, amount); + return escrowUnlockPreclaimHelper(ctx.view, account, amount, false); }, amount.asset().value()); !isTesSuccess(ret)) diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 50f2e8b8596..e2a1b5c2f83 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -9,8 +9,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -18,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -179,162 +179,6 @@ EscrowCreate::preflight(PreflightContext const& ctx) return tesSUCCESS; } -template -static TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount); - -template <> -TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - auto const& issue = amount.get(); - 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 = ctx.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 = ctx.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(ctx.view, issue, account); !isTesSuccess(ter)) - return ter; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter)) - return ter; - - // If the issuer has frozen the account, return tecFROZEN - if (isFrozen(ctx.view, account, issue)) - return tecFROZEN; - - // If the issuer has frozen the destination, return tecFROZEN - if (isFrozen(ctx.view, dest, issue)) - return tecFROZEN; - - STAmount const spendableAmount = accountHolds( - ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.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 <> -TER -escrowCreatePreclaimHelper( - PreclaimContext const& ctx, - AccountID const& account, - AccountID const& dest, - STAmount const& amount) -{ - 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().getMptID()); - auto const sleIssuance = ctx.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 (!ctx.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(); - if (auto const ter = requireAuth(ctx.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(ctx.view, mptIssue, dest, AuthType::WeakAuth); - !isTesSuccess(ter)) - return ter; - - // If the issuer has frozen the account, return tecLOCKED - if (isFrozen(ctx.view, account, mptIssue)) - return tecLOCKED; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - // If the mpt cannot be transferred, return tecNO_AUTH - if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter)) - return ter; - - STAmount const spendableAmount = accountHolds( - ctx.view, - account, - amount.get(), - FreezeHandling::IgnoreFreeze, - AuthHandling::IgnoreAuth, - ctx.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; -} - TER EscrowCreate::preclaim(PreclaimContext const& ctx) { @@ -360,7 +204,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) if (auto const ret = std::visit( [&](T const&) { - return escrowCreatePreclaimHelper(ctx, account, dest, amount); + return escrowLockPreclaimHelper(ctx.view, account, dest, amount, ctx.j); }, amount.asset().value()); !isTesSuccess(ret)) @@ -369,54 +213,6 @@ EscrowCreate::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -template -static TER -escrowLockApplyHelper( - ApplyView& view, - AccountID const& issuer, - AccountID const& sender, - STAmount const& amount, - beast::Journal journal); - -template <> -TER -escrowLockApplyHelper( - 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(), journal); - if (!isTesSuccess(ter)) - return ter; // LCOV_EXCL_LINE - return tesSUCCESS; -} - -template <> -TER -escrowLockApplyHelper( - 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; -} - TER EscrowCreate::doApply() { diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 8bc98c7aa8e..4769aac6296 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -12,15 +12,10 @@ #include #include #include -#include -#include -#include #include #include #include #include -#include -#include #include #include #include @@ -129,68 +124,6 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) return Transactor::calculateBaseFee(view, tx) + extraFee; } -template -static TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount); - -template <> -TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount) -{ - AccountID const& issuer = amount.getIssuer(); - // If the issuer is the same as the account, return tesSUCCESS - if (issuer == dest) - return tesSUCCESS; - - // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.get(), dest); !isTesSuccess(ter)) - return ter; - - // If the issuer has deep frozen the destination, return tecFROZEN - if (isDeepFrozen(ctx.view, dest, amount.get().currency, amount.getIssuer())) - return tecFROZEN; - - return tesSUCCESS; -} - -template <> -TER -escrowFinishPreclaimHelper( - PreclaimContext const& ctx, - AccountID const& dest, - STAmount const& amount) -{ - AccountID const& issuer = amount.getIssuer(); - // If the issuer is the same as the dest, return tesSUCCESS - if (issuer == dest) - return tesSUCCESS; - - // If the mpt does not exist, return tecOBJECT_NOT_FOUND - auto const issuanceKey = keylet::mptokenIssuance(amount.get().getMptID()); - auto const sleIssuance = ctx.view.read(issuanceKey); - if (!sleIssuance) - return tecOBJECT_NOT_FOUND; - - // If the issuer has requireAuth set, check if the destination is - // authorized - auto const& mptIssue = amount.get(); - if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); - !isTesSuccess(ter)) - return ter; - - // If the issuer has frozen the destination, return tecLOCKED - if (isFrozen(ctx.view, dest, mptIssue)) - return tecLOCKED; - - return tesSUCCESS; -} - TER EscrowFinish::preclaim(PreclaimContext const& ctx) { @@ -215,7 +148,7 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) { if (auto const ret = std::visit( [&](T const&) { - return escrowFinishPreclaimHelper(ctx, dest, amount); + return escrowUnlockPreclaimHelper(ctx.view, dest, amount); }, amount.asset().value()); !isTesSuccess(ret)) diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index bf0bc5c7d77..ec25a04e8d8 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index f19743026c4..74d192293b5 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 4015f5ddc8a..074926801f6 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include // IWYU pragma: keep #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -45,52 +44,6 @@ namespace xrpl::test { struct EscrowToken_test : public beast::unit_test::Suite { - static uint64_t - mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt) - { - auto const sle = env.le(keylet::mptoken(mpt.mpt(), account)); - if (sle && sle->isFieldPresent(sfLockedAmount)) - return (*sle)[sfLockedAmount]; - return 0; - } - - static uint64_t - issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt) - { - auto const sle = env.le(keylet::mptokenIssuance(mpt.mpt())); - if (sle && sle->isFieldPresent(sfLockedAmount)) - return (*sle)[sfLockedAmount]; - return 0; - } - - static jtx::PrettyAmount - issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue) - { - json::Value params; - params[jss::account] = account.human(); - auto jrr = env.rpc("json", "gateway_balances", to_string(params)); - auto const result = jrr[jss::result]; - auto const obligations = result[jss::obligations][to_string(issue.currency)]; - if (obligations.isNull()) - return {STAmount(issue, 0), account.name()}; - STAmount const amount = amountFromString(issue, obligations.asString()); - return {amount, account.name()}; - } - - static jtx::PrettyAmount - issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue) - { - json::Value params; - params[jss::account] = account.human(); - auto jrr = env.rpc("json", "gateway_balances", to_string(params)); - auto const result = jrr[jss::result]; - auto const locked = result[jss::locked][to_string(issue.currency)]; - if (locked.isNull()) - return {STAmount(issue, 0), account.name()}; - STAmount const amount = amountFromString(issue, locked.asString()); - return {amount, account.name()}; - } - void testIOUEnablement(FeatureBitset features) { diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 50684721354..22bd21649eb 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include // IWYU pragma: keep @@ -9,11 +8,11 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -30,12 +29,9 @@ #include #include #include -#include #include #include #include -#include -#include #include #include #include @@ -66,27 +62,6 @@ struct PayChan_test : public beast::unit_test::Suite return {k.key, view.read(k)}; } - static Buffer - signClaimAuth( - PublicKey const& pk, - SecretKey const& sk, - uint256 const& channel, - STAmount const& authAmt) - { - Serializer msg; - serializePayChanAuthorization(msg, channel, authAmt.xrp()); - return sign(pk, sk, msg.slice()); - } - - static STAmount - channelAmount(ReadView const& view, uint256 const& chan) - { - auto const slep = view.read({ltPAYCHAN, chan}); - if (!slep) - return XRPAmount{-1}; - return (*slep)[sfAmount]; - } - static std::optional channelExpiration(ReadView const& view, uint256 const& chan) { diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index f20aac68f90..bdf5de3bcee 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index e7a2808f079..fa78208e862 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -19,8 +19,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -381,6 +381,20 @@ checkArraySize(json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +/* Token (IOU/MPT) Locking */ +/******************************************************************************/ +uint64_t +mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt); + +uint64_t +issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt); + +jtx::PrettyAmount +issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue); + +jtx::PrettyAmount +issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue); + // Helper function that returns the sponsored owner count on an account. std::uint32_t sponsoredOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account); @@ -597,66 +611,6 @@ accountBalance(Env& env, Account const& acct); [[nodiscard]] bool expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedValue); -/* Payment Channel */ -/******************************************************************************/ -namespace paychan { - -json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt); - -inline json::Value -create( - Account const& account, - Account const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter = std::nullopt, - std::optional const& dstTag = std::nullopt) -{ - return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); -} - -json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration = std::nullopt); - -json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance = std::nullopt, - std::optional const& amount = std::nullopt, - std::optional const& signature = std::nullopt, - std::optional const& pk = std::nullopt); - -uint256 -channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue); - -inline uint256 -channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) -{ - return channel(account.id(), dst.id(), seqProxyValue); -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan); - -bool -channelExists(ReadView const& view, uint256 const& chan); - -} // namespace paychan - /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 4d3869b4f91..fcc0fd73886 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -16,18 +16,15 @@ #include #include -#include #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -37,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -52,7 +48,6 @@ #include #include -#include #include #include #include @@ -94,6 +89,54 @@ ownerCount(Env const& env, Account const& account) return env.ownerCount(account); } +/* Token (IOU/MPT) Locking */ +/******************************************************************************/ +uint64_t +mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt) +{ + auto const sle = env.le(keylet::mptoken(mpt.mpt(), account)); + if (sle && sle->isFieldPresent(sfLockedAmount)) + return (*sle)[sfLockedAmount]; + return 0; +} + +uint64_t +issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt) +{ + auto const sle = env.le(keylet::mptokenIssuance(mpt.mpt())); + if (sle && sle->isFieldPresent(sfLockedAmount)) + return (*sle)[sfLockedAmount]; + return 0; +} + +jtx::PrettyAmount +issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue) +{ + json::Value params; + params[jss::account] = account.human(); + auto jrr = env.rpc("json", "gateway_balances", to_string(params)); + auto const result = jrr[jss::result]; + auto const obligations = result[jss::obligations][to_string(issue.currency)]; + if (obligations.isNull()) + return {STAmount(issue, 0), account.name()}; + STAmount const amount = amountFromString(issue, obligations.asString()); + return {amount, account.name()}; +} + +jtx::PrettyAmount +issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue) +{ + json::Value params; + params[jss::account] = account.human(); + auto jrr = env.rpc("json", "gateway_balances", to_string(params)); + auto const result = jrr[jss::result]; + auto const locked = result[jss::locked][to_string(issue.currency)]; + if (locked.isNull()) + return {STAmount(issue, 0), account.name()}; + STAmount const amount = amountFromString(issue, locked.asString()); + return {amount, account.name()}; +} + std::uint32_t sponsoredOwnerCount(Env const& env, Account const& account) { @@ -499,100 +542,6 @@ expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedVal return accountBalance(env, acct) == to_string(expectedValue.xrp()); } -/* Payment Channel */ -/******************************************************************************/ -namespace paychan { - -json::Value -create( - AccountID const& account, - AccountID const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk, - std::optional const& cancelAfter, - std::optional const& dstTag) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Account] = to_string(account); - jv[jss::Destination] = to_string(to); - jv[jss::Amount] = amount.getJson(JsonOptions::Values::None); - jv[jss::SettleDelay] = settleDelay.count(); - jv[sfPublicKey.fieldName] = strHex(pk.slice()); - if (cancelAfter) - jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count(); - if (dstTag) - jv[sfDestinationTag.fieldName] = *dstTag; - return jv; -} - -json::Value -fund( - AccountID const& account, - uint256 const& channel, - STAmount const& amount, - std::optional const& expiration) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelFund; - jv[jss::Account] = to_string(account); - jv[sfChannel.fieldName] = to_string(channel); - jv[jss::Amount] = amount.getJson(JsonOptions::Values::None); - if (expiration) - jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); - return jv; -} - -json::Value -claim( - AccountID const& account, - uint256 const& channel, - std::optional const& balance, - std::optional const& amount, - std::optional const& signature, - std::optional const& pk) -{ - json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelClaim; - jv[jss::Account] = to_string(account); - jv["Channel"] = to_string(channel); - if (amount) - jv[jss::Amount] = amount->getJson(JsonOptions::Values::None); - if (balance) - jv["Balance"] = balance->getJson(JsonOptions::Values::None); - if (signature) - jv["Signature"] = strHex(*signature); - if (pk) - jv["PublicKey"] = strHex(pk->slice()); - return jv; -} - -uint256 -channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue) -{ - auto const k = keylet::payChannel(account, dst, seqProxyValue); - return k.key; -} - -STAmount -channelBalance(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - if (!slep) - return XRPAmount{-1}; - return (*slep)[sfBalance]; -} - -bool -channelExists(ReadView const& view, uint256 const& chan) -{ - auto const slep = view.read({ltPAYCHAN, chan}); - return bool(slep); -} - -} // namespace paychan - /* Crossing Limits */ /******************************************************************************/ diff --git a/src/test/jtx/impl/paychan.cpp b/src/test/jtx/impl/paychan.cpp new file mode 100644 index 00000000000..eab5b364718 --- /dev/null +++ b/src/test/jtx/impl/paychan.cpp @@ -0,0 +1,138 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * Paychan operations. + */ +namespace xrpl::test::jtx::paychan { + +json::Value +create( + AccountID const& account, + AccountID const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter, + std::optional const& dstTag) +{ + json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Account] = to_string(account); + jv[jss::Destination] = to_string(to); + jv[jss::Amount] = amount.getJson(JsonOptions::Values::None); + jv[jss::SettleDelay] = settleDelay.count(); + jv[sfPublicKey.fieldName] = strHex(pk.slice()); + if (cancelAfter) + jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count(); + if (dstTag) + jv[sfDestinationTag.fieldName] = *dstTag; + return jv; +} + +json::Value +fund( + AccountID const& account, + uint256 const& channel, + STAmount const& amount, + std::optional const& expiration) +{ + json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelFund; + jv[jss::Account] = to_string(account); + jv[sfChannel.fieldName] = to_string(channel); + jv[jss::Amount] = amount.getJson(JsonOptions::Values::None); + if (expiration) + jv[sfExpiration.fieldName] = expiration->time_since_epoch().count(); + return jv; +} + +json::Value +claim( + AccountID const& account, + uint256 const& channel, + std::optional const& balance, + std::optional const& amount, + std::optional const& signature, + std::optional const& pk) +{ + json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelClaim; + jv[jss::Account] = to_string(account); + jv["Channel"] = to_string(channel); + if (amount) + jv[jss::Amount] = amount->getJson(JsonOptions::Values::None); + if (balance) + jv["Balance"] = balance->getJson(JsonOptions::Values::None); + if (signature) + jv["Signature"] = strHex(*signature); + if (pk) + jv["PublicKey"] = strHex(pk->slice()); + return jv; +} + +uint256 +channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue) +{ + auto const k = keylet::payChannel(account, dst, seqProxyValue); + return k.key; +} + +STAmount +channelBalance(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + if (!slep) + return XRPAmount{-1}; + return (*slep)[sfBalance]; +} + +STAmount +channelAmount(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + if (!slep) + return XRPAmount{-1}; + return (*slep)[sfAmount]; +} + +bool +channelExists(ReadView const& view, uint256 const& chan) +{ + auto const slep = view.read({ltPAYCHAN, chan}); + return bool(slep); +} + +Buffer +signClaimAuth( + PublicKey const& pk, + SecretKey const& sk, + uint256 const& channel, + STAmount const& authAmt) +{ + Serializer msg; + serializePayChanAuthorization(msg, channel, authAmt.xrp()); + return sign(pk, sk, msg.slice()); +} + +} // namespace xrpl::test::jtx::paychan diff --git a/src/test/jtx/paychan.h b/src/test/jtx/paychan.h new file mode 100644 index 00000000000..4e46469b62b --- /dev/null +++ b/src/test/jtx/paychan.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * Paychan operations. + */ +namespace xrpl::test::jtx::paychan { + +json::Value +create( + AccountID const& account, + AccountID const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter = std::nullopt, + std::optional const& dstTag = std::nullopt); + +inline json::Value +create( + Account const& account, + Account const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk, + std::optional const& cancelAfter = std::nullopt, + std::optional const& dstTag = std::nullopt) +{ + return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag); +} + +json::Value +fund( + AccountID const& account, + uint256 const& channel, + STAmount const& amount, + std::optional const& expiration = std::nullopt); + +json::Value +claim( + AccountID const& account, + uint256 const& channel, + std::optional const& balance = std::nullopt, + std::optional const& amount = std::nullopt, + std::optional const& signature = std::nullopt, + std::optional const& pk = std::nullopt); + +uint256 +channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue); + +inline uint256 +channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue) +{ + return channel(account.id(), dst.id(), seqProxyValue); +} + +STAmount +channelBalance(ReadView const& view, uint256 const& chan); + +STAmount +channelAmount(ReadView const& view, uint256 const& chan); + +bool +channelExists(ReadView const& view, uint256 const& chan); + +Buffer +signClaimAuth( + PublicKey const& pk, + SecretKey const& sk, + uint256 const& channel, + STAmount const& authAmt); + +} // namespace xrpl::test::jtx::paychan