mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 23:20:33 +00:00
Merge branch 'xrplf/sponsor' of https://github.com/XRPLF/rippled into mvadari/sponsor/adjustment
This commit is contained in:
@@ -101,7 +101,7 @@ baseAccountReserve(ReadView const& view, Adjustment adj);
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkInsufficientReserve(
|
||||
ReadView const& view,
|
||||
ApplyView const& view,
|
||||
STTx const& tx,
|
||||
SLE::const_ref accSle,
|
||||
STAmount const& accBalance,
|
||||
|
||||
@@ -173,6 +173,28 @@ getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
isLedgerEntrySupportedBySponsorship(T const& sle)
|
||||
{
|
||||
switch (sle->getType())
|
||||
{
|
||||
case ltCHECK:
|
||||
case ltESCROW:
|
||||
case ltPAYCHAN:
|
||||
case ltMPTOKEN:
|
||||
case ltDELEGATE:
|
||||
case ltDEPOSIT_PREAUTH:
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
case ltSIGNER_LIST:
|
||||
case ltCREDENTIAL:
|
||||
case ltRIPPLE_STATE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::uint32_t
|
||||
getLedgerEntryOwnerCount(T const& sle)
|
||||
|
||||
@@ -303,7 +303,7 @@ baseAccountReserve(ReadView const& view, Adjustment adj)
|
||||
|
||||
TER
|
||||
checkInsufficientReserve(
|
||||
ReadView const& view,
|
||||
ApplyView const& view,
|
||||
STTx const& tx,
|
||||
SLE::const_ref accSle,
|
||||
STAmount const& accBalance,
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -147,30 +146,8 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
|
||||
if (!sle)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
// v1 scope: an object is only sponsorable via SponsorshipTransfer if
|
||||
// its creating transaction type is itself permitted to set
|
||||
// spfSponsorReserve (the allow-list in preflight1Sponsor). Otherwise
|
||||
// an Oracle / Ticket / DID / etc. could be retroactively sponsored
|
||||
// even though its creating tx cannot be, leaving downstream
|
||||
// transactors with no path to maintain the sponsorship invariants.
|
||||
switch (sle->getType())
|
||||
{
|
||||
case ltDELEGATE:
|
||||
case ltDEPOSIT_PREAUTH:
|
||||
case ltMPTOKEN:
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
case ltCREDENTIAL:
|
||||
case ltRIPPLE_STATE:
|
||||
case ltSIGNER_LIST:
|
||||
case ltCHECK:
|
||||
case ltESCROW:
|
||||
case ltPAYCHAN:
|
||||
break;
|
||||
default:
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
std::uint32_t const ownerCountDelta = 1;
|
||||
if (!isLedgerEntrySupportedBySponsorship(sle))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeID);
|
||||
if (!owner.has_value() || owner.value() != sponseeID)
|
||||
@@ -210,19 +187,6 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
|
||||
if (account != sponsor && account != sponseeID)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// check new sponsor have sufficient balance
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx.view,
|
||||
ctx.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.ownerCountDelta = ownerCountDelta},
|
||||
ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -258,22 +222,6 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
|
||||
if (account != sponsor && account != sponseeID)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// check account have sufficient balance
|
||||
// In the case of removing an account sponsor, accSle should have no sfSponsor set
|
||||
// (AccountReserve = 0). However, by setting accountCountDelta = 1 here, we are able to
|
||||
// calculate the actual required Account Reserve.
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx.view,
|
||||
ctx.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.accountCountDelta = 1},
|
||||
ctx.j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -336,6 +284,12 @@ SponsorshipTransfer::doApply()
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
auto const balanceBeforeFee = [&](SLE::const_ref sle) -> STAmount {
|
||||
if (sle->getAccountID(sfAccount) == accountID_)
|
||||
return STAmount{preFeeBalance_};
|
||||
return sle->getFieldAmount(sfBalance);
|
||||
};
|
||||
|
||||
if (isObjectSponsor)
|
||||
{
|
||||
auto const hasSignature = tx.isFieldPresent(sfSponsorSignature);
|
||||
@@ -361,6 +315,22 @@ SponsorshipTransfer::doApply()
|
||||
{
|
||||
auto const newSponsorID = tx.getAccountID(sfSponsor);
|
||||
XRPL_ASSERT(!!newSponsorID, "New sponsor is required when creating sponsorship");
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// check new sponsor have sufficient balance
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.ownerCountDelta = ownerCountDelta},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// update owner's sponsored count
|
||||
if (auto const ter =
|
||||
@@ -370,9 +340,6 @@ SponsorshipTransfer::doApply()
|
||||
view().update(ownerSle);
|
||||
|
||||
// increment new sponsor's sponsoring count
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (auto const ter =
|
||||
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
|
||||
!isTesSuccess(ter))
|
||||
@@ -396,14 +363,30 @@ SponsorshipTransfer::doApply()
|
||||
{
|
||||
auto const newSponsorID = tx.getAccountID(sfSponsor);
|
||||
XRPL_ASSERT(!!newSponsorID, "New sponsor is required when reassigning sponsorship");
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const oldSponsorID = objSle->getAccountID(sponsorField);
|
||||
XRPL_ASSERT(!!oldSponsorID, "Old sponsor is required when reassigning sponsorship");
|
||||
|
||||
// decrement old sponsor's sponsoring count
|
||||
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorID));
|
||||
if (!oldSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// check new sponsor have sufficient balance
|
||||
// NOLINTNEXTLINE(readability-suspicious-call-argument)
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.ownerCountDelta = ownerCountDelta},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// decrement old sponsor's sponsoring count
|
||||
if (auto const ter =
|
||||
setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta);
|
||||
!isTesSuccess(ter))
|
||||
@@ -411,9 +394,6 @@ SponsorshipTransfer::doApply()
|
||||
view().update(oldSponsorSle);
|
||||
|
||||
// increment new sponsor's sponsoring count
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (auto const ter =
|
||||
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
|
||||
!isTesSuccess(ter))
|
||||
@@ -442,6 +422,19 @@ SponsorshipTransfer::doApply()
|
||||
if (!oldSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// The owner takes the reserve burden back when the object is
|
||||
// no longer sponsored.
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
ownerSle,
|
||||
balanceBeforeFee(ownerSle),
|
||||
SLE::pointer(),
|
||||
{.ownerCountDelta = ownerCountDelta},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// decrement sponsored count
|
||||
if (auto const ter =
|
||||
setSponsorFieldU32(sponseeSle, sfSponsoredOwnerCount, -ownerCountDelta);
|
||||
@@ -471,6 +464,18 @@ SponsorshipTransfer::doApply()
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.accountCountDelta = 1},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
@@ -488,6 +493,18 @@ SponsorshipTransfer::doApply()
|
||||
auto const newSponsorSle = view().peek(keylet::account(newSponsorID));
|
||||
if (!newSponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sponseeSle,
|
||||
sponseeSle->getFieldAmount(sfBalance),
|
||||
newSponsorSle,
|
||||
{.accountCountDelta = 1},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
@@ -511,6 +528,20 @@ SponsorshipTransfer::doApply()
|
||||
{
|
||||
// dissolve account sponsor
|
||||
auto const oldSponsorID = sponseeSle->getAccountID(sfSponsor);
|
||||
|
||||
// The sponsee must be able to hold its own account reserve after
|
||||
// the sponsorship is removed.
|
||||
if (auto const ter = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sponseeSle,
|
||||
balanceBeforeFee(sponseeSle),
|
||||
SLE::pointer(),
|
||||
{.accountCountDelta = 1},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
sponseeSle->makeFieldAbsent(sfSponsor);
|
||||
view().update(sponseeSle);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <variant>
|
||||
@@ -439,21 +440,35 @@ EscrowCreate::doApply()
|
||||
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
// First check: whoever is on the hook for the new owner increment
|
||||
// can cover it. When sponsored this hits the sponsor branch and
|
||||
// validates the sponsor's reserve + remaining credit. When
|
||||
// unsponsored this hits the source branch and validates the
|
||||
// source's pre-lock balance against base + (currentOC+1)*increment.
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
ctx_.view(), ctx_.tx, sle, balance, *sponsorSle, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount))
|
||||
{
|
||||
// Second check (XRP escrow only): after locking the escrowed
|
||||
// amount, the source must still meet its own reserve floor.
|
||||
// Always passes `{}` so the source branch runs (the sponsor's
|
||||
// reserve was already validated above; here we're verifying the
|
||||
// source can fund the lock without dipping below its own
|
||||
// reserve). ownerCountAdj differs by case:
|
||||
// - sponsored: adj=0 — sponsor covers the new owner increment,
|
||||
// so the source only owes its base reserve.
|
||||
// - unsponsored: adj=1 — source owes base + the new increment.
|
||||
std::int32_t const ownerCountAdj = *sponsorSle ? 0 : 1;
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sle,
|
||||
balance - STAmount(amount).xrp(),
|
||||
{},
|
||||
{.ownerCountDelta = 1},
|
||||
{.ownerCountDelta = ownerCountAdj},
|
||||
j_);
|
||||
!isTesSuccess(ret))
|
||||
return tecUNFUNDED;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -134,28 +135,38 @@ PaymentChannelCreate::doApply()
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.view().rules().enabled(featureSponsor))
|
||||
{
|
||||
auto const sponsorSle = getTxReserveSponsor(ctx_.view(), ctx_.tx);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error();
|
||||
// First check: whoever is on the hook for the new owner increment
|
||||
// can cover it. When sponsored this hits the sponsor branch and
|
||||
// validates the sponsor's reserve + remaining credit. When
|
||||
// unsponsored this hits the source branch and validates the
|
||||
// source's pre-lock balance against base + (currentOC+1)*increment.
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sle,
|
||||
STAmount{preFeeBalance_},
|
||||
*sponsorSle,
|
||||
{.ownerCountDelta = 1},
|
||||
j_);
|
||||
ctx_.view(), ctx_.tx, sle, preFeeBalance_, *sponsorSle, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Second check: after locking sfAmount in the channel, the source
|
||||
// must still meet its own reserve floor. Always passes `{}` so the
|
||||
// source branch runs (the sponsor's reserve was already validated
|
||||
// above; here we're verifying the source can fund the lock without
|
||||
// dipping below its own reserve). ownerCountAdj differs by case:
|
||||
// - sponsored: adj=0 — sponsor covers the new owner increment,
|
||||
// so the source only owes its base reserve.
|
||||
// - unsponsored: adj=1 — source owes base + the new increment.
|
||||
std::int32_t const ownerCountAdj = *sponsorSle ? 0 : 1;
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
ctx_.view(),
|
||||
ctx_.tx,
|
||||
sle,
|
||||
STAmount{preFeeBalance_ - ctx_.tx[sfAmount].xrp()},
|
||||
preFeeBalance_ - ctx_.tx[sfAmount].xrp(),
|
||||
{},
|
||||
{.ownerCountDelta = 1},
|
||||
{.ownerCountDelta = ownerCountAdj},
|
||||
j_);
|
||||
!isTesSuccess(ret))
|
||||
return tecUNFUNDED;
|
||||
@@ -208,9 +219,6 @@ PaymentChannelCreate::doApply()
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
adjustOwnerCount(ctx_.view(), sle, *sponsorSle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(slep, *sponsorSle);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -2986,6 +2986,50 @@ public:
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
}
|
||||
|
||||
// A sponsored EscrowCreate must still verify that the source
|
||||
// can fund the escrow amount and stay above its own base
|
||||
// reserve. The sponsor covers the new object's owner
|
||||
// increment, but cannot cover the source's base reserve.
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
env.fund(XRP(10000), alice, bob, sponsor);
|
||||
env.close();
|
||||
|
||||
// alice's balance is just above the base reserve. After
|
||||
// locking escrowAmount she would dip below it.
|
||||
adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
|
||||
|
||||
auto const escrowAmount = XRP(2);
|
||||
auto const seq = env.seq(alice);
|
||||
|
||||
if (cosigning)
|
||||
{
|
||||
env(escrow::create(alice, bob, escrowAmount),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(tecUNFUNDED));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
|
||||
env(escrow::create(alice, bob, escrowAmount),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Ter(tecUNFUNDED));
|
||||
}
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice, seq)));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -3252,6 +3296,48 @@ public:
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
|
||||
}
|
||||
|
||||
// A sponsored PaymentChannelCreate must still verify that the
|
||||
// source can fund the channel amount and stay above its own
|
||||
// base reserve. The sponsor covers the new object's owner
|
||||
// increment, but cannot cover the source's base reserve.
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
env.fund(XRP(10000), alice, bob, sponsor);
|
||||
env.close();
|
||||
|
||||
// alice's balance is just above the base reserve. After
|
||||
// locking channelAmount she would dip below it.
|
||||
adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
|
||||
|
||||
auto const pk = alice.pk();
|
||||
auto const settleDelay = 10s;
|
||||
auto const channelAmount = XRP(2);
|
||||
auto const chan = paychan::channel(alice, bob, env.seq(alice));
|
||||
|
||||
if (cosigning)
|
||||
{
|
||||
env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(tecUNFUNDED));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
|
||||
env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Ter(tecUNFUNDED));
|
||||
}
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!paychan::channelExists(*env.current(), chan));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
Reference in New Issue
Block a user