Files
rippled/src/test/app/EscrowToken_test.cpp

3956 lines
145 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/ledger/Dir.h>
#include <xrpld/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().accountReserve(0);
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().accountReserve(0);
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