mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-14 11:45:22 +00:00
Merge branch 'ximinez/lending-XLS-66-ongoing' into ximinez/lending-number-simple
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#ifndef XRPL_APP_PATHS_CREDIT_H_INCLUDED
|
||||
#define XRPL_APP_PATHS_CREDIT_H_INCLUDED
|
||||
#ifndef XRPL_LEDGER_CREDIT_H_INCLUDED
|
||||
#define XRPL_LEDGER_CREDIT_H_INCLUDED
|
||||
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
@@ -710,6 +710,8 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canWithdraw(
|
||||
@@ -717,6 +719,7 @@ canWithdraw(
|
||||
ReadView const& view,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag);
|
||||
|
||||
/** Checks that can withdraw funds from an object to itself or a destination.
|
||||
@@ -730,12 +733,15 @@ canWithdraw(
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canWithdraw(
|
||||
AccountID const& from,
|
||||
ReadView const& view,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag);
|
||||
|
||||
/** Checks that can withdraw funds from an object to itself or a destination.
|
||||
@@ -749,6 +755,8 @@ canWithdraw(
|
||||
* - If withdrawing to self, succeed.
|
||||
* - If not, checks if the receiver requires deposit authorization, and if
|
||||
* the sender has it.
|
||||
* - Checks that the receiver will not exceed the limit (IOU trustline limit
|
||||
* or MPT MaximumAmount).
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canWithdraw(ReadView const& view, STTx const& tx);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -1341,12 +1342,58 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if a withdrawal amount into the destination account exceeds
|
||||
* any applicable receiving limit.
|
||||
* Called by VaultWithdraw and LoanBrokerCoverWithdraw.
|
||||
*
|
||||
* IOU : Performs the trustline check against the destination account's
|
||||
* credit limit to ensure the account's trust maximum is not exceeded.
|
||||
*
|
||||
* MPT: The limit check is effectively skipped (returns true). This is
|
||||
* because MPT MaximumAmount relates to token supply, and withdrawal does not
|
||||
* involve minting new tokens that could exceed the global cap.
|
||||
* On withdrawal, tokens are simply transferred from the vault's pseudo-account
|
||||
* to the destination account. Since no new MPT tokens are minted during this
|
||||
* transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap
|
||||
* even if `from` is the issuer.
|
||||
*/
|
||||
static TER
|
||||
withdrawToDestExceedsLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount)
|
||||
{
|
||||
auto const& issuer = amount.getIssuer();
|
||||
if (from == to || to == issuer || isXRP(issuer))
|
||||
return tesSUCCESS;
|
||||
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
if constexpr (std::is_same_v<TIss, Issue>)
|
||||
{
|
||||
auto const& currency = issue.currency;
|
||||
auto const owed = creditBalance(view, to, issuer, currency);
|
||||
if (owed <= beast::zero)
|
||||
{
|
||||
auto const limit = creditLimit(view, to, issuer, currency);
|
||||
if (-owed >= limit || amount > (limit + owed))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
},
|
||||
amount.asset().value());
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
canWithdraw(
|
||||
AccountID const& from,
|
||||
ReadView const& view,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
|
||||
@@ -1361,7 +1408,7 @@ canWithdraw(
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
return withdrawToDestExceedsLimit(view, from, to, amount);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -1369,11 +1416,12 @@ canWithdraw(
|
||||
AccountID const& from,
|
||||
ReadView const& view,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
auto const toSle = view.read(keylet::account(to));
|
||||
|
||||
return canWithdraw(from, view, to, toSle, hasDestinationTag);
|
||||
return canWithdraw(from, view, to, toSle, amount, hasDestinationTag);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -1382,7 +1430,8 @@ canWithdraw(ReadView const& view, STTx const& tx)
|
||||
auto const from = tx[sfAccount];
|
||||
auto const to = tx[~sfDestination].value_or(from);
|
||||
|
||||
return canWithdraw(from, view, to, tx.isFieldPresent(sfDestinationTag));
|
||||
return canWithdraw(
|
||||
from, view, to, tx[sfAmount], tx.isFieldPresent(sfDestinationTag));
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -1647,6 +1647,283 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
env(loanBroker::set(lender, vaultKeylet.key), ter(tecFROZEN));
|
||||
}
|
||||
|
||||
void
|
||||
testRIPD4274IOU()
|
||||
{
|
||||
using namespace jtx;
|
||||
Account issuer("broker");
|
||||
Account broker("issuer");
|
||||
Account dest("destination");
|
||||
auto const token = issuer["IOU"];
|
||||
|
||||
enum TrustState {
|
||||
RequireAuth,
|
||||
ZeroLimit,
|
||||
ReachedLimit,
|
||||
NearLimit,
|
||||
NoTrustLine,
|
||||
};
|
||||
|
||||
auto test = [&](TrustState trustState) {
|
||||
Env env(*this);
|
||||
|
||||
testcase << "RIPD-4274 IOU with state: "
|
||||
<< static_cast<int>(trustState);
|
||||
|
||||
auto setTrustLine = [&](Account const& acct, TrustState state) {
|
||||
switch (state)
|
||||
{
|
||||
case RequireAuth:
|
||||
env(trust(issuer, token(0), acct, tfSetfAuth));
|
||||
break;
|
||||
case ZeroLimit: {
|
||||
auto jv = trust(acct, token(0));
|
||||
// set QualityIn so that the trustline is not
|
||||
// auto-deleted
|
||||
jv[sfQualityIn] = 10'000'000;
|
||||
env(jv);
|
||||
}
|
||||
break;
|
||||
case ReachedLimit: {
|
||||
env(trust(acct, token(1'000)));
|
||||
env(pay(issuer, acct, token(1'000)));
|
||||
env.close();
|
||||
}
|
||||
break;
|
||||
case NearLimit: {
|
||||
env(trust(acct, token(1'000)));
|
||||
env(pay(issuer, acct, token(950)));
|
||||
env.close();
|
||||
}
|
||||
break;
|
||||
case NoTrustLine:
|
||||
// don't create a trustline
|
||||
break;
|
||||
default:
|
||||
BEAST_EXPECT(false);
|
||||
}
|
||||
env.close();
|
||||
};
|
||||
|
||||
env.fund(XRP(1'000), issuer, broker, dest);
|
||||
env.close();
|
||||
|
||||
if (trustState == RequireAuth)
|
||||
{
|
||||
env(fset(issuer, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
setTrustLine(broker, RequireAuth);
|
||||
}
|
||||
|
||||
setTrustLine(dest, trustState);
|
||||
|
||||
env(trust(broker, token(2'000), 0));
|
||||
env(pay(issuer, broker, token(2'000)));
|
||||
env.close();
|
||||
|
||||
Vault vault(env);
|
||||
auto const [tx, keylet] =
|
||||
vault.create({.owner = broker, .asset = token.asset()});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Test Vault withdraw
|
||||
env(vault.deposit(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}));
|
||||
env.close();
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}),
|
||||
loanBroker::destination(dest),
|
||||
ter(std::ignore));
|
||||
BEAST_EXPECT(env.ter() == tecNO_LINE);
|
||||
env.close();
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}));
|
||||
|
||||
// Test LoanBroker withdraw
|
||||
auto const brokerKeylet =
|
||||
keylet::loanbroker(broker, env.seq(broker));
|
||||
|
||||
env(loanBroker::set(broker, keylet.key));
|
||||
env.close();
|
||||
|
||||
env(loanBroker::coverDeposit(
|
||||
broker, brokerKeylet.key, token(1'000)));
|
||||
env.close();
|
||||
|
||||
env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
|
||||
loanBroker::destination(dest),
|
||||
ter(std::ignore));
|
||||
BEAST_EXPECT(env.ter() == tecNO_LINE);
|
||||
env.close();
|
||||
|
||||
// Clearing RequireAuth shouldn't change the result
|
||||
if (trustState == RequireAuth)
|
||||
{
|
||||
env(fclear(issuer, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
env(loanBroker::coverWithdraw(
|
||||
broker, brokerKeylet.key, token(100)),
|
||||
loanBroker::destination(dest),
|
||||
ter(std::ignore));
|
||||
BEAST_EXPECT(env.ter() == tecNO_LINE);
|
||||
env.close();
|
||||
}
|
||||
};
|
||||
|
||||
test(RequireAuth);
|
||||
test(ZeroLimit);
|
||||
test(ReachedLimit);
|
||||
test(NearLimit);
|
||||
test(NoTrustLine);
|
||||
}
|
||||
|
||||
void
|
||||
testRIPD4274MPT()
|
||||
{
|
||||
using namespace jtx;
|
||||
Account issuer("broker");
|
||||
Account broker("issuer");
|
||||
Account dest("destination");
|
||||
|
||||
enum MPTState {
|
||||
RequireAuth,
|
||||
ReachedMAX,
|
||||
NoMPT,
|
||||
};
|
||||
|
||||
auto test = [&](MPTState MPTState) {
|
||||
Env env(*this);
|
||||
|
||||
testcase << "RIPD-4274 MPT with state: "
|
||||
<< static_cast<int>(MPTState);
|
||||
|
||||
env.fund(XRP(1'000), issuer, broker, dest);
|
||||
env.close();
|
||||
|
||||
auto const maybeToken = [&]() -> std::optional<MPT> {
|
||||
switch (MPTState)
|
||||
{
|
||||
case RequireAuth: {
|
||||
auto tester = MPTTester(
|
||||
{.env = env,
|
||||
.issuer = issuer,
|
||||
.holders = {broker, dest},
|
||||
.pay = 2'000,
|
||||
.flags = MPTDEXFlags | tfMPTRequireAuth,
|
||||
.authHolder = true,
|
||||
.maxAmt = 5'000});
|
||||
// unauthorize dest
|
||||
tester.authorize(
|
||||
{.account = issuer,
|
||||
.holder = dest,
|
||||
.flags = tfMPTUnauthorize});
|
||||
return tester;
|
||||
}
|
||||
case ReachedMAX: {
|
||||
auto tester = MPTTester(
|
||||
{.env = env,
|
||||
.issuer = issuer,
|
||||
.holders = {broker, dest},
|
||||
.pay = 2'000,
|
||||
.flags = MPTDEXFlags,
|
||||
.maxAmt = 4'000});
|
||||
BEAST_EXPECT(
|
||||
env.balance(issuer, tester) == tester(-4'000));
|
||||
return tester;
|
||||
}
|
||||
case NoMPT: {
|
||||
return MPTTester(
|
||||
{.env = env,
|
||||
.issuer = issuer,
|
||||
.holders = {broker},
|
||||
.pay = 2'000,
|
||||
.flags = MPTDEXFlags,
|
||||
.maxAmt = 4'000});
|
||||
}
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}();
|
||||
if (!BEAST_EXPECT(maybeToken))
|
||||
return;
|
||||
|
||||
auto const& token = *maybeToken;
|
||||
|
||||
Vault vault(env);
|
||||
auto const [tx, keylet] =
|
||||
vault.create({.owner = broker, .asset = token.asset()});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Test Vault withdraw
|
||||
env(vault.deposit(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}));
|
||||
env.close();
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}),
|
||||
loanBroker::destination(dest),
|
||||
ter(std::ignore));
|
||||
|
||||
// Shouldn't fail if at MaximumAmount since no new tokens are issued
|
||||
TER const err =
|
||||
MPTState == ReachedMAX ? TER(tesSUCCESS) : tecNO_AUTH;
|
||||
BEAST_EXPECT(env.ter() == err);
|
||||
env.close();
|
||||
|
||||
if (err != tesSUCCESS)
|
||||
{
|
||||
env(vault.withdraw(
|
||||
{.depositor = broker,
|
||||
.id = keylet.key,
|
||||
.amount = token(1'000)}));
|
||||
}
|
||||
|
||||
// Test LoanBroker withdraw
|
||||
auto const brokerKeylet =
|
||||
keylet::loanbroker(broker, env.seq(broker));
|
||||
|
||||
env(loanBroker::set(broker, keylet.key));
|
||||
env.close();
|
||||
|
||||
env(loanBroker::coverDeposit(
|
||||
broker, brokerKeylet.key, token(1'000)));
|
||||
env.close();
|
||||
|
||||
env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
|
||||
loanBroker::destination(dest),
|
||||
ter(std::ignore));
|
||||
BEAST_EXPECT(env.ter() == err);
|
||||
env.close();
|
||||
};
|
||||
|
||||
test(RequireAuth);
|
||||
test(ReachedMAX);
|
||||
test(NoMPT);
|
||||
}
|
||||
|
||||
void
|
||||
testRIPD4274()
|
||||
{
|
||||
testRIPD4274IOU();
|
||||
testRIPD4274MPT();
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -1666,6 +1943,8 @@ public:
|
||||
testRIPD4323();
|
||||
testAMB06_VaultFreezeCheckMissing();
|
||||
|
||||
testRIPD4274();
|
||||
|
||||
// TODO: Write clawback failure tests with an issuer / MPT that doesn't
|
||||
// have the right flags set.
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <xrpld/app/paths/AMMContext.h>
|
||||
#include <xrpld/app/paths/Credit.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/paths/detail/AmountSpec.h>
|
||||
#include <xrpld/app/paths/detail/Steps.h>
|
||||
#include <xrpld/app/paths/detail/StrandFlow.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <xrpld/app/paths/Credit.h>
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
#include <xrpld/app/paths/detail/Steps.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <xrpld/app/misc/AMMHelpers.h>
|
||||
#include <xrpld/app/paths/AMMContext.h>
|
||||
#include <xrpld/app/paths/Credit.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/paths/detail/AmountSpec.h>
|
||||
#include <xrpld/app/paths/detail/FlatSets.h>
|
||||
@@ -11,6 +10,7 @@
|
||||
#include <xrpld/app/paths/detail/Steps.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <xrpld/app/paths/Credit.h>
|
||||
#include <xrpld/app/paths/detail/AmountSpec.h>
|
||||
#include <xrpld/app/paths/detail/StepChecks.h>
|
||||
#include <xrpld/app/paths/detail/Steps.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/Credit.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
Reference in New Issue
Block a user