Remove temSTRING_TOO_LARGE, fix authorizedDomain, refactor Vault_test

This commit is contained in:
Bronek Kozicki
2025-01-21 16:41:32 +00:00
parent 580a85f2c8
commit c08f86d473
9 changed files with 389 additions and 491 deletions

View File

@@ -141,7 +141,6 @@ enum TEMcodes : TERUnderlyingType {
temARRAY_TOO_LARGE,
temBAD_TRANSFER_FEE,
temSTRING_TOO_LARGE,
};
//------------------------------------------------------------------------------

View File

@@ -20,7 +20,6 @@
#include <test/jtx/Account.h>
#include <test/jtx/fee.h>
#include <test/jtx/mpt.h>
#include <test/jtx/subcases.h>
#include <test/jtx/utility.h>
#include <test/jtx/vault.h>
#include <xrpl/protocol/Asset.h>
@@ -34,246 +33,348 @@ using namespace test::jtx;
class Vault_test : public beast::unit_test::suite
{
void
testSequence(
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Vault& vault,
PrettyAsset const& asset)
{
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
BEAST_EXPECT(env.le(keylet));
{
testcase("fail to deposit more than assets held");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(10000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
testcase("deposit non-zero amount");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(50)});
env(tx);
}
{
testcase("deposit non-zero amount again");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(50)});
env(tx);
}
{
testcase("fail to delete non-empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx, ter(tecHAS_OBLIGATIONS));
}
{
testcase("fail to update because wrong owner");
auto tx = vault.set({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase("fail to update immutable flags");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfFlags] = tfVaultPrivate;
env(tx, ter(temINVALID_FLAG));
}
{
testcase("fail to set maximum lower than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = asset(50).number();
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
testcase("set maximum higher than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = asset(200).number();
env(tx);
}
{
testcase("fail to deposit more than maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(200)});
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
testcase("fail to withdraw more than assets held");
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
testcase("deposit up to maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(100)});
env(tx);
}
if (!asset.raw().native())
{
testcase("fail to clawback because wrong issuer");
auto tx = vault.clawback(
{.issuer = owner,
.id = keylet.key,
.holder = depositor,
.amount = asset(50)});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase("clawback");
auto code =
asset.raw().native() ? ter(tecNO_PERMISSION) : ter(tesSUCCESS);
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = depositor,
.amount = asset(50)});
env(tx, code);
}
// TODO: redeem.
{
testcase("withdraw non-zero assets");
auto number = asset.raw().native() ? 200 : 150;
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(number)});
env(tx);
}
{
testcase("fail to delete because wrong owner");
auto tx = vault.del({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase("delete empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx);
BEAST_EXPECT(!env.le(keylet));
}
}
TEST_CASE(Sequences)
testSequences()
{
using namespace test::jtx;
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
auto vault = env.vault();
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
SUBCASE("XRP")
{
PrettyAsset asset{xrpIssue(), 1'000'000};
testSequence(env, issuer, owner, depositor, vault, asset);
}
SUBCASE("IOU")
{
PrettyAsset asset = issuer["IOU"];
env.trust(asset(1000), depositor);
env(pay(issuer, depositor, asset(1000)));
auto const testSequence = [this](
std::string const& prefix,
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Vault& vault,
PrettyAsset const& asset) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
testSequence(env, issuer, owner, depositor, vault, asset);
}
BEAST_EXPECT(env.le(keylet));
SUBCASE("MPT")
{
MPTTester mptt{env, issuer, {.fund = false}};
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset asset = mptt.issuanceID();
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
env.close();
testSequence(env, issuer, owner, depositor, vault, asset);
}
{
testcase(prefix + " fail to deposit more than assets held");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(10000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
testcase(prefix + " deposit non-zero amount");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(50)});
env(tx);
}
{
testcase(prefix + " deposit non-zero amount again");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(50)});
env(tx);
}
{
testcase(prefix + " fail to delete non-empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx, ter(tecHAS_OBLIGATIONS));
}
{
testcase(prefix + " fail to update because wrong owner");
auto tx = vault.set({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase(prefix + " fail to update immutable flags");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfFlags] = tfVaultPrivate;
env(tx, ter(temINVALID_FLAG));
}
{
testcase(
prefix + " fail to set maximum lower than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = asset(50).number();
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
testcase(prefix + " set maximum higher than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = asset(200).number();
env(tx);
}
{
testcase(prefix + " fail to deposit more than maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(200)});
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
testcase(prefix + " fail to withdraw more than assets held");
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
testcase(prefix + " deposit up to maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(100)});
env(tx);
}
if (!asset.raw().native())
{
testcase(prefix + " fail to clawback because wrong issuer");
auto tx = vault.clawback(
{.issuer = owner,
.id = keylet.key,
.holder = depositor,
.amount = asset(50)});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase(prefix + " clawback");
auto code = asset.raw().native() ? ter(tecNO_PERMISSION)
: ter(tesSUCCESS);
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = depositor,
.amount = asset(50)});
env(tx, code);
}
// TODO: redeem.
{
testcase("withdraw non-zero assets");
auto number = asset.raw().native() ? 200 : 150;
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(number)});
env(tx);
}
{
testcase("fail to delete because wrong owner");
auto tx = vault.del({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
testcase("delete empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx);
BEAST_EXPECT(!env.le(keylet));
}
};
auto testCases =
[this, &testSequence](
std::string prefix,
std::function<PrettyAsset(
Env & env, Account const& issuer, Account const& depositor)>
setup) {
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
auto vault = env.vault();
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
PrettyAsset asset = setup(env, issuer, depositor);
testSequence(
prefix, env, issuer, owner, depositor, vault, asset);
};
testCases(
"XRP",
[](Env& env, Account const& issuer, Account const& depositor)
-> PrettyAsset { return {xrpIssue(), 1'000'000};
});
testCases(
"IOU",
[](Env& env,
Account const& issuer,
Account const& depositor) -> Asset {
PrettyAsset asset = issuer["IOU"];
env.trust(asset(1000), depositor);
env(pay(issuer, depositor, asset(1000)));
env.close();
return asset;
});
testCases(
"MPT",
[](Env& env,
Account const& issuer,
Account const& depositor) -> Asset {
MPTTester mptt{env, issuer, {.fund = false}};
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset asset = mptt.issuanceID();
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
env.close();
return asset;
});
}
// Test for non-asset specific behaviors.
TEST_CASE(CreateFailXRP)
void
testCreateFailXRP()
{
using namespace test::jtx;
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
auto vault = env.vault();
Asset asset = xrpIssue();
SUBCASE("nothing to delete")
{
auto testCase = [this](std::function<void(
Env & env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault)> test) {
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
auto vault = env.vault();
Asset asset = xrpIssue();
test(env, issuer, owner, depositor, asset, vault);
};
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
testcase("nothing to delete");
auto tx = vault.del({.owner = issuer, .id = keylet::skip().key});
env(tx, ter(tecOBJECT_NOT_FOUND));
}
});
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("transaction is good");
env(tx);
});
SUBCASE("insufficient fee")
{
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("insufficient fee");
env(tx, fee(env.current()->fees().base), ter(telINSUF_FEE_P));
}
});
SUBCASE("insufficient reserve")
{
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("insufficient reserve");
// It is possible to construct a complicated mathematical
// expression for this amount, but it is sadly not easy.
env(pay(owner, issuer, XRP(775)));
env.close();
env(tx, ter(tecINSUFFICIENT_RESERVE));
}
});
SUBCASE("data too large")
{
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("empty data");
tx[sfData] = "";
env(tx, ter(temMALFORMED));
});
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("data too large");
// A hexadecimal string of 257 bytes.
tx[sfData] = std::string(514, 'A');
env(tx, ter(temSTRING_TOO_LARGE));
}
env(tx, ter(temMALFORMED));
});
SUBCASE("metadata too large")
{
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("empty metadata");
tx[sfMPTokenMetadata] = "";
env(tx, ter(temMALFORMED));
});
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
testcase("metadata too large");
// This metadata is for the share token.
// A hexadecimal string of 1025 bytes.
tx[sfMPTokenMetadata] = std::string(2050, 'B');
env(tx, ter(temSTRING_TOO_LARGE));
}
env(tx, ter(temMALFORMED));
});
}
TEST_CASE(CreateFailIOU)
void
testCreateFailIOU()
{
using namespace test::jtx;
Env env{*this};
@@ -287,16 +388,14 @@ class Vault_test : public beast::unit_test::suite
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
SUBCASE("global freeze")
{
env(fset(issuer, asfGlobalFreeze));
env.close();
env(tx, ter(tecFROZEN));
env.close();
}
env(fset(issuer, asfGlobalFreeze));
env.close();
env(tx, ter(tecFROZEN));
env.close();
}
TEST_CASE(CreateFailMPT)
void
testCreateFailMPT()
{
using namespace test::jtx;
Env env{*this};
@@ -309,43 +408,67 @@ class Vault_test : public beast::unit_test::suite
MPTTester mptt{env, issuer, {.fund = false}};
SUBCASE("cannot transfer")
{
// Locked because that is the default flag.
mptt.create();
Asset asset = mptt.issuanceID();
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx, ter(tecLOCKED));
}
// Locked because that is the default flag.
mptt.create();
Asset asset = mptt.issuanceID();
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx, ter(tecLOCKED));
}
TEST_CASE(WithMPT)
void
testWithMPT()
{
using namespace test::jtx;
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
auto vault = env.vault();
MPTTester mptt{env, issuer, {.fund = false}};
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset asset = mptt.issuanceID();
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
env.close();
auto testCase = [this](std::function<void(
Env & env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault,
MPTTester& mptt)> test) {
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
auto vault = env.vault();
SUBCASE("global lock")
{
MPTTester mptt{env, issuer, {.fund = false}};
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
PrettyAsset asset = mptt.issuanceID();
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
env.close();
test(env, issuer, owner, depositor, asset, vault, mptt);
};
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault,
MPTTester& mptt) {
testcase("global lock");
mptt.set({.account = issuer, .flags = tfMPTLock});
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx, ter(tecLOCKED));
}
});
SUBCASE("deposit non-zero amount")
{
testCase([this](
Env& env,
Account const& issuer,
Account const& owner,
Account const& depositor,
Asset const& asset,
Vault& vault,
MPTTester& mptt) {
testcase("deposit non-zero amount");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
@@ -365,18 +488,18 @@ class Vault_test : public beast::unit_test::suite
Number outstandingShares = issuance->at(sfOutstandingAmount);
BEAST_EXPECT(outstandingShares > 0);
BEAST_EXPECT(outstandingShares == 100);
}
});
}
public:
void
run() override
{
EXECUTE(Sequences);
EXECUTE(CreateFailXRP);
EXECUTE(CreateFailIOU);
EXECUTE(CreateFailMPT);
EXECUTE(WithMPT);
testSequences();
testCreateFailXRP();
testCreateFailIOU();
testCreateFailMPT();
testWithMPT();
}
};

View File

@@ -200,7 +200,7 @@ public:
operator()(T v) const
{
STAmount amount{asset_, v * scale_};
return {amount, "uhh"};
return {amount, ""};
}
};
//------------------------------------------------------------------------------

View File

@@ -1,89 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx/subcases.h>
#include <iostream>
#include <stdexcept>
namespace subcases {
thread_local Subcase* Subcase::lastCreated;
Subcase::Subcase(Context& context, char const* name)
: context_(context), name_(name)
{
lastCreated = this;
}
Subcase::operator bool() const
{
auto& _ = context_;
++_.level;
if (_.level >= MAXIMUM_SUBCASE_DEPTH)
throw std::logic_error("maximum subcase depth exceeded");
if (_.entered < _.level && _.skip[_.level] == _.skipped)
{
_.entered = _.level;
_.names[_.level] = name_;
_.skipped = 0;
return true;
}
++_.skipped;
return false;
}
Subcase::~Subcase()
{
auto& _ = context_;
if (_.level == _.entered && _.skipped == 0)
{
// We are destroying the leaf subcase that executed on this pass.
// Didn't have time to debug this. Cannot explain what is going wrong
// with jtx. Just switch to a better test framework already.
_.suite.pass();
// We call `suite::testcase()` here, after the subcase is finished,
// because only now do we know which subcase was the leaf,
// and we only want to print one name line for each subcase.
_.suite.testcase(_.name());
// Let the runner know that a test executed,
// even if `BEAST_EXPECT` was never called.
_.suite.pass();
}
if (_.skipped == 0)
{
++_.skip[_.level];
_.skip[_.level + 1] = 0;
}
--_.level;
}
void
execute(beast::unit_test::suite* suite, char const* name, Supercase supercase)
{
Context context{*suite};
context.names[0] = name;
do
{
context.lap();
supercase(context);
} while (context.skipped != 0);
}
} // namespace subcases

View File

@@ -1,139 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_SUBCASES_H_INCLUDED
#define RIPPLE_TEST_JTX_SUBCASES_H_INCLUDED
#include <xrpl/beast/unit_test/suite.h>
#include <cstdint>
#include <functional>
namespace subcases {
constexpr std::size_t MAXIMUM_SUBCASE_DEPTH = 10;
/**
* This short library implements a pattern found in doctest and Catch:
*
* TEST_CASE(testName) {
* // setup
* SUBCASE("one") {
* // actions and assertions
* }
* SUBCASE("two") {
* // actions and assertions
* }
* SUBCASE("three") {
* // actions and assertions
* }
* // assertions before teardown
* }
*
* EXECUTE(testName);
*
* In short:
*
* - Top-level test cases are declared with `TEST_CASE(name)`.
* The name must be a legal identifier.
* It will become the name of a function.
* - Subcases are declared with `SUBCASE("description")`.
* Descriptions do not need to be unique.
* - Test cases are executed with `EXECUTE(name)`,
* where `name` is the one that was passed to `TEST_CASE`.
* When executing a test case, it will loop,
* executing exactly one leaf subcase in each pass,
* until all subcases have executed.
* The top-level test case is considered a subcase too.
*
* This lets test authors easily share common setup among multiple subcases.
* Subcases can be nested up to `MAXIMUM_SUBCASE_DEPTH`.
*/
struct Context
{
beast::unit_test::suite& suite;
// The number of subcases to skip at each level to reach the next subcase.
std::uint8_t skip[MAXIMUM_SUBCASE_DEPTH] = {0};
// The subcase names at each level.
char const* names[MAXIMUM_SUBCASE_DEPTH] = {""};
// The current level.
std::uint8_t level = 0;
// The maximum depth at which we entered a subcase.
std::uint8_t entered = 0;
// The number of subcases we skipped on this or deeper levels
// since entering a subcase.
std::uint8_t skipped = 0;
std::string
name() const
{
std::string n;
for (auto i = 0; i <= level; ++i)
{
if (i != 0)
{
n += " > ";
}
n += names[i];
}
return n;
}
void
lap()
{
level = 0;
entered = 0;
skipped = 0;
}
};
struct Subcase
{
Context& context_;
char const* name_;
Subcase(Context& context, char const* name);
~Subcase();
/** Return true if we should enter this subcase. */
operator bool() const;
thread_local static Subcase* lastCreated;
};
using Supercase = std::function<void(Context&)>;
void
execute(beast::unit_test::suite* suite, char const* name, Supercase supercase);
} // namespace subcases
#define TEST_CASE(name) void name(subcases::Context& _09876)
#define SUBCASE(name) if (subcases::Subcase sc##__COUNTER__{_09876, name})
#define SKIP(name) if (false)
#define EXECUTE(name) \
subcases::execute(this, #name, [&](auto& ctx) { name(ctx); })
// `AND_THEN` defines a subcase to contain all remaining subcases,
// without having to indent them in a nested block.
#define AND_THEN(name) \
subcases::Subcase sc##__COUNTER__{_09876, name}; \
if (!*subcases::Subcase::lastCreated) \
return
#define SECTION(name_) _09876.suite.testcase(_09876.name() + " > " + name_)
#endif

View File

@@ -20,6 +20,7 @@
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/ledger/View.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/digest.h>
@@ -193,18 +194,20 @@ authorizedDomain(
uint256 domainID,
AccountID const& subject)
{
auto const sle = view.read(keylet::permissionedDomain(domainID));
if (!sle || !sle->isFieldPresent(sfAcceptedCredentials))
auto const slePD = view.read(keylet::permissionedDomain(domainID));
if (!slePD || !slePD->isFieldPresent(sfAcceptedCredentials))
return tefINTERNAL;
for (auto const& h : sle->getFieldArray(sfAcceptedCredentials))
for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
{
if (!h.isFieldPresent(sfIssuer) || !h.isFieldPresent(sfCredentialType))
return tefINTERNAL;
auto const issuer = h.getAccountID(sfIssuer);
auto const type = makeSlice(h.getFieldVL(sfCredentialType));
if (view.exists(keylet::credential(subject, issuer, type)))
auto const sleCredential =
view.read(keylet::credential(subject, issuer, type));
if (sleCredential && sleCredential->getFlags() & lsfAccepted)
return tesSUCCESS;
}

View File

@@ -42,8 +42,8 @@ VaultCreate::preflight(PreflightContext const& ctx)
if (auto const data = ctx.tx[~sfData])
{
if (data->length() > maxVaultDataLength)
return temSTRING_TOO_LARGE;
if (data->empty() || data->length() > maxVaultDataLength)
return temMALFORMED;
}
if (auto const domain = ctx.tx[~sfDomainID])

View File

@@ -25,6 +25,7 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
namespace ripple {
@@ -57,8 +58,8 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
{
if (auto const domain = vault->at(~sfVaultID))
{
if (!credentials::authorizedDomain(
ctx.view, *domain, ctx.tx[sfAccount]))
if (credentials::authorizedDomain(
ctx.view, *domain, ctx.tx[sfAccount]) != tesSUCCESS)
return tecNO_PERMISSION;
}
}

View File

@@ -39,8 +39,8 @@ VaultSet::preflight(PreflightContext const& ctx)
return temINVALID_FLAG;
if (auto const data = ctx.tx[~sfData])
{
if (data->length() > maxVaultDataLength)
return temSTRING_TOO_LARGE;
if (data->empty() || data->length() > maxVaultDataLength)
return temMALFORMED;
}
auto const domain = ctx.tx[~sfDomainID];