Compare commits

..

1 Commits

Author SHA1 Message Date
Bart
ce2a02e828 refactor: Apply various minor improvements and corrections 2026-04-29 06:37:51 -04:00
10 changed files with 72 additions and 381 deletions

View File

@@ -19,7 +19,6 @@ XRPL_FIX (Cleanup3_2_0, Supported::no, VoteBehavior::DefaultNo
XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -81,12 +81,9 @@ setCurrentThreadNameImpl(std::string_view name)
{
// truncate and set the thread name.
char boundedName[maxThreadNameLength + 1];
std::snprintf(
boundedName,
sizeof(boundedName),
"%.*s",
static_cast<int>(maxThreadNameLength),
name.data()); // NOLINT(bugprone-suspicious-stringview-data-usage)
auto const boundedSize = name.size() < maxThreadNameLength ? name.size() : maxThreadNameLength;
name.copy(boundedName, boundedSize);
boundedName[boundedSize] = '\0';
pthread_setname_np(pthread_self(), boundedName);

View File

@@ -1857,18 +1857,8 @@ loanMakePayment(
// -------------------------------------------------------------
// overpayment handling
//
// If the "fixSecurity3_1_3" amendment is enabled, truncate "amount",
// at the loan scale. If the raw value is used, the overpayment
// amount could be meaningless dust. Trying to process such a small
// amount will, at best, waste time when all the result values round
// to zero. At worst, it can cause logical errors with tiny amounts
// of interest that don't add up correctly.
auto const roundedAmount = view.rules().enabled(fixSecurity3_1_3)
? roundToAsset(asset, amount, loanScale, Number::towards_zero)
: amount;
if (paymentType == LoanPaymentType::overpayment && loan->isFlag(lsfLoanOverpayment) &&
paymentRemainingProxy > 0 && totalPaid < roundedAmount &&
paymentRemainingProxy > 0 && totalPaid < amount &&
numPayments < loanMaximumPaymentsPerTransaction)
{
TenthBips32 const overpaymentInterestRate{loan->at(sfOverpaymentInterestRate)};
@@ -1877,7 +1867,7 @@ loanMakePayment(
// It shouldn't be possible for the overpayment to be greater than
// totalValueOutstanding, because that would have been processed as
// another normal payment. But cap it just in case.
Number const overpayment = std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy);
Number const overpayment = std::min(amount - totalPaid, *totalValueOutstandingProxy);
detail::ExtendedPaymentComponents const overpaymentComponents =
detail::computeOverpaymentComponents(

View File

@@ -30,7 +30,6 @@
#include <bit>
#include <cstdint>
#include <memory>
#include <vector>
namespace xrpl {
@@ -144,7 +143,6 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
Number const numPaymentEstimate = static_cast<std::int64_t>(amount / regularPayment);
// Charge one base fee per paymentsPerFeeIncrement payments, rounding up.
// This set round is safe because there's a mode guard just above
Number::setround(Number::upward);
auto const feeIncrements = std::max(
std::int64_t(1),
@@ -442,10 +440,9 @@ LoanPay::doApply()
// Vault object state changes
view.update(vaultSle);
Number const assetsAvailableBefore = *assetsAvailableProxy;
Number const assetsTotalBefore = *assetsTotalProxy;
#if !NDEBUG
{
Number const assetsAvailableBefore = *assetsAvailableProxy;
Number const pseudoAccountBalanceBefore = accountHolds(
view,
vaultPseudoAccount,
@@ -469,6 +466,16 @@ LoanPay::doApply()
"xrpl::LoanPay::doApply",
"assets available must not be greater than assets outstanding");
if (*assetsAvailableProxy > *assetsTotalProxy)
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Vault assets available must not be greater "
"than assets outstanding. Available: "
<< *assetsAvailableProxy << ", Total: " << *assetsTotalProxy;
return tecINTERNAL;
// LCOV_EXCL_STOP
}
JLOG(j_.debug()) << "total paid to vault raw: " << totalPaidToVaultRaw
<< ", total paid to vault rounded: " << totalPaidToVaultRounded
<< ", total paid to broker: " << totalPaidToBroker
@@ -494,68 +501,12 @@ LoanPay::doApply()
associateAsset(*vaultSle, asset);
// Duplicate some checks after rounding
Number const assetsAvailableAfter = *assetsAvailableProxy;
Number const assetsTotalAfter = *assetsTotalProxy;
XRPL_ASSERT_PARTS(
assetsAvailableAfter <= assetsTotalAfter,
*assetsAvailableProxy <= *assetsTotalProxy,
"xrpl::LoanPay::doApply",
"assets available must not be greater than assets outstanding");
if (assetsAvailableAfter == assetsAvailableBefore)
{
// An unchanged assetsAvailable indicates that the amount paid to the
// vault was zero, or rounded to zero. That should be impossible, but I
// can't rule it out for extreme edge cases, so fail gracefully if it
// happens.
//
// LCOV_EXCL_START
JLOG(j_.warn()) << "LoanPay: Vault assets available unchanged after rounding: " //
<< "Before: " << assetsAvailableBefore //
<< ", After: " << assetsAvailableAfter;
return tecPRECISION_LOSS;
// LCOV_EXCL_STOP
}
if (paymentParts->valueChange != beast::zero && assetsTotalAfter == assetsTotalBefore)
{
// Non-zero valueChange with an unchanged assetsTotal indicates that the
// actual value change rounded to zero. That should be impossible, but I
// can't rule it out for extreme edge cases, so fail gracefully if it
// happens.
//
// LCOV_EXCL_START
JLOG(j_.warn())
<< "LoanPay: Vault assets expected change, but unchanged after rounding: " //
<< "Before: " << assetsTotalBefore //
<< ", After: " << assetsTotalAfter //
<< ", ValueChange: " << paymentParts->valueChange;
return tecPRECISION_LOSS;
// LCOV_EXCL_STOP
}
if (paymentParts->valueChange == beast::zero && assetsTotalAfter != assetsTotalBefore)
{
// A change in assetsTotal when there was no valueChange indicates that
// something really weird happened. That should be flat out impossible.
//
// LCOV_EXCL_START
JLOG(j_.fatal()) << "LoanPay: Vault assets changed unexpectedly after rounding: " //
<< "Before: " << assetsTotalBefore //
<< ", After: " << assetsTotalAfter //
<< ", ValueChange: " << paymentParts->valueChange;
return tecINTERNAL;
// LCOV_EXCL_STOP
}
if (assetsAvailableAfter > assetsTotalAfter)
{
// Assets available are not allowed to be larger than assets total.
// LCOV_EXCL_START
JLOG(j_.fatal()) << "LoanPay: Vault assets available must not be greater "
"than assets outstanding. Available: "
<< assetsAvailableAfter << ", Total: " << assetsTotalAfter;
return tecINTERNAL;
// LCOV_EXCL_STOP
}
// These three values are used to check that funds are conserved after the transfers
#if !NDEBUG
auto const accountBalanceBefore = accountHolds(
view,
account_,
@@ -584,6 +535,7 @@ LoanPay::doApply()
ahIGNORE_AUTH,
j_,
SpendableHandling::shFULL_BALANCE);
#endif
if (totalPaidToVaultRounded != beast::zero)
{
@@ -619,22 +571,19 @@ LoanPay::doApply()
return ter;
#if !NDEBUG
{
Number const pseudoAccountBalanceAfter = accountHolds(
view,
vaultPseudoAccount,
asset,
FreezeHandling::fhIGNORE_FREEZE,
AuthHandling::ahIGNORE_AUTH,
j_);
XRPL_ASSERT_PARTS(
assetsAvailableAfter == pseudoAccountBalanceAfter,
"xrpl::LoanPay::doApply",
"vault pseudo balance agrees after");
}
#endif
Number const assetsAvailableAfter = *assetsAvailableProxy;
Number const pseudoAccountBalanceAfter = accountHolds(
view,
vaultPseudoAccount,
asset,
FreezeHandling::fhIGNORE_FREEZE,
AuthHandling::ahIGNORE_AUTH,
j_);
XRPL_ASSERT_PARTS(
assetsAvailableAfter == pseudoAccountBalanceAfter,
"xrpl::LoanPay::doApply",
"vault pseudo balance agrees after");
// Check that funds are conserved
auto const accountBalanceAfter = accountHolds(
view,
account_,
@@ -663,121 +612,14 @@ LoanPay::doApply()
ahIGNORE_AUTH,
j_,
SpendableHandling::shFULL_BALANCE);
auto const balanceScale = [&]() {
// Find a reasonable scale to use for the balance comparisons.
//
// First find the minimum and maximum exponent of all the non-zero balances, before and
// after. If min and max are equal, use that value. If they are not, use "max + 1" to reduce
// rounding discrepancies without making the result meaningless. Cap the scale at
// STAmount::cMaxOffset, just in case the numbers are all very large.
std::vector<int> exponents;
for (auto const& a : {
accountBalanceBefore,
vaultBalanceBefore,
brokerBalanceBefore,
accountBalanceAfter,
vaultBalanceAfter,
brokerBalanceAfter,
})
{
// Exclude zeroes
if (a != beast::zero)
exponents.push_back(a.exponent());
}
if (exponents.empty())
{
UNREACHABLE("xrpl::LoanPay::doApply : all zeroes");
return 0;
}
auto const [minItr, maxItr] = std::ranges::minmax_element(exponents);
auto const min = *minItr;
auto const max = *maxItr;
JLOG(j_.trace()) << "Min scale: " << min << ", max scale: " << max;
// IOU rounding can be interesting. We want all the balance checks to agree, but don't want
// to round to such an extreme that it becomes meaningless. e.g. Everything rounds to one
// digit. So add 1 to the max (reducing the number of digits after the decimal point by 1)
// if the scales are not already all the same.
return std::min(min == max ? max : max + 1, STAmount::cMaxOffset);
}();
// No object changes are made below this point
XRPL_ASSERT_PARTS(
Number::getround() == Number::to_nearest,
"xrpl::LoanPay::doApply",
"Number rounding to_nearest");
NumberRoundModeGuard const mg(Number::to_nearest);
auto const accountBalanceBeforeRounded = roundToScale(accountBalanceBefore, balanceScale);
auto const vaultBalanceBeforeRounded = roundToScale(vaultBalanceBefore, balanceScale);
auto const brokerBalanceBeforeRounded = roundToScale(brokerBalanceBefore, balanceScale);
auto const totalBalanceBefore = accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore;
auto const totalBalanceBeforeRounded = roundToScale(totalBalanceBefore, balanceScale);
JLOG(j_.trace()) << "Before: " //
<< "account " << Number(accountBalanceBeforeRounded) << " ("
<< Number(accountBalanceBefore) << ")"
<< ", vault " << Number(vaultBalanceBeforeRounded) << " ("
<< Number(vaultBalanceBefore) << ")"
<< ", broker " << Number(brokerBalanceBeforeRounded) << " ("
<< Number(brokerBalanceBefore) << ")"
<< ", total " << Number(totalBalanceBeforeRounded) << " ("
<< Number(totalBalanceBefore) << ")";
auto const accountBalanceAfterRounded = roundToScale(accountBalanceAfter, balanceScale);
auto const vaultBalanceAfterRounded = roundToScale(vaultBalanceAfter, balanceScale);
auto const brokerBalanceAfterRounded = roundToScale(brokerBalanceAfter, balanceScale);
auto const totalBalanceAfter = accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter;
auto const totalBalanceAfterRounded = roundToScale(totalBalanceAfter, balanceScale);
JLOG(j_.trace()) << "After: " //
<< "account " << Number(accountBalanceAfterRounded) << " ("
<< Number(accountBalanceAfter) << ")"
<< ", vault " << Number(vaultBalanceAfterRounded) << " ("
<< Number(vaultBalanceAfter) << ")"
<< ", broker " << Number(brokerBalanceAfterRounded) << " ("
<< Number(brokerBalanceAfter) << ")"
<< ", total " << Number(totalBalanceAfterRounded) << " ("
<< Number(totalBalanceAfter) << ")";
auto const accountBalanceChange = accountBalanceAfter - accountBalanceBefore;
auto const vaultBalanceChange = vaultBalanceAfter - vaultBalanceBefore;
auto const brokerBalanceChange = brokerBalanceAfter - brokerBalanceBefore;
auto const totalBalanceChange = accountBalanceChange + vaultBalanceChange + brokerBalanceChange;
auto const totalBalanceChangeRounded = roundToScale(totalBalanceChange, balanceScale);
JLOG(j_.trace()) << "Changes: " //
<< "account " << to_string(accountBalanceChange) //
<< ", vault " << to_string(vaultBalanceChange) //
<< ", broker " << to_string(brokerBalanceChange) //
<< ", total " << to_string(totalBalanceChangeRounded) << " ("
<< Number(totalBalanceChange) << ")";
if (totalBalanceBeforeRounded != totalBalanceAfterRounded)
{
JLOG(j_.warn()) << "Total rounded balances don't match"
<< (totalBalanceChangeRounded == beast::zero ? ", but total changes do"
: "");
}
if (totalBalanceChangeRounded != beast::zero)
{
JLOG(j_.warn()) << "Total balance changes don't match"
<< (totalBalanceBeforeRounded == totalBalanceAfterRounded
? ", but total balances do"
: "");
}
// Rounding for IOUs can be weird, so check a few different ways to show
// that funds are conserved.
XRPL_ASSERT_PARTS(
totalBalanceBeforeRounded == totalBalanceAfterRounded ||
totalBalanceChangeRounded == beast::zero,
accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore ==
accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter,
"xrpl::LoanPay::doApply",
"funds are conserved (with rounding)");
XRPL_ASSERT_PARTS(
accountBalanceAfter >= beast::zero, "xrpl::LoanPay::doApply", "positive account balance");
XRPL_ASSERT_PARTS(
accountBalanceAfter < accountBalanceBefore || account_ == asset.getIssuer(),
"xrpl::LoanPay::doApply",
@@ -798,6 +640,7 @@ LoanPay::doApply()
vaultBalanceAfter > vaultBalanceBefore || brokerBalanceAfter > brokerBalanceBefore,
"xrpl::LoanPay::doApply",
"vault and/or broker balance increased");
#endif
return tesSUCCESS;
}

View File

@@ -68,15 +68,12 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
if (hasExpired(ctx.view, (*offerSLE)[~sfExpiration]))
{
// Before fixExpiredNFTokenOfferRemoval amendment, expired
// offers caused tecEXPIRED in preclaim, leaving them on ledger
// forever. After the amendment, we allow expired offers to
// reach doApply() where they get deleted and tecEXPIRED is
// returned.
if (!ctx.view.rules().enabled(fixExpiredNFTokenOfferRemoval))
// Before fixSecurity3_1_3 amendment, expired offers caused tecEXPIRED in preclaim,
// leaving them on ledger forever. After the amendment, we allow expired offers to
// reach doApply() where they get deleted and tecEXPIRED is returned.
if (!ctx.view.rules().enabled(fixSecurity3_1_3))
return {nullptr, tecEXPIRED};
// Amendment enabled: return the expired offer to be handled in
// doApply
// Amendment enabled: return the expired offer to be handled in doApply.
}
if ((*offerSLE)[sfAmount].negative())
@@ -450,10 +447,9 @@ NFTokenAcceptOffer::doApply()
auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
// With fixExpiredNFTokenOfferRemoval amendment, check for expired offers
// and delete them, returning tecEXPIRED. This ensures expired offers
// are properly cleaned up from the ledger.
if (view().rules().enabled(fixExpiredNFTokenOfferRemoval))
// With fixSecurity3_1_3 amendment, check for expired offers and delete them, returning
// tecEXPIRED. This ensures expired offers are properly cleaned up from the ledger.
if (view().rules().enabled(fixSecurity3_1_3))
{
bool foundExpired = false;

View File

@@ -370,11 +370,16 @@ protected:
env.balance(vaultPseudo, broker.asset).number());
if (ownerCount == 0)
{
// The Vault must be perfectly balanced if there
// are no loans outstanding
// Allow some slop for rounding IOUs
// TODO: This needs to be an exact match once all the
// other rounding issues are worked out.
auto const total = vaultSle->at(sfAssetsTotal);
auto const available = vaultSle->at(sfAssetsAvailable);
env.test.BEAST_EXPECT(total == available);
env.test.BEAST_EXPECT(
total == available ||
(!broker.asset.integral() && available != 0 &&
((total - available) / available < Number(1, -6))));
env.test.BEAST_EXPECT(vaultSle->at(sfLossUnrealized) == 0);
}
}
@@ -7063,140 +7068,6 @@ protected:
BEAST_EXPECT(afterSecondCoverAvailable == 0);
}
void
testYieldTheftRounding(std::uint32_t flags)
{
testcase("Rounding manipulation does not permit yield theft");
using namespace jtx;
using namespace loan;
// 1. Setup Environment
Env env(*this, all);
Account const issuer{"issuer"};
Account const lender{"lender"};
Account const borrower{"borrower"};
env.fund(XRP(1000), issuer, lender, borrower);
env.close();
// 2. Asset Selection
PrettyAsset const iou = issuer["USD"];
env(trust(lender, iou(100'000'000)));
env(trust(borrower, iou(100'000'000)));
env(pay(issuer, lender, iou(100'000'000)));
env(pay(issuer, borrower, iou(100'000'000)));
env.close();
// 3. Create Vault and Broker with High Debt Limit (100M)
auto const brokerInfo = createVaultAndBroker(
env,
iou,
lender,
{
.vaultDeposit = 5'000'000,
.debtMax = Number{100'000'000},
.coverDeposit = 500'000,
});
auto const [currentSeq, vaultKeylet] = [&]() {
auto const brokerSle = env.le(keylet::loanbroker(brokerInfo.brokerID));
auto const currentSeq = brokerSle->at(sfLoanSequence);
auto const vaultKeylet = keylet::vault(brokerSle->at(sfVaultID));
return std::make_tuple(currentSeq, vaultKeylet);
}();
// 4. Loan Parameters (Attack Vector)
Number const principal = 1'000'000;
TenthBips32 const interestRate = TenthBips32{1}; // 0.001%
std::uint32_t const paymentInterval = 86400;
std::uint32_t const paymentTotal = 3650;
auto const loanSetFee = fee(env.current()->fees().base * 2);
env(set(borrower, brokerInfo.brokerID, iou(principal).value(), flags),
sig(sfCounterpartySignature, lender),
loan::interestRate(interestRate),
loan::paymentInterval(paymentInterval),
loan::paymentTotal(paymentTotal),
fee(loanSetFee));
env.close();
// --- RETRIEVE OBJECTS & SETUP ATTACK ---
auto borrowerBalance = [&]() { return env.balance(borrower, iou); };
auto const borrowerScale = static_cast<STAmount const&>(borrowerBalance()).exponent();
auto const loanKeylet = keylet::loan(brokerInfo.brokerID, currentSeq);
auto const maybePeriodicPayment = [&]() -> std::optional<STAmount> {
auto const loanSle = env.le(loanKeylet);
if (!BEAST_EXPECT(loanSle))
return std::nullopt;
// Construct Payment
return STAmount{iou, loanSle->at(sfPeriodicPayment)};
}();
if (!maybePeriodicPayment)
return;
auto const periodicPayment = *maybePeriodicPayment;
auto const roundedPayment = roundToScale(periodicPayment, borrowerScale, Number::upward);
// ATTACK: Add dust buffer (1e-9) to force 'excess' logic execution
STAmount const paymentBuffer{iou, Number(1, -9)};
STAmount const attackPayment = periodicPayment + paymentBuffer;
auto const maybeInitialVaultAssets = [&]() -> std::optional<Number> {
auto const vault = env.le(vaultKeylet);
if (!BEAST_EXPECT(vault))
return std::nullopt;
return vault->at(sfAssetsTotal);
}();
if (!maybeInitialVaultAssets)
return;
auto const initialVaultAssets = *maybeInitialVaultAssets;
// 5. Execution Loop
int yieldTheftCount = 0;
auto previousAssetsTotal = initialVaultAssets;
for (int i = 0; i < 100; ++i)
{
auto const balanceBefore = borrowerBalance();
env(pay(borrower, loanKeylet.key, attackPayment, flags));
env.close();
auto const borrowerDelta = balanceBefore - borrowerBalance();
BEAST_EXPECT(borrowerDelta.signum() == roundedPayment.signum());
auto const loanSle = env.le(loanKeylet);
if (!BEAST_EXPECT(loanSle))
break;
auto const updatedPayment = STAmount{iou, loanSle->at(sfPeriodicPayment)};
BEAST_EXPECT(
(roundToScale(updatedPayment, borrowerScale, Number::upward) == roundedPayment));
BEAST_EXPECT(
(updatedPayment == periodicPayment) ||
(flags == tfLoanOverpayment && i >= 2 && updatedPayment < periodicPayment));
auto const currentVaultSle = env.le(vaultKeylet);
if (!BEAST_EXPECT(currentVaultSle))
break;
auto const currentAssetsTotal = currentVaultSle->at(sfAssetsTotal);
auto const delta = currentAssetsTotal - previousAssetsTotal;
BEAST_EXPECT(
(delta == beast::zero && borrowerDelta <= roundedPayment) ||
(delta > beast::zero && borrowerDelta > roundedPayment));
// If tx succeeded but Assets Total didn't change, interest was
// stolen.
if (delta == beast::zero && borrowerDelta > roundedPayment)
{
yieldTheftCount++;
}
previousAssetsTotal = currentAssetsTotal;
}
BEAST_EXPECTS(yieldTheftCount == 0, std::to_string(yieldTheftCount));
}
// Tests that vault withdrawals work correctly when the vault has unrealized
// loss from an impaired loan, ensuring the invariant check properly
// accounts for the loss.
@@ -7335,11 +7206,6 @@ public:
testLoanPayLateFullPaymentBypassesPenalties();
testLoanCoverMinimumRoundingExploit();
#endif
for (auto const flags : {0u, tfLoanOverpayment})
{
testYieldTheftRounding(flags);
}
testWithdrawReflectsUnrealizedLoss();
testInvalidLoanSet();

View File

@@ -1096,10 +1096,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
// The buy offer must not have expired.
// NOTE: this is only a preclaim check with the
// fixExpiredNFTokenOfferRemoval amendment disabled.
// fixSecurity3_1_3 amendment disabled.
env(token::acceptBuyOffer(alice, buyerExpOfferIndex), ter(tecEXPIRED));
env.close();
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
buyerCount--;
}
@@ -1117,12 +1117,12 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
// The sell offer must not have expired.
// NOTE: this is only a preclaim check with the
// fixExpiredNFTokenOfferRemoval amendment disabled.
// fixSecurity3_1_3 amendment disabled.
env(token::acceptSellOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
env.close();
// Alice's count is decremented by one when the expired offer is
// removed.
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
aliceCount--;
}
@@ -3101,10 +3101,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
// No one can accept an expired sell offer.
env(token::acceptSellOffer(buyer, offer1), ter(tecEXPIRED));
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
// With fixSecurity3_1_3 amendment, the first accept
// attempt deletes the expired offer. Without the amendment,
// the offer remains and we can try to accept it again.
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
// After amendment: offer was deleted by first accept attempt
minterCount--;
@@ -3123,7 +3123,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
if (!features[fixExpiredNFTokenOfferRemoval])
if (!features[fixSecurity3_1_3])
{
// Before amendment: expired offer still exists and needs to be
// cancelled
@@ -3189,10 +3189,10 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
// An expired buy offer cannot be accepted.
env(token::acceptBuyOffer(minter, offer1), ter(tecEXPIRED));
// With fixExpiredNFTokenOfferRemoval amendment, the first accept
// With fixSecurity3_1_3 amendment, the first accept
// attempt deletes the expired offer. Without the amendment,
// the offer remains and we can try to accept it again.
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
// After amendment: offer was deleted by first accept attempt
buyerCount--;
@@ -3211,7 +3211,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
if (!features[fixExpiredNFTokenOfferRemoval])
if (!features[fixSecurity3_1_3])
{
// Before amendment: expired offer still exists and can be
// cancelled
@@ -3288,7 +3288,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
env(token::brokerOffers(issuer, buyOffer1, sellOffer1), ter(tecEXPIRED));
env.close();
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
// With amendment: expired offers are deleted
minterCount--;
@@ -3298,7 +3298,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
BEAST_EXPECT(ownerCount(env, minter) == minterCount);
BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
// The buy offer was deleted, so no need to cancel it
// The sell offer still exists, so we can cancel it
@@ -3377,7 +3377,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
env.close();
BEAST_EXPECT(ownerCount(env, issuer) == 0);
if (features[fixExpiredNFTokenOfferRemoval])
if (features[fixSecurity3_1_3])
{
// After amendment: expired offers were deleted during broker
// attempt
@@ -3463,7 +3463,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
// The expired offers are still in the ledger.
BEAST_EXPECT(ownerCount(env, issuer) == 0);
if (!features[fixExpiredNFTokenOfferRemoval])
if (!features[fixSecurity3_1_3])
{
// Before amendment: expired offers still exist in ledger
BEAST_EXPECT(ownerCount(env, minter) == 2);
@@ -7190,7 +7190,7 @@ public:
{
testWithFeats(
allFeatures - fixNFTokenReserve - featureNFTokenMintOffer - featureDynamicNFT -
fixExpiredNFTokenOfferRemoval);
fixSecurity3_1_3);
}
};
@@ -7227,7 +7227,7 @@ class NFTokenWOExpiredOfferRemoval_test : public NFTokenBaseUtil_test
void
run() override
{
testWithFeats(allFeatures - fixExpiredNFTokenOfferRemoval);
testWithFeats(allFeatures - fixSecurity3_1_3);
}
};

View File

@@ -1201,7 +1201,7 @@ class LedgerEntry_test : public beast::unit_test::suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
"Invalid field 'authorized_credentials', not array.");
"Invalid field 'authorized_credentials', not array of objects.");
}
{
@@ -1219,7 +1219,7 @@ class LedgerEntry_test : public beast::unit_test::suite
checkErrorValue(
jrr[jss::result],
"malformedAuthorizedCredentials",
"Invalid field 'authorized_credentials', not array.");
"Invalid field 'authorized_credentials', not array of objects.");
}
{

View File

@@ -2639,7 +2639,7 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
{
fee_.update(
Resource::feeModerateBurdenPeer,
" Reply limit reached. Truncating reply.");
"Reply limit reached. Truncating reply.");
break;
}
}

View File

@@ -267,7 +267,7 @@ parseAuthorizeCredentials(Json::Value const& jv)
if (!jo.isObject())
{
return LedgerEntryHelpers::invalidFieldError(
"malformedAuthorizedCredentials", jss::authorized_credentials, "array");
"malformedAuthorizedCredentials", jss::authorized_credentials, "array of objects");
}
if (auto const value = LedgerEntryHelpers::hasRequired(