mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 23:20:33 +00:00
Add support for XLS-85 Token Escrow (#5185)
- Specification: https://github.com/XRPLF/XRPL-Standards/pull/272 - Amendment: `TokenEscrow` - Enables escrowing of IOU and MPT tokens in addition to native XRP. - Allows accounts to lock issued tokens (IOU/MPT) in escrow objects, with support for freeze, authorization, and transfer rates. - Adds new ledger fields (`sfLockedAmount`, `sfIssuerNode`, etc.) to track locked balances for IOU and MPT escrows. - Updates EscrowCreate, EscrowFinish, and EscrowCancel transaction logic to support IOU and MPT assets, including proper handling of trustlines and MPT authorization, transfer rates, and locked balances. - Enforces invariant checks for escrowed IOU/MPT amounts. - Extends GatewayBalances RPC to report locked (escrowed) balances.
This commit is contained in:
@@ -3651,10 +3651,10 @@ private:
|
||||
// Can't pay into AMM with escrow.
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
|
||||
condition(cb1),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
env(escrow::create(carol, ammAlice.ammAccount(), XRP(1)),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 1s),
|
||||
escrow::cancel_time(env.now() + 2s),
|
||||
fee(baseFee * 150),
|
||||
ter(tecNO_PERMISSION));
|
||||
});
|
||||
|
||||
@@ -335,26 +335,11 @@ public:
|
||||
env(check::cancel(becky, checkId));
|
||||
env.close();
|
||||
|
||||
// Lambda to create an escrow.
|
||||
auto escrowCreate = [](jtx::Account const& account,
|
||||
jtx::Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::time_point const& cancelAfter) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[sfFinishAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 1;
|
||||
jv[sfCancelAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 2;
|
||||
return jv;
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::uint32_t const escrowSeq{env.seq(alice)};
|
||||
env(escrowCreate(alice, becky, XRP(333), env.now() + 2s));
|
||||
env(escrow::create(alice, becky, XRP(333)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
// alice and becky should be unable to delete their accounts because
|
||||
@@ -366,17 +351,39 @@ public:
|
||||
// Now cancel the escrow, but create a payment channel between
|
||||
// alice and becky.
|
||||
|
||||
// Lambda to cancel an escrow.
|
||||
auto escrowCancel =
|
||||
[](Account const& account, Account const& from, std::uint32_t seq) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
};
|
||||
env(escrowCancel(becky, alice, escrowSeq));
|
||||
bool const withTokenEscrow =
|
||||
env.current()->rules().enabled(featureTokenEscrow);
|
||||
if (withTokenEscrow)
|
||||
{
|
||||
Account const gw1("gw1");
|
||||
Account const carol("carol");
|
||||
auto const USD = gw1["USD"];
|
||||
env.fund(XRP(100000), carol, gw1);
|
||||
env(fset(gw1, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
env.trust(USD(10000), carol);
|
||||
env.close();
|
||||
env(pay(gw1, carol, USD(100)));
|
||||
env.close();
|
||||
|
||||
std::uint32_t const escrowSeq{env.seq(carol)};
|
||||
env(escrow::create(carol, becky, USD(1)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
incLgrSeqForAccDel(env, gw1);
|
||||
|
||||
env(acctdelete(gw1, becky),
|
||||
fee(acctDelFee),
|
||||
ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
env(escrow::cancel(becky, carol, escrowSeq));
|
||||
env.close();
|
||||
}
|
||||
|
||||
env(escrow::cancel(becky, alice, escrowSeq));
|
||||
env.close();
|
||||
|
||||
Keylet const alicePayChanKey{
|
||||
|
||||
@@ -714,12 +714,12 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
if (!supportsPreauth)
|
||||
{
|
||||
auto const seq1 = env.seq(alice);
|
||||
env(escrow(alice, becky, XRP(100)),
|
||||
finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, becky, XRP(100)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// Failed as rule is disabled
|
||||
env(finish(gw, alice, seq1),
|
||||
env(escrow::finish(gw, alice, seq1),
|
||||
fee(1500),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
@@ -1387,12 +1387,13 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, bob, XRP(1000)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// zelda can't finish escrow with invalid credentials
|
||||
{
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({}),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
@@ -1404,14 +1405,14 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
"0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
|
||||
"01E034";
|
||||
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({invalidIdx}),
|
||||
ter(tecBAD_CREDENTIALS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{ // Ledger closed, time increased, zelda can't finish escrow
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({credIdx}),
|
||||
fee(1500),
|
||||
ter(tecEXPIRED));
|
||||
|
||||
3736
src/test/app/EscrowToken_test.cpp
Normal file
3736
src/test/app/EscrowToken_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1694,15 +1694,6 @@ class MPToken_test : public beast::unit_test::suite
|
||||
jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::SendMax.c_str());
|
||||
}
|
||||
// EscrowCreate
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::Destination] = carol.human();
|
||||
jv[jss::Amount] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::Amount.c_str());
|
||||
}
|
||||
// OfferCreate
|
||||
{
|
||||
Json::Value jv = offer(alice, USD(100), mpt);
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <test/jtx/deposit.h>
|
||||
#include <test/jtx/did.h>
|
||||
#include <test/jtx/domain.h>
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/invoice_id.h>
|
||||
|
||||
@@ -472,6 +472,15 @@ public:
|
||||
PrettyAmount
|
||||
balance(Account const& account, Issue const& issue) const;
|
||||
|
||||
PrettyAmount
|
||||
balance(Account const& account, MPTIssue const& mptIssue) const;
|
||||
|
||||
/** Returns the IOU limit on an account.
|
||||
Returns 0 if the trust line does not exist.
|
||||
*/
|
||||
PrettyAmount
|
||||
limit(Account const& account, Issue const& issue) const;
|
||||
|
||||
/** Return the number of objects owned by an account.
|
||||
* Returns 0 if the account does not exist.
|
||||
*/
|
||||
|
||||
@@ -233,127 +233,6 @@ expectLedgerEntryRoot(
|
||||
Account const& acct,
|
||||
STAmount const& expectedValue);
|
||||
|
||||
/* Escrow */
|
||||
/******************************************************************************/
|
||||
|
||||
Json::Value
|
||||
escrow(AccountID const& account, AccountID const& to, STAmount const& amount);
|
||||
|
||||
inline Json::Value
|
||||
escrow(Account const& account, Account const& to, STAmount const& amount)
|
||||
{
|
||||
return escrow(account.id(), to.id(), amount);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
finish(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return finish(account.id(), from.id(), seq);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
cancel(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return cancel(account.id(), from, seq);
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 39> constexpr cb1 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC,
|
||||
0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
|
||||
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95,
|
||||
0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55, 0x81, 0x01, 0x00}};
|
||||
|
||||
// A PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 4> const fb1 = {{0xA0, 0x02, 0x80, 0x00}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
struct finish_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit finish_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
struct cancel_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit cancel_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
struct condition
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit condition(Slice const& cond) : value_(strHex(cond))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit condition(std::array<std::uint8_t, N> const& c)
|
||||
: condition(makeSlice(c))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct fulfillment
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit fulfillment(Slice condition) : value_(strHex(condition))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit fulfillment(std::array<std::uint8_t, N> f)
|
||||
: fulfillment(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
185
src/test/jtx/escrow.h
Normal file
185
src/test/jtx/escrow.h
Normal file
@@ -0,0 +1,185 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_ESCROW_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_ESCROW_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/rate.h>
|
||||
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Escrow operations. */
|
||||
namespace escrow {
|
||||
|
||||
Json::Value
|
||||
create(AccountID const& account, AccountID const& to, STAmount const& amount);
|
||||
|
||||
inline Json::Value
|
||||
create(Account const& account, Account const& to, STAmount const& amount)
|
||||
{
|
||||
return create(account.id(), to.id(), amount);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
finish(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return finish(account.id(), from.id(), seq);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
cancel(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return cancel(account.id(), from, seq);
|
||||
}
|
||||
|
||||
Rate
|
||||
rate(Env& env, Account const& account, std::uint32_t const& seq);
|
||||
|
||||
// A PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 4> const fb1 = {{0xA0, 0x02, 0x80, 0x00}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb1 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC,
|
||||
0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
|
||||
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95,
|
||||
0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55, 0x81, 0x01, 0x00}};
|
||||
|
||||
// Another PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 7> const fb2 = {
|
||||
{0xA0, 0x05, 0x80, 0x03, 0x61, 0x61, 0x61}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb2 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0x98, 0x34, 0x87, 0x6D, 0xCF, 0xB0,
|
||||
0x5C, 0xB1, 0x67, 0xA5, 0xC2, 0x49, 0x53, 0xEB, 0xA5, 0x8C,
|
||||
0x4A, 0xC8, 0x9B, 0x1A, 0xDF, 0x57, 0xF2, 0x8F, 0x2F, 0x9D,
|
||||
0x09, 0xAF, 0x10, 0x7E, 0xE8, 0xF0, 0x81, 0x01, 0x03}};
|
||||
|
||||
// Another PreimageSha256 fulfillment and its associated condition.
|
||||
std::array<std::uint8_t, 8> const fb3 = {
|
||||
{0xA0, 0x06, 0x80, 0x04, 0x6E, 0x69, 0x6B, 0x62}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb3 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0x6E, 0x4C, 0x71, 0x45, 0x30, 0xC0,
|
||||
0xA4, 0x26, 0x8B, 0x3F, 0xA6, 0x3B, 0x1B, 0x60, 0x6F, 0x2D,
|
||||
0x26, 0x4A, 0x2D, 0x85, 0x7B, 0xE8, 0xA0, 0x9C, 0x1D, 0xFD,
|
||||
0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
struct finish_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit finish_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
struct cancel_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit cancel_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
struct condition
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit condition(Slice const& cond) : value_(strHex(cond))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit condition(std::array<std::uint8_t, N> const& c)
|
||||
: condition(makeSlice(c))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct fulfillment
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit fulfillment(Slice condition) : value_(strHex(condition))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit fulfillment(std::array<std::uint8_t, N> f)
|
||||
: fulfillment(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -96,6 +96,9 @@ private:
|
||||
case asfDisallowIncomingTrustline:
|
||||
mask_ |= lsfDisallowIncomingTrustline;
|
||||
break;
|
||||
case asfAllowTrustLineLocking:
|
||||
mask_ |= lsfAllowTrustLineLocking;
|
||||
break;
|
||||
default:
|
||||
Throw<std::runtime_error>("unknown flag");
|
||||
}
|
||||
|
||||
@@ -199,6 +199,48 @@ Env::balance(Account const& account, Issue const& issue) const
|
||||
return {amount, lookup(issue.account).name()};
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
Env::balance(Account const& account, MPTIssue const& mptIssue) const
|
||||
{
|
||||
MPTID const id = mptIssue.getMptID();
|
||||
if (!id)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
AccountID const issuer = mptIssue.getIssuer();
|
||||
if (account.id() == issuer)
|
||||
{
|
||||
// Issuer balance
|
||||
auto const sle = le(keylet::mptIssuance(id));
|
||||
if (!sle)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
STAmount const amount{mptIssue, sle->getFieldU64(sfOutstandingAmount)};
|
||||
return {amount, lookup(issuer).name()};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Holder balance
|
||||
auto const sle = le(keylet::mptoken(id, account));
|
||||
if (!sle)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)};
|
||||
return {amount, lookup(issuer).name()};
|
||||
}
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
Env::limit(Account const& account, Issue const& issue) const
|
||||
{
|
||||
auto const sle = le(keylet::line(account.id(), issue));
|
||||
if (!sle)
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
auto const aHigh = account.id() > issue.account;
|
||||
if (sle && sle->isFieldPresent(aHigh ? sfLowLimit : sfHighLimit))
|
||||
return {(*sle)[aHigh ? sfLowLimit : sfHighLimit], account.name()};
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
Env::ownerCount(Account const& account) const
|
||||
{
|
||||
|
||||
@@ -211,42 +211,6 @@ expectLedgerEntryRoot(
|
||||
return accountBalance(env, acct) == to_string(expectedValue.xrp());
|
||||
}
|
||||
|
||||
/* Escrow */
|
||||
/******************************************************************************/
|
||||
|
||||
Json::Value
|
||||
escrow(AccountID const& account, AccountID const& to, STAmount const& amount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowFinish;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = to_string(from);
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
Json::Value
|
||||
|
||||
82
src/test/jtx/impl/escrow.cpp
Normal file
82
src/test/jtx/impl/escrow.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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/escrow.h>
|
||||
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Escrow operations. */
|
||||
namespace escrow {
|
||||
|
||||
Json::Value
|
||||
create(AccountID const& account, AccountID const& to, STAmount const& amount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowFinish;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = to_string(from);
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Rate
|
||||
rate(Env& env, Account const& account, std::uint32_t const& seq)
|
||||
{
|
||||
auto const sle = env.le(keylet::escrow(account.id(), seq));
|
||||
if (sle->isFieldPresent(sfTransferRate))
|
||||
return ripple::Rate((*sle)[sfTransferRate]);
|
||||
return Rate{0};
|
||||
}
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -731,21 +731,6 @@ class Invariants_test : public beast::unit_test::suite
|
||||
using namespace test::jtx;
|
||||
testcase << "no zero escrow";
|
||||
|
||||
doInvariantCheck(
|
||||
{{"Cannot return non-native STAmount as XRPAmount"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
// escrow with nonnative amount
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto sleNew = std::make_shared<SLE>(
|
||||
keylet::escrow(A1, (*sle)[sfSequence] + 2));
|
||||
STAmount nonNative(A2["USD"](51));
|
||||
sleNew->setFieldAmount(sfAmount, nonNative);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"XRP net change of -1000000 doesn't match fee 0"},
|
||||
{"escrow specifies invalid amount"}},
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -668,6 +670,366 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddXRP()
|
||||
{
|
||||
testcase("can add xrp");
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(0));
|
||||
STAmount amt2(XRPAmount(1000));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(0));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two positive XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(500));
|
||||
STAmount amt2(XRPAmount(1500));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two negative XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(-500));
|
||||
STAmount amt2(XRPAmount(-1500));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative XRP amount
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(-1000));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
STAmount amt2(XRPAmount(1));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
amt1 += XRPAmount(1);
|
||||
STAmount amt2(XRPAmount(-1));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddIOU()
|
||||
{
|
||||
testcase("can add iou");
|
||||
|
||||
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||
Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
|
||||
|
||||
// Adding two IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, 500);
|
||||
STAmount amt2(usd, 1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative IOU amount
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(usd, -1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, std::numeric_limits<int64_t>::max());
|
||||
STAmount amt2(usd, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, std::numeric_limits<std::int64_t>::min());
|
||||
STAmount amt2(usd, -1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding XRP and IOU
|
||||
{
|
||||
STAmount amt1(XRPAmount(1));
|
||||
STAmount amt2(usd, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different IOU issues (non zero)
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different IOU issues (zero)
|
||||
{
|
||||
STAmount amt1(usd, 0);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddMPT()
|
||||
{
|
||||
testcase("can add mpt");
|
||||
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, 0);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two positive MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, 500);
|
||||
STAmount amt2(mpt, 1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two negative MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, -500);
|
||||
STAmount amt2(mpt, -1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative MPT amount
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, -1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max MPT amounts
|
||||
{
|
||||
STAmount amt1(
|
||||
mpt, std::numeric_limits<MPTAmount::value_type>::max());
|
||||
STAmount amt2(mpt, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min MPT amounts
|
||||
// Note: Cannot check min MPT overflow because you cannot initialize the
|
||||
// STAmount with a negative MPT amount.
|
||||
|
||||
// Adding MPT and XRP
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt2, 500);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt2, 0);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractXRP()
|
||||
{
|
||||
testcase("can subtract xrp");
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(0));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(0));
|
||||
STAmount amt2(XRPAmount(1000));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting two positive XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(1500));
|
||||
STAmount amt2(XRPAmount(500));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting two negative XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(-1500));
|
||||
STAmount amt2(XRPAmount(-500));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting a positive and a negative XRP amount
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(-1000));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Underflow check for min XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
amt1 += XRPAmount(1);
|
||||
STAmount amt2(XRPAmount(1));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for max XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
STAmount amt2(XRPAmount(-1));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractIOU()
|
||||
{
|
||||
testcase("can subtract iou");
|
||||
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||
Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
|
||||
|
||||
// Subtracting two IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, 1500);
|
||||
STAmount amt2(usd, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting XRP and IOU
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(usd, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different IOU issues (non zero)
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different IOU issues (zero)
|
||||
{
|
||||
STAmount amt1(usd, 0);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractMPT()
|
||||
{
|
||||
testcase("can subtract mpt");
|
||||
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, 0);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting two positive MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, 1500);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting two negative MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, -1500);
|
||||
STAmount amt2(mpt, -500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting a positive and a negative MPT amount
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, -1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Underflow check for min MPT amounts
|
||||
// Note: Cannot check min MPT underflow because you cannot initialize
|
||||
// the STAmount with a negative MPT amount.
|
||||
|
||||
// Overflow check for max positive MPT amounts (should fail)
|
||||
{
|
||||
STAmount amt1(
|
||||
mpt, std::numeric_limits<MPTAmount::value_type>::max());
|
||||
STAmount amt2(mpt, -2);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting MPT and XRP
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt2, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different MPT issues (zero)
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt2, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
@@ -681,6 +1043,12 @@ public:
|
||||
testRounding();
|
||||
testConvertXRP();
|
||||
testConvertIOU();
|
||||
testCanAddXRP();
|
||||
testCanAddIOU();
|
||||
testCanAddMPT();
|
||||
testCanSubtractXRP();
|
||||
testCanSubtractIOU();
|
||||
testCanSubtractMPT();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -573,21 +573,6 @@ public:
|
||||
env.fund(XRP(10000), alice, becky, gw1);
|
||||
env.close();
|
||||
|
||||
// A couple of helper lambdas
|
||||
auto escrow = [&env](
|
||||
Account const& account,
|
||||
Account const& to,
|
||||
STAmount const& amount) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
NetClock::time_point finish = env.now() + 1s;
|
||||
jv[sfFinishAfter.jsonName] = finish.time_since_epoch().count();
|
||||
return jv;
|
||||
};
|
||||
|
||||
auto payChan = [](Account const& account,
|
||||
Account const& to,
|
||||
STAmount const& amount,
|
||||
@@ -623,8 +608,10 @@ public:
|
||||
env.close();
|
||||
|
||||
// Escrow, in each direction
|
||||
env(escrow(alice, becky, XRP(1000)));
|
||||
env(escrow(becky, alice, XRP(1000)));
|
||||
env(escrow::create(alice, becky, XRP(1000)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env(escrow::create(becky, alice, XRP(1000)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
|
||||
// Pay channels, in each direction
|
||||
env(payChan(alice, becky, XRP(1000), 100s, alice.pk()));
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
// is tested elsewhere.
|
||||
continue;
|
||||
}
|
||||
if (flag == asfAllowTrustLineLocking)
|
||||
{
|
||||
// These flags are part of the AllowTokenLocking amendment
|
||||
// and are tested elsewhere
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::find(goodFlags.begin(), goodFlags.end(), flag) !=
|
||||
goodFlags.end())
|
||||
|
||||
Reference in New Issue
Block a user