add prefunded sponsor tests

This commit is contained in:
tequ
2025-10-08 16:48:47 +09:00
parent a65cf6e07d
commit d8dc000488
11 changed files with 1656 additions and 1274 deletions

View File

@@ -1041,13 +1041,19 @@ ownerCount(std::shared_ptr<SLE const> const& sponsorSle)
return ownerCount + sponsoringOwnerCount - sponsoredOwnerCount;
}
bool
isReserveSponsored(STTx const& tx)
{
auto const sponsor = tx.getFieldObject(sfSponsor);
return sponsor.isFlag(tfSponsorReserve);
}
bool
isSponsorReserveCoSigning(STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsorSignature))
return false;
auto const sponsor = tx.getFieldObject(sfSponsor);
return sponsor.isFlag(tfSponsorReserve);
return isReserveSponsored(tx);
}
TER
@@ -1141,14 +1147,37 @@ getTxReserveSponsor(ReadView const& view, STTx const& tx)
return std::nullopt;
}
std::optional<AccountID>
getLedgerEntryReserveSponsorAccountID(
std::shared_ptr<SLE const> sle,
SF_ACCOUNT const& field)
{
if (sle->isFieldPresent(field))
return sle->getAccountID(field);
return std::nullopt;
}
std::optional<std::shared_ptr<SLE>>
getLedgerEntryReserveSponsor(
ApplyView& view,
std::shared_ptr<SLE> sle,
SF_ACCOUNT const& field)
{
if (sle->isFieldPresent(field))
return view.peek(keylet::account(sle->getAccountID(field)));
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.peek(keylet::account(*sponsorID));
return std::nullopt;
}
std::optional<std::shared_ptr<SLE const>>
getLedgerEntryReserveSponsor(
ReadView const& view,
std::shared_ptr<SLE const> sle,
SF_ACCOUNT const& field)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.read(keylet::account(*sponsorID));
return std::nullopt;
}
@@ -1242,11 +1271,13 @@ adjustOwnerCount(
XRPL_ASSERT(
sle, "ripple::adjustOwnerCount : co-signing sponsor not found");
auto const currentReserveCount = sle->getFieldU32(sfReserveCount);
XRPL_ASSERT(
sle->at(sfReserveCount) >= amount,
currentReserveCount >= amount,
"ripple::adjustOwnerCount : reserve count not enough");
sle->at(sfReserveCount) = sle->getFieldU32(sfReserveCount) + amount;
sle->at(sfReserveCount) = currentReserveCount - amount;
view.update(sle);
}
}
@@ -1550,12 +1581,18 @@ authorizeMPToken(
auto const sponsor = getTxReserveSponsor(view, tx);
auto const isSponsoredAndPreFunded =
sponsor && !isSponsorReserveCoSigning(tx);
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
if (ownerCount(sponsor.value_or(sleAcct)) >= 2)
// If PreFunded Sponsor, it must be checked whether sufficient
// ReserveCount exists.
if (ownerCount(sponsor.value_or(sleAcct)) >= 2 ||
isSponsoredAndPreFunded)
{
if (auto const ret = checkInsufficientReserve(
view, tx, sleAcct, priorBalance, sponsor, 1);

File diff suppressed because it is too large Load Diff

View File

@@ -135,17 +135,26 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
return terNO_RIPPLE;
}
auto const sponsor = getTxReserveSponsorAccountID(ctx.tx);
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
// Check the reserve for LPToken trustline
STAmount const xrpBalance =
xrpLiquid(ctx.view, sponsor.value_or(accountID), 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::zero)
auto const accountSle = ctx.view.read(keylet::account(accountID));
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance),
sponsorSle,
1);
!isTesSuccess(ret))
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
auto const ownerCountAdj = isReserveSponsored(ctx.tx) ? 0 : 1;
STAmount const xrpBalance =
xrpLiquid(ctx.view, accountID, ownerCountAdj, ctx.j);
auto insufficientBalance = [&](STAmount const& asset) {
if (isXRP(asset))
return xrpBalance < asset;

View File

@@ -169,8 +169,6 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
{
auto const accountID = ctx.tx[sfAccount];
auto const sponsor = getTxReserveSponsorAccountID(ctx.tx);
auto const ammSle =
ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
if (!ammSle)
@@ -228,8 +226,18 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
// Adjust the reserve if LP doesn't have LPToken trustline
auto const sle = ctx.view.read(
keylet::line(accountID, lpIssue.account, lpIssue.currency));
if (xrpLiquid(ctx.view, sponsor.value_or(accountID), !sle, ctx.j) >=
deposit)
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
auto const accountSle = ctx.view.read(keylet::account(accountID));
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance) - deposit,
sponsorSle,
1,
!sle);
isTesSuccess(ret))
return TER(tesSUCCESS);
if (sle)
return tecUNFUNDED_AMM;
@@ -357,10 +365,17 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
// We checked above but need to check again if depositing IOU only.
if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
{
STAmount const xrpBalance =
xrpLiquid(ctx.view, sponsor.value_or(accountID), 1, ctx.j);
auto const accountSle = ctx.view.read(keylet::account(accountID));
auto const sponsor = getTxReserveSponsor(ctx.view, ctx.tx);
// Insufficient reserve
if (xrpBalance <= beast::zero)
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance),
sponsor,
1);
!isTesSuccess(ret))
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;

View File

@@ -600,12 +600,13 @@ AMMWithdraw::withdraw(
return tesSUCCESS;
if (!view.exists(keylet::line(account, issue)))
{
auto const sleAccount =
view.read(keylet::account(sponsor.value_or(account)));
auto const sleAccount = view.read(keylet::account(account));
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sleAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const balance = (*sleAccount)[sfBalance].xrp();
std::uint32_t const count = ownerCount(sleAccount);
std::uint32_t const count =
ownerCount(sponsorSle ? *sponsorSle : sleAccount);
if (count >= 2)
{
if (auto const ret = checkInsufficientReserve(

View File

@@ -62,19 +62,19 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id)
view.succ(first.key, last.key.next()).value_or(last.key)));
}
static std::shared_ptr<SLE>
static Expected<std::shared_ptr<SLE>, TER>
getPageForToken(
ApplyView& view,
STTx const& tx,
AccountID const& owner,
std::optional<AccountID> const& sponsor,
uint256 const& id,
std::function<void(
ApplyView&,
STTx const&,
std::shared_ptr<SLE> const&,
AccountID const&,
std::optional<AccountID> const&)> const& createCallback)
std::function<
TER(ApplyView&,
STTx const&,
std::shared_ptr<SLE> const&,
AccountID const&,
std::optional<AccountID> const&)> const& createCallback)
{
auto const base = keylet::nftpage_min(owner);
auto const first = keylet::nftpage(base, id);
@@ -94,7 +94,10 @@ getPageForToken(
cp = std::make_shared<SLE>(last);
cp->setFieldArray(sfNFTokens, arr);
view.insert(cp);
createCallback(view, tx, cp, owner, sponsor);
if (auto const ret = createCallback(view, tx, cp, owner, sponsor);
!isTesSuccess(ret))
return Unexpected(ret);
return cp;
}
@@ -222,7 +225,9 @@ getPageForToken(
cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp);
createCallback(view, tx, np, owner, sponsor);
if (auto const ret = createCallback(view, tx, np, owner, sponsor);
ret != tesSUCCESS)
return Unexpected(ret);
// fixNFTokenDirV1 corrects a bug in the initial implementation that
// would put an NFT in the wrong page. The problem was caused by an
@@ -298,7 +303,7 @@ insertToken(
// First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert
// the NFT.
std::shared_ptr<SLE> page = getPageForToken(
auto page = getPageForToken(
view,
tx,
owner,
@@ -308,10 +313,20 @@ insertToken(
STTx const& tx,
std::shared_ptr<SLE> const& newPage,
AccountID const& owner,
std::optional<AccountID> const& sponsor) {
std::optional<AccountID> const& sponsor) -> TER {
std::optional<std::shared_ptr<SLE>> const sponsorSle = sponsor
? view.peek(keylet::account(*sponsor))
: std::optional<std::shared_ptr<SLE>>{std::nullopt};
if (isReserveSponsored(tx))
{
auto const ownerSle = view.read(keylet::account(owner));
auto const ownerBalance = ownerSle->getFieldAmount(sfBalance);
if (auto const ret = checkInsufficientReserve(
view, tx, ownerSle, ownerBalance, sponsorSle, 1);
!isTesSuccess(ret))
return ret;
}
adjustOwnerCount(
view,
tx,
@@ -320,13 +335,17 @@ insertToken(
1,
beast::Journal{beast::Journal::getNullSink()});
addSponsorToLedgerEntry(newPage, sponsorSle);
return tesSUCCESS;
});
if (!page)
if (!page.has_value())
return page.error();
if (!(*page))
return tecNO_SUITABLE_NFTOKEN_PAGE;
{
auto arr = page->getFieldArray(sfNFTokens);
auto arr = (*page)->getFieldArray(sfNFTokens);
arr.push_back(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
@@ -334,10 +353,10 @@ insertToken(
o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
page->setFieldArray(sfNFTokens, arr);
(*page)->setFieldArray(sfNFTokens, arr);
}
view.update(page);
view.update((*page));
return tesSUCCESS;
}

View File

@@ -58,6 +58,12 @@ SetOracle::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
uint32_t
calculateOracleReserve(std::size_t count)
{
return count > 5 ? 2 : 1;
}
TER
SetOracle::preclaim(PreclaimContext const& ctx)
{
@@ -143,10 +149,18 @@ SetOracle::preclaim(PreclaimContext const& ctx)
if (!pairsDel.empty())
return tecTOKEN_PAIR_NOT_FOUND;
auto const oldCount =
sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
auto const newCount = pairs.size() > 5 ? 2 : 1;
adjustReserve = newCount - oldCount;
auto const oldCount = calculateOracleReserve(
sle->getFieldArray(sfPriceDataSeries).size());
auto const newCount = calculateOracleReserve(pairs.size());
// if different sponsors, check with newCount
auto const currentSponsor = getLedgerEntryReserveSponsorAccountID(sle);
auto const newSponsor = getTxReserveSponsorAccountID(ctx.tx);
if ((!currentSponsor && !newSponsor) ||
(currentSponsor && newSponsor && *currentSponsor == *newSponsor))
adjustReserve = newCount - oldCount;
else
adjustReserve = newCount;
}
else
{
@@ -155,7 +169,7 @@ SetOracle::preclaim(PreclaimContext const& ctx)
if (!ctx.tx.isFieldPresent(sfProvider) ||
!ctx.tx.isFieldPresent(sfAssetClass))
return temMALFORMED;
adjustReserve = pairs.size() > 5 ? 2 : 1;
adjustReserve = calculateOracleReserve(pairs.size());
}
if (pairs.empty())
@@ -237,7 +251,7 @@ SetOracle::doApply()
sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
pairs.emplace(tokenPairKey(entry), std::move(priceData));
}
auto const oldCount = pairs.size() > 5 ? 2 : 1;
auto const oldCount = calculateOracleReserve(pairs.size());
// update/add/delete pairs
for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
{
@@ -276,7 +290,7 @@ SetOracle::doApply()
(*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
}
auto const newCount = pairs.size() > 5 ? 2 : 1;
auto const newCount = calculateOracleReserve(pairs.size());
auto const adjust = newCount - oldCount;
if (adjust > 0)
@@ -353,7 +367,7 @@ SetOracle::doApply()
(*sle)[sfOwnerNode] = *page;
auto const count = series.size() > 5 ? 2 : 1;
auto const count = calculateOracleReserve(series.size());
auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx);
if (!adjustOwnerCount(ctx_, sponsor, count))
return tefINTERNAL; // LCOV_EXCL_LINE

View File

@@ -403,7 +403,12 @@ SetTrust::doApply()
txSponsorSle = view().peek(keylet::account(*txSponsorAcc));
std::uint32_t const uOwnerCount = ownerCount(txSponsorSle.value_or(sle));
bool const freeTrustLine = uOwnerCount < 2;
bool const isSponsoredAndPreFunded =
txSponsorSle && !isSponsorReserveCoSigning(ctx_.tx);
// If PreFunded Sponsor, it must be checked whether sufficient
// ReserveCount exists.
bool const freeTrustLine = uOwnerCount < 2 && !isSponsoredAndPreFunded;
std::uint32_t uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0);
std::uint32_t uQualityOut(
@@ -639,6 +644,17 @@ SetTrust::doApply()
if (bLowReserveSet && !bLowReserved)
{
// should be checked PreFunded Sponsor before adjustOwnerCount()
if (auto const ret = checkInsufficientReserve(
view(),
ctx_.tx,
sleLowAccount,
mPriorBalance,
txSponsorSle,
1);
isSponsoredAndPreFunded && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
// Set reserve for low account.
adjustOwnerCount(
view(), ctx_.tx, sleLowAccount, txSponsorSle, 1, viewJ);
@@ -663,6 +679,17 @@ SetTrust::doApply()
if (bHighReserveSet && !bHighReserved)
{
// should be checked PreFunded Sponsor before adjustOwnerCount()
if (auto const ret = checkInsufficientReserve(
view(),
ctx_.tx,
sleHighAccount,
mPriorBalance,
txSponsorSle,
1);
isSponsoredAndPreFunded && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
// Set reserve for high account.
adjustOwnerCount(
view(), ctx_.tx, sleHighAccount, txSponsorSle, 1, viewJ);

View File

@@ -311,7 +311,17 @@ SponsorshipSet::doApply()
{
// transfer feeAmount to ledger entry
(*sponsorAccSle)[sfBalance] -= *feeAmount;
(*sponsorObjSle)[sfFeeAmount] += *feeAmount;
if ((*sponsorObjSle).isFieldPresent(sfFeeAmount))
{
auto const oldFeeAmount =
(*sponsorObjSle).getFieldAmount(sfFeeAmount);
auto const newFeeAmount = oldFeeAmount + *feeAmount;
(*sponsorObjSle).setFieldAmount(sfFeeAmount, newFeeAmount);
}
else
{
(*sponsorObjSle).setFieldAmount(sfFeeAmount, *feeAmount);
}
}
if (maxFee)
@@ -321,7 +331,7 @@ SponsorshipSet::doApply()
if (reserveCount)
(*sponsorObjSle)[sfReserveCount] =
(*sponsorObjSle)[sfReserveCount] + *reserveCount;
(*sponsorObjSle).getFieldU32(sfReserveCount) + *reserveCount;
// update Flags
auto flags = sponsorObjSle->getFieldU32(sfFlags);

View File

@@ -162,12 +162,25 @@ VaultCreate::doApply()
if (auto ter = dirLink(view(), account_, vault))
return ter;
auto const sponsor = getTxReserveSponsor(view(), tx);
adjustOwnerCount(view(), tx, owner, sponsor, 1, j_);
addSponsorToLedgerEntry(vault, sponsor);
if (auto const ret = checkInsufficientReserve(
view(), tx, owner, mPriorBalance, sponsor, 0);
!isTesSuccess(ret))
return ret;
if (!ctx_.view().rules().enabled(featureSponsor))
{
adjustOwnerCount(view(), tx, owner, sponsor, 1, j_);
addSponsorToLedgerEntry(vault, sponsor);
if (auto const ret = checkInsufficientReserve(
view(), tx, owner, mPriorBalance, sponsor, 0);
!isTesSuccess(ret))
return ret;
}
else
{
// after Sponsor Amendment, check insufficient reserve first
if (auto const ret = checkInsufficientReserve(
view(), tx, owner, mPriorBalance, sponsor, 1);
!isTesSuccess(ret))
return ret;
adjustOwnerCount(view(), tx, owner, sponsor, 1, j_);
addSponsorToLedgerEntry(vault, sponsor);
}
auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID);
if (!maybePseudo)