Files
rippled/src/test/jtx/Env_test.cpp
2026-02-19 23:30:00 +00:00

856 lines
28 KiB
C++

#include <test/jtx.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/server/NetworkOPs.h>
#include <boost/lexical_cast.hpp>
#include <optional>
#include <utility>
namespace xrpl {
namespace test {
class Env_test : public beast::unit_test::suite
{
public:
template <class T>
static std::string
to_string(T const& t)
{
return boost::lexical_cast<std::string>(t);
}
// Declarations in Account.h
void
testAccount()
{
using namespace jtx;
{
Account a("chad");
Account b(a);
a = b;
a = std::move(b);
Account c(std::move(a));
}
Account("alice");
Account("alice", KeyType::secp256k1);
Account("alice", KeyType::ed25519);
auto const gw = Account("gw");
[](AccountID) {}(gw);
auto const USD = gw["USD"];
void(Account("alice") < gw);
std::set<Account>().emplace(gw);
std::unordered_set<Account, beast::uhash<>>().emplace("alice");
}
// Declarations in amount.h
void
testAmount()
{
using namespace jtx;
PrettyAmount(0);
PrettyAmount(1);
PrettyAmount(0u);
PrettyAmount(1u);
PrettyAmount(-1);
static_assert(!std::is_trivially_constructible<PrettyAmount, char>::value, "");
static_assert(!std::is_trivially_constructible<PrettyAmount, unsigned char>::value, "");
static_assert(!std::is_trivially_constructible<PrettyAmount, short>::value, "");
static_assert(!std::is_trivially_constructible<PrettyAmount, unsigned short>::value, "");
try
{
XRP(0.0000001);
fail("missing exception");
}
catch (std::domain_error const&)
{
pass();
}
XRP(-0.000001);
try
{
XRP(-0.0000009);
fail("missing exception");
}
catch (std::domain_error const&)
{
pass();
}
BEAST_EXPECT(to_string(XRP(5)) == "5 XRP");
BEAST_EXPECT(to_string(XRP(.80)) == "0.8 XRP");
BEAST_EXPECT(to_string(XRP(.005)) == "5000 drops");
BEAST_EXPECT(to_string(XRP(0.1)) == "0.1 XRP");
BEAST_EXPECT(to_string(XRP(10000)) == "10000 XRP");
BEAST_EXPECT(to_string(drops(10)) == "10 drops");
BEAST_EXPECT(to_string(drops(123400000)) == "123.4 XRP");
BEAST_EXPECT(to_string(XRP(-5)) == "-5 XRP");
BEAST_EXPECT(to_string(XRP(-.99)) == "-0.99 XRP");
BEAST_EXPECT(to_string(XRP(-.005)) == "-5000 drops");
BEAST_EXPECT(to_string(XRP(-0.1)) == "-0.1 XRP");
BEAST_EXPECT(to_string(drops(-10)) == "-10 drops");
BEAST_EXPECT(to_string(drops(-123400000)) == "-123.4 XRP");
BEAST_EXPECT(XRP(1) == drops(1000000));
BEAST_EXPECT(XRP(1) == STAmount(1000000));
BEAST_EXPECT(STAmount(1000000) == XRP(1));
auto const gw = Account("gw");
auto const USD = gw["USD"];
BEAST_EXPECT(to_string(USD(0)) == "0/USD(gw)");
BEAST_EXPECT(to_string(USD(10)) == "10/USD(gw)");
BEAST_EXPECT(to_string(USD(-10)) == "-10/USD(gw)");
BEAST_EXPECT(USD(0) == STAmount(USD, 0));
BEAST_EXPECT(USD(1) == STAmount(USD, 1));
BEAST_EXPECT(USD(-1) == STAmount(USD, -1));
auto const get = [](AnyAmount a) { return a; };
BEAST_EXPECT(!get(USD(10)).is_any);
BEAST_EXPECT(get(any(USD(10))).is_any);
}
// Test Env
void
testEnv()
{
using namespace jtx;
auto const n = XRP(10000);
auto const gw = Account("gw");
auto const USD = gw["USD"];
auto const alice = Account("alice");
// unfunded
{
Env env(*this);
env(pay("alice", "bob", XRP(1000)), seq(1), fee(10), sig("alice"), ter(terNO_ACCOUNT));
}
// fund
{
Env env(*this);
// variadics
env.fund(n, "alice");
env.fund(n, "bob", "carol");
env.fund(n, "dave", noripple("eric"));
env.fund(n, "fred", noripple("gary", "hank"));
env.fund(n, noripple("irene"));
env.fund(n, noripple("jim"), "karen");
env.fund(n, noripple("lisa", "mary"));
// flags
env.fund(n, noripple("xavier"));
env.require(nflags("xavier", asfDefaultRipple));
env.fund(n, "zachary");
env.require(flags("zachary", asfDefaultRipple));
}
// trust
{
Env env(*this);
env.fund(n, "alice", "bob", gw);
env.close();
env(trust("alice", USD(100)), require(lines("alice", 1)));
}
// balance
{
Env env(*this);
BEAST_EXPECT(env.balance(alice) == 0);
BEAST_EXPECT(env.balance(alice, USD) != 0);
BEAST_EXPECT(env.balance(alice, USD) == USD(0));
env.fund(n, alice, gw);
env.close();
BEAST_EXPECT(env.balance(alice) == n);
BEAST_EXPECT(env.balance(gw) == n);
env.trust(USD(1000), alice);
env(pay(gw, alice, USD(10)));
BEAST_EXPECT(to_string(env.balance("alice", USD)) == "10/USD(gw)");
BEAST_EXPECT(to_string(env.balance(gw, alice["USD"])) == "-10/USD(alice)");
}
// seq
{
Env env(*this);
env.fund(n, noripple("alice", gw));
BEAST_EXPECT(env.seq("alice") == 3);
BEAST_EXPECT(env.seq(gw) == 3);
}
// autofill
{
Env env(*this);
env.fund(n, "alice");
env.require(balance("alice", n));
env(noop("alice"), fee(1), ter(telINSUF_FEE_P));
env(noop("alice"), seq(none), ter(temMALFORMED));
env(noop("alice"), seq(none), fee(10), ter(temMALFORMED));
env(noop("alice"), fee(none), ter(temMALFORMED));
env(noop("alice"), sig(none), ter(temMALFORMED));
env(noop("alice"), fee(autofill));
env(noop("alice"), fee(autofill), seq(autofill));
env(noop("alice"), fee(autofill), seq(autofill), sig(autofill));
}
}
// Env::require
void
testRequire()
{
using namespace jtx;
Env env(*this);
auto const gw = Account("gw");
auto const USD = gw["USD"];
env.require(balance("alice", none));
env.require(balance("alice", XRP(none)));
env.fund(XRP(10000), "alice", gw);
env.close();
env.require(balance("alice", USD(none)));
env.trust(USD(100), "alice");
env.require(balance("alice", XRP(10000))); // fee refunded
env.require(balance("alice", USD(0)));
env(pay(gw, "alice", USD(10)), require(balance("alice", USD(10))));
env.require(nflags("alice", asfRequireDest));
env(fset("alice", asfRequireDest), require(flags("alice", asfRequireDest)));
env(fclear("alice", asfRequireDest), require(nflags("alice", asfRequireDest)));
}
// Signing with secp256k1 and ed25519 keys
void
testKeyType()
{
using namespace jtx;
Env env{*this, testable_amendments()};
Account const alice("alice", KeyType::ed25519);
Account const bob("bob", KeyType::secp256k1);
Account const carol("carol");
env.fund(XRP(10000), alice, bob);
// Master key only
env(noop(alice));
env(noop(bob));
env(noop(alice), sig("alice"), ter(tefBAD_AUTH));
env(noop(alice), sig(Account("alice", KeyType::secp256k1)), ter(tefBAD_AUTH));
env(noop(bob), sig(Account("bob", KeyType::ed25519)), ter(tefBAD_AUTH));
env(noop(alice), sig(carol), ter(tefBAD_AUTH));
// Master and Regular key
env(regkey(alice, bob));
env(noop(alice));
env(noop(alice), sig(bob));
env(noop(alice), sig(alice));
// Regular key only
env(fset(alice, asfDisableMaster), sig(alice));
env(noop(alice));
env(noop(alice), sig(bob));
env(noop(alice), sig(alice), ter(tefMASTER_DISABLED));
env(fclear(alice, asfDisableMaster), sig(alice), ter(tefMASTER_DISABLED));
env(fclear(alice, asfDisableMaster), sig(bob));
env(noop(alice), sig(alice));
}
// Payment basics
void
testPayments()
{
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.require(balance("alice", XRP(10000)));
env.require(balance("bob", XRP(10000)));
env.require(balance("carol", XRP(10000)));
env.require(balance(gw, XRP(10000)));
env(pay(env.master, "alice", XRP(1000)), fee(none), ter(temMALFORMED));
env(pay(env.master, "alice", XRP(1000)), fee(1), ter(telINSUF_FEE_P));
env(pay(env.master, "alice", XRP(1000)), seq(none), ter(temMALFORMED));
env(pay(env.master, "alice", XRP(1000)), seq(20), ter(terPRE_SEQ));
env(pay(env.master, "alice", XRP(1000)), sig(none), ter(temMALFORMED));
env(pay(env.master, "alice", XRP(1000)), sig("bob"), ter(tefBAD_AUTH));
env(pay(env.master, "dilbert", XRP(1000)), sig(env.master));
env.trust(USD(100), "alice", "bob", "carol");
env.require(owners("alice", 1), lines("alice", 1));
env(rate(gw, 1.05));
env(pay(gw, "carol", USD(50)));
env.require(balance("carol", USD(50)));
env.require(balance(gw, Account("carol")["USD"](-50)));
env(offer("carol", XRP(50), USD(50)), require(owners("carol", 2)));
env(pay("alice", "bob", any(USD(10))), ter(tecPATH_DRY));
env(pay("alice", "bob", any(USD(10))), paths(XRP), sendmax(XRP(10)), ter(tecPATH_PARTIAL));
env(pay("alice", "bob", any(USD(10))), paths(XRP), sendmax(XRP(20)));
env.require(balance("bob", USD(10)));
env.require(balance("carol", USD(39.5)));
env.memoize("eric");
env(regkey("alice", "eric"));
env(noop("alice"));
env(noop("alice"), sig("alice"));
env(noop("alice"), sig("eric"));
env(noop("alice"), sig("bob"), ter(tefBAD_AUTH));
env(fset("alice", asfDisableMaster), ter(tecNEED_MASTER_KEY));
env(fset("alice", asfDisableMaster), sig("eric"), ter(tecNEED_MASTER_KEY));
env.require(nflags("alice", asfDisableMaster));
env(fset("alice", asfDisableMaster), sig("alice"));
env.require(flags("alice", asfDisableMaster));
env(regkey("alice", disabled), ter(tecNO_ALTERNATIVE_KEY));
env(noop("alice"));
env(noop("alice"), sig("alice"), ter(tefMASTER_DISABLED));
env(noop("alice"), sig("eric"));
env(noop("alice"), sig("bob"), ter(tefBAD_AUTH));
env(fclear("alice", asfDisableMaster), sig("bob"), ter(tefBAD_AUTH));
env(fclear("alice", asfDisableMaster), sig("alice"), ter(tefMASTER_DISABLED));
env(fclear("alice", asfDisableMaster));
env.require(nflags("alice", asfDisableMaster));
env(regkey("alice", disabled));
env(noop("alice"), sig("eric"), ter(tefBAD_AUTH));
env(noop("alice"));
}
// Rudimentary test to ensure fail_hard
// transactions are neither queued nor
// held.
void
testFailHard()
{
using namespace jtx;
Env env(*this);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
auto const localTxCnt = env.app().getOPs().getLocalTxCount();
auto const queueTxCount = env.app().getTxQ().getMetrics(*env.current()).txCount;
auto const openTxCount = env.current()->txCount();
BEAST_EXPECT(localTxCnt == 2 && queueTxCount == 0 && openTxCount == 2);
auto applyTxn = [&env](auto&&... txnArgs) {
auto jt = env.jt(txnArgs...);
Serializer s;
jt.stx->add(s);
Json::Value args{Json::objectValue};
args[jss::tx_blob] = strHex(s.slice());
args[jss::fail_hard] = true;
return env.rpc("json", "submit", args.toStyledString());
};
auto jr = applyTxn(noop(alice), fee(1));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "telINSUF_FEE_P");
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt);
BEAST_EXPECT(env.current()->txCount() == openTxCount);
jr = applyTxn(noop(alice), sig("bob"));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tefBAD_AUTH");
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt);
BEAST_EXPECT(env.current()->txCount() == openTxCount);
jr = applyTxn(noop(alice), seq(20));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "terPRE_SEQ");
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt);
BEAST_EXPECT(env.current()->txCount() == openTxCount);
jr = applyTxn(offer(alice, XRP(1000), USD(1000)));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tecUNFUNDED_OFFER");
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt);
BEAST_EXPECT(env.current()->txCount() == openTxCount);
jr = applyTxn(noop(alice), fee(drops(-10)));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "temBAD_FEE");
BEAST_EXPECT(env.app().getTxQ().getMetrics(*env.current()).txCount == queueTxCount);
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt);
BEAST_EXPECT(env.current()->txCount() == openTxCount);
jr = applyTxn(noop(alice));
BEAST_EXPECT(jr[jss::result][jss::engine_result] == "tesSUCCESS");
BEAST_EXPECT(env.app().getOPs().getLocalTxCount() == localTxCnt + 1);
BEAST_EXPECT(env.current()->txCount() == openTxCount + 1);
}
// Multi-sign basics
void
testMultiSign()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
env(signers("alice", 1, {{"alice", 1}, {"bob", 2}}), ter(temBAD_SIGNER));
env(signers("alice", 1, {{"bob", 1}, {"carol", 2}}));
env(noop("alice"));
auto const baseFee = env.current()->fees().base;
env(noop("alice"), msig("bob"), fee(2 * baseFee));
env(noop("alice"), msig("carol"), fee(2 * baseFee));
env(noop("alice"), msig("bob", "carol"), fee(3 * baseFee));
env(noop("alice"),
msig("bob", "carol", "dilbert"),
fee(4 * baseFee),
ter(tefBAD_SIGNATURE));
env(signers("alice", none));
}
void
testTicket()
{
using namespace jtx;
// create syntax
ticket::create("alice", 1);
{
Env env(*this);
env.fund(XRP(10000), "alice");
env(noop("alice"), require(owners("alice", 0), tickets("alice", 0)));
env(ticket::create("alice", 1), require(owners("alice", 1), tickets("alice", 1)));
}
}
struct UDT
{
};
void
testJTxProperties()
{
struct T
{
};
using namespace jtx;
JTx jt1;
// Test a straightforward
// property
BEAST_EXPECT(!jt1.get<int>());
jt1.set<int>(7);
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 7);
BEAST_EXPECT(!jt1.get<UDT>());
// Test that the property is
// replaced if it exists.
jt1.set<int>(17);
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 17);
BEAST_EXPECT(!jt1.get<UDT>());
// Test that modifying the
// returned prop is saved
*jt1.get<int>() = 42;
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 42);
BEAST_EXPECT(!jt1.get<UDT>());
// Test get() const
auto const& jt2 = jt1;
BEAST_EXPECT(jt2.get<int>());
BEAST_EXPECT(*jt2.get<int>() == 42);
BEAST_EXPECT(!jt2.get<UDT>());
}
void
testProp()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(100000), "alice");
auto jt1 = env.jt(noop("alice"));
BEAST_EXPECT(!jt1.get<std::uint16_t>());
auto jt2 = env.jt(noop("alice"), prop<std::uint16_t>(-1));
BEAST_EXPECT(jt2.get<std::uint16_t>());
BEAST_EXPECT(*jt2.get<std::uint16_t>() == 65535);
auto jt3 = env.jt(noop("alice"), prop<std::string>("Hello, world!"), prop<bool>(false));
BEAST_EXPECT(jt3.get<std::string>());
BEAST_EXPECT(*jt3.get<std::string>() == "Hello, world!");
BEAST_EXPECT(jt3.get<bool>());
BEAST_EXPECT(!*jt3.get<bool>());
}
void
testJTxCopy()
{
struct T
{
};
using namespace jtx;
JTx jt1;
jt1.set<int>(7);
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 7);
BEAST_EXPECT(!jt1.get<UDT>());
JTx jt2(jt1);
BEAST_EXPECT(jt2.get<int>());
BEAST_EXPECT(*jt2.get<int>() == 7);
BEAST_EXPECT(!jt2.get<UDT>());
JTx jt3;
jt3 = jt1;
BEAST_EXPECT(jt3.get<int>());
BEAST_EXPECT(*jt3.get<int>() == 7);
BEAST_EXPECT(!jt3.get<UDT>());
}
void
testJTxMove()
{
struct T
{
};
using namespace jtx;
JTx jt1;
jt1.set<int>(7);
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 7);
BEAST_EXPECT(!jt1.get<UDT>());
JTx jt2(std::move(jt1));
BEAST_EXPECT(!jt1.get<int>());
BEAST_EXPECT(!jt1.get<UDT>());
BEAST_EXPECT(jt2.get<int>());
BEAST_EXPECT(*jt2.get<int>() == 7);
BEAST_EXPECT(!jt2.get<UDT>());
jt1 = std::move(jt2);
BEAST_EXPECT(!jt2.get<int>());
BEAST_EXPECT(!jt2.get<UDT>());
BEAST_EXPECT(jt1.get<int>());
BEAST_EXPECT(*jt1.get<int>() == 7);
BEAST_EXPECT(!jt1.get<UDT>());
}
void
testMemo()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
env(noop("alice"), memo_data("data"));
env(noop("alice"), memo_format("format"));
env(noop("alice"), memo_type("type"));
env(noop("alice"), memo("data", "format", "type"));
env(noop("alice"), memo("data1", "format1", "type1"), memo("data2", "format2", "type2"));
}
void
testMemoResult()
{
using namespace jtx;
Env env(*this);
JTx jt(noop("alice"));
memo("data", "format", "type")(env, jt);
auto const& memo = jt.jv["Memos"][0u]["Memo"];
BEAST_EXPECT(memo["MemoData"].asString() == strHex(std::string("data")));
BEAST_EXPECT(memo["MemoFormat"].asString() == strHex(std::string("format")));
BEAST_EXPECT(memo["MemoType"].asString() == strHex(std::string("type")));
}
void
testAdvance()
{
using namespace jtx;
Env env(*this);
auto seq = env.current()->seq();
BEAST_EXPECT(seq == env.closed()->seq() + 1);
env.close();
BEAST_EXPECT(env.closed()->seq() == seq);
BEAST_EXPECT(env.current()->seq() == seq + 1);
env.close();
BEAST_EXPECT(env.closed()->seq() == seq + 1);
BEAST_EXPECT(env.current()->seq() == seq + 2);
}
void
testClose()
{
using namespace jtx;
Env env(*this);
env.close();
env.close();
env.fund(XRP(100000), "alice", "bob");
env.close();
env(pay("alice", "bob", XRP(100)));
env.close();
env(noop("alice"));
env.close();
env(noop("bob"));
}
void
testPath()
{
using namespace jtx;
Env env(*this);
auto const gw = Account("gw");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob");
env.close();
env.json(
pay("alice", "bob", USD(10)),
path(Account("alice")),
path("bob"),
path(USD),
path(~XRP),
path(~USD),
path("bob", USD, ~XRP, ~USD));
}
// Test that jtx can re-sign a transaction that's already been signed.
void
testResignSigned()
{
using namespace jtx;
Env env(*this);
env.fund(XRP(10000), "alice");
auto const baseFee = env.current()->fees().base;
std::uint32_t const aliceSeq = env.seq("alice");
// Sign jsonNoop.
Json::Value jsonNoop = env.json(noop("alice"), fee(baseFee), seq(aliceSeq), sig("alice"));
// Re-sign jsonNoop.
JTx jt = env.jt(jsonNoop);
env(jt);
}
void
testSignAndSubmit()
{
using namespace jtx;
Env env(*this);
Env_ss envs(env);
auto const baseFee = env.current()->fees().base;
auto const alice = Account("alice");
env.fund(XRP(10000), alice);
{
envs(noop(alice), fee(none), seq(none))();
// Make sure we get the right account back.
auto tx = env.tx();
if (BEAST_EXPECT(tx))
{
BEAST_EXPECT(tx->getAccountID(sfAccount) == alice.id());
BEAST_EXPECT(tx->getTxnType() == ttACCOUNT_SET);
}
}
{
auto params = Json::Value(Json::nullValue);
envs(noop(alice), fee(none), seq(none))(params);
// Make sure we get the right account back.
auto tx = env.tx();
if (BEAST_EXPECT(tx))
{
BEAST_EXPECT(tx->getAccountID(sfAccount) == alice.id());
BEAST_EXPECT(tx->getTxnType() == ttACCOUNT_SET);
}
}
{
auto params = Json::Value(Json::objectValue);
// Force the factor low enough to fail
params[jss::fee_mult_max] = 1;
params[jss::fee_div_max] = 2;
auto const expectedErrorString = "Fee of " + std::to_string(baseFee.drops()) +
" exceeds the requested tx limit of " + std::to_string(baseFee.drops() / 2);
envs(noop(alice), fee(none), seq(none), rpc(rpcHIGH_FEE, expectedErrorString))(params);
auto tx = env.tx();
BEAST_EXPECT(!tx);
}
}
void
testFeatures()
{
testcase("Env features");
using namespace jtx;
auto const supported = testable_amendments();
// this finds a feature that is not in
// the supported amendments list and tests that it can be
// enabled explicitly
auto const neverSupportedFeat = [&]() -> std::optional<uint256> {
auto const n = supported.size();
for (size_t i = 0; i < n; ++i)
if (!supported[i])
return bitsetIndexToFeature(i);
return std::nullopt;
}();
if (!neverSupportedFeat)
{
log << "No unsupported features found - skipping test." << std::endl;
pass();
return;
}
auto hasFeature = [](Env& env, uint256 const& f) {
return (env.app().config().features.find(f) != env.app().config().features.end());
};
{
// default Env has all supported features
Env env{*this};
BEAST_EXPECT(supported.count() == env.app().config().features.size());
foreachFeature(
supported, [&](uint256 const& f) { this->BEAST_EXPECT(hasFeature(env, f)); });
}
{
// a Env FeatureBitset has *only* those features
Env env{*this, FeatureBitset{featureDynamicMPT | featureTokenEscrow}};
BEAST_EXPECT(env.app().config().features.size() == 2);
foreachFeature(supported, [&](uint256 const& f) {
bool const has = (f == featureDynamicMPT || f == featureTokenEscrow);
this->BEAST_EXPECT(has == hasFeature(env, f));
});
}
auto const missingSomeFeatures =
testable_amendments() - featureDynamicMPT - featureTokenEscrow;
BEAST_EXPECT(missingSomeFeatures.count() == (supported.count() - 2));
{
// a Env supported_features_except is missing *only* those features
Env env{*this, missingSomeFeatures};
BEAST_EXPECT(env.app().config().features.size() == (supported.count() - 2));
foreachFeature(supported, [&](uint256 const& f) {
bool hasnot = (f == featureDynamicMPT || f == featureTokenEscrow);
this->BEAST_EXPECT(hasnot != hasFeature(env, f));
});
}
{
// add a feature that is NOT in the supported amendments list
// along with a list of explicit amendments
// the unsupported feature should be enabled along with
// the two supported ones
Env env{
*this, FeatureBitset{featureDynamicMPT, featureTokenEscrow, *neverSupportedFeat}};
// this app will have just 2 supported amendments and
// one additional never supported feature flag
BEAST_EXPECT(env.app().config().features.size() == (2 + 1));
BEAST_EXPECT(hasFeature(env, *neverSupportedFeat));
foreachFeature(supported, [&](uint256 const& f) {
bool has = (f == featureDynamicMPT || f == featureTokenEscrow);
this->BEAST_EXPECT(has == hasFeature(env, f));
});
}
{
// add a feature that is NOT in the supported amendments list
// and omit a few standard amendments
// the unsupported features should be enabled
Env env{*this, missingSomeFeatures | FeatureBitset{*neverSupportedFeat}};
// this app will have all supported amendments minus 2 and then the
// one additional never supported feature flag
BEAST_EXPECT(env.app().config().features.size() == (supported.count() - 2 + 1));
BEAST_EXPECT(hasFeature(env, *neverSupportedFeat));
foreachFeature(supported, [&](uint256 const& f) {
bool hasnot = (f == featureDynamicMPT || f == featureTokenEscrow);
this->BEAST_EXPECT(hasnot != hasFeature(env, f));
});
}
{
// add a feature that is NOT in the supported amendments list
// along with all supported amendments
// the unsupported features should be enabled
Env env{*this, testable_amendments().set(*neverSupportedFeat)};
// this app will have all supported amendments and then the
// one additional never supported feature flag
BEAST_EXPECT(env.app().config().features.size() == (supported.count() + 1));
BEAST_EXPECT(hasFeature(env, *neverSupportedFeat));
foreachFeature(
supported, [&](uint256 const& f) { this->BEAST_EXPECT(hasFeature(env, f)); });
}
}
void
testExceptionalShutdown()
{
except([this] {
jtx::Env env{
*this,
jtx::envconfig([](std::unique_ptr<Config> cfg) {
(*cfg).deprecatedClearSection("port_rpc");
return cfg;
}),
nullptr,
beast::severities::kDisabled};
});
pass();
}
void
run() override
{
testAccount();
testAmount();
testEnv();
testRequire();
testKeyType();
testPayments();
testFailHard();
testMultiSign();
testTicket();
testJTxProperties();
testProp();
testJTxCopy();
testJTxMove();
testMemo();
testMemoResult();
testAdvance();
testClose();
testPath();
testResignSigned();
testSignAndSubmit();
testFeatures();
testExceptionalShutdown();
}
};
BEAST_DEFINE_TESTSUITE(Env, jtx, xrpl);
} // namespace test
} // namespace xrpl