Files
xahaud/src/test/app/URIToken_test.cpp
2023-08-14 13:16:47 -04:00

2348 lines
81 KiB
C++
Raw Blame History

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <ripple/basics/chrono.h>
#include <ripple/ledger/Directory.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
#include <ripple/core/ConfigSections.h>
#include <chrono>
namespace ripple {
namespace test {
struct URIToken_test : public beast::unit_test::suite
{
static uint256
tokenid(jtx::Account const& account, std::string const& uri)
{
auto const k = keylet::uritoken(account, Blob(uri.begin(), uri.end()));
return k.key;
}
static bool
inOwnerDir(
ReadView const& view,
jtx::Account const& acct,
uint256 const& tid)
{
auto const uritSle = view.read({ltURI_TOKEN, tid});
ripple::Dir const ownerDir(view, keylet::ownerDir(acct.id()));
return std::find(ownerDir.begin(), ownerDir.end(), uritSle) !=
ownerDir.end();
}
static std::size_t
ownerDirCount(ReadView const& view, jtx::Account const& acct)
{
ripple::Dir const ownerDir(view, keylet::ownerDir(acct.id()));
return std::distance(ownerDir.begin(), ownerDir.end());
};
static std::pair<uint256, std::shared_ptr<SLE const>>
uriTokenKeyAndSle(
ReadView const& view,
jtx::Account const& account,
std::string const& uri)
{
auto const k = keylet::uritoken(account, Blob(uri.begin(), uri.end()));
return {k.key, view.read(k)};
}
static STAmount
limitAmount(
jtx::Env const& env,
jtx::Account const& account,
jtx::Account const& gw,
jtx::IOU const& iou)
{
auto const aHigh = account.id() > gw.id();
auto const sle = env.le(keylet::line(account, gw, iou.currency));
if (sle && sle->isFieldPresent(aHigh ? sfLowLimit : sfHighLimit))
return (*sle)[aHigh ? sfLowLimit : sfHighLimit];
return STAmount(iou, 0);
}
static AccountID
tokenOwner(ReadView const& view, uint256 const& id)
{
auto const slep = view.read({ltURI_TOKEN, id});
if (!slep)
return AccountID();
return slep->getAccountID(sfOwner);
}
static uint256
tokenDigest(ReadView const& view, uint256 const& id)
{
auto const slep = view.read({ltURI_TOKEN, id});
if (!slep)
return uint256{0};
return slep->getFieldH256(sfDigest);
}
static STAmount
tokenAmount(ReadView const& view, uint256 const& id)
{
auto const slep = view.read({ltURI_TOKEN, id});
if (!slep)
return XRPAmount{-1};
if (slep->getFieldAmount(sfAmount))
return (*slep)[sfAmount];
return XRPAmount{-1};
}
static STAmount
lineBalance(
jtx::Env const& env,
jtx::Account const& account,
jtx::Account const& gw,
jtx::IOU const& iou)
{
auto const sle = env.le(keylet::line(account, gw, iou.currency));
if (sle && sle->isFieldPresent(sfBalance))
return (*sle)[sfBalance];
return STAmount(iou, 0);
}
static Json::Value
mint(jtx::Account const& account, std::string const& uri)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::URITokenMint;
jv[jss::Account] = account.human();
jv[sfURI.jsonName] = strHex(uri);
return jv;
}
static Json::Value
burn(jtx::Account const& account, std::string const& id)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::URITokenBurn;
jv[jss::Account] = account.human();
jv[sfURITokenID.jsonName] = id;
return jv;
}
static Json::Value
buy(jtx::Account const& account,
std::string const& id,
STAmount const& amount)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::URITokenBuy;
jv[jss::Account] = account.human();
jv[jss::Amount] = amount.getJson(JsonOptions::none);
jv[sfURITokenID.jsonName] = id;
return jv;
}
static Json::Value
sell(
jtx::Account const& account,
std::string const& id,
STAmount const& amount)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::URITokenCreateSellOffer;
jv[jss::Account] = account.human();
jv[jss::Amount] = amount.getJson(JsonOptions::none);
jv[sfURITokenID.jsonName] = id;
return jv;
}
static Json::Value
clear(jtx::Account const& account, std::string const& id)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::URITokenCancelSellOffer;
jv[jss::Account] = account.human();
jv[sfURITokenID.jsonName] = id;
return jv;
}
void
testEnabled(FeatureBitset features)
{
testcase("enabled");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
auto const alice = Account("alice");
auto const bob = Account("bob");
for (bool const withURIToken : {false, true})
{
// If the URIToken amendment is not enabled, you should not be able
// to mint, burn, buy, sell or clear uri tokens.
auto const amend =
withURIToken ? features : features - featureURIToken;
Env env{*this, amend};
env.fund(XRP(1000), alice, bob);
env.close();
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
auto const txResult =
withURIToken ? ter(tesSUCCESS) : ter(temDISABLED);
auto const ownerDir = withURIToken ? 1 : 0;
// MINT
env(mint(alice, uri), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == ownerDir);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0);
// SELL
env(sell(alice, id, XRP(10)), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == ownerDir);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0);
// BUY
env(buy(bob, id, XRP(10)), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == ownerDir);
// SELL
env(sell(bob, id, XRP(10)), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == ownerDir);
// CLEAR
env(clear(bob, id), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == ownerDir);
// BURN
env(burn(bob, id), txResult);
env.close();
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0);
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0);
}
}
void
testMintInvalid(FeatureBitset features)
{
testcase("mint_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
auto const USD = gw["USD"];
env.fund(XRP(200), gw, alice);
env.close();
env.trust(USD(100'000), alice);
env.close();
env(pay(gw, alice, USD(10'000)));
std::string const uri(2, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
//----------------------------------------------------------------------
// preflight
{
// temBAD_AMOUNT - !isLegalNet - !value.native()
// temBAD_AMOUNT - !isLegalNet - value.mantissa() <= cMaxNativeN
// temBAD_AMOUNT - negative XRP
Json::Value negXrpTx = mint(alice, uri);
negXrpTx[jss::Amount] = XRP(-100).value().getJson(JsonOptions::none);
env(negXrpTx, ter(temBAD_AMOUNT));
env.close();
IOU const BAD{bob, badCurrency()};
// temBAD_CURRENCY - bad currency
Json::Value badXrpTx = mint(alice, uri);
badXrpTx[jss::Amount] = BAD(100).value().getJson(JsonOptions::none);
env(badXrpTx, ter(temBAD_CURRENCY));
env.close();
// temMALFORMED - XRP must cannot be 0 without sfDestination
Json::Value nodestTx = mint(alice, uri);
nodestTx[jss::Amount] = XRP(0).value().getJson(JsonOptions::none);
env(nodestTx, ter(temMALFORMED));
env.close();
// temREDUNDANT - sfDestination cannot be sfAccount
Json::Value destSameTx = mint(alice, uri);
destSameTx[jss::Destination] = alice.human();
destSameTx[jss::Amount] = XRP(0).value().getJson(JsonOptions::none);
env(destSameTx, ter(temREDUNDANT));
env.close();
// temINVALID_FLAG - invalid flags
env(mint(alice, uri), txflags(tfAllowXRP), ter(temINVALID_FLAG));
env.close();
// temMALFORMED - no uri & no flags
std::string const nouri(0, '?');
env(mint(alice, nouri), ter(temMALFORMED));
env.close();
// temMALFORMED - bad uri 257 len
std::string const longuri(maxTokenURILength + 1, '?');
env(mint(alice, longuri), ter(temMALFORMED));
env.close();
}
//----------------------------------------------------------------------
// preclaim
{
env.fund(XRP(251), bob);
env.close();
auto const btid = tokenid(bob, uri);
std::string const bhexid{strHex(btid)};
// tecDUPLICATE - duplicate uri token
env(mint(bob, uri), txflags(tfBurnable));
env(mint(bob, uri), ter(tecDUPLICATE));
env(burn(bob, bhexid));
env.close();
}
//----------------------------------------------------------------------
// doApply
{
// tecINSUFFICIENT_RESERVE - out of xrp
env(mint(alice, uri), ter(tecINSUFFICIENT_RESERVE));
env.close();
// tecDIR_FULL - directory full
}
}
void
testBurnInvalid(FeatureBitset features)
{
testcase("burn_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const dave = Account("dave");
env.fund(XRP(1000), alice, bob, carol);
env.close();
//----------------------------------------------------------------------
// preflight
// temDISABLED - ignore
// mint non burnable token
std::string const uri(2, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env.close();
// temINVALID_FLAG - invalid flags
env(burn(alice, hexid), txflags(tfAllowXRP), ter(temINVALID_FLAG));
env.close();
//----------------------------------------------------------------------
// preclaim
// tecNO_ENTRY - no exists item
std::string const neuri(3, '?');
auto const netid = tokenid(alice, neuri);
std::string const hexneuri{strHex(netid)};
env(burn(alice, hexneuri), ter(tecNO_ENTRY));
env.close();
// tecNO_ENTRY - no owner exists
// impossible test
// tecNO_PERMISSION - not owner and not (issuer/burnable)
env(burn(bob, hexid), ter(tecNO_PERMISSION));
env.close();
//----------------------------------------------------------------------
// doApply
// tecNO_PERMISSION - no permission
env(burn(carol, hexid), ter(tecNO_PERMISSION));
env.close();
// tefBAD_LEDGER - could not remove object
}
void
testSellInvalid(FeatureBitset features)
{
testcase("sell_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto const ngw = Account{"ngateway"};
auto const USD = gw["USD"];
auto const NUSD = ngw["USD"];
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// mint token
std::string const uri(2, '?');
std::string const id{strHex(tokenid(alice, uri))};
env(mint(alice, uri));
env.close();
//----------------------------------------------------------------------
// preflight
// temBAD_AMOUNT - bad xrp/amount
env(sell(alice, id, XRP(-1)), ter(temBAD_AMOUNT));
env.close();
// temBAD_AMOUNT - bad ft/amount
env(sell(alice, id, USD(-1)), ter(temBAD_AMOUNT));
env.close();
// temBAD_CURRENCY - bad currency
IOU const BAD{gw, badCurrency()};
env(sell(alice, id, BAD(10)), ter(temBAD_CURRENCY));
//----------------------------------------------------------------------
// preclaim
// tecNO_PERMISSION - invalid account
env(sell(bob, id, USD(10)), ter(tecNO_PERMISSION));
env.close();
// tecNO_ISSUER - invalid issuer
env(sell(alice, id, NUSD(10)), ter(tecNO_ISSUER));
env.close();
//----------------------------------------------------------------------
// doApply
// tecNO_PERMISSION - invalid account
env(sell(bob, id, USD(10)), ter(tecNO_PERMISSION));
env.close();
}
void
testBuyInvalid(FeatureBitset features)
{
testcase("buy_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const dave = Account("dave");
auto const echo = Account("echo");
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
env.fund(XRP(1000), alice, bob, carol, gw);
env.trust(USD(100000), alice, bob, carol);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
// mint token
std::string const uri(2, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env.close();
//----------------------------------------------------------------------
// preclaim
// tecNO_PERMISSION - not for sale
env(buy(bob, hexid, USD(10)), ter(tecNO_PERMISSION));
env.close();
// set sell
env(sell(alice, hexid, USD(10)), jtx::token::destination(bob));
env.close();
// tecNO_PERMISSION - for sale to dest, you are not dest
env(buy(carol, hexid, USD(10)), ter(tecNO_PERMISSION));
env.close();
// temBAD_CURRENCY - invalid buy sell amounts
env(buy(bob, hexid, EUR(10)), ter(temBAD_CURRENCY));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient buy offer amount
env(buy(bob, hexid, USD(9)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
env(clear(alice, hexid));
env(sell(alice, hexid, XRP(10000)));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient xrp - fees
env(buy(bob, hexid, XRP(1000)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
// clear sell and reset new sell
env(clear(alice, hexid));
env(sell(alice, hexid, USD(10000)));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient amount
env(buy(bob, hexid, USD(1000)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
//----------------------------------------------------------------------
// doApply
// clear sell
env(clear(alice, hexid));
env.close();
// tecNO_PERMISSION - not listed
env(buy(bob, hexid, USD(10)), ter(tecNO_PERMISSION));
env.close();
// set sell
env(sell(alice, hexid, USD(10)), jtx::token::destination(bob));
env.close();
// tecNO_PERMISSION - for sale to dest, you are not dest
env(buy(carol, hexid, USD(10)), ter(tecNO_PERMISSION));
env.close();
// temBAD_CURRENCY - invalid buy sell amounts
env(buy(bob, hexid, EUR(10)), ter(temBAD_CURRENCY));
env.close();
// clear sell and set xrp sell
env(clear(alice, hexid));
env(sell(alice, hexid, XRP(1000)));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient xrp sent
env(buy(bob, hexid, XRP(900)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient xrp - fees
env(buy(bob, hexid, XRP(1000)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// clear sell and set usd sell
env(clear(alice, hexid));
env(sell(alice, hexid, USD(1000)));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient amount sent
env(buy(bob, hexid, USD(900)), ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient amount sent
env(buy(bob, hexid, USD(10000)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// tecNO_LINE_INSUF_RESERVE - insuficient xrp to create line
{
// fund dave 251 xrp (not enough for line reserve)
env.fund(XRP(251), echo);
env.fund(XRP(301), dave);
env.close();
env.trust(USD(100000), dave);
env.close();
env(pay(gw, dave, USD(1000)));
env.close();
// mint token
std::string const uri(3, '?');
auto const tid = tokenid(echo, uri);
std::string const hexid{strHex(tid)};
env(mint(echo, uri));
env(sell(echo, hexid, USD(1)));
env.close();
// tecNO_LINE_INSUF_RESERVE - insuficient xrp to create line
env(buy(dave, hexid, USD(1)), ter(tecNO_LINE_INSUF_RESERVE));
env.close();
}
// tecDIR_FULL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tefBAD_LEDGER - unknown how to test/handle
// tefBAD_LEDGER - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
// tecINTERNAL - unknown how to test/handle
}
void
testClearInvalid(FeatureBitset features)
{
testcase("clear_invalid");
using namespace jtx;
using namespace std::literals::chrono_literals;
// setup env
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 EUR = gw["EUR"];
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// mint token
std::string const uri(2, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env.close();
//----------------------------------------------------------------------
// operator preflight
// temDISABLED
// temINVALID_FLAG - invalid flag
env(clear(alice, hexid), txflags(tfAllowXRP), ter(temINVALID_FLAG));
env.close();
//----------------------------------------------------------------------
// preclaim
// tecNO_PERMISSION - not your uritoken
env(clear(bob, hexid), ter(tecNO_PERMISSION));
env.close();
}
void
testMintValid(FeatureBitset features)
{
testcase("mint");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
// setup env
Env env{*this, features};
env.fund(XRP(1000), alice, bob);
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
std::string const digestval =
"C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5CCC";
// has digest - has uri - no flags
{
// mint
env(mint(alice, uri), json(sfDigest.fieldName, digestval));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(
to_string(tokenDigest(*env.current(), tid)) == digestval);
// cleanup
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// has digest - has uri - burnable flag
{
// mint
env(mint(alice, uri),
txflags(tfBurnable),
json(sfDigest.fieldName, digestval));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(
to_string(tokenDigest(*env.current(), tid)) == digestval);
// cleanup
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// has uri - no flags
{
// mint
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// cleanup
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// has uri - burnable flag
{
// mint
env(mint(alice, uri), txflags(tfBurnable));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// cleanup
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// 0 amount and destination
{
Json::Value tx = mint(alice, uri);
tx[jss::Destination] = bob.human();
tx[jss::Amount] = XRP(0).value().getJson(JsonOptions::none);
// mint
env(tx, txflags(tfBurnable), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// buy
env(buy(bob, hexid, XRP(0)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// cleanup
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// has amount and destination
{
Json::Value tx = mint(alice, uri);
tx[jss::Destination] = bob.human();
tx[jss::Amount] = XRP(10).value().getJson(JsonOptions::none);
// mint
env(tx, txflags(tfBurnable), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// buy
env(buy(bob, hexid, XRP(10)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// cleanup
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// has amount and no destination
{
Json::Value tx = mint(alice, uri);
tx[jss::Amount] = XRP(10).value().getJson(JsonOptions::none);
// mint
env(tx, txflags(tfBurnable), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// buy
env(buy(bob, hexid, XRP(10)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// cleanup
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
}
void
testBurnValid(FeatureBitset features)
{
testcase("burn");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
// setup env
Env env{*this, features};
env.fund(XRP(1000), alice, bob);
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
// issuer can burn
{
// alice mints
env(mint(alice, uri), txflags(tfBurnable));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, XRP(1)));
env.close();
// bob buys
env(buy(bob, hexid, XRP(1)));
env.close();
// alice burns
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// issuer cannot burn
{
// alice mints
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, XRP(1)));
env.close();
// bob buys
env(buy(bob, hexid, XRP(1)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// alice tries to burn
env(burn(alice, hexid), ter(tecNO_PERMISSION));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// burn for test reset
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// owner can burn
{
// alice mints
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, XRP(1)));
env.close();
// bob buys
env(buy(bob, hexid, XRP(1)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
// bob burns
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
}
void
testBuyValid(FeatureBitset features)
{
testcase("buy");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
// setup env
env.fund(XRP(1000), alice, bob, gw);
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
auto const feeDrops = env.current()->fees().base;
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
// bob can buy with XRP
{
// alice mints
const auto delta = XRP(10);
auto preAlice = env.balance(alice);
auto preBob = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(env.balance(alice) == preAlice - (1 * feeDrops));
// alice sells
env(sell(alice, hexid, delta));
BEAST_EXPECT(env.balance(alice) == preAlice - (2 * feeDrops));
env.close();
// bob buys
env(buy(bob, hexid, delta));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(
env.balance(alice) == preAlice + delta - (2 * feeDrops));
BEAST_EXPECT(env.balance(bob) == preBob - delta - feeDrops);
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// bob can buy with USD
{
// alice mints
const auto delta = USD(10);
auto preAlice = env.balance(alice, USD.issue());
auto preAliceXrp = env.balance(alice);
auto preBob = env.balance(bob, USD.issue());
auto preBobXrp = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(env.balance(alice) == preAliceXrp - (1 * feeDrops));
// alice sells
env(sell(alice, hexid, delta));
BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops));
BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice);
env.close();
// bob buys
env(buy(bob, hexid, delta));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta);
BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
BEAST_EXPECT(env.balance(bob) == preBobXrp - (1 * feeDrops));
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
}
void
testSellValid(FeatureBitset features)
{
testcase("sell");
using namespace jtx;
using namespace std::literals::chrono_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"];
// setup env
Env env{*this, features};
env.fund(XRP(1000), alice, bob, carol, gw);
env.close();
env.trust(USD(100000), alice, bob, carol);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
auto const feeDrops = env.current()->fees().base;
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
// alice can sell with XRP
{
// alice mints
const auto delta = XRP(10);
auto preAlice = env.balance(alice);
auto preBob = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// alice clears and sells again at a higher price
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
env(sell(alice, hexid, XRP(11)));
env.close();
BEAST_EXPECT(XRP(11) == tokenAmount(*env.current(), tid));
// bob tries to buy at original price and fails
env(buy(bob, hexid, delta), ter(tecINSUFFICIENT_PAYMENT));
// bob buys at higher price
env(buy(bob, hexid, XRP(11)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(
env.balance(alice) == preAlice + XRP(11) - (4 * feeDrops));
BEAST_EXPECT(env.balance(bob) == preBob - XRP(11) - (2 * feeDrops));
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// alice can sell with XRP and dest
{
// alice mints
const auto delta = XRP(10);
auto preAlice = env.balance(alice);
auto preBob = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta), jtx::token::destination(bob));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// carol tries to buy but cannot
env(buy(carol, hexid, delta), ter(tecNO_PERMISSION));
env.close();
// bob buys
env(buy(bob, hexid, delta));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(
env.balance(alice) == preAlice + delta - (2 * feeDrops));
BEAST_EXPECT(env.balance(bob) == preBob - delta - (1 * feeDrops));
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// alice can sell with USD
{
// alice mints
const auto delta = USD(10);
auto preAlice = env.balance(alice, USD.issue());
auto preAliceXrp = env.balance(alice);
auto preBob = env.balance(bob, USD.issue());
auto preBobXrp = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// alice clears and sells again at a higher price
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
env(sell(alice, hexid, USD(11)));
env.close();
BEAST_EXPECT(USD(11) == tokenAmount(*env.current(), tid));
// bob tries to buy at original price and fails
env(buy(bob, hexid, delta), ter(tecINSUFFICIENT_PAYMENT));
// bob buys at higher price
env(buy(bob, hexid, USD(11)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + USD(11));
BEAST_EXPECT(env.balance(alice) == preAliceXrp - (4 * feeDrops));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - USD(11));
BEAST_EXPECT(env.balance(bob) == preBobXrp - (2 * feeDrops));
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
// alice can sell with USD and dest
{
// alice mints
const auto delta = USD(10);
auto preAlice = env.balance(alice, USD.issue());
auto preAliceXrp = env.balance(alice);
auto preBob = env.balance(bob, USD.issue());
auto preBobXrp = env.balance(bob);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta), jtx::token::destination(bob));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// carol tries to buy but cannot
env(buy(carol, hexid, delta), ter(tecNO_PERMISSION));
env.close();
// bob buys
env(buy(bob, hexid, delta));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta);
BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
BEAST_EXPECT(env.balance(bob) == preBobXrp - (1 * feeDrops));
BEAST_EXPECT(bob.id() == tokenOwner(*env.current(), tid));
// bob burns to reset tests
env(burn(bob, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
}
}
void
testClearValid(FeatureBitset features)
{
testcase("clear");
using namespace jtx;
using namespace std::literals::chrono_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"];
// setup env
Env env{*this, features};
env.fund(XRP(1000), alice, bob, carol, gw);
env.close();
env.trust(USD(100000), alice, bob, carol);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
// auto const feeDrops = env.current()->fees().base;
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
// alice can clear / reset XRP amount
{
// alice mints
const auto delta = XRP(10);
auto preAlice = env.balance(alice);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// alice clears the sell amount
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
// alice sets sell for higher amount
env(sell(alice, hexid, XRP(11)));
env.close();
BEAST_EXPECT(XRP(11) == tokenAmount(*env.current(), tid));
// alice clears the sell amount
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
// alice burns to reset tests
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
// alice can clear / reset USD amount
{
// alice mints
const auto delta = USD(10);
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// alice sells
env(sell(alice, hexid, delta));
env.close();
BEAST_EXPECT(delta == tokenAmount(*env.current(), tid));
// alice clears the sell amount
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
// alice sets sell for higher amount
env(sell(alice, hexid, USD(11)));
env.close();
BEAST_EXPECT(USD(11) == tokenAmount(*env.current(), tid));
// alice clears the sell amount
env(clear(alice, hexid));
BEAST_EXPECT(XRPAmount{-1} == tokenAmount(*env.current(), tid));
// alice burns to reset tests
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
}
}
void
testMetaAndOwnership(FeatureBitset features)
{
testcase("metadata_and_onwnership");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto const USD = gw["USD"];
std::string const uri(maxTokenURILength, '?');
uint256 const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
{
// Test without adding the uritoken to the recipient's owner
// directory
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
env(mint(alice, uri));
env(sell(alice, hexid, USD(10)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1);
// // alice sets the sell offer
// // bob sets the buy offer
env(buy(bob, hexid, USD(10)));
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1);
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2);
}
{
// Test with adding the uritoken to the recipient's owner
// directory
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
env(mint(alice, uri));
env(sell(alice, hexid, USD(10)));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 2);
BEAST_EXPECT(!inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1);
// // alice sets the sell offer
// // bob sets the buy offer
env(buy(bob, hexid, USD(10)));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1);
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2);
}
}
// TODO: THIS TEST IS NOT COMPLETE
void
testAccountDelete(FeatureBitset features)
{
testcase("account_delete");
using namespace test::jtx;
using namespace std::literals::chrono_literals;
auto rmAccount = [this](
Env& env,
Account const& toRm,
Account const& dst,
TER expectedTer = tesSUCCESS) {
// only allow an account to be deleted if the account's sequence
// number is at least 256 less than the current ledger sequence
for (auto minRmSeq = env.seq(toRm) + 257;
env.current()->seq() < minRmSeq;
env.close())
{
}
env(acctdelete(toRm, dst),
fee(drops(env.current()->fees().increment)),
ter(expectedTer));
env.close();
this->BEAST_EXPECT(
isTesSuccess(expectedTer) ==
!env.closed()->exists(keylet::account(toRm.id())));
};
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"];
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
{
Env env{*this, features};
env.fund(XRP(1000), alice, bob, carol, gw);
env.close();
env.trust(USD(100000), alice, bob, carol);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
// mint a uritoken from alice
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
env(sell(alice, hexid, USD(10)));
env.close();
// alice has trustline + mint + sell
rmAccount(env, alice, bob, tecHAS_OBLIGATIONS);
env(clear(alice, hexid));
env(burn(alice, hexid));
env.close();
BEAST_EXPECT(!inOwnerDir(*env.current(), alice, tid));
// alice still has a trustline
rmAccount(env, alice, bob, tecHAS_OBLIGATIONS);
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1);
// drain pay all back and drain trustlin
env.trust(USD(0), alice);
env(pay(alice, gw, env.balance(alice, USD.issue())));
BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0);
// alice can delete account
rmAccount(env, alice, bob);
// buy should fail if the uri token was removed
auto preBob = env.balance(bob, USD.issue());
env(buy(bob, hexid, USD(10)), ter(tecNO_ENTRY));
env.close();
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob);
// bob can mint same exact token because alice burned it
env(mint(bob, uri));
// need to use bobs account for tokenid
auto const btid = tokenid(bob, uri);
BEAST_EXPECT(inOwnerDir(*env.current(), bob, btid));
}
}
void
testTickets(FeatureBitset features)
{
testcase("tickets");
using namespace jtx;
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account{"gateway"};
auto USD = gw["USD"];
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// alice and bob grab enough tickets for all of the following
// transactions. Note that once the tickets are acquired alice's
// and bob's account sequence numbers should not advance.
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
env(ticket::create(alice, 10));
std::uint32_t const aliceSeq{env.seq(alice)};
std::uint32_t bobTicketSeq{env.seq(bob) + 1};
env(ticket::create(bob, 10));
std::uint32_t const bobSeq{env.seq(bob)};
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri), ticket::use(aliceTicketSeq++));
env(sell(alice, hexid, USD(1000)), ticket::use(aliceTicketSeq++));
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
BEAST_EXPECT(env.seq(alice) == aliceSeq);
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
// A transaction that generates a tec still consumes its ticket.
env(buy(bob, hexid, USD(1500)),
ticket::use(bobTicketSeq++),
ter(tecINSUFFICIENT_FUNDS));
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
env(buy(bob, hexid, USD(1000)), ticket::use(bobTicketSeq++));
env.require(tickets(bob, env.seq(bob) - bobTicketSeq));
BEAST_EXPECT(env.seq(bob) == bobSeq);
BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid));
}
void
testRippleState(FeatureBitset features)
{
testcase("ripple_state");
using namespace test::jtx;
using namespace std::literals;
//
// USE lineBalance(env, ...) over env.balance(...)
// I did this to check the exact sign "-/+"
//
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 USD = t.gw["USD"];
env.fund(XRP(5000), t.src, t.dst, t.gw);
env.close();
if (t.hasTrustline)
env.trust(USD(100000), t.src, t.dst);
else
env.trust(USD(100000), t.src);
env.close();
env(pay(t.gw, t.src, USD(10000)));
if (t.hasTrustline)
env(pay(t.gw, t.dst, USD(10000)));
env.close();
// dst can create uritoken
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(t.dst, uri))};
env(mint(t.dst, uri));
env.close();
// dst can create sell
auto const delta = USD(1000);
auto const preSrc = lineBalance(env, t.src, t.gw, USD);
auto const preDst = lineBalance(env, t.dst, t.gw, USD);
env(sell(t.dst, id, delta));
env.close();
BEAST_EXPECT(preDst == preDst);
// src can create buy
env(buy(t.src, id, delta));
env.close();
BEAST_EXPECT(
lineBalance(env, t.src, t.gw, USD) ==
(t.negative ? (preSrc + delta) : (preSrc - delta)));
BEAST_EXPECT(
lineBalance(env, t.dst, t.gw, USD) ==
(t.negative ? (preDst - delta) : (preDst + delta)));
}
}
void
testGateway(FeatureBitset features)
{
testcase("gateway");
using namespace test::jtx;
using namespace std::literals;
struct TestAccountData
{
Account acct;
Account gw;
bool hasTrustline;
bool negative;
};
std::array<TestAccountData, 4> tests = {{
// acct no trustline
// acct > issuer
{Account("alice2"), Account{"gw0"}, false, true},
// acct < issuer
{Account("carol0"), Account{"gw1"}, false, false},
// acct has trustline
// acct > issuer
{Account("alice2"), Account{"gw0"}, true, true},
// acct < issuer
{Account("carol0"), Account{"gw1"}, true, false},
}};
// test gateway is buyer
for (auto const& t : tests)
{
Env env{*this, features};
auto const USD = t.gw["USD"];
env.fund(XRP(5000), t.acct, t.gw);
env.close();
if (t.hasTrustline)
env.trust(USD(100000), t.acct);
env.close();
if (t.hasTrustline)
env(pay(t.gw, t.acct, USD(10000)));
env.close();
// acct can create uritoken
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(t.acct, uri))};
env(mint(t.acct, uri));
env.close();
// acct can create sell w/out token
auto const delta = USD(1000);
auto const preAcct = lineBalance(env, t.acct, t.gw, USD);
auto const preGw = lineBalance(env, t.gw, t.acct, USD);
env(sell(t.acct, id, delta));
env.close();
auto const preAmount = t.hasTrustline ? 10000 : 0;
BEAST_EXPECT(
preAcct == (t.negative ? -USD(preAmount) : USD(preAmount)));
// gw can create buy
env(buy(t.gw, id, delta));
env.close();
auto const postAmount = t.hasTrustline ? 11000 : 1000;
BEAST_EXPECT(
lineBalance(env, t.acct, t.gw, USD) ==
(t.negative ? -USD(postAmount) : USD(postAmount)));
BEAST_EXPECT(
lineBalance(env, t.gw, t.acct, USD) ==
(t.negative ? -USD(postAmount) : USD(postAmount)));
}
// test gateway is seller
// ignore hasTrustline
for (auto const& t : tests)
{
Env env{*this, features};
auto const USD = t.gw["USD"];
env.fund(XRP(5000), t.acct, t.gw);
env.close();
env.trust(USD(100000), t.acct);
env.close();
env(pay(t.gw, t.acct, USD(10000)));
env.close();
// gw can create uritoken
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(t.gw, uri))};
env(mint(t.gw, uri));
env.close();
// gw can create sell w/out token
auto const delta = USD(1000);
auto const preAcct = lineBalance(env, t.acct, t.gw, USD);
auto const preGw = lineBalance(env, t.gw, t.acct, USD);
env(sell(t.gw, id, delta));
env.close();
auto const preAmount = 10000;
BEAST_EXPECT(
preAcct == (t.negative ? -USD(preAmount) : USD(preAmount)));
// acct can create buy
env(buy(t.acct, id, delta));
env.close();
auto const postAmount = 9000;
BEAST_EXPECT(
lineBalance(env, t.acct, t.gw, USD) ==
(t.negative ? -USD(postAmount) : USD(postAmount)));
BEAST_EXPECT(
lineBalance(env, t.gw, t.acct, USD) ==
(t.negative ? -USD(postAmount) : USD(postAmount)));
}
}
void
testRequireAuth(FeatureBitset features)
{
testcase("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"];
// test asfRequireAuth
{
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env(fset(gw, asfRequireAuth));
env.close();
env(trust(gw, bobUSD(10000)), txflags(tfSetfAuth));
env(trust(bob, USD(10000)));
env.close();
env(pay(gw, bob, USD(1000)));
env.close();
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env(sell(alice, hexid, USD(10)));
env.close();
// bob cannot buy because alice's trustline is not authorized
// all parties must be authorized
env(buy(bob, hexid, USD(10)), ter(tecNO_AUTH));
env.close();
env(trust(gw, aliceUSD(10000)), txflags(tfSetfAuth));
env(trust(alice, USD(10000)));
env.close();
env(pay(gw, alice, USD(1000)));
env.close();
// bob can now buy because alice's trustline is authorized
env(buy(bob, hexid, USD(10)));
env.close();
}
}
void
testFreeze(FeatureBitset features)
{
testcase("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
{
// setup env
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
env(fset(gw, asfGlobalFreeze));
env.close();
// setup mint
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env(sell(alice, hexid, USD(10)));
env.close();
// bob cannot buy
env(buy(bob, hexid, USD(10)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// clear global freeze
env(fclear(gw, asfGlobalFreeze));
env.close();
// bob can buy
env(buy(bob, hexid, USD(10)));
env.close();
}
// test Individual Freeze
{
// Env Setup
Env env{*this, features};
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
// set freeze on alice trustline
env(trust(gw, USD(10000), bob, tfSetFreeze));
env.close();
// setup mint
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env(sell(alice, hexid, USD(10)));
env.close();
// buy uritoken fails - frozen trustline
env(buy(bob, hexid, USD(10)), ter(tecINSUFFICIENT_FUNDS));
env.close();
// clear freeze on alice trustline
env(trust(gw, USD(10000), bob, tfClearFreeze));
env.close();
// buy uri success
env(buy(bob, hexid, USD(10)));
env.close();
}
}
void
testTransferRate(FeatureBitset features)
{
testcase("transfer_rate");
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 transfer rate
{
Env env{*this, features};
env.fund(XRP(10000), alice, bob, gw);
env(rate(gw, 1.25));
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
env(pay(gw, bob, USD(1000)));
env.close();
auto const preBob = env.balance(bob, USD.issue());
// setup mint
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
auto const delta = USD(100);
env(mint(alice, uri));
env(sell(alice, id, delta));
env.close();
env(buy(bob, id, delta));
env.close();
BEAST_EXPECT(env.balance(alice, USD.issue()) == USD(1125));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
}
// test rate change
{
Env env{*this, features};
env.fund(XRP(10000), alice, bob, gw);
env(rate(gw, 1.25));
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(10000)));
env(pay(gw, bob, USD(10000)));
env.close();
// setup
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(tokenid(alice, uri))};
auto const delta = USD(100);
auto preBob = env.balance(bob, USD.issue());
// alice mints and sells
env(mint(alice, uri));
env(sell(alice, id, delta));
env.close();
// bob buys at higher rate and burns
env(buy(bob, id, delta));
env.close();
BEAST_EXPECT(env.balance(alice, USD.issue()) == USD(10125));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
env(burn(bob, id));
// issuer changes rate lower
env(rate(gw, 1.00));
env.close();
preBob = env.balance(bob, USD.issue());
// alice mints and sells
env(mint(alice, uri));
env(sell(alice, id, delta));
env.close();
// bob buys at lower rate
env(buy(bob, id, delta));
env.close();
BEAST_EXPECT(env.balance(alice, USD.issue()) == USD(10225));
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
}
// test issuer doesnt pay own rate
{
Env env{*this, features};
env.fund(XRP(10000), alice, gw);
env(rate(gw, 1.25));
env.close();
env.trust(USD(100000), alice);
env.close();
env(pay(gw, alice, USD(10000)));
env.close();
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
auto const delta = USD(10);
auto const preAlice = env.balance(alice, USD.issue());
// alice mints
env(mint(alice, uri));
env.close();
// alice sells
env(sell(alice, hexid, delta));
env.close();
// gw buys
env(buy(gw, hexid, delta));
env.close();
BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta);
}
}
void
testDisallowXRP(FeatureBitset features)
{
// auth amount defaults to balance if not present
testcase("disallow_xrp");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
{
// Create a channel where src/dst disallows XRP
// Ignore that flag, since it's just advisory.
Env env(*this, features);
env.fund(XRP(10000), alice, bob);
env(fset(alice, asfDisallowXRP));
env(fset(bob, asfDisallowXRP));
env.close();
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
// alice mints
env(mint(alice, uri));
env.close();
// alice sells
env(sell(alice, hexid, XRP(10)));
env.close();
// bob buys
env(buy(bob, hexid, XRP(10)));
env.close();
}
}
void
testLimitAmount(FeatureBitset features)
{
testcase("limit_amount");
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"];
{
Env env{*this, features};
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env.trust(USD(1000), bob);
env.trust(USD(1000), carol);
env.close();
env(pay(gw, bob, USD(1000)));
env(pay(gw, carol, USD(1000)));
env.close();
std::string const uri(maxTokenURILength, '?');
auto const tid = tokenid(alice, uri);
std::string const hexid{strHex(tid)};
env(mint(alice, uri));
env.close();
BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid));
env(sell(alice, hexid, USD(10)));
env.close();
auto preLimit = limitAmount(env, alice, gw, USD);
BEAST_EXPECT(preLimit == USD(0));
env(buy(bob, hexid, USD(10)));
env.close();
auto const postLimit = limitAmount(env, bob, gw, USD);
BEAST_EXPECT(postLimit == preLimit);
env(pay(alice, carol, USD(1)), ter(tecPATH_DRY));
}
}
void
testURIUTF8(FeatureBitset features)
{
// https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
testcase("uri_utf8");
using namespace test::jtx;
using namespace std::literals;
auto const alice = Account("alice");
auto const bob = Account("bob");
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
std::string uri = "";
// test utf-8 success
{
// case: kosme
uri = "κόσμε";
env(mint(alice, uri));
// case: single ASCII character
uri = "a";
env(mint(alice, uri));
// case: single non-ASCII character
uri = "é";
env(mint(alice, uri));
// case: valid multi-byte UTF-8 sequence
uri = "";
env(mint(alice, uri));
// case: ipfs cid
uri = "QmaCtDKZFVvvfufvbdy4estZbhQH7DXh16CTpv1howmBGy";
env(mint(alice, uri));
// case: empty ipfs cid url
uri = "ipfs://";
env(mint(alice, uri));
// case: ipfs cid url
uri = "ipfs://QmaCtDKZFVvvfufvbdy4estZbhQH7DXh16CTpv1howmBGy";
env(mint(alice, uri));
// case: ipfs metadata url
uri = "https://example.com/ipfs/";
env(mint(alice, uri));
// BOUNDRY - START
// ----------------------------------------------------------------
// case: 1 byte (U-00000000)
uri = "\x00";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 2 bytes (U-00000080)
uri = "\xC2\x80";
env(mint(alice, uri));
// case: 3 bytes (U-00000800)
uri = "\xE0\xA0\x80";
env(mint(alice, uri));
// case: 4 bytes (U-00010000)
uri = "\xF0\x90\x80\x80";
env(mint(alice, uri));
// case: 5 bytes (U-00200000)
uri = "\xF8\x88\x80\x80\x80";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 6 bytes (U-04000000)
uri = "\xFC\x84\x80\x80\x80\x80";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// BOUNDRY - END
// ----------------------------------------------------------------
// case: 1 byte (U-0000007F)
uri = "\x7F";
env(mint(alice, uri));
// case: 2 bytes (U-000007FF)
uri = "\xDF\xBF";
env(mint(alice, uri));
// case: 3 bytes (U-0000FFFF)
uri = "\xEF\xBF\xBF";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 4 bytes (U-001FFFFF)
uri = "\xF7\xBF\xBF\xBF";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 5 bytes (U-03FFFFFF)
uri = "\xFB\xBF\xBF\xBF\xBF";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 6 bytes (U-7FFFFFFF)
uri = "\xFD\xBF\xBF\xBF\xBF\xBF";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// // BOUNDRY - OTHER
// ----------------------------------------------------------------
// case: 1 bytes (U-0000D7FF)
uri = "\xD7\xFF";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 2 bytes (U-0000E000)
uri = "\xEE\x80\x80";
env(mint(alice, uri));
// case: 3 bytes (U-0000FFFD)
uri = "\xEF\xBF\xBD";
env(mint(alice, uri));
// // case: 4 bytes (U-0010FFFF)
uri = "\xF4\x8F\xBF\xBF";
env(mint(alice, uri));
// // case: 4 bytes (U-00110000)
uri = "\xF4\x90\x80\x80";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
}
// test utf8 malformed
{
// MALFORMED - END
// ----------------------------------------------------------------
// First continuation byte 0x80:
uri = "\x80";
env(mint(alice, uri), ter(temMALFORMED));
// Last continuation byte 0xbf
uri = "\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// 2 continuation bytes
uri = "<EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 3 continuation bytes
uri = "<EFBFBD><EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 4 continuation bytes
uri = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 5 continuation bytes
uri = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 6 continuation bytes
uri = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 7 continuation bytes
uri = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// Sequence of all 64 possible continuation bytes (0x80-0xbf)
uri =
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E"
"\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D"
"\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC"
"\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB"
"\xBC\xBD\xBE\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// TODO: REVIEW - THIS IS NOT THE CORRECT 32 byte sequence.
// All 32 first bytes of 2-byte sequences (0xc0-0xdf), each followed
// by a space character
// uri = "\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83
// \xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 \xE0\x80\x88
// \xE0\x80\x89 \xE0\x80\x8A \xE0\x80\x8B \xE0\x80\x8C \xE0\x80\x8D
// \xE0\x80\x8E \xE0\x80\x8F \xE0\x80\x90"; env(mint(alice, uri),
// ter(temMALFORMED));
// All 16 first bytes of 3-byte sequences (0xe0-0xef), each followed
// by a space character
uri =
"\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83 "
"\xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 "
"\xE0\x80\x88 \xE0\x80\x89 \xE0\x80\x8A \xE0\x80\x8B "
"\xE0\x80\x8C \xE0\x80\x8D \xE0\x80\x8E \xE0\x80\x8F "
"\xE0\x80\x90";
env(mint(alice, uri), ter(temMALFORMED));
// All 8 first bytes of 4-byte sequences (0xf0-0xf7), each followed
// by a space character
uri =
"\xF0\x90\x80\x80 \xF0\x90\x80\x81 \xF0\x90\x80\x82 "
"\xF0\x90\x80\x83 \xF0\x90\x80\x84 \xF0\x90\x80\x85 "
"\xF0\x90\x80\x86 \xF0\x90\x80\x87";
env(mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// All 4 first bytes of 5-byte sequences (0xf8-0xfb), each followed
// by a space character
uri =
"\xF8\x88\x80\x80\x80 \xF8\x88\x80\x80\x81 "
"\xF8\x88\x80\x80\x82 \xF8\x88\x80\x80\x83";
env(mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD FAIL
// All 2 first bytes of 6-byte sequences (0xfc-0xfd), each followed
// by a space character
uri = "\xFC\x84\x80\x80\x80\x80 \xFC\x84\x80\x80\x80\x81";
env(mint(alice, uri), ter(temMALFORMED));
// Sequences with last continuation byte missing
// Concatenation of incomplete sequences
// Impossible bytes
uri = "\xFE";
env(mint(alice, uri), ter(temMALFORMED));
uri = "\xFF";
env(mint(alice, uri), ter(temMALFORMED));
uri = "\xFE\xFE\xFF\xFF";
env(mint(alice, uri), ter(temMALFORMED));
// Examples of an overlong ASCII character
// case: (U+002F)
uri = "\xC0\xAF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+002F)
uri = "\xE0\x80\xAF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+002F)
uri = "\xF0\x80\x80\xAF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+002F)
uri = "\xF0\x80\x80\x80\xAF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+002F)
uri = "\xF0\x80\x80\x80\x80\xAF";
env(mint(alice, uri), ter(temMALFORMED));
// Maximum overlong sequences
// case: (U+0000007F)
uri = "\xC1\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+000007FF)
uri = "\xE0\x9F\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+0000FFFF)
uri = "\xF0\x8F\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+001FFFFF)
uri = "\xF8\x87\xBF\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+03FFFFFF)
uri = "\xFC\x83\xBF\xBF\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// Overlong representation of the NUL character
// case: (U+0000)
uri = "\xC0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+0000)
uri = "\xC0\x80\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+0000)
uri = "\xC0\x80\x80\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+0000)
uri = "\xC0\x80\x80\x80\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+0000)
uri = "\xC0\x80\x80\x80\x80\x80";
env(mint(alice, uri), ter(temMALFORMED));
// Single UTF-16 surrogates
// case: (U+D800)
uri = "\xED\xA0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB7F)
uri = "\xED\xAD\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB80)
uri = "\xED\xAE\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DBFF)
uri = "\xED\xAF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DC00)
uri = "\xED\xB0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DF80)
uri = "\xED\xBE\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DFFF)
uri = "\xED\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// Paired UTF-16 surrogates
// case: (U+D800 U+DC00)
uri = "\xED\xA0\x80\xED\xB0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+D800 U+DFFF)
uri = "\xED\xA0\x80\xED\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB7F U+DC00)
uri = "\xED\xAD\xBF\xED\xB0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB7F U+DFFF)
uri = "\xED\xAD\xBF\xED\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB80 U+DC00)
uri = "\xED\xAE\x80\xED\xB0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DB80 U+DFFF)
uri = "\xED\xAE\x80\xED\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DBFF U+DC00)
uri = "\xED\xAF\xBF\xED\xB0\x80";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+DBFF U+DFFF)
uri = "\xED\xAF\xBF\xED\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
// problematic noncharacters in 16-bit applications
// case: (U+FFFE)
uri = "\xEF\xBF\xBE";
env(mint(alice, uri), ter(temMALFORMED));
// case: (U+FFFF)
uri = "\xEF\xBF\xBF";
env(mint(alice, uri), ter(temMALFORMED));
}
}
void
testWithFeats(FeatureBitset features)
{
testEnabled(features);
testMintInvalid(features);
testBurnInvalid(features);
testSellInvalid(features);
testBuyInvalid(features);
testClearInvalid(features);
testMintValid(features);
testBurnValid(features);
testBuyValid(features);
testSellValid(features);
testClearValid(features);
testMetaAndOwnership(features);
testAccountDelete(features);
testTickets(features);
testRippleState(features);
testGateway(features);
testRequireAuth(features);
testFreeze(features);
testTransferRate(features);
testDisallowXRP(features);
testLimitAmount(features);
testURIUTF8(features);
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeats(sa);
}
};
BEAST_DEFINE_TESTSUITE(URIToken, app, ripple);
} // namespace test
} // namespace ripple