Update tests, update pseudo-account checking

This commit is contained in:
Ed Hennis
2025-04-02 18:45:31 -04:00
parent 47fa8ff59f
commit d4c2d46984
4 changed files with 151 additions and 16 deletions

View File

@@ -20,6 +20,7 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/mpt.h>
#include <test/jtx/vault.h>
#include <xrpl/basics/base_uint.h>
@@ -42,34 +43,141 @@ class LoanBroker_test : public beast::unit_test::suite
void
testDisabled()
{
// Lending Protocol depends on Single Asset Vault. Test combinations of
// the two amendments. Single Asset Vault depends on MPTokensV1, but
// don't test every combo of that.
// Lending Protocol depends on Single Asset Vault (SAV). Test
// combinations of the two amendments.
// Single Asset Vault depends on MPTokensV1, but don't test every combo
// of that.
using namespace jtx;
FeatureBitset const all{jtx::supported_amendments()};
{
auto const noMPTs = all - featureMPTokensV1;
Env env(*this, noMPTs);
auto failAll = [this](FeatureBitset features, bool goodVault = false) {
Env env(*this, features);
Account const alice{"alice"};
env.fund(XRP(10000), alice);
// Can't create a vault
// Try to create a vault
PrettyAsset const asset{xrpIssue(), 1'000'000};
Vault vault{env};
auto const [tx, keylet] =
vault.create({.owner = alice, .asset = asset});
env(tx, ter(temDISABLED));
env(tx, ter(goodVault ? ter(tesSUCCESS) : ter(temDISABLED)));
env.close();
BEAST_EXPECT(!env.le(keylet));
BEAST_EXPECT(static_cast<bool>(env.le(keylet)) == goodVault);
using namespace loanBroker;
// Can't create a loan broker with the non-existent vault
// Can't create a loan broker regardless of whether the vault exists
env(set(alice, keylet.key), ter(temDISABLED));
}
};
// failAll(all - featureMPTokensV1);
failAll(all - featureSingleAssetVault - featureLendingProtocol);
failAll(all - featureSingleAssetVault);
failAll(all - featureLendingProtocol, true);
}
void
testCreateAndUpdate()
{
using namespace jtx;
// Create 3 loan brokers: one for XRP, one for an IOU, and one for an
// MPT. That'll require three corresponding SAVs.
Env env(*this);
Account issuer{"issuer"};
// For simplicity, alice will be the sole actor for the vault & brokers.
Account alice{"alice"};
// Evan will attempt to be naughty
Account evan{"evan"};
Vault vault{env};
env.fund(XRP(1000), issuer, alice, evan);
// Create assets
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
PrettyAsset const iouAsset = issuer["IOU"];
env(trust(alice, iouAsset(1000)));
env(trust(evan, iouAsset(1000)));
env(pay(issuer, evan, iouAsset(1000)));
env(pay(issuer, alice, iouAsset(1000)));
env.close();
MPTTester mptt{env, issuer, mptInitNoFund};
mptt.create(
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset const mptAsset = mptt.issuanceID();
mptt.authorize({.account = alice});
mptt.authorize({.account = evan});
env(pay(issuer, alice, mptAsset(1000)));
env(pay(issuer, evan, mptAsset(1000)));
env.close();
std::array const assets{xrpAsset, iouAsset, mptAsset};
// Create vaults
struct Vault
{
auto const neither = all - featureMPTokensV1 -
featureSingleAssetVault - featureLendingProtocol;
PrettyAsset const& asset;
uint256 vaultID;
};
std::vector<Vault> vaults;
for (auto const& asset : assets)
{
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
BEAST_EXPECT(env.le(keylet));
vaults.emplace_back(asset, keylet.key);
env(vault.deposit(
{.depositor = alice, .id = keylet.key, .amount = asset(50)}));
}
// Create and update Loan Brokers
for (auto const& vault : vaults)
{
using namespace loanBroker;
// Try some failure cases
env(set(evan, vault.vaultID), ter(tecNO_PERMISSION));
// flags are checked first
env(set(evan, vault.vaultID, ~tfUniversal), ter(temINVALID_FLAG));
// field length validation
// sfData: good length, bad account
env(set(evan, vault.vaultID),
data(strHex(std::string(maxDataPayloadLength, '0'))),
ter(tecNO_PERMISSION));
// sfData: too long
env(set(evan, vault.vaultID),
data(strHex(std::string(maxDataPayloadLength + 1, '0'))),
ter(temINVALID));
// sfManagementFeeRate: good value, bad account
env(set(evan, vault.vaultID),
managementFeeRate(maxFeeRate),
ter(tecNO_PERMISSION));
// sfManagementFeeRate: too big
env(set(evan, vault.vaultID),
managementFeeRate(maxFeeRate + 1),
ter(temINVALID));
// sfCoverRateMinimum: good value, bad account
env(set(evan, vault.vaultID),
coverRateMinimum(maxCoverRate),
ter(tecNO_PERMISSION));
// sfCoverRateMinimum: too big
env(set(evan, vault.vaultID),
coverRateMinimum(maxCoverRate + 1),
ter(temINVALID));
// sfCoverRateLiquidation: good value, bad account
env(set(evan, vault.vaultID),
coverRateLiquidation(maxCoverRate),
ter(tecNO_PERMISSION));
// sfCoverRateLiquidation: too big
env(set(evan, vault.vaultID),
coverRateLiquidation(maxCoverRate + 1),
ter(temINVALID));
auto keylet = keylet::loanbroker(alice.id(), env.seq(alice));
env(set(alice, vault.vaultID));
BEAST_EXPECT(env.le(keylet));
}
}
@@ -78,6 +186,7 @@ public:
run() override
{
testDisabled();
testCreateAndUpdate();
}
};

View File

@@ -916,10 +916,13 @@ ValidNewAccountRoot::finalize(
result == tesSUCCESS)
{
bool const pseudoAccount =
(pseudoAccount_ && view.rules().enabled(featureSingleAssetVault));
(pseudoAccount_ &&
(view.rules().enabled(featureSingleAssetVault) ||
view.rules().enabled(featureLendingProtocol)));
if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE &&
tx.getTxnType() != ttVAULT_CREATE)
tx.getTxnType() != ttVAULT_CREATE &&
tx.getTxnType() != ttLOAN_BROKER_SET)
{
JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
"wrong transaction type";

View File

@@ -281,7 +281,7 @@ Transactor::preflight(PreflightContext const& ctx)
template <class T>
bool
Transactor::validNumericRange(std::optional<T> value, T min, T max)
Transactor::validNumericRange(std::optional<T> value, T max, T min)
{
if (!value)
return true;

View File

@@ -1071,6 +1071,29 @@ static std::array<SField const*, 2> const pseudoAccountOwnerFields = {
&sfVaultID, //
};
[[nodiscard]] bool
isPseudoAccount(std::shared_ptr<SLE const> sleAcct)
{
// Note, the list of the pseudo-account designator fields below MUST be
// maintained but it does NOT need to be amendment-gated, since a non-active
// amendment will not set any field, by definition. Specific properties of a
// pseudo-account are NOT checked here, that's what InvariantCheck is for.
static std::array const fields = //
{
&sfAMMID, //
&sfVaultID, //
&sfLoanBrokerID, //
};
// Intentionally use defensive coding here because it's cheap and makes the
// semantics of true return value clean.
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
std::count_if(
fields.begin(), fields.end(), [&sleAcct](SField const* sf) -> bool {
return sleAcct->isFieldPresent(*sf);
}) > 0;
}
Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(
ApplyView& view,