revise tests

This commit is contained in:
John Freeman
2024-12-03 11:33:26 -06:00
committed by Bronek Kozicki
parent ea30f44247
commit 12646cb89e
6 changed files with 269 additions and 242 deletions

View File

@@ -20,10 +20,12 @@
#ifndef RIPPLE_JSON_JSON_VALUE_H_INCLUDED
#define RIPPLE_JSON_JSON_VALUE_H_INCLUDED
#include <xrpl/basics/Number.h>
#include <xrpl/json/json_forwards.h>
#include <cstring>
#include <map>
#include <string>
#include <utility>
#include <vector>
/** \brief JSON (JavaScript Object Notation).
@@ -237,6 +239,11 @@ public:
Value&
operator=(Value&& other);
template <typename T>
requires(!std::convertible_to<T, Value>)
Value&
operator=(T const& rhs);
Value(Value&& other) noexcept;
/// Swap values.
@@ -682,6 +689,51 @@ public:
}
};
// https://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/
namespace detail {
inline Value
to_json(ripple::Number const& number)
{
return to_string(number);
}
struct to_json_fn
{
template <typename T>
constexpr Value
operator()(T&& t) const
{
return to_json(std::forward<T>(t));
}
};
template <typename T>
struct static_const
{
static constexpr T value{};
};
template <typename T>
constexpr T static_const<T>::value;
} // namespace detail
namespace {
constexpr auto const& to_json = detail::static_const<detail::to_json_fn>::value;
}
template <typename T>
requires(!std::convertible_to<T, Value>)
Value&
Value::operator=(T const& rhs)
{
*this = to_json(rhs);
return *this;
}
} // namespace Json
#endif // CPPTL_JSON_H_INCLUDED

View File

@@ -29,11 +29,166 @@
namespace ripple {
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(100)});
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);
}
// TODO: redeem.
{
testcase("withdraw non-zero assets");
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(200)});
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)
{
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)));
testSequence(env, issuer, owner, depositor, vault, asset);
}
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)));
testSequence(env, issuer, owner, depositor, vault, asset);
}
}
// Test for non-asset specific behaviors.
TEST_CASE(WithXRP)
TEST_CASE(CreateFailXRP)
{
using namespace test::jtx;
Env env{*this};
@@ -79,120 +234,9 @@ class Vault_test : public beast::unit_test::suite
tx[sfMPTokenMetadata] = blob1025;
env(tx, ter(temSTRING_TOO_LARGE));
}
AND_THEN("create");
env(tx);
env.close();
BEAST_EXPECT(env.le(keylet));
{
STEP("fail to deposit more than assets held");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
STEP("deposit non-zero amount");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(100)});
env(tx);
}
{
STEP("fail to delete non-empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx, ter(tecHAS_OBLIGATIONS));
}
{
STEP("fail to update because wrong owner");
auto tx = vault.set({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
STEP("fail to update immutable flags");
tx[sfFlags] = tfVaultPrivate;
env(tx, ter(temINVALID_FLAG));
}
{
STEP("fail to set maximum lower than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = XRP(50);
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
STEP("set maximum higher than current amount");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = XRP(200);
env(tx);
env.close();
}
{
STEP("fail to deposit more than maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(200)});
env(tx, ter(tecLIMIT_EXCEEDED));
}
{
STEP("fail to withdraw more than assets held");
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
}
{
STEP("deposit up to maximum");
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(100)});
env(tx);
env.close();
}
// TODO: redeem.
{
STEP("withdraw non-zero assets");
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = XRP(200)});
env(tx);
env.close();
}
{
STEP("fail to delete because wrong owner");
auto tx = vault.del({.owner = issuer, .id = keylet.key});
env(tx, ter(tecNO_PERMISSION));
}
{
STEP("delete empty vault");
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx);
env.close();
BEAST_EXPECT(!env.le(keylet));
}
}
TEST_CASE(WithIOU)
TEST_CASE(CreateFailIOU)
{
using namespace test::jtx;
Env env{*this};
@@ -215,7 +259,7 @@ class Vault_test : public beast::unit_test::suite
}
}
TEST_CASE(WithMPT)
TEST_CASE(CreateFailMPT)
{
using namespace test::jtx;
Env env{*this};
@@ -238,145 +282,25 @@ class Vault_test : public beast::unit_test::suite
}
AND_THEN("create");
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
Asset asset = mptt.issuanceID();
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
SUBCASE("create")
{
env(tx);
env.close();
SUBCASE("update")
{
auto tx = vault.set({.owner = owner, .id = keylet.key});
SUBCASE("happy path")
{
tx[sfData] = "ABCD";
tx[sfAssetMaximum] = 123;
env(tx);
env.close();
}
}
}
SUBCASE("global lock")
{
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
mptt.set({.account = issuer, .flags = tfMPTLock});
env(tx, ter(tecLOCKED));
}
SUBCASE("MPT cannot transfer")
{
MPTTester mptt{env, issuer, {.fund = false}};
}
SUBCASE("transfer XRP")
{
// Construct asset.
Asset asset{xrpIssue()};
// Depositor already holds asset.
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
{
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(123)});
env(tx);
env.close();
}
}
SUBCASE("transfer IOU")
{
// Construct asset.
Asset asset = issuer["IOU"];
// Fund depositor with asset.
env.trust(asset(1000), depositor);
env(pay(issuer, depositor, asset(1000)));
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
{
// Deposit non-zero amount.
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(123)});
env(tx);
env.close();
}
}
SUBCASE("transfer MPT")
{
// Construct asset.
MPTTester mptt{env, issuer, {.fund = false}};
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
Asset asset = mptt.issuanceID();
// Fund depositor with asset.
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
}
// TODO: VaultSet (update) succeed
// TODO: VaultSet (update) fail: wrong owner
// TODO: VaultSet (update) fail: Data too large
// TODO: VaultSet (update) fail: tfPrivate flag
// TODO: VaultSet (update) fail: tfShareNonTransferable flag
// TODO: Payment to VaultSet.PA fail
// TODO: VaultSet (update) fail: missing vault
BEAST_EXPECT(true);
}
TEST_CASE(Sequence)
{
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();
SUBCASE("IOU")
{
// Construct asset.
Asset asset = issuer["IOU"];
// Fund depositor with asset.
env.trust(asset(1000), depositor);
env(pay(issuer, depositor, asset(1000)));
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
}
}
public:
void
run() override
{
pass();
// EXECUTE(CreateUpdateDelete);
// EXECUTE(WithXRP);
EXECUTE(Sequences);
EXECUTE(CreateFailXRP);
EXECUTE(CreateFailIOU);
EXECUTE(CreateFailMPT);
}
};

View File

@@ -26,6 +26,7 @@
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <concepts>
#include <cstdint>
#include <ostream>
#include <string>
@@ -125,6 +126,12 @@ public:
return amount_;
}
Number
number() const
{
return amount_;
}
operator STAmount const&() const
{
return amount_;
@@ -153,6 +160,43 @@ operator!=(PrettyAmount const& lhs, PrettyAmount const& rhs)
std::ostream&
operator<<(std::ostream& os, PrettyAmount const& amount);
struct PrettyAsset
{
private:
Asset asset_;
unsigned int scale_;
public:
template <typename A>
requires std::convertible_to<A, Asset>
PrettyAsset(A const& asset, unsigned int scale = 1)
: PrettyAsset{Asset{asset}, scale}
{
}
PrettyAsset(Asset const& asset, unsigned int scale = 1)
: asset_(asset), scale_(scale)
{
}
operator Asset const&() const
{
return asset_;
}
operator Json::Value() const
{
return to_json(asset_);
}
template <std::integral T>
PrettyAmount
operator()(T v) const
{
STAmount amount{asset_, v * scale_};
return {amount, "uhh"};
}
};
//------------------------------------------------------------------------------
// Specifies an order book

View File

@@ -19,6 +19,7 @@
#include <test/jtx/subcases.h>
#include <iostream>
#include <stdexcept>
namespace subcases {
@@ -54,6 +55,9 @@ Subcase::~Subcase()
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.

View File

@@ -132,7 +132,8 @@ execute(beast::unit_test::suite* suite, char const* name, Supercase supercase);
// 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 STEP(name_) _09876.suite.testcase(_09876.name() + " > " + name_)
if (!*subcases::Subcase::lastCreated) \
return
#define SECTION(name_) _09876.suite.testcase(_09876.name() + " > " + name_)
#endif

View File

@@ -921,6 +921,8 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey)
account->setFieldU32(
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
// Link the pseudo-account with its owner object.
// TODO: This debate is unresolved. There is allegedly a need to know
// whether a pseudo-account belongs to an AMM specifically.
// account->setFieldH256(sfPseudoOwner, pseudoOwnerKey);
view.insert(account);