mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-28 01:20:32 +00:00
Per XLS-0095, we are taking steps to rename ripple(d) to xrpl(d). This change specifically removes all copyright notices referencing Ripple, XRPLF, and certain affiliated contributors upon mutual agreement, so the notice in the LICENSE.md file applies throughout. Copyright notices referencing external contributions remain as-is. Duplicate verbiage is also removed.
3937 lines
144 KiB
C++
3937 lines
144 KiB
C++
#include <test/jtx.h>
|
|
|
|
#include <xrpld/app/tx/applySteps.h>
|
|
|
|
#include <xrpl/ledger/Dir.h>
|
|
#include <xrpl/ledger/Sandbox.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
namespace ripple {
|
|
namespace test {
|
|
|
|
struct EscrowToken_test : public beast::unit_test::suite
|
|
{
|
|
static uint64_t
|
|
mptEscrowed(
|
|
jtx::Env const& env,
|
|
jtx::Account const& account,
|
|
jtx::MPT const& mpt)
|
|
{
|
|
auto const sle = env.le(keylet::mptoken(mpt.mpt(), account));
|
|
if (sle && sle->isFieldPresent(sfLockedAmount))
|
|
return (*sle)[sfLockedAmount];
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t
|
|
issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt)
|
|
{
|
|
auto const sle = env.le(keylet::mptIssuance(mpt.mpt()));
|
|
if (sle && sle->isFieldPresent(sfLockedAmount))
|
|
return (*sle)[sfLockedAmount];
|
|
return 0;
|
|
}
|
|
|
|
jtx::PrettyAmount
|
|
issuerBalance(
|
|
jtx::Env& env,
|
|
jtx::Account const& account,
|
|
Issue const& issue)
|
|
{
|
|
Json::Value params;
|
|
params[jss::account] = account.human();
|
|
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
|
auto const result = jrr[jss::result];
|
|
auto const obligations =
|
|
result[jss::obligations][to_string(issue.currency)];
|
|
if (obligations.isNull())
|
|
return {STAmount(issue, 0), account.name()};
|
|
STAmount const amount = amountFromString(issue, obligations.asString());
|
|
return {amount, account.name()};
|
|
}
|
|
|
|
jtx::PrettyAmount
|
|
issuerEscrowed(
|
|
jtx::Env& env,
|
|
jtx::Account const& account,
|
|
Issue const& issue)
|
|
{
|
|
Json::Value params;
|
|
params[jss::account] = account.human();
|
|
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
|
auto const result = jrr[jss::result];
|
|
auto const locked = result[jss::locked][to_string(issue.currency)];
|
|
if (locked.isNull())
|
|
return {STAmount(issue, 0), account.name()};
|
|
STAmount const amount = amountFromString(issue, locked.asString());
|
|
return {amount, account.name()};
|
|
}
|
|
|
|
void
|
|
testIOUEnablement(FeatureBitset features)
|
|
{
|
|
testcase("IOU Enablement");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
for (bool const withTokenEscrow : {false, true})
|
|
{
|
|
auto const amend =
|
|
withTokenEscrow ? features : features - featureTokenEscrow;
|
|
Env env{*this, amend};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env.close();
|
|
|
|
auto const createResult =
|
|
withTokenEscrow ? ter(tesSUCCESS) : ter(temBAD_AMOUNT);
|
|
auto const finishResult =
|
|
withTokenEscrow ? ter(tesSUCCESS) : ter(tecNO_TARGET);
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
createResult);
|
|
env.close();
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
finishResult);
|
|
env.close();
|
|
|
|
auto const seq2 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb2),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
createResult);
|
|
env.close();
|
|
env(escrow::cancel(bob, alice, seq2), finishResult);
|
|
env.close();
|
|
}
|
|
|
|
for (bool const withTokenEscrow : {false, true})
|
|
{
|
|
auto const amend =
|
|
withTokenEscrow ? features : features - featureTokenEscrow;
|
|
Env env{*this, amend};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_TARGET));
|
|
env.close();
|
|
|
|
env(escrow::cancel(bob, alice, seq1), ter(tecNO_TARGET));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUAllowLockingFlag(FeatureBitset features)
|
|
{
|
|
testcase("IOU Allow Locking Flag");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env.close();
|
|
|
|
// Create Escrow #1 & #2
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
auto const seq2 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 3s),
|
|
fee(baseFee),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// Clear the asfAllowTrustLineLocking flag
|
|
env(fclear(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.require(nflags(gw, asfAllowTrustLineLocking));
|
|
|
|
// Cannot Create Escrow without asfAllowTrustLineLocking
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
|
|
// Can finish the escrow created before the flag was cleared
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// Can cancel the escrow created before the flag was cleared
|
|
env(escrow::cancel(bob, alice, seq2), ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testIOUCreatePreflight(FeatureBitset features)
|
|
{
|
|
testcase("IOU Create Preflight");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// temBAD_FEE: Exercises invalid preflight1.
|
|
{
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(XRP(-1)),
|
|
ter(temBAD_FEE));
|
|
env.close();
|
|
}
|
|
|
|
// temBAD_AMOUNT: amount <= 0
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
|
|
env(escrow::create(alice, bob, USD(-1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(temBAD_AMOUNT));
|
|
env.close();
|
|
}
|
|
|
|
// temBAD_CURRENCY: badCurrency() == amount.getCurrency()
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const BAD = IOU(gw, badCurrency());
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
|
|
env(escrow::create(alice, bob, BAD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(temBAD_CURRENCY));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUCreatePreclaim(FeatureBitset features)
|
|
{
|
|
testcase("IOU Create Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_PERMISSION: issuer is the same as the account
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
|
|
env(escrow::create(gw, alice, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_ISSUER: Issuer does not exist
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob);
|
|
env.close();
|
|
env.memoize(gw);
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_ISSUER));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_PERMISSION: asfAllowTrustLineLocking is not set
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env.close();
|
|
|
|
env(escrow::create(gw, alice, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_LINE: account does not have a trustline to the issuer
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_LINE));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_PERMISSION: Not testable
|
|
// tecNO_PERMISSION: Not testable
|
|
// tecNO_AUTH: requireAuth
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(fset(gw, asfRequireAuth));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_AUTH: requireAuth
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const aliceUSD = alice["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(fset(gw, asfRequireAuth));
|
|
env.close();
|
|
env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth));
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecFROZEN: account is frozen
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// set freeze on alice trustline
|
|
env(trust(gw, USD(10'000), alice, tfSetFreeze));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
}
|
|
|
|
// tecFROZEN: dest is frozen
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
}
|
|
|
|
// tecINSUFFICIENT_FUNDS
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_FUNDS));
|
|
env.close();
|
|
}
|
|
|
|
// tecINSUFFICIENT_FUNDS
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(10'001)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_FUNDS));
|
|
env.close();
|
|
}
|
|
|
|
// tecPRECISION_LOSS
|
|
{
|
|
Env env{*this, features};
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100000000000000000), alice);
|
|
env.trust(USD(100000000000000000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10000000000000000)));
|
|
env(pay(gw, bob, USD(1)));
|
|
env.close();
|
|
|
|
// alice cannot create escrow for 1/10 iou - precision loss
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecPRECISION_LOSS));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUFinishPreclaim(FeatureBitset features)
|
|
{
|
|
testcase("IOU Finish Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_AUTH: requireAuth set: dest not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const aliceUSD = alice["USD"];
|
|
auto const bobUSD = bob["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(fset(gw, asfRequireAuth));
|
|
env.close();
|
|
env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth));
|
|
env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth));
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(pay(bob, gw, USD(10'000)));
|
|
env(trust(gw, bobUSD(0)), txflags(tfSetfAuth));
|
|
env(trust(bob, USD(0)));
|
|
env.close();
|
|
|
|
env.trust(USD(10'000), bob);
|
|
env.close();
|
|
|
|
// bob cannot finish because he is not authorized
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecFROZEN: issuer has deep frozen the dest
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze));
|
|
|
|
// bob cannot finish because of deep freeze
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUFinishDoApply(FeatureBitset features)
|
|
{
|
|
testcase("IOU Finish Do Apply");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_LINE_INSUF_RESERVE: insufficient reserve to create line
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const acctReserve = env.current()->fees().reserve;
|
|
auto const incReserve = env.current()->fees().increment;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, gw);
|
|
env.fund(acctReserve + (incReserve - 1), bob);
|
|
env.close();
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// bob cannot finish because insufficient reserve to create line
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_LINE_INSUF_RESERVE));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_LINE: alice submits; finish IOU not created
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env.close();
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// alice cannot finish because bob does not have a trustline
|
|
env(escrow::finish(alice, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_LINE));
|
|
env.close();
|
|
}
|
|
|
|
// tecLIMIT_EXCEEDED: alice submits; IOU Limit < balance + amount
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env.close();
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(1000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(1000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(5)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env.trust(USD(1), bob);
|
|
env.close();
|
|
|
|
// alice cannot finish because bobs limit is too low
|
|
env(escrow::finish(alice, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecLIMIT_EXCEEDED));
|
|
env.close();
|
|
}
|
|
|
|
// tesSUCCESS: bob submits; IOU Limit < balance + amount
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env.close();
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(1000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(1000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(5)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env.trust(USD(1), bob);
|
|
env.close();
|
|
|
|
// bob can finish even if bobs limit is too low
|
|
auto const bobPreLimit = env.limit(bob, USD);
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// bobs limit is not changed
|
|
BEAST_EXPECT(env.limit(bob, USD) == bobPreLimit);
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUCancelPreclaim(FeatureBitset features)
|
|
{
|
|
testcase("IOU Cancel Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_AUTH: requireAuth set: account not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
auto const aliceUSD = alice["USD"];
|
|
auto const bobUSD = bob["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(fset(gw, asfRequireAuth));
|
|
env.close();
|
|
env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth));
|
|
env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth));
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(pay(alice, gw, USD(9'999)));
|
|
env(trust(gw, aliceUSD(0)), txflags(tfSetfAuth));
|
|
env(trust(alice, USD(0)));
|
|
env.close();
|
|
|
|
env.trust(USD(10'000), alice);
|
|
env.close();
|
|
|
|
// alice cannot cancel because she is not authorized
|
|
env(escrow::cancel(bob, alice, seq1),
|
|
fee(baseFee),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUBalances(FeatureBitset features)
|
|
{
|
|
testcase("IOU Balances");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5'000)));
|
|
env(pay(gw, bob, USD(5'000)));
|
|
env.close();
|
|
|
|
auto const outstandingUSD = USD(10'000);
|
|
|
|
// Create & Finish Escrow
|
|
auto const seq1 = env.seq(alice);
|
|
{
|
|
auto const preAliceUSD = env.balance(alice, USD);
|
|
auto const preBobUSD = env.balance(bob, USD);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000));
|
|
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
|
|
BEAST_EXPECT(
|
|
issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000));
|
|
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000));
|
|
}
|
|
{
|
|
auto const preAliceUSD = env.balance(alice, USD);
|
|
auto const preBobUSD = env.balance(bob, USD);
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD);
|
|
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD + USD(1'000));
|
|
BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD);
|
|
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0));
|
|
}
|
|
|
|
// Create & Cancel Escrow
|
|
auto const seq2 = env.seq(alice);
|
|
{
|
|
auto const preAliceUSD = env.balance(alice, USD);
|
|
auto const preBobUSD = env.balance(bob, USD);
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb2),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD - USD(1'000));
|
|
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
|
|
BEAST_EXPECT(
|
|
issuerBalance(env, gw, USD) == outstandingUSD - USD(1'000));
|
|
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(1'000));
|
|
}
|
|
{
|
|
auto const preAliceUSD = env.balance(alice, USD);
|
|
auto const preBobUSD = env.balance(bob, USD);
|
|
env(escrow::cancel(bob, alice, seq2), ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAliceUSD + USD(1'000));
|
|
BEAST_EXPECT(env.balance(bob, USD) == preBobUSD);
|
|
BEAST_EXPECT(issuerBalance(env, gw, USD) == outstandingUSD);
|
|
BEAST_EXPECT(issuerEscrowed(env, gw, USD) == USD(0));
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUMetaAndOwnership(FeatureBitset features)
|
|
{
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
{
|
|
testcase("IOU Metadata to self");
|
|
|
|
Env env{*this, features};
|
|
env.fund(XRP(5000), alice, bob, carol, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob, carol);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env(pay(gw, carol, USD(5000)));
|
|
env.close();
|
|
auto const aseq = env.seq(alice);
|
|
auto const bseq = env.seq(bob);
|
|
|
|
env(escrow::create(alice, alice, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 500s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
auto const aa = env.le(keylet::escrow(alice.id(), aseq));
|
|
BEAST_EXPECT(aa);
|
|
{
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), aa) != aod.end());
|
|
}
|
|
|
|
{
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), aa) != iod.end());
|
|
}
|
|
|
|
env(escrow::create(bob, bob, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
auto const bb = env.le(keylet::escrow(bob.id(), bseq));
|
|
BEAST_EXPECT(bb);
|
|
|
|
{
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) != bod.end());
|
|
}
|
|
|
|
{
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bb) != iod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::finish(alice, alice, aseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), aa) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) != bod.end());
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bb) != iod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::cancel(bob, bob, bseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(bob.id(), bseq)));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) == bod.end());
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bb) == iod.end());
|
|
}
|
|
}
|
|
{
|
|
testcase("IOU Metadata to other");
|
|
|
|
Env env{*this, features};
|
|
env.fund(XRP(5000), alice, bob, carol, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob, carol);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, bob, USD(5000)));
|
|
env(pay(gw, carol, USD(5000)));
|
|
env.close();
|
|
auto const aseq = env.seq(alice);
|
|
auto const bseq = env.seq(bob);
|
|
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
env(escrow::create(bob, carol, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
|
|
auto const ab = env.le(keylet::escrow(alice.id(), aseq));
|
|
BEAST_EXPECT(ab);
|
|
|
|
auto const bc = env.le(keylet::escrow(bob.id(), bseq));
|
|
BEAST_EXPECT(bc);
|
|
|
|
{
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) != aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) != bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) != bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(cod.begin(), cod.end(), bc) != cod.end());
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), ab) != iod.end());
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bc) != iod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::finish(alice, alice, aseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(env.le(keylet::escrow(bob.id(), bseq)));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) == bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) != bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), ab) == iod.end());
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bc) != iod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::cancel(bob, bob, bseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(!env.le(keylet::escrow(bob.id(), bseq)));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) == bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) == bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), ab) == iod.end());
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), bc) == iod.end());
|
|
}
|
|
}
|
|
|
|
{
|
|
testcase("IOU Metadata to issuer");
|
|
|
|
Env env{*this, features};
|
|
env.fund(XRP(5000), alice, carol, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, carol);
|
|
env.close();
|
|
env(pay(gw, alice, USD(5000)));
|
|
env(pay(gw, carol, USD(5000)));
|
|
env.close();
|
|
auto const aseq = env.seq(alice);
|
|
|
|
env(escrow::create(alice, gw, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s));
|
|
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
env(escrow::create(gw, carol, USD(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
ter(tecNO_PERMISSION));
|
|
env.close(5s);
|
|
|
|
auto const ag = env.le(keylet::escrow(alice.id(), aseq));
|
|
BEAST_EXPECT(ag);
|
|
|
|
{
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ag) != aod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), ag) != iod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::finish(alice, alice, aseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ag) == aod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);
|
|
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), ag) == iod.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOURippleState(FeatureBitset features)
|
|
{
|
|
testcase("IOU RippleState");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
struct TestAccountData
|
|
{
|
|
Account src;
|
|
Account dst;
|
|
Account gw;
|
|
bool hasTrustline;
|
|
bool negative;
|
|
};
|
|
|
|
std::array<TestAccountData, 8> tests = {{
|
|
// src > dst && src > issuer && dst no trustline
|
|
{Account("alice2"), Account("bob0"), Account{"gw0"}, false, true},
|
|
// src < dst && src < issuer && dst no trustline
|
|
{Account("carol0"), Account("dan1"), Account{"gw1"}, false, false},
|
|
// dst > src && dst > issuer && dst no trustline
|
|
{Account("dan1"), Account("alice2"), Account{"gw0"}, false, true},
|
|
// dst < src && dst < issuer && dst no trustline
|
|
{Account("bob0"), Account("carol0"), Account{"gw1"}, false, false},
|
|
// src > dst && src > issuer && dst has trustline
|
|
{Account("alice2"), Account("bob0"), Account{"gw0"}, true, true},
|
|
// src < dst && src < issuer && dst has trustline
|
|
{Account("carol0"), Account("dan1"), Account{"gw1"}, true, false},
|
|
// dst > src && dst > issuer && dst has trustline
|
|
{Account("dan1"), Account("alice2"), Account{"gw0"}, true, true},
|
|
// dst < src && dst < issuer && dst has trustline
|
|
{Account("bob0"), Account("carol0"), Account{"gw1"}, true, false},
|
|
}};
|
|
|
|
for (auto const& t : tests)
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const USD = t.gw["USD"];
|
|
env.fund(XRP(5000), t.src, t.dst, t.gw);
|
|
env(fset(t.gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
|
|
if (t.hasTrustline)
|
|
env.trust(USD(100'000), t.src, t.dst);
|
|
else
|
|
env.trust(USD(100'000), t.src);
|
|
env.close();
|
|
|
|
env(pay(t.gw, t.src, USD(10'000)));
|
|
if (t.hasTrustline)
|
|
env(pay(t.gw, t.dst, USD(10'000)));
|
|
env.close();
|
|
|
|
// src can create escrow
|
|
auto const seq1 = env.seq(t.src);
|
|
auto const delta = USD(1'000);
|
|
env(escrow::create(t.src, t.dst, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// dst can finish escrow
|
|
auto const preSrc = env.balance(t.src, USD);
|
|
auto const preDst = env.balance(t.dst, USD);
|
|
|
|
env(escrow::finish(t.dst, t.src, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(t.src, USD) == preSrc);
|
|
BEAST_EXPECT(env.balance(t.dst, USD) == preDst + delta);
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUGateway(FeatureBitset features)
|
|
{
|
|
testcase("IOU Gateway");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
struct TestAccountData
|
|
{
|
|
Account src;
|
|
Account dst;
|
|
bool hasTrustline;
|
|
};
|
|
|
|
// issuer is source
|
|
{
|
|
auto const gw = Account{"gateway"};
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const USD = gw["USD"];
|
|
env.fund(XRP(5000), alice, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.close();
|
|
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env.close();
|
|
|
|
// issuer cannot create escrow
|
|
env(escrow::create(gw, alice, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
|
|
std::array<TestAccountData, 4> gwDstTests = {{
|
|
// src > dst && src > issuer && dst has trustline
|
|
{Account("alice2"), Account{"gw0"}, true},
|
|
// src < dst && src < issuer && dst has trustline
|
|
{Account("carol0"), Account{"gw1"}, true},
|
|
// dst > src && dst > issuer && dst has trustline
|
|
{Account("dan1"), Account{"gw0"}, true},
|
|
// dst < src && dst < issuer && dst has trustline
|
|
{Account("bob0"), Account{"gw1"}, true},
|
|
}};
|
|
|
|
// issuer is destination
|
|
for (auto const& t : gwDstTests)
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const USD = t.dst["USD"];
|
|
env.fund(XRP(5000), t.dst, t.src);
|
|
env(fset(t.dst, asfAllowTrustLineLocking));
|
|
env.close();
|
|
|
|
env.trust(USD(100'000), t.src);
|
|
env.close();
|
|
|
|
env(pay(t.dst, t.src, USD(10'000)));
|
|
env.close();
|
|
|
|
// issuer can receive escrow
|
|
auto const seq1 = env.seq(t.src);
|
|
auto const preSrc = env.balance(t.src, USD);
|
|
env(escrow::create(t.src, t.dst, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// issuer can finish escrow, no dest trustline
|
|
env(escrow::finish(t.dst, t.src, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const preAmount = 10'000;
|
|
BEAST_EXPECT(preSrc == USD(preAmount));
|
|
auto const postAmount = 9000;
|
|
BEAST_EXPECT(env.balance(t.src, USD) == USD(postAmount));
|
|
BEAST_EXPECT(env.balance(t.dst, USD) == USD(0));
|
|
}
|
|
|
|
// issuer is source and destination
|
|
{
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(5000), gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
|
|
// issuer cannot receive escrow
|
|
env(escrow::create(gw, gw, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOULockedRate(FeatureBitset features)
|
|
{
|
|
testcase("IOU Locked Rate");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
// test locked rate
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(rate(gw, 1.25));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, USD);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
// bob can finish escrow
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta);
|
|
BEAST_EXPECT(env.balance(bob, USD) == USD(10'100));
|
|
}
|
|
// test rate change - higher
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(rate(gw, 1.25));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, USD);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
// issuer changes rate higher
|
|
env(rate(gw, 1.26));
|
|
env.close();
|
|
|
|
// bob can finish escrow - rate unchanged
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta);
|
|
BEAST_EXPECT(env.balance(bob, USD) == USD(10'100));
|
|
}
|
|
|
|
// test rate change - lower
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(rate(gw, 1.25));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, USD);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
// issuer changes rate lower
|
|
env(rate(gw, 1.00));
|
|
env.close();
|
|
|
|
// bob can finish escrow - rate changed
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAlice - delta);
|
|
BEAST_EXPECT(env.balance(bob, USD) == USD(10125));
|
|
}
|
|
|
|
// test cancel doesnt charge rate
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(rate(gw, 1.25));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, USD);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 3s),
|
|
fee(baseFee));
|
|
env.close();
|
|
auto transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
// issuer changes rate lower
|
|
env(rate(gw, 1.00));
|
|
env.close();
|
|
|
|
// alice can cancel escrow - rate is not charged
|
|
env(escrow::cancel(alice, alice, seq1), fee(baseFee));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, USD) == preAlice);
|
|
BEAST_EXPECT(env.balance(bob, USD) == USD(10000));
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOULimitAmount(FeatureBitset features)
|
|
{
|
|
testcase("IOU Limit");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
// test LimitAmount
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(1'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(10'000), alice, bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(1'000)));
|
|
env(pay(gw, bob, USD(1'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow
|
|
auto seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// bob can finish
|
|
auto const preBobLimit = env.limit(bob, USD);
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const postBobLimit = env.limit(bob, USD);
|
|
// bobs limit is NOT changed
|
|
BEAST_EXPECT(postBobLimit == preBobLimit);
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOURequireAuth(FeatureBitset features)
|
|
{
|
|
testcase("IOU Require Auth");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
auto const aliceUSD = alice["USD"];
|
|
auto const bobUSD = bob["USD"];
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(1'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env(fset(gw, asfRequireAuth));
|
|
env.close();
|
|
env(trust(gw, aliceUSD(10'000)), txflags(tfSetfAuth));
|
|
env(trust(alice, USD(10'000)));
|
|
env(trust(bob, USD(10'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(1'000)));
|
|
env.close();
|
|
|
|
// alice cannot create escrow - fails without auth
|
|
auto seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
|
|
// set auth on bob
|
|
env(trust(gw, bobUSD(10'000)), txflags(tfSetfAuth));
|
|
env(trust(bob, USD(10'000)));
|
|
env.close();
|
|
env(pay(gw, bob, USD(1'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow - bob has auth
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// bob can finish
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testIOUFreeze(FeatureBitset features)
|
|
{
|
|
testcase("IOU Freeze");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
// test Global Freeze
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
env(fset(gw, asfGlobalFreeze));
|
|
env.close();
|
|
|
|
// setup transaction
|
|
auto seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
|
|
// create escrow fails - frozen trustline
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
|
|
// clear global freeze
|
|
env(fclear(gw, asfGlobalFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set global freeze
|
|
env(fset(gw, asfGlobalFreeze));
|
|
env.close();
|
|
|
|
// bob finish escrow success regardless of frozen assets
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// clear global freeze
|
|
env(fclear(gw, asfGlobalFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::cancel_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set global freeze
|
|
env(fset(gw, asfGlobalFreeze));
|
|
env.close();
|
|
|
|
// bob cancel escrow success regardless of frozen assets
|
|
env(escrow::cancel(bob, alice, seq1), fee(baseFee));
|
|
env.close();
|
|
}
|
|
|
|
// test Individual Freeze
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// set freeze on alice trustline
|
|
env(trust(gw, USD(10'000), alice, tfSetFreeze));
|
|
env.close();
|
|
|
|
// setup transaction
|
|
auto seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
|
|
// create escrow fails - frozen trustline
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
|
|
// clear freeze on alice trustline
|
|
env(trust(gw, USD(10'000), alice, tfClearFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze));
|
|
env.close();
|
|
|
|
// bob finish escrow success regardless of frozen assets
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// reset freeze on bob and alice trustline
|
|
env(trust(gw, USD(10'000), alice, tfClearFreeze));
|
|
env(trust(gw, USD(10'000), bob, tfClearFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::cancel_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze));
|
|
env.close();
|
|
|
|
// bob cancel escrow success regardless of frozen assets
|
|
env(escrow::cancel(bob, alice, seq1), fee(baseFee));
|
|
env.close();
|
|
}
|
|
|
|
// test Deep Freeze
|
|
{
|
|
// Env Setup
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env(trust(alice, USD(100'000)));
|
|
env(trust(bob, USD(100'000)));
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// set freeze on alice trustline
|
|
env(trust(gw, USD(10'000), alice, tfSetFreeze | tfSetDeepFreeze));
|
|
env.close();
|
|
|
|
// setup transaction
|
|
auto seq1 = env.seq(alice);
|
|
auto const delta = USD(125);
|
|
|
|
// create escrow fails - frozen trustline
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
|
|
// clear freeze on alice trustline
|
|
env(trust(
|
|
gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze));
|
|
env.close();
|
|
|
|
// bob finish escrow fails because of deep frozen assets
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecFROZEN));
|
|
env.close();
|
|
|
|
// reset freeze on alice and bob trustline
|
|
env(trust(
|
|
gw, USD(10'000), alice, tfClearFreeze | tfClearDeepFreeze));
|
|
env(trust(gw, USD(10'000), bob, tfClearFreeze | tfClearDeepFreeze));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::cancel_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// set freeze on bob trustline
|
|
env(trust(gw, USD(10'000), bob, tfSetFreeze | tfSetDeepFreeze));
|
|
env.close();
|
|
|
|
// bob cancel escrow fails because of deep frozen assets
|
|
env(escrow::cancel(bob, alice, seq1),
|
|
fee(baseFee),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
}
|
|
void
|
|
testIOUINSF(FeatureBitset features)
|
|
{
|
|
testcase("IOU Insuficient Funds");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
{
|
|
// test tecPATH_PARTIAL
|
|
// ie. has 10'000, escrow 1'000 then try to pay 10'000
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
// create escrow success
|
|
auto const delta = USD(1'000);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
env(pay(alice, gw, USD(10'000)), ter(tecPATH_PARTIAL));
|
|
}
|
|
{
|
|
// test tecINSUFFICIENT_FUNDS
|
|
// ie. has 10'000 escrow 1'000 then try to escrow 10'000
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100'000), alice);
|
|
env.trust(USD(100'000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10'000)));
|
|
env(pay(gw, bob, USD(10'000)));
|
|
env.close();
|
|
|
|
auto const delta = USD(1'000);
|
|
env(escrow::create(alice, bob, delta),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, USD(10'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_FUNDS));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUPrecisionLoss(FeatureBitset features)
|
|
{
|
|
testcase("IOU Precision Loss");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
// test min create precision loss
|
|
{
|
|
Env env(*this, features);
|
|
auto const baseFee = env.current()->fees().base;
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env(fset(gw, asfAllowTrustLineLocking));
|
|
env.close();
|
|
env.trust(USD(100000000000000000), alice);
|
|
env.trust(USD(100000000000000000), bob);
|
|
env.close();
|
|
env(pay(gw, alice, USD(10000000000000000)));
|
|
env(pay(gw, bob, USD(1)));
|
|
env.close();
|
|
|
|
// alice cannot create escrow for 1/10 iou - precision loss
|
|
env(escrow::create(alice, bob, USD(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecPRECISION_LOSS));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
// alice can create escrow for 1'000 iou
|
|
env(escrow::create(alice, bob, USD(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// bob finish escrow success
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTEnablement(FeatureBitset features)
|
|
{
|
|
testcase("MPT Enablement");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
for (bool const withTokenEscrow : {false, true})
|
|
{
|
|
auto const amend =
|
|
withTokenEscrow ? features : features - featureTokenEscrow;
|
|
Env env{*this, amend};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(5000), bob);
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const createResult =
|
|
withTokenEscrow ? ter(tesSUCCESS) : ter(temBAD_AMOUNT);
|
|
auto const finishResult =
|
|
withTokenEscrow ? ter(tesSUCCESS) : ter(tecNO_TARGET);
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
createResult);
|
|
env.close();
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
finishResult);
|
|
env.close();
|
|
auto const seq2 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb2),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
createResult);
|
|
env.close();
|
|
env(escrow::cancel(bob, alice, seq2), finishResult);
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTCreatePreflight(FeatureBitset features)
|
|
{
|
|
testcase("MPT Create Preflight");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
for (bool const withMPT : {true, false})
|
|
{
|
|
auto const amend =
|
|
withMPT ? features : features - featureMPTokensV1;
|
|
Env env{*this, amend};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(1'000), alice, bob, gw);
|
|
|
|
Json::Value jv = escrow::create(alice, bob, XRP(1));
|
|
jv.removeMember(jss::Amount);
|
|
jv[jss::Amount][jss::mpt_issuance_id] =
|
|
"00000004A407AF5856CCF3C42619DAA925813FC955C72983";
|
|
jv[jss::Amount][jss::value] = "-1";
|
|
|
|
auto const result = withMPT ? ter(temBAD_AMOUNT) : ter(temDISABLED);
|
|
env(jv,
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
result);
|
|
env.close();
|
|
}
|
|
|
|
// temBAD_AMOUNT: amount < 0
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, MPT(-1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(temBAD_AMOUNT));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTCreatePreclaim(FeatureBitset features)
|
|
{
|
|
testcase("MPT Create Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_PERMISSION: issuer is the same as the account
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(gw, alice, MPT(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
|
|
// tecOBJECT_NOT_FOUND: mpt does not exist
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(10'000), alice, bob, gw);
|
|
env.close();
|
|
|
|
auto const mpt = ripple::test::jtx::MPT(
|
|
alice.name(), makeMptID(env.seq(alice), alice));
|
|
Json::Value jv = escrow::create(alice, bob, mpt(2));
|
|
jv[jss::Amount][jss::mpt_issuance_id] =
|
|
"00000004A407AF5856CCF3C42619DAA925813FC955C72983";
|
|
env(jv,
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecOBJECT_NOT_FOUND));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_PERMISSION: tfMPTCanEscrow is not enabled
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, MPT(3)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
|
|
// tecOBJECT_NOT_FOUND: account does not have the mpt
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
auto const MPT = mptGw["MPT"];
|
|
|
|
env(escrow::create(alice, bob, MPT(4)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecOBJECT_NOT_FOUND));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_AUTH: requireAuth set: account not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags =
|
|
tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = gw, .holder = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
// unauthorize account
|
|
mptGw.authorize(
|
|
{.account = gw, .holder = alice, .flags = tfMPTUnauthorize});
|
|
|
|
env(escrow::create(alice, bob, MPT(5)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_AUTH: requireAuth set: dest not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags =
|
|
tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = gw, .holder = alice});
|
|
mptGw.authorize({.account = bob});
|
|
mptGw.authorize({.account = gw, .holder = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// unauthorize dest
|
|
mptGw.authorize(
|
|
{.account = gw, .holder = bob, .flags = tfMPTUnauthorize});
|
|
|
|
env(escrow::create(alice, bob, MPT(6)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecLOCKED: issuer has locked the account
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// lock account
|
|
mptGw.set({.account = gw, .holder = alice, .flags = tfMPTLock});
|
|
|
|
env(escrow::create(alice, bob, MPT(7)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecLOCKED));
|
|
env.close();
|
|
}
|
|
|
|
// tecLOCKED: issuer has locked the dest
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// lock dest
|
|
mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock});
|
|
|
|
env(escrow::create(alice, bob, MPT(8)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecLOCKED));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_AUTH: mpt cannot be transferred
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, MPT(9)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecINSUFFICIENT_FUNDS: spendable amount is zero
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, bob, MPT(10)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, MPT(11)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_FUNDS));
|
|
env.close();
|
|
}
|
|
|
|
// tecINSUFFICIENT_FUNDS: spendable amount is less than the amount
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10)));
|
|
env(pay(gw, bob, MPT(10)));
|
|
env.close();
|
|
|
|
env(escrow::create(alice, bob, MPT(11)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_FUNDS));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTFinishPreclaim(FeatureBitset features)
|
|
{
|
|
testcase("MPT Finish Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_AUTH: requireAuth set: dest not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags =
|
|
tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = gw, .holder = alice});
|
|
mptGw.authorize({.account = bob});
|
|
mptGw.authorize({.account = gw, .holder = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// unauthorize dest
|
|
mptGw.authorize(
|
|
{.account = gw, .holder = bob, .flags = tfMPTUnauthorize});
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecOBJECT_NOT_FOUND: MPT issuance does not exist
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
env.fund(XRP(10'000), alice, bob);
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env.app().openLedger().modify(
|
|
[&](OpenView& view, beast::Journal j) {
|
|
Sandbox sb(&view, tapNONE);
|
|
auto sleNew =
|
|
std::make_shared<SLE>(keylet::escrow(alice, seq1));
|
|
MPTIssue const mpt{
|
|
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
|
STAmount amt(mpt, 10);
|
|
sleNew->setAccountID(sfDestination, bob);
|
|
sleNew->setFieldAmount(sfAmount, amt);
|
|
sb.insert(sleNew);
|
|
sb.apply(view);
|
|
return true;
|
|
});
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecOBJECT_NOT_FOUND));
|
|
env.close();
|
|
}
|
|
|
|
// tecLOCKED: issuer has locked the dest
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(8)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// lock dest
|
|
mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock});
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecLOCKED));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTFinishDoApply(FeatureBitset features)
|
|
{
|
|
testcase("MPT Finish Do Apply");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecINSUFFICIENT_RESERVE: insufficient reserve to create MPT
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const acctReserve = env.current()->fees().reserve;
|
|
auto const incReserve = env.current()->fees().increment;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(acctReserve + (incReserve - 1), bob);
|
|
env.close();
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecINSUFFICIENT_RESERVE));
|
|
env.close();
|
|
}
|
|
|
|
// tesSUCCESS: bob submits; finish MPT created
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(10'000), bob);
|
|
env.close();
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
}
|
|
|
|
// tecNO_PERMISSION: carol submits; finish MPT not created
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(10'000), bob, carol);
|
|
env.close();
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(escrow::finish(carol, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_PERMISSION));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTCancelPreclaim(FeatureBitset features)
|
|
{
|
|
testcase("MPT Cancel Preclaim");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecNO_AUTH: requireAuth set: account not authorized
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags =
|
|
tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = gw, .holder = alice});
|
|
mptGw.authorize({.account = bob});
|
|
mptGw.authorize({.account = gw, .holder = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
escrow::condition(escrow::cb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
// unauthorize account
|
|
mptGw.authorize(
|
|
{.account = gw, .holder = alice, .flags = tfMPTUnauthorize});
|
|
|
|
env(escrow::cancel(bob, alice, seq1), ter(tecNO_AUTH));
|
|
env.close();
|
|
}
|
|
|
|
// tecOBJECT_NOT_FOUND: MPT issuance does not exist
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
env.fund(XRP(10'000), alice, bob);
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env.app().openLedger().modify(
|
|
[&](OpenView& view, beast::Journal j) {
|
|
Sandbox sb(&view, tapNONE);
|
|
auto sleNew =
|
|
std::make_shared<SLE>(keylet::escrow(alice, seq1));
|
|
MPTIssue const mpt{
|
|
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
|
STAmount amt(mpt, 10);
|
|
sleNew->setAccountID(sfDestination, bob);
|
|
sleNew->setFieldAmount(sfAmount, amt);
|
|
sb.insert(sleNew);
|
|
sb.apply(view);
|
|
return true;
|
|
});
|
|
|
|
env(escrow::cancel(bob, alice, seq1),
|
|
fee(baseFee),
|
|
ter(tecOBJECT_NOT_FOUND));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTBalances(FeatureBitset features)
|
|
{
|
|
testcase("MPT Balances");
|
|
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(5000), bob);
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, carol}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = carol});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, carol, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto outstandingMPT = env.balance(gw, MPT);
|
|
|
|
// Create & Finish Escrow
|
|
auto const seq1 = env.seq(alice);
|
|
{
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000);
|
|
}
|
|
{
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
|
|
// Create & Cancel Escrow
|
|
auto const seq2 = env.seq(alice);
|
|
{
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb2),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000);
|
|
}
|
|
{
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
env(escrow::cancel(bob, alice, seq2), ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT + MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
|
|
// Self Escrow Create & Finish
|
|
{
|
|
auto const seq = env.seq(alice);
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
env(escrow::create(alice, alice, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000);
|
|
|
|
env(escrow::finish(alice, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
|
|
// Self Escrow Create & Cancel
|
|
{
|
|
auto const seq = env.seq(alice);
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
env(escrow::create(alice, alice, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1'000);
|
|
|
|
env(escrow::cancel(alice, alice, seq), ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
|
|
// Multiple Escrows
|
|
{
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
auto const preCarolMPT = env.balance(carol, MPT);
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(escrow::create(carol, bob, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(carol, MPT) == preCarolMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, carol, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 2'000);
|
|
}
|
|
|
|
// Max MPT Amount Issued (Escrow 1 MPT)
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(maxMPTokenAmount)));
|
|
env.close();
|
|
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
auto const outstandingMPT = env.balance(gw, MPT);
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(1)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 1);
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), alice))
|
|
->isFieldPresent(sfLockedAmount));
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT + MPT(1));
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
BEAST_EXPECT(!env.le(keylet::mptIssuance(MPT.mpt()))
|
|
->isFieldPresent(sfLockedAmount));
|
|
}
|
|
|
|
// Max MPT Amount Issued (Escrow Max MPT)
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(maxMPTokenAmount)));
|
|
env.close();
|
|
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preBobMPT = env.balance(bob, MPT);
|
|
auto const outstandingMPT = env.balance(gw, MPT);
|
|
|
|
// Escrow Max MPT - 10
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(maxMPTokenAmount - 10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// Escrow 10 MPT
|
|
auto const seq2 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(
|
|
env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == maxMPTokenAmount);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBobMPT);
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == maxMPTokenAmount);
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(escrow::finish(bob, alice, seq2),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(
|
|
env.balance(alice, MPT) == preAliceMPT - MPT(maxMPTokenAmount));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(
|
|
env.balance(bob, MPT) == preBobMPT + MPT(maxMPTokenAmount));
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == outstandingMPT);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTMetaAndOwnership(FeatureBitset features)
|
|
{
|
|
using namespace jtx;
|
|
using namespace std::chrono;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
{
|
|
testcase("MPT Metadata to self");
|
|
|
|
Env env{*this, features};
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
auto const aseq = env.seq(alice);
|
|
auto const bseq = env.seq(bob);
|
|
|
|
env(escrow::create(alice, alice, MPT(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 500s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
auto const aa = env.le(keylet::escrow(alice.id(), aseq));
|
|
BEAST_EXPECT(aa);
|
|
{
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), aa) != aod.end());
|
|
}
|
|
|
|
{
|
|
ripple::Dir iod(*env.current(), keylet::ownerDir(gw.id()));
|
|
BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(iod.begin(), iod.end(), aa) == iod.end());
|
|
}
|
|
|
|
env(escrow::create(bob, bob, MPT(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
auto const bb = env.le(keylet::escrow(bob.id(), bseq));
|
|
BEAST_EXPECT(bb);
|
|
|
|
{
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) != bod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::finish(alice, alice, aseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), aa) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) != bod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::cancel(bob, bob, bseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(bob.id(), bseq)));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bb) == bod.end());
|
|
}
|
|
}
|
|
|
|
{
|
|
testcase("MPT Metadata to other");
|
|
|
|
Env env{*this, features};
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob, carol}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
mptGw.authorize({.account = carol});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env(pay(gw, carol, MPT(10'000)));
|
|
env.close();
|
|
auto const aseq = env.seq(alice);
|
|
auto const bseq = env.seq(bob);
|
|
|
|
env(escrow::create(alice, bob, MPT(1'000)),
|
|
escrow::finish_time(env.now() + 1s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
env(escrow::create(bob, carol, MPT(1'000)),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s));
|
|
BEAST_EXPECT(
|
|
(*env.meta())[sfTransactionResult] ==
|
|
static_cast<std::uint8_t>(tesSUCCESS));
|
|
env.close(5s);
|
|
|
|
auto const ab = env.le(keylet::escrow(alice.id(), aseq));
|
|
BEAST_EXPECT(ab);
|
|
|
|
auto const bc = env.le(keylet::escrow(bob.id(), bseq));
|
|
BEAST_EXPECT(bc);
|
|
|
|
{
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) != aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) != bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) != bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(cod.begin(), cod.end(), bc) != cod.end());
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::finish(alice, alice, aseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(env.le(keylet::escrow(bob.id(), bseq)));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) == bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) != bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2);
|
|
}
|
|
|
|
env.close(5s);
|
|
env(escrow::cancel(bob, bob, bseq));
|
|
{
|
|
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), aseq)));
|
|
BEAST_EXPECT(!env.le(keylet::escrow(bob.id(), bseq)));
|
|
|
|
ripple::Dir aod(*env.current(), keylet::ownerDir(alice.id()));
|
|
BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(aod.begin(), aod.end(), ab) == aod.end());
|
|
|
|
ripple::Dir bod(*env.current(), keylet::ownerDir(bob.id()));
|
|
BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1);
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), ab) == bod.end());
|
|
BEAST_EXPECT(
|
|
std::find(bod.begin(), bod.end(), bc) == bod.end());
|
|
|
|
ripple::Dir cod(*env.current(), keylet::ownerDir(carol.id()));
|
|
BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTGateway(FeatureBitset features)
|
|
{
|
|
testcase("MPT Gateway Balances");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// issuer is dest; alice w/ authorization
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
// issuer can be destination
|
|
auto const seq1 = env.seq(alice);
|
|
auto const preAliceMPT = env.balance(alice, MPT);
|
|
auto const preOutstanding = env.balance(gw, MPT);
|
|
auto const preEscrowed = issuerMPTEscrowed(env, MPT);
|
|
BEAST_EXPECT(preOutstanding == MPT(-10'000));
|
|
BEAST_EXPECT(preEscrowed == 0);
|
|
|
|
env(escrow::create(alice, gw, MPT(1'000)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 1'000);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed + 1'000);
|
|
|
|
// issuer (dest) can finish escrow
|
|
env(escrow::finish(gw, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding + MPT(1'000));
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed);
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTLockedRate(FeatureBitset features)
|
|
{
|
|
testcase("MPT Locked Rate");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const carol = Account("carol");
|
|
auto const gw = Account{"gateway"};
|
|
auto const USD = gw["USD"];
|
|
|
|
// test locked rate: finish
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.transferFee = 25000,
|
|
.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, MPT);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = MPT(125);
|
|
env(escrow::create(alice, bob, MPT(125)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000));
|
|
|
|
// bob can finish escrow
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == MPT(10'100));
|
|
|
|
auto const escrowedWithFix =
|
|
env.current()->rules().enabled(fixTokenEscrowV1) ? 0 : 25;
|
|
auto const outstandingWithFix =
|
|
env.current()->rules().enabled(fixTokenEscrowV1) ? MPT(19'975)
|
|
: MPT(20'000);
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == escrowedWithFix);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == escrowedWithFix);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == -outstandingWithFix);
|
|
}
|
|
|
|
// test locked rate: cancel
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.transferFee = 25000,
|
|
.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, MPT);
|
|
auto const preBob = env.balance(bob, MPT);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = MPT(125);
|
|
env(escrow::create(alice, bob, MPT(125)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 3s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
// alice can cancel escrow
|
|
env(escrow::cancel(alice, alice, seq1), fee(baseFee));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAlice);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == preBob);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
}
|
|
|
|
// test locked rate: issuer is destination
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.transferFee = 25000,
|
|
.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// alice can create escrow w/ xfer rate
|
|
auto const preAlice = env.balance(alice, MPT);
|
|
auto const seq1 = env.seq(alice);
|
|
auto const delta = MPT(125);
|
|
env(escrow::create(alice, gw, MPT(125)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
auto const transferRate = escrow::rate(env, alice, seq1);
|
|
BEAST_EXPECT(
|
|
transferRate.value == std::uint32_t(1'000'000'000 * 1.25));
|
|
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000));
|
|
|
|
// bob can finish escrow
|
|
env(escrow::finish(gw, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta);
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == MPT(-19'875));
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTRequireAuth(FeatureBitset features)
|
|
{
|
|
testcase("MPT Require Auth");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTRequireAuth});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = gw, .holder = alice});
|
|
mptGw.authorize({.account = bob});
|
|
mptGw.authorize({.account = gw, .holder = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto seq = env.seq(alice);
|
|
auto const delta = MPT(125);
|
|
// alice can create escrow - is authorized
|
|
env(escrow::create(alice, bob, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// bob can finish escrow - is authorized
|
|
env(escrow::finish(bob, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testMPTLock(FeatureBitset features)
|
|
{
|
|
testcase("MPT Lock");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer | tfMPTCanLock});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// alice create escrow
|
|
auto seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// lock account & dest
|
|
mptGw.set({.account = gw, .holder = alice, .flags = tfMPTLock});
|
|
mptGw.set({.account = gw, .holder = bob, .flags = tfMPTLock});
|
|
|
|
// bob cannot finish
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tecLOCKED));
|
|
env.close();
|
|
|
|
// bob can cancel
|
|
env(escrow::cancel(bob, alice, seq1));
|
|
env.close();
|
|
}
|
|
|
|
void
|
|
testMPTCanTransfer(FeatureBitset features)
|
|
{
|
|
testcase("MPT Can Transfer");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
// alice cannot create escrow to non issuer
|
|
env(escrow::create(alice, bob, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150),
|
|
ter(tecNO_AUTH));
|
|
env.close();
|
|
|
|
// Escrow Create & Finish
|
|
{
|
|
// alice an create escrow to issuer
|
|
auto seq = env.seq(alice);
|
|
env(escrow::create(alice, gw, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// gw can finish
|
|
env(escrow::finish(gw, alice, seq),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
}
|
|
|
|
// Escrow Create & Cancel
|
|
{
|
|
// alice an create escrow to issuer
|
|
auto seq = env.seq(alice);
|
|
env(escrow::create(alice, gw, MPT(100)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
escrow::cancel_time(env.now() + 2s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
// alice can cancel
|
|
env(escrow::cancel(alice, alice, seq));
|
|
env.close();
|
|
}
|
|
}
|
|
|
|
void
|
|
testMPTDestroy(FeatureBitset features)
|
|
{
|
|
testcase("MPT Destroy");
|
|
using namespace test::jtx;
|
|
using namespace std::literals;
|
|
|
|
// tecHAS_OBLIGATIONS: issuer cannot destroy issuance
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice, bob}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
mptGw.authorize({.account = bob});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env(pay(gw, bob, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150));
|
|
env.close();
|
|
|
|
env(pay(alice, gw, MPT(10'000)), ter(tecPATH_PARTIAL));
|
|
env(pay(alice, gw, MPT(9'990)));
|
|
env(pay(bob, gw, MPT(10'000)));
|
|
BEAST_EXPECT(env.balance(alice, MPT) == MPT(0));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10);
|
|
BEAST_EXPECT(env.balance(bob, MPT) == MPT(0));
|
|
BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0);
|
|
BEAST_EXPECT(env.balance(gw, MPT) == MPT(-10));
|
|
mptGw.authorize({.account = bob, .flags = tfMPTUnauthorize});
|
|
mptGw.destroy(
|
|
{.id = mptGw.issuanceID(),
|
|
.ownerCount = 1,
|
|
.err = tecHAS_OBLIGATIONS});
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(pay(bob, gw, MPT(10)));
|
|
mptGw.destroy({.id = mptGw.issuanceID(), .ownerCount = 0});
|
|
}
|
|
|
|
// tecHAS_OBLIGATIONS: holder cannot destroy mptoken
|
|
{
|
|
Env env{*this, features};
|
|
auto const baseFee = env.current()->fees().base;
|
|
auto const alice = Account("alice");
|
|
auto const bob = Account("bob");
|
|
auto const gw = Account("gw");
|
|
env.fund(XRP(10'000), bob);
|
|
env.close();
|
|
|
|
MPTTester mptGw(env, gw, {.holders = {alice}});
|
|
mptGw.create(
|
|
{.ownerCount = 1,
|
|
.holderCount = 0,
|
|
.flags = tfMPTCanEscrow | tfMPTCanTransfer});
|
|
mptGw.authorize({.account = alice});
|
|
auto const MPT = mptGw["MPT"];
|
|
env(pay(gw, alice, MPT(10'000)));
|
|
env.close();
|
|
|
|
auto const seq1 = env.seq(alice);
|
|
env(escrow::create(alice, bob, MPT(10)),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::finish_time(env.now() + 1s),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
env(pay(alice, gw, MPT(9'990)));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == MPT(0));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10);
|
|
mptGw.authorize(
|
|
{.account = alice,
|
|
.flags = tfMPTUnauthorize,
|
|
.err = tecHAS_OBLIGATIONS});
|
|
|
|
env(escrow::finish(bob, alice, seq1),
|
|
escrow::condition(escrow::cb1),
|
|
escrow::fulfillment(escrow::fb1),
|
|
fee(baseFee * 150),
|
|
ter(tesSUCCESS));
|
|
env.close();
|
|
|
|
BEAST_EXPECT(env.balance(alice, MPT) == MPT(0));
|
|
BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0);
|
|
mptGw.authorize({.account = alice, .flags = tfMPTUnauthorize});
|
|
BEAST_EXPECT(!env.le(keylet::mptoken(MPT.mpt(), alice)));
|
|
}
|
|
}
|
|
|
|
void
|
|
testIOUWithFeats(FeatureBitset features)
|
|
{
|
|
testIOUEnablement(features);
|
|
testIOUAllowLockingFlag(features);
|
|
testIOUCreatePreflight(features);
|
|
testIOUCreatePreclaim(features);
|
|
testIOUFinishPreclaim(features);
|
|
testIOUFinishDoApply(features);
|
|
testIOUCancelPreclaim(features);
|
|
testIOUBalances(features);
|
|
testIOUMetaAndOwnership(features);
|
|
testIOURippleState(features);
|
|
testIOUGateway(features);
|
|
testIOULockedRate(features);
|
|
testIOULimitAmount(features);
|
|
testIOURequireAuth(features);
|
|
testIOUFreeze(features);
|
|
testIOUINSF(features);
|
|
testIOUPrecisionLoss(features);
|
|
}
|
|
|
|
void
|
|
testMPTWithFeats(FeatureBitset features)
|
|
{
|
|
testMPTEnablement(features);
|
|
testMPTCreatePreflight(features);
|
|
testMPTCreatePreclaim(features);
|
|
testMPTFinishPreclaim(features);
|
|
testMPTFinishDoApply(features);
|
|
testMPTCancelPreclaim(features);
|
|
testMPTBalances(features);
|
|
testMPTMetaAndOwnership(features);
|
|
testMPTGateway(features);
|
|
testMPTLockedRate(features);
|
|
testMPTRequireAuth(features);
|
|
testMPTLock(features);
|
|
testMPTCanTransfer(features);
|
|
testMPTDestroy(features);
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
using namespace test::jtx;
|
|
FeatureBitset const all{testable_amendments()};
|
|
testIOUWithFeats(all);
|
|
testMPTWithFeats(all);
|
|
testMPTWithFeats(all - fixTokenEscrowV1);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(EscrowToken, app, ripple);
|
|
|
|
} // namespace test
|
|
} // namespace ripple
|