mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-05 09:46:53 +00:00
add prefunded sponsor tests
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user