diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 322bbcafb5..398ec86bb7 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -179,6 +179,7 @@ issueIOU( AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j); [[nodiscard]] TER diff --git a/include/xrpl/ledger/helpers/SponsorHelpers.h b/include/xrpl/ledger/helpers/SponsorHelpers.h index 3c4615b325..6d00ac5bc7 100644 --- a/include/xrpl/ledger/helpers/SponsorHelpers.h +++ b/include/xrpl/ledger/helpers/SponsorHelpers.h @@ -19,6 +19,35 @@ namespace xrpl { +inline std::unordered_set const kReserveSponsorAllowed = { + // Explicitly allow-listed for v1. + ttDELEGATE_SET, + ttDEPOSIT_PREAUTH, + ttPAYMENT, + ttSIGNER_LIST_SET, + ttCHECK_CANCEL, + ttCHECK_CASH, + ttCHECK_CREATE, + ttESCROW_CANCEL, + ttESCROW_CREATE, + ttESCROW_FINISH, + ttPAYCHAN_CLAIM, + ttPAYCHAN_CREATE, + ttPAYCHAN_FUND, + ttCLAWBACK, + ttMPTOKEN_AUTHORIZE, + ttMPTOKEN_ISSUANCE_CREATE, + ttMPTOKEN_ISSUANCE_DESTROY, + ttMPTOKEN_ISSUANCE_SET, + ttTRUST_SET, + ttCREDENTIAL_ACCEPT, + ttCREDENTIAL_CREATE, + ttCREDENTIAL_DELETE, + ttACCOUNT_SET, + ttREGULAR_KEY_SET, + ttSPONSORSHIP_TRANSFER, +}; + inline bool isFeeSponsored(STTx const& tx) { diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 249e843683..880f66c25a 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -224,6 +224,7 @@ adjustOwnerCountSigned( if (!accountSle) return; // LCOV_EXCL_LINE + auto const accountID = accountSle->getAccountID(sfAccount); auto const sleType = accountSle->getType(); bool const validType = sponsorSle ? sleType == ltACCOUNT_ROOT : sleType == ltLOAN_BROKER || sleType == ltACCOUNT_ROOT; @@ -233,7 +234,6 @@ adjustOwnerCountSigned( XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCountSigned : nonzero adjustment input"); - auto const accountID = accountSle->getAccountID(sfAccount); if (sponsorSle) { bool const validSponsorType = sponsorSle->getType() == ltACCOUNT_ROOT; diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 5f86642360..56716f3bac 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -378,7 +378,7 @@ updateTrustLine( // VFALCO Where is the line being deleted? // Clear the reserve of the sender, possibly delete the line! auto const currentSponsor = - getLedgerEntryReserveSponsor(view, state, !bSenderHigh ? sfLowSponsor : sfHighSponsor); + getLedgerEntryReserveSponsor(view, state, bSenderHigh ? sfHighSponsor : sfLowSponsor); decreaseOwnerCount(view, sle, currentSponsor, 1, j); // Clear reserve flag. @@ -393,12 +393,14 @@ updateTrustLine( return false; } +// Only used in tests TER issueIOU( ApplyView& view, AccountID const& account, STAmount const& amount, Issue const& issue, + SLE::ref sponsorSle, beast::Journal j) { XRPL_ASSERT( diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 1447554812..f4de26b406 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -180,21 +180,18 @@ preflight1Sponsor(PreflightContext const& ctx, AccountID const& id) if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor)) return temDISABLED; - if (!hasSponsor) + if (hasSponsor != hasSponsorFlags) { - if (hasSponsorFlags) - { - JLOG(ctx.j.debug()) << "preflight1: sponsor flags without sponsor definition"; - return temINVALID_FLAG; - } - - if (hasSponsorSig) - { - JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition"; - return temMALFORMED; - } + JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch"; + return temINVALID_FLAG; } - else if (hasSponsorFlags) + if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags)) + { + JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition"; + return temMALFORMED; + } + + if (hasSponsorFlags) { auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags); if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0) @@ -205,36 +202,8 @@ preflight1Sponsor(PreflightContext const& ctx, AccountID const& id) // Reserve sponsorship is only permitted for an explicit allow-list of // transaction types, for v1. All other tx types reject spfSponsorReserve here. - if ((sponsorFlags & spfSponsorReserve) != 0u) + if (isReserveSponsored(ctx.tx)) { - static std::unordered_set const kReserveSponsorAllowed = { - // Explicitly allow-listed for v1. - ttDELEGATE_SET, - ttDEPOSIT_PREAUTH, - ttPAYMENT, - ttSIGNER_LIST_SET, - ttCHECK_CANCEL, - ttCHECK_CASH, - ttCHECK_CREATE, - ttESCROW_CANCEL, - ttESCROW_CREATE, - ttESCROW_FINISH, - ttPAYCHAN_CLAIM, - ttPAYCHAN_CREATE, - ttPAYCHAN_FUND, - ttCLAWBACK, - ttMPTOKEN_AUTHORIZE, - ttMPTOKEN_ISSUANCE_CREATE, - ttMPTOKEN_ISSUANCE_DESTROY, - ttMPTOKEN_ISSUANCE_SET, - ttTRUST_SET, - ttCREDENTIAL_ACCEPT, - ttCREDENTIAL_CREATE, - ttCREDENTIAL_DELETE, - ttACCOUNT_SET, - ttREGULAR_KEY_SET, - ttSPONSORSHIP_TRANSFER, - }; if (!kReserveSponsorAllowed.contains(ctx.tx.getTxnType())) { JLOG(ctx.j.debug()) @@ -243,11 +212,6 @@ preflight1Sponsor(PreflightContext const& ctx, AccountID const& id) } } } - else - { - JLOG(ctx.j.debug()) << "preflight1: no sponsor flags"; - return temINVALID_FLAG; - } if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == id) { diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 866ba5b6c3..b226017ae7 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -1027,7 +1027,7 @@ applyCreateAccountAttestations( // Check reserve auto const balance = (*sleDoor)[sfBalance]; - auto const reserve = accountReserve(view, sleDoor, j, {.ownerCountDelta = 1}); + auto const reserve = accountReserve(psb, sleDoor, j, {.ownerCountDelta = 1}); if (balance < reserve) return std::unexpected(tecINSUFFICIENT_RESERVE); diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index c8d386a97a..0de89bf137 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -94,6 +94,20 @@ CredentialAccept::doApply() if (!sleSubject || !sleIssuer) return tefINTERNAL; // LCOV_EXCL_LINE + auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); + if (!txSponsorSle) + return txSponsorSle.error(); // LCOV_EXCL_LINE + + if (auto const ret = checkReserve( + ctx_.getApplyViewContext(), + sleSubject, + preFeeBalance_, + *txSponsorSle, + {.ownerCountDelta = 1}, + j_); + !isTesSuccess(ret)) + return ret; + auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(accountID_, issuer, credType); auto const sleCred = view().peek(credentialKey); // Checked in preclaim() @@ -110,29 +124,12 @@ CredentialAccept::doApply() sleCred->setFieldU32(sfFlags, lsfAccepted); - auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); - if (!txSponsorSle) - return txSponsorSle.error(); // LCOV_EXCL_LINE - // Release the original creation sponsor from the credential (it covered // the issuer's reserve), then assign the accept tx's sponsor (if any) so // the credential reflects whoever is now covering the subject's reserve. decreaseOwnerCountForObject(view(), sleIssuer, sleCred, 1, j_); removeSponsorFromLedgerEntry(sleCred); - if (view().rules().enabled(featureSponsor) || view().rules().enabled(fixCleanup3_3_0)) - { - if (auto const ret = checkReserve( - ctx_.getApplyViewContext(), - sleSubject, - preFeeBalance_, - *txSponsorSle, - {.ownerCountDelta = 1}, - j_); - !isTesSuccess(ret)) - return ret; - } - addSponsorToLedgerEntry(sleCred, *txSponsorSle); increaseOwnerCount(view(), sleSubject, *txSponsorSle, 1, j_); view().update(sleCred); diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index 4d6524bba3..ff27a58ad7 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -661,10 +661,9 @@ AMMWithdraw::withdraw( return tecINTERNAL; // LCOV_EXCL_LINE auto const balance = (*sleAccount)[sfBalance]->xrp(); - std::uint32_t const currentOwnerCount = ownerCount(sleAccount, journal); // See also TrustSet::doApply() and MPTokenAuthorize::authorize() XRPAmount const reserve( - (currentOwnerCount < 2) + (ownerCount(sleAccount, journal) < 2) ? XRPAmount(beast::kZero) : accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1})); diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index 9a158aafca..f3b2e432e1 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -333,8 +333,8 @@ NFTokenMint::doApply() // requiring the reserve to be met each time. The reserve is // only managed when a new NFT page or sell offer is added. auto const sleAccount = view().read(keylet::account(accountID_)); - if (!sleAccount) - return tecINTERNAL; // LCOV_EXCL_LINE + if (!sleAccount && view().rules().enabled(featureSponsor)) + return tefINTERNAL; // LCOV_EXCL_LINE if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index e1974a0bcd..01b6495c0c 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -70,7 +71,9 @@ OracleDelete::deleteOracle( if (!sleOwner) return tecINTERNAL; // LCOV_EXCL_LINE - std::uint32_t const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1; + std::uint32_t const count = + calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size()); + ; decreaseOwnerCount(view, sleOwner, {}, count, j); view.erase(sle); diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 41614b1943..40b6a95c36 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -396,44 +396,42 @@ Payment::preclaim(PreclaimContext const& ctx) if (ctx.parentBatchId && ctx.view.rules().enabled(featureBatchV1_1)) return tefNO_DST_PARTIAL; } - if (ctx.tx.isFlag(tfSponsorCreatedAccount)) - { - // The minimum amount when creating a Sponsored Account is 1 drop. - // Since the reserve is covered by the sponsor, you don't need to hold the 1-increment - // reserve yourself. - } - else if (dstAmount < STAmount(ctx.view.fees().reserve)) + if (dstAmount < STAmount(ctx.view.fees().reserve)) { // accountReserve is the minimum amount that an account can have. // Reserve is not scaled by load. - JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " - << "Insufficent payment to create account."; + if (!ctx.tx.isFlag(tfSponsorCreatedAccount)) + { + // The minimum amount when creating a Sponsored Account is 1 drop. + // Since the reserve is covered by the sponsor, you don't need to hold the + // 1-increment reserve yourself. + JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. " + << "Insufficent payment to create account."; - // TODO: de-dupe - // Another transaction could create the account and then this - // transaction would succeed. - return tecNO_DST_INSUF_XRP; + // TODO: de-dupe + // Another transaction could create the account and then this + // transaction would succeed. + return tecNO_DST_INSUF_XRP; + } } } - else + else if (ctx.tx.isFlag(tfSponsorCreatedAccount)) { // The tfSponsorCreatedAccount flag is specific to account creation via // sponsorship. If the destination account already exists, applying this // flag is invalid. - if (ctx.tx.isFlag(tfSponsorCreatedAccount)) - return tecNO_SPONSOR_PERMISSION; + return tecNO_SPONSOR_PERMISSION; + } + else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) + { + // The tag is basically account-specific information we don't + // understand, but we can require someone to fill it in. - if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) - { - // The tag is basically account-specific information we don't - // understand, but we can require someone to fill it in. + // We didn't make this test for a newly-formed account because there's + // no way for this field to be set. + JLOG(ctx.j.trace()) << "Malformed transaction: DestinationTag required."; - // We didn't make this test for a newly-formed account because - // there's no way for this field to be set. - JLOG(ctx.j.trace()) << "Malformed transaction: DestinationTag required."; - - return tecDST_TAG_NEEDED; - } + return tecDST_TAG_NEEDED; } // Payment with at least one intermediate step and uses transitive balances. diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 3425877026..8f1b0b1672 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -82,8 +82,7 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureSponsor)) { auto const balance = (*sle)[sfBalance]; - auto const fees = ctx.view.fees(); - auto const reserve = fees.reserve + fees.increment * ((*sle)[sfOwnerCount] + 1); + auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1, 1); if (balance < reserve) return tecINSUFFICIENT_RESERVE; diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 3fcddcad16..4e043786d2 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -93,14 +93,15 @@ PaymentChannelFund::doApply() auto const sponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext()); if (!sponsorSle) return sponsorSle.error(); // LCOV_EXCL_LINE - if (auto const ret = - checkReserve(ctx_.getApplyViewContext(), sle, balance, *sponsorSle, {}, j_); - !isTesSuccess(ret)) - return ret; - if (auto const ret = checkReserve( - ctx_.getApplyViewContext(), sle, balance - ctx_.tx[sfAmount], {}, {}, j_); - !isTesSuccess(ret)) + auto const reserve = accountReserve(view(), sle, j_); + + // There are no objects being created here, so sponsorship does not need to be taken into + // account + if (balance < reserve) + return tecINSUFFICIENT_RESERVE; + + if (balance < reserve + ctx_.tx[sfAmount]) return tecUNFUNDED; } diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp similarity index 100% rename from src/libxrpl/tx/transactors/Sponsor/SponsorshipSet.cpp rename to src/libxrpl/tx/transactors/sponsor/SponsorshipSet.cpp diff --git a/src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp b/src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp similarity index 100% rename from src/libxrpl/tx/transactors/Sponsor/SponsorshipTransfer.cpp rename to src/libxrpl/tx/transactors/sponsor/SponsorshipTransfer.cpp diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index d1d1dd806c..926eaad579 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -350,6 +350,16 @@ Batch::preflight(PreflightContext const& ctx) return ret; } } + if (stx.isFieldPresent(sfSponsorFlags)) + { + if (isFeeSponsored(stx)) + { + JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: " + << "spfSponsorFee is not allowed on inner Batch. " + << "txID: " << hash; + return temINVALID_FLAG; + } + } // Check that the Fee is native asset (XRP) and zero if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::kZero) diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index d1cda85f4e..e19dc9fe96 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -125,7 +125,7 @@ TicketCreate::doApply() sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount); // Every added Ticket counts against the creator's reserve. - increaseOwnerCount(view(), accountID_, {}, ticketCount, viewJ); + increaseOwnerCount(view(), sleAccountRoot, {}, ticketCount, viewJ); // TicketCreate is the only transaction that can cause an account root's // Sequence field to increase by more than one. October 2018. diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index ff1a46f638..bca698ce3a 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -5152,7 +5152,7 @@ class Invariants_test : public beast::unit_test::Suite { using namespace test::jtx; using namespace std::string_literals; - testcase << "Sponsorship"; + testcase("Sponsorship"); { auto const expectMessage = "SponsoredOwnerCount does not equal " diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 1c06680385..8a94a55dd0 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -4608,10 +4608,22 @@ public: env(signers(sponsor, 1, {Signer(signerAccount, 1)})); env.close(); + { + auto jt = env.jtnofill(noop(alice), sponsor::As(sponsor, spfSponsorFee)); + jt.jv.removeMember(sfTxnSignature.jsonName); + + auto const seq = env.seq(alice); + // should fail because Inner transaction cannot include SponsorFlags.SponsorFee + env(batch::outer(alice, seq, XRP(1), tfAllOrNothing), + batch::Inner(jt.jv, seq + 1), + batch::Inner(ticket::create(alice, 1), seq + 2), + Ter(temINVALID_FLAG)); + } + { auto jt = env.jtnofill( noop(alice), - sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + sponsor::As(sponsor, spfSponsorReserve), Sig(sfSponsorSignature, sponsor)); jt.jv.removeMember(sfTxnSignature.jsonName); @@ -4628,7 +4640,7 @@ public: { auto jt = env.jtnofill( noop(alice), - sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + sponsor::As(sponsor, spfSponsorReserve), Msig(sfSponsorSignature, sponsor, signerAccount)); jt.jv.removeMember(sfTxnSignature.jsonName); @@ -4645,7 +4657,7 @@ public: { auto jt = env.jtnofill( noop(alice), - sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + sponsor::As(sponsor, spfSponsorReserve), Sig(sfSponsorSignature, sponsor)); jt.jv.removeMember(sfTxnSignature.jsonName); jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName); @@ -4675,8 +4687,7 @@ public: BEAST_EXPECT(env.balance(sponsor) == XRP(900)); auto jt = env.jtnofill( - check::create(alice, bob, XRP(1)), - sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee)); + check::create(alice, bob, XRP(1)), sponsor::As(sponsor, spfSponsorReserve)); // remove txn signature since it is filled by env.jtnofill() jt.jv.removeMember(jss::TxnSignature); @@ -4711,7 +4722,7 @@ public: auto jt = env.jtnofill( check::create(alice, bob, XRP(1)), - sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee), + sponsor::As(sponsor, spfSponsorReserve), Sig(sfSponsorSignature, sponsor)); // remove txn signature since it is filled by env.jtnofill() jt.jv.removeMember(sfTxnSignature.jsonName); diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index eec30fe668..0a6af1e0ff 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -236,7 +236,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j); - BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS); + BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, {}, j) == tesSUCCESS); BEAST_EXPECT( accountHolds( pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==