Merge branch 'develop' into ximinez/lending-XLS-66

This commit is contained in:
Bronek Kozicki
2025-06-10 21:51:45 +08:00
9 changed files with 467 additions and 155 deletions

View File

@@ -2,16 +2,6 @@
convenience variables and sanity checks
#]===================================================================]
include(ProcessorCount)
if (NOT ep_procs)
ProcessorCount(ep_procs)
if (ep_procs GREATER 1)
# never use more than half of cores for EP builds
math (EXPR ep_procs "${ep_procs} / 2")
message (STATUS "Using ${ep_procs} cores for ExternalProject builds.")
endif ()
endif ()
get_property(is_multiconfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
set (CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)

View File

@@ -581,7 +581,10 @@ canAdd(STAmount const& a, STAmount const& b)
return true;
}
// LCOV_EXCL_START
UNREACHABLE("STAmount::canAdd : unexpected STAmount type");
return false;
// LCOV_EXCL_STOP
}
/**
@@ -653,8 +656,10 @@ canSubtract(STAmount const& a, STAmount const& b)
return false;
return true;
}
// LCOV_EXCL_START
UNREACHABLE("STAmount::canSubtract : unexpected STAmount type");
return false;
// LCOV_EXCL_STOP
}
void

View File

@@ -21,9 +21,11 @@
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/ledger/Dir.h>
#include <xrpld/ledger/Sandbox.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
@@ -56,27 +58,39 @@ struct EscrowToken_test : public beast::unit_test::suite
return 0;
}
void
issuerIOUEscrowed(
jtx::PrettyAmount
issuerBalance(
jtx::Env& env,
jtx::Account const& account,
Currency const& currency,
int const& outstanding,
int const& locked)
Issue const& issue)
{
Json::Value params;
params[jss::account] = account.human();
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
auto const result = jrr[jss::result];
auto const actualOutstanding =
result[jss::obligations][to_string(currency)];
BEAST_EXPECT(actualOutstanding == to_string(outstanding));
if (locked != 0)
{
auto const actualEscrowed =
result[jss::locked][to_string(currency)];
BEAST_EXPECT(actualEscrowed == to_string(locked));
}
auto const obligations =
result[jss::obligations][to_string(issue.currency)];
if (obligations.isNull())
return {STAmount(issue, 0), account.name()};
STAmount const amount = amountFromString(issue, obligations.asString());
return {amount, account.name()};
}
jtx::PrettyAmount
issuerEscrowed(
jtx::Env& env,
jtx::Account const& account,
Issue const& issue)
{
Json::Value params;
params[jss::account] = account.human();
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
auto const result = jrr[jss::result];
auto const locked = result[jss::locked][to_string(issue.currency)];
if (locked.isNull())
return {STAmount(issue, 0), account.name()};
STAmount const amount = amountFromString(issue, locked.asString());
return {amount, account.name()};
}
void
@@ -136,6 +150,37 @@ struct EscrowToken_test : public beast::unit_test::suite
env(escrow::cancel(bob, alice, seq2), finishResult);
env.close();
}
for (bool const withTokenEscrow : {false, true})
{
auto const amend =
withTokenEscrow ? features : features - featureTokenEscrow;
Env env{*this, amend};
auto const baseFee = env.current()->fees().base;
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(5000), alice, bob, gw);
env(fset(gw, asfAllowTrustLineLocking));
env.close();
env.trust(USD(10'000), alice, bob);
env.close();
env(pay(gw, alice, USD(5000)));
env(pay(gw, bob, USD(5000)));
env.close();
auto const seq1 = env.seq(alice);
env(escrow::finish(bob, alice, seq1),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150),
ter(tecNO_TARGET));
env.close();
env(escrow::cancel(bob, alice, seq1), ter(tecNO_TARGET));
env.close();
}
}
void
@@ -865,34 +910,76 @@ struct EscrowToken_test : public beast::unit_test::suite
env.close();
env.trust(USD(10'000), alice, bob);
env.close();
env(pay(gw, alice, USD(5000)));
env(pay(gw, bob, USD(5000)));
env(pay(gw, alice, USD(5'000)));
env(pay(gw, bob, USD(5'000)));
env.close();
auto const outstandingUSD = USD(10'000);
// Create & Finish Escrow
auto const seq1 = env.seq(alice);
env(escrow::create(alice, bob, USD(1'000)),
escrow::condition(escrow::cb1),
escrow::finish_time(env.now() + 1s),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
env(escrow::finish(bob, alice, seq1),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
{
auto const preAliceUSD = env.balance(alice, USD);
auto const preBobUSD = env.balance(bob, USD);
env(escrow::create(alice, bob, USD(1'000)),
escrow::condition(escrow::cb1),
escrow::finish_time(env.now() + 1s),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000));
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
BEAST_EXPECT(
issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000));
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000));
}
{
auto const preAliceUSD = env.balance(alice, USD);
auto const preBobUSD = env.balance(bob, USD);
env(escrow::finish(bob, alice, seq1),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD);
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD + USD(1'000));
BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD);
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0));
}
// Create & Cancel Escrow
auto const seq2 = env.seq(alice);
env(escrow::create(alice, bob, USD(1'000)),
escrow::condition(escrow::cb2),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
env(escrow::cancel(bob, alice, seq2), ter(tesSUCCESS));
env.close();
{
auto const preAliceUSD = env.balance(alice, USD);
auto const preBobUSD = env.balance(bob, USD);
env(escrow::create(alice, bob, USD(1'000)),
escrow::condition(escrow::cb2),
escrow::finish_time(env.now() + 1s),
escrow::cancel_time(env.now() + 2s),
fee(baseFee * 150),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000));
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
BEAST_EXPECT(
issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000));
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000));
}
{
auto const preAliceUSD = env.balance(alice, USD);
auto const preBobUSD = env.balance(bob, USD);
env(escrow::cancel(bob, alice, seq2), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD + USD(1'000));
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD);
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0));
}
}
void
@@ -2430,7 +2517,6 @@ struct EscrowToken_test : public beast::unit_test::suite
mptGw.authorize({.account = alice});
mptGw.authorize({.account = bob});
auto const MPT = mptGw["MPT"];
env(pay(gw, alice, MPT(10)));
env(pay(gw, bob, MPT(10)));
env.close();
@@ -2521,6 +2607,39 @@ struct EscrowToken_test : public beast::unit_test::suite
env.close();
}
// tecOBJECT_NOT_FOUND: MPT issuance does not exist
{
Env env{*this, features};
auto const baseFee = env.current()->fees().base;
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(10'000), alice, bob);
env.close();
auto const seq1 = env.seq(alice);
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) {
Sandbox sb(&view, tapNONE);
auto sleNew =
std::make_shared<SLE>(keylet::escrow(alice, seq1));
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
STAmount amt(mpt, 10);
sleNew->setAccountID(sfDestination, bob);
sleNew->setFieldAmount(sfAmount, amt);
sb.insert(sleNew);
sb.apply(view);
return true;
});
env(escrow::finish(bob, alice, seq1),
escrow::condition(escrow::cb1),
escrow::fulfillment(escrow::fb1),
fee(baseFee * 150),
ter(tecOBJECT_NOT_FOUND));
env.close();
}
// tecLOCKED: issuer has locked the dest
{
Env env{*this, features};
@@ -2726,6 +2845,36 @@ struct EscrowToken_test : public beast::unit_test::suite
env(escrow::cancel(bob, alice, seq1), ter(tecNO_AUTH));
env.close();
}
// tecOBJECT_NOT_FOUND: MPT issuance does not exist
{
Env env{*this, features};
auto const baseFee = env.current()->fees().base;
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(10'000), alice, bob);
auto const seq1 = env.seq(alice);
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) {
Sandbox sb(&view, tapNONE);
auto sleNew =
std::make_shared<SLE>(keylet::escrow(alice, seq1));
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
STAmount amt(mpt, 10);
sleNew->setAccountID(sfDestination, bob);
sleNew->setFieldAmount(sfAmount, amt);
sb.insert(sleNew);
sb.apply(view);
return true;
});
env(escrow::cancel(bob, alice, seq1),
fee(baseFee),
ter(tecOBJECT_NOT_FOUND));
env.close();
}
}
void
@@ -3603,12 +3752,14 @@ struct EscrowToken_test : public beast::unit_test::suite
fee(baseFee * 150));
env.close();
env(pay(alice, gw, MPT(10'000)), ter(tecPATH_PARTIAL));
env(pay(alice, gw, MPT(9'990)));
env(pay(bob, gw, MPT(10'000)));
BEAST_EXPECT(env.balance(alice, MPT) == MPT(0));
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10);
BEAST_EXPECT(env.balance(bob, MPT) == MPT(0));
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
BEAST_EXPECT(env.balance(gw, MPT) == MPT(10));
mptGw.authorize({.account = bob, .flags = tfMPTUnauthorize});
mptGw.destroy(
{.id = mptGw.issuanceID(),

View File

@@ -369,6 +369,12 @@ struct Escrow_test : public beast::unit_test::suite
env.fund(XRP(5000), "alice", "bob", "gw");
env.close();
// temINVALID_FLAG
env(escrow::create("alice", "bob", XRP(1000)),
escrow::finish_time(env.now() + 5s),
txflags(tfPassive),
ter(temINVALID_FLAG));
// Finish time is in the past
env(escrow::create("alice", "bob", XRP(1000)),
escrow::finish_time(env.now() - 5s),

View File

@@ -810,6 +810,153 @@ class Invariants_test : public beast::unit_test::suite
ac.view().insert(sleNew);
return true;
});
// IOU < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// escrow with too-little iou
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
auto sleNew = std::make_shared<SLE>(
keylet::escrow(A1, (*sle)[sfSequence] + 2));
Issue const usd{
Currency(0x5553440000000000), AccountID(0x4985601)};
STAmount amt(usd, -1);
sleNew->setFieldAmount(sfAmount, amt);
ac.view().insert(sleNew);
return true;
});
// IOU bad currency
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// escrow with bad iou currency
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
auto sleNew = std::make_shared<SLE>(
keylet::escrow(A1, (*sle)[sfSequence] + 2));
Issue const bad{badCurrency(), AccountID(0x4985601)};
STAmount amt(bad, 1);
sleNew->setFieldAmount(sfAmount, amt);
ac.view().insert(sleNew);
return true;
});
// MPT < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// escrow with too-little mpt
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
auto sleNew = std::make_shared<SLE>(
keylet::escrow(A1, (*sle)[sfSequence] + 2));
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
STAmount amt(mpt, -1);
sleNew->setFieldAmount(sfAmount, amt);
ac.view().insert(sleNew);
return true;
});
// MPT OutstandingAmount < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// mpissuance outstanding is negative
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
auto sleNew =
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
sleNew->setFieldU64(sfOutstandingAmount, -1);
ac.view().insert(sleNew);
return true;
});
// MPT LockedAmount < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// mpissuance locked is less than locked
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
auto sleNew =
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
sleNew->setFieldU64(sfLockedAmount, -1);
ac.view().insert(sleNew);
return true;
});
// MPT OutstandingAmount < LockedAmount
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// mpissuance outstanding is less than locked
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
auto sleNew =
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
sleNew->setFieldU64(sfOutstandingAmount, 1);
sleNew->setFieldU64(sfLockedAmount, 10);
ac.view().insert(sleNew);
return true;
});
// MPT MPTAmount < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// mptoken amount is negative
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
auto sleNew =
std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
sleNew->setFieldU64(sfMPTAmount, -1);
ac.view().insert(sleNew);
return true;
});
// MPT LockedAmount < 0
doInvariantCheck(
{{"escrow specifies invalid amount"}},
[](Account const& A1, Account const&, ApplyContext& ac) {
// mptoken locked amount is negative
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
MPTIssue const mpt{
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
auto sleNew =
std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
sleNew->setFieldU64(sfLockedAmount, -1);
ac.view().insert(sleNew);
return true;
});
}
void

View File

@@ -596,7 +596,7 @@ EscrowCreate::doApply()
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
return ret; // LCOV_EXCL_LINE
}
// increment owner count
@@ -771,26 +771,26 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return err;
}
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
if (ctx.view.rules().enabled(featureTokenEscrow))
{
if (!ctx.view.rules().enabled(featureTokenEscrow))
return temDISABLED; // LCOV_EXCL_LINE
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
AccountID const dest = (*slep)[sfDestination];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
}
return tesSUCCESS;
}
@@ -1020,7 +1020,12 @@ EscrowFinish::doApply()
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE
return tecNO_TARGET;
}
// If a cancel time is present, a finish operation should only succeed prior
// to that time. fix1571 corrects a logic error in the check that would make
@@ -1251,7 +1256,7 @@ escrowCancelPreclaimHelper<MPTIssue>(
keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
@@ -1267,26 +1272,27 @@ escrowCancelPreclaimHelper<MPTIssue>(
TER
EscrowCancel::preclaim(PreclaimContext const& ctx)
{
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
AccountID const account = (*slep)[sfAccount];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
if (ctx.view.rules().enabled(featureTokenEscrow))
{
if (!ctx.view.rules().enabled(featureTokenEscrow))
return temDISABLED; // LCOV_EXCL_LINE
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
AccountID const account = (*slep)[sfAccount];
STAmount const amount = (*slep)[sfAmount];
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowCancelPreclaimHelper<T>(
ctx, account, amount);
},
amount.asset().value());
!isTesSuccess(ret))
return ret;
}
}
return tesSUCCESS;
}
@@ -1297,7 +1303,12 @@ EscrowCancel::doApply()
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
if (!slep)
{
if (ctx_.view().rules().enabled(featureTokenEscrow))
return tecINTERNAL; // LCOV_EXCL_LINE
return tecNO_TARGET;
}
if (ctx_.view().rules().enabled(fix1571))
{

View File

@@ -324,26 +324,6 @@ NoZeroEscrow::visitEntry(
std::shared_ptr<SLE const> const& after)
{
auto isBad = [](STAmount const& amount) {
// IOU case
if (amount.holds<Issue>())
{
if (amount <= beast::zero)
return true;
if (badCurrency() == amount.getCurrency())
return true;
}
// MPT case
if (amount.holds<MPTIssue>())
{
if (amount <= beast::zero)
return true;
if (amount.mpt() > MPTAmount{maxMPTokenAmount})
return true;
}
// XRP case
if (amount.native())
{
@@ -353,7 +333,28 @@ NoZeroEscrow::visitEntry(
if (amount.xrp() >= INITIAL_XRP)
return true;
}
else
{
// IOU case
if (amount.holds<Issue>())
{
if (amount <= beast::zero)
return true;
if (badCurrency() == amount.getCurrency())
return true;
}
// MPT case
if (amount.holds<MPTIssue>())
{
if (amount <= beast::zero)
return true;
if (amount.mpt() > MPTAmount{maxMPTokenAmount})
return true; // LCOV_EXCL_LINE
}
}
return false;
};

View File

@@ -61,7 +61,7 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
return tecHAS_OBLIGATIONS;
if ((*sleMPT)[~sfLockedAmount].value_or(0) != 0)
return tecHAS_OBLIGATIONS;
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
return tesSUCCESS;
}

View File

@@ -2809,18 +2809,18 @@ rippleLockEscrowMPT(
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
}
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
if (amount.getIssuer() == sender)
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
@@ -2828,23 +2828,23 @@ rippleLockEscrowMPT(
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleLockEscrowMPT: MPToken not found for " << sender;
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
}
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
auto const amt = sle->getFieldU64(sfMPTAmount);
auto const pay = amount.mpt().value();
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleLockEscrowMPT: insufficient MPTAmount for "
<< to_string(sender) << ": " << amt << " < " << pay;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] = amt - pay;
@@ -2852,12 +2852,12 @@ rippleLockEscrowMPT(
uint64_t const locked = (*sle)[~sfLockedAmount].value_or(0);
if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleLockEscrowMPT: overflow on locked amount for "
<< to_string(sender) << ": " << locked << " + " << pay;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sle->isFieldPresent(sfLockedAmount))
(*sle)[sfLockedAmount] += pay;
@@ -2877,13 +2877,13 @@ rippleLockEscrowMPT(
// Overflow check for addition
if (!canAdd(
STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay)))
{
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance "
"locked amount for "
<< mptIssue.getMptID() << ": " << issuanceEscrowed
<< " + " << pay;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sleIssuance->isFieldPresent(sfLockedAmount))
(*sleIssuance)[sfLockedAmount] += pay;
@@ -2908,21 +2908,21 @@ rippleUnlockEscrowMPT(
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
{
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
}
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
// Decrease the Issuance EscrowedAmount
{
if (!sleIssuance->isFieldPresent(sfLockedAmount))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: no locked amount in issuance for "
<< mptIssue.getMptID();
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
auto const redeem = amount.mpt().value();
@@ -2930,12 +2930,12 @@ rippleUnlockEscrowMPT(
// Underflow check for subtraction
if (!canSubtract(
STAmount(mptIssue, locked), STAmount(mptIssue, redeem)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: insufficient locked amount for "
<< mptIssue.getMptID() << ": " << locked << " < " << redeem;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const newLocked = locked - redeem;
if (newLocked == 0)
@@ -2951,23 +2951,23 @@ rippleUnlockEscrowMPT(
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto sle = view.peek(mptokenID);
if (!sle)
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: MPToken not found for " << receiver;
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
}
} // LCOV_EXCL_STOP
auto current = sle->getFieldU64(sfMPTAmount);
auto delta = amount.mpt().value();
// Overflow check for addition
if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: overflow on MPTAmount for "
<< to_string(receiver) << ": " << current << " + " << delta;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
(*sle)[sfMPTAmount] += delta;
view.update(sle);
@@ -2981,55 +2981,56 @@ rippleUnlockEscrowMPT(
// Underflow check for subtraction
if (!canSubtract(
STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: insufficient outstanding amount for "
<< mptIssue.getMptID() << ": " << outstanding << " < "
<< redeem;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
}
if (issuer == sender)
{
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, "
"cannot unlock MPTs.";
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
else
{
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto sle = view.peek(mptokenID);
if (!sle)
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: MPToken not found for " << sender;
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
}
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
if (!sle->isFieldPresent(sfLockedAmount))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: no locked amount in MPToken for "
<< to_string(sender);
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = sle->getFieldU64(sfLockedAmount);
auto const delta = amount.mpt().value();
// Underflow check for subtraction
// LCOV_EXCL_START
if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta)))
{
{ // LCOV_EXCL_START
JLOG(j.error())
<< "rippleUnlockEscrowMPT: insufficient locked amount for "
<< to_string(sender) << ": " << locked << " < " << delta;
return tecINTERNAL; // LCOV_EXCL_LINE
}
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const newLocked = locked - delta;
if (newLocked == 0)