Add LoanBroker update tests

- Move some helper functions.
- Check the regular key in the pseudo-transaction invariant check.
- Transactor::checkSign will always fail for a pseudo-account, so even
  if someone figures out how to get a good signature, it won't work.
This commit is contained in:
Ed Hennis
2025-04-04 20:00:40 -04:00
parent 1db5e9b4d3
commit b7152496c1
6 changed files with 177 additions and 59 deletions

View File

@@ -34,15 +34,6 @@
namespace ripple {
namespace test {
static inline bool
checkVL(
std::shared_ptr<SLE const> const& sle,
SField const& field,
std::string const& expected)
{
return strHex(expected) == strHex(sle->getFieldVL(field));
}
struct Credentials_test : public beast::unit_test::suite
{
void

View File

@@ -27,14 +27,6 @@
namespace ripple {
namespace test {
bool
checkVL(Slice const& result, std::string expected)
{
Serializer s;
s.addRaw(result);
return s.getString() == expected;
}
struct DID_test : public beast::unit_test::suite
{
void

View File

@@ -96,7 +96,8 @@ class LoanBroker_test : public beast::unit_test::suite
// Evan will attempt to be naughty
Account evan{"evan"};
Vault vault{env};
env.fund(XRP(1000), issuer, alice, evan);
env.fund(XRP(1000), issuer, noripple(alice, evan));
env.close();
// Create assets
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
@@ -137,6 +138,7 @@ class LoanBroker_test : public beast::unit_test::suite
env(vault.deposit(
{.depositor = alice, .id = keylet.key, .amount = asset(50)}));
env.close();
}
// Create and update Loan Brokers
@@ -144,47 +146,145 @@ class LoanBroker_test : public beast::unit_test::suite
{
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 badKeylet = keylet::vault(alice.id(), env.seq(alice));
// Try some failure cases
// not the vault owner
env(set(evan, vault.vaultID), ter(tecNO_PERMISSION));
// not a vault
env(set(alice, badKeylet.key), ter(tecNO_ENTRY));
// 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));
auto keylet = keylet::loanbroker(alice.id(), env.seq(alice));
env(set(alice, vault.vaultID));
env.close();
auto broker = env.le(keylet);
if (BEAST_EXPECT(broker))
{
// Check the fields
BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID);
BEAST_EXPECT(broker->at(sfAccount) != alice.id());
BEAST_EXPECT(broker->at(sfOwner) == alice.id());
BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate));
BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum));
BEAST_EXPECT(
!broker->isFieldPresent(sfCoverRateLiquidation));
BEAST_EXPECT(broker->at(sfFlags) == 0);
BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1);
BEAST_EXPECT(!broker->isFieldPresent(sfData));
BEAST_EXPECT(broker->at(sfOwnerCount) == 0);
BEAST_EXPECT(broker->at(sfDebtTotal) == 0);
BEAST_EXPECT(broker->at(sfDebtMaximum) == 0);
BEAST_EXPECT(broker->at(sfCoverAvailable) == 0);
BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0);
BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0);
// Load the pseudo-account
// Update the fields
auto nextKeylet =
keylet::loanbroker(alice.id(), env.seq(alice));
// no-op
env(set(alice, vault.vaultID), loanBrokerID(keylet.key));
// fields that can't be changed
// LoanBrokerID
env(set(alice, vault.vaultID),
loanBrokerID(nextKeylet.key),
ter(tecNO_ENTRY));
// VaultID
env(set(alice, nextKeylet.key),
loanBrokerID(keylet.key),
ter(tecNO_PERMISSION));
// Owner
env(set(evan, vault.vaultID),
loanBrokerID(keylet.key),
ter(tecNO_PERMISSION));
// ManagementFeeRate
env(set(alice, vault.vaultID),
loanBrokerID(keylet.key),
managementFeeRate(maxFeeRate),
ter(temINVALID));
// CoverRateMinimum
env(set(alice, vault.vaultID),
loanBrokerID(keylet.key),
coverRateMinimum(maxFeeRate),
ter(temINVALID));
// CoverRateLiquidation
env(set(alice, vault.vaultID),
loanBrokerID(keylet.key),
coverRateLiquidation(maxFeeRate),
ter(temINVALID));
// fields that can be changed
std::string const testData("Test Data 1234");
// Bad data must be hex encoded
try
{
env(set(alice, vault.vaultID),
loanBrokerID(keylet.key),
data(testData),
ter(temINVALID));
fail();
}
catch (std::exception const& e)
{
BEAST_EXPECT(
e.what() ==
std::string("invalidParamsField 'tx_json.Data' has "
"invalid data."));
}
// Bad debt maximum
// Data & Debt maximum
env(set(alice, vault.vaultID),
loanBrokerID(keylet.key),
data(strHex(testData)),
debtMaximum(Number(175, -1)));
env.close();
// Check the updated fields
broker = env.le(keylet);
BEAST_EXPECT(checkVL(broker->at(sfData), testData));
BEAST_EXPECT(
broker->at(sfDebtMaximum) ==
Number(175, -1));
}
}
}
}

View File

@@ -289,6 +289,25 @@ checkArraySize(Json::Value const& val, unsigned int size);
std::uint32_t
ownerCount(test::jtx::Env const& env, test::jtx::Account const& account);
inline
[[nodiscard]] bool
checkVL(Slice const& result, std::string expected)
{
Serializer s;
s.addRaw(result);
return s.getString() == expected;
}
inline
[[nodiscard]] bool
checkVL(
std::shared_ptr<SLE const> const& sle,
SField const& field,
std::string const& expected)
{
return strHex(expected) == strHex(sle->getFieldVL(field));
}
/* Path finding */
/******************************************************************************/
void

View File

@@ -1783,7 +1783,11 @@ ValidPseudoAccounts::visitEntry(
lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth))
{
errors_.emplace_back(
"Invariant failed: pseudo-account flags are not set");
"pseudo-account flags are not set");
}
if (after->isFieldPresent(sfRegularKey))
{
errors_.emplace_back("pseudo-account has a regular key");
}
}
}

View File

@@ -539,6 +539,18 @@ Transactor::apply()
NotTEC
Transactor::checkSign(PreclaimContext const& ctx)
{
{
auto const id = ctx.tx.getAccountID(sfAccount);
auto const sle = ctx.view.read(keylet::account(id));
if (ctx.view.rules().enabled(featureLendingProtocol) &&
isPseudoAccount(sle))
// Pseudo-accounts can't sign transactions. This check is gated on
// the Lending Protocol amendment because that's the project it was
// added under, and it doesn't justify another amendment
return tefBAD_AUTH;
}
if (ctx.flags & tapDRY_RUN)
{
// This code must be different for `simulate`