Compare commits

...

9 Commits

Author SHA1 Message Date
Denis Angell
d24c134612 add emitted order test (#273) 2024-03-11 11:46:03 +11:00
Denis Angell
cdac69a111 Fix: URIToken Test (#254)
* update tests for fixXahauV1
2024-03-11 10:06:15 +11:00
Denis Angell
1500522427 fix tsh on nftoken (#269) 2024-03-11 09:38:45 +11:00
Denis Angell
75aba531d6 Amendment: featureRemit (#278)
* Remit Amendment

Co-authored-by: Denis Angell <dangell@transia.co>

Co-authored-by: Richard Holland <richard.holland@starstone.co.nz>
2024-03-11 09:29:39 +11:00
Wietse Wind
caa8b382d8 🤦 2024-02-22 23:28:19 +01:00
Wietse Wind
82e04073be Revert checkout v3 2024-02-14 15:21:43 +01:00
Wietse Wind
e1b78f9682 Do clean 2024-02-14 15:20:24 +01:00
Wietse Wind
901d1d4e8d Update checkout CI to v4 2024-02-14 15:17:45 +01:00
Wietse Wind
aca5241515 Build Container per user 2024-02-14 15:15:12 +01:00
29 changed files with 4519 additions and 42 deletions

View File

@@ -455,6 +455,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/GenesisMint.cpp
src/ripple/app/tx/impl/Import.cpp
src/ripple/app/tx/impl/Invoke.cpp
src/ripple/app/tx/impl/Remit.cpp
src/ripple/app/tx/impl/SetSignerList.cpp
src/ripple/app/tx/impl/SetTrust.cpp
src/ripple/app/tx/impl/SignerEntries.cpp
@@ -740,6 +741,7 @@ if (tests)
src/test/app/RCLCensorshipDetector_test.cpp
src/test/app/RCLValidations_test.cpp
src/test/app/Regression_test.cpp
src/test/app/Remit_test.cpp
src/test/app/SHAMapStore_test.cpp
src/test/app/SetAuth_test.cpp
src/test/app/SetRegularKey_test.cpp
@@ -889,6 +891,7 @@ if (tests)
src/test/jtx/impl/rate.cpp
src/test/jtx/impl/regkey.cpp
src/test/jtx/impl/reward.cpp
src/test/jtx/impl/remit.cpp
src/test/jtx/impl/sendmax.cpp
src/test/jtx/impl/seq.cpp
src/test/jtx/impl/sig.cpp

View File

@@ -31,6 +31,7 @@
#define ttURITOKEN_BUY 47
#define ttURITOKEN_CREATE_SELL_OFFER 48
#define ttURITOKEN_CANCEL_SELL_OFFER 49
#define ttREMIT 95
#define ttGENESIS_MINT 96
#define ttIMPORT 97
#define ttCLAIM_REWARD 98

View File

@@ -12,10 +12,13 @@ if [[ "$GITHUB_REPOSITORY" == "" ]]; then
BUILD_CORES=8
fi
CONTAINER_NAME=xahaud_cached_builder_$(echo "$GITHUB_ACTOR" | awk '{print tolower($0)}')
echo "-- BUILD CORES: $BUILD_CORES"
echo "-- GITHUB_REPOSITORY: $GITHUB_REPOSITORY"
echo "-- GITHUB_SHA: $GITHUB_SHA"
echo "-- GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER"
echo "-- CONTAINER_NAME: $CONTAINER_NAME"
which docker 2> /dev/null 2> /dev/null
if [ "$?" -eq "1" ]
@@ -31,13 +34,13 @@ then
exit 1
fi
STATIC_CONTAINER=$(docker ps -a | grep xahaud_cached_builder |wc -l)
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME |wc -l)
if [[ "$STATIC_CONTAINER" -gt "0" && "$GITHUB_REPOSITORY" != "" ]]; then
echo "Static container, execute in static container to have max. cache"
docker start xahaud_cached_builder
docker exec -i xahaud_cached_builder /hbb_exe/activate-exec bash -x /io/build-core.sh "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$BUILD_CORES" "$GITHUB_RUN_NUMBER"
docker stop xahaud_cached_builder
docker start $CONTAINER_NAME
docker exec -i $CONTAINER_NAME /hbb_exe/activate-exec bash -x /io/build-core.sh "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$BUILD_CORES" "$GITHUB_RUN_NUMBER"
docker stop $CONTAINER_NAME
else
echo "No static container, build on temp container"
rm -rf release-build;
@@ -50,10 +53,10 @@ else
else
# GH Action, runner
echo "GH Action, runner, clean & re-create create persistent container"
docker rm -f xahaud_cached_builder
docker run -di --user 0:$(id -g) --name xahaud_cached_builder -v /data/builds:/data/builds -v `pwd`:/io --network host ghcr.io/foobarwidget/holy-build-box-x64 /hbb_exe/activate-exec bash
docker exec -i xahaud_cached_builder /hbb_exe/activate-exec bash -x /io/build-full.sh "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$BUILD_CORES" "$GITHUB_RUN_NUMBER"
docker stop xahaud_cached_builder
docker rm -f $CONTAINER_NAME
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v `pwd`:/io --network host ghcr.io/foobarwidget/holy-build-box-x64 /hbb_exe/activate-exec bash
docker exec -i $CONTAINER_NAME /hbb_exe/activate-exec bash -x /io/build-full.sh "$GITHUB_REPOSITORY" "$GITHUB_SHA" "$BUILD_CORES" "$GITHUB_RUN_NUMBER"
docker stop $CONTAINER_NAME
fi
fi

View File

@@ -70,6 +70,45 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
switch (tt)
{
case ttREMIT: {
if (destAcc)
ADD_TSH(*destAcc, tshSTRONG);
if (tx.isFieldPresent(sfInform))
{
auto const inform = tx.getAccountID(sfInform);
if (*otxnAcc != inform && *destAcc != inform)
ADD_TSH(inform, tshWEAK);
}
if (tx.isFieldPresent(sfURITokenIDs))
{
STVector256 tokenIds = tx.getFieldV256(sfURITokenIDs);
for (uint256 const klRaw : tokenIds)
{
Keylet const id{ltURI_TOKEN, klRaw};
if (!rv.exists(id))
continue;
auto const ut = rv.read(id);
if (!ut ||
ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
continue;
auto const owner = ut->getAccountID(sfOwner);
auto const issuer = ut->getAccountID(sfIssuer);
if (issuer != owner && issuer != *destAcc)
{
ADD_TSH(
issuer,
(ut->getFlags() & lsfBurnable) ? tshSTRONG
: tshWEAK);
}
}
}
break;
}
case ttIMPORT: {
if (tx.isFieldPresent(sfIssuer))
ADD_TSH(tx.getAccountID(sfIssuer), fixV2 ? tshWEAK : tshSTRONG);
@@ -256,14 +295,14 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
{
ADD_TSH(bo->getAccountID(sfOwner), tshSTRONG);
if (bo->isFieldPresent(sfDestination))
ADD_TSH(bo->getAccountID(sfDestination), tshWEAK);
ADD_TSH(bo->getAccountID(sfDestination), tshSTRONG);
}
if (so)
{
ADD_TSH(so->getAccountID(sfOwner), tshSTRONG);
if (so->isFieldPresent(sfDestination))
ADD_TSH(so->getAccountID(sfDestination), tshWEAK);
ADD_TSH(so->getAccountID(sfDestination), tshSTRONG);
}
break;
@@ -279,7 +318,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
auto const offer = getNFTOffer(offerID, rv);
if (offer)
{
ADD_TSH(offer->getAccountID(sfOwner), tshSTRONG);
ADD_TSH(offer->getAccountID(sfOwner), tshWEAK);
if (offer->isFieldPresent(sfDestination))
ADD_TSH(offer->getAccountID(sfDestination), tshWEAK);

View File

@@ -597,7 +597,8 @@ ValidNewAccountRoot::finalize(
return false;
}
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT) &&
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT ||
tt == ttREMIT) &&
result == tesSUCCESS)
{
std::uint32_t const startingSeq{

View File

@@ -0,0 +1,629 @@
//------------------------------------------------------------------------------
/*
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/app/tx/impl/Remit.h>
#include <ripple/app/tx/impl/URIToken.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
TxConsequences
Remit::makeTxConsequences(PreflightContext const& ctx)
{
XRPAmount native = ([&ctx]() -> XRPAmount {
if (!ctx.tx.isFieldPresent(sfAmounts))
return beast::zero;
STArray const& sEntries(ctx.tx.getFieldArray(sfAmounts));
for (STObject const& sEntry : sEntries)
{
if (!sEntry.isFieldPresent(sfAmount))
continue;
STAmount const amount = sEntry.getFieldAmount(sfAmount);
if (isXRP(amount))
return amount.xrp();
}
return beast::zero;
})();
return TxConsequences{ctx.tx, native};
}
NotTEC
Remit::preflight(PreflightContext const& ctx)
{
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx.getFlags() & tfUniversalMask)
{
// There are no flags (other than universal).
JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
AccountID const dstID = ctx.tx.getAccountID(sfDestination);
AccountID const srcID = ctx.tx.getAccountID(sfAccount);
if (dstID == srcID)
{
JLOG(ctx.j.warn()) << "Malformed transaction: Remit to self.";
return temREDUNDANT;
}
if (ctx.tx.isFieldPresent(sfInform))
{
AccountID const infID = ctx.tx.getAccountID(sfInform);
if (infID == dstID || srcID == infID)
{
JLOG(ctx.j.warn()) << "Malformed transaction: sfInform is same as "
"source or destination.";
return temMALFORMED;
}
}
if (ctx.tx.isFieldPresent(sfBlob) &&
ctx.tx.getFieldVL(sfBlob).size() > (128 * 1024))
{
JLOG(ctx.j.warn()) << "Blob was more than 128kib "
<< ctx.tx.getTransactionID();
return temMALFORMED;
}
// sanity check amounts
if (ctx.tx.isFieldPresent(sfAmounts))
{
if (ctx.tx.getFieldArray(sfAmounts).size() > 32)
{
JLOG(ctx.j.warn()) << "Malformed: AmountEntry count exceeds `32`.";
return temMALFORMED;
}
std::map<Currency, std::set<AccountID>> already;
bool nativeAlready = false;
STArray const& sEntries(ctx.tx.getFieldArray(sfAmounts));
for (STObject const& sEntry : sEntries)
{
// Validate the AmountEntry.
if (sEntry.getFName() != sfAmountEntry)
{
JLOG(ctx.j.warn()) << "Malformed: Expected AmountEntry.";
return temMALFORMED;
}
STAmount const amount = sEntry.getFieldAmount(sfAmount);
if (!isLegalNet(amount) || amount.signum() <= 0)
{
JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: "
<< amount.getFullText();
return temBAD_AMOUNT;
}
if (isBadCurrency(amount.getCurrency()))
{
JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
return temBAD_CURRENCY;
}
if (isXRP(amount))
{
if (nativeAlready)
{
JLOG(ctx.j.warn()) << "Malformed transaction: Native "
"Currency appears more than once.";
return temMALFORMED;
}
nativeAlready = true;
continue;
}
auto found = already.find(amount.getCurrency());
if (found == already.end())
{
already.emplace(
amount.getCurrency(),
std::set<AccountID>{amount.getIssuer()});
continue;
}
if (found->second.find(amount.getIssuer()) != found->second.end())
{
JLOG(ctx.j.warn()) << "Malformed transaction: Issued Currency "
"appears more than once.";
return temMALFORMED;
}
found->second.emplace(amount.getIssuer());
}
}
// sanity check minturitoken
if (ctx.tx.isFieldPresent(sfMintURIToken))
{
STObject const& mint = const_cast<ripple::STTx&>(ctx.tx)
.getField(sfMintURIToken)
.downcast<STObject>();
for (auto const& mintElement : mint)
{
auto const& name = mintElement.getFName();
if (name != sfURI && name != sfFlags && name != sfDigest)
{
JLOG(ctx.j.trace()) << "Malformed transaction: sfMintURIToken "
"contains invalid field.";
return temMALFORMED;
}
}
if (!mint.isFieldPresent(sfURI))
{
JLOG(ctx.j.warn())
<< "Malformed transaction: URI was not provided.";
return temMALFORMED;
}
Blob const uri = mint.getFieldVL(sfURI);
if (uri.size() < 1 || uri.size() > 256)
{
JLOG(ctx.j.warn())
<< "Malformed transaction: URI was too short/long.";
return temMALFORMED;
}
if (!URIToken::validateUTF8(uri))
{
JLOG(ctx.j.warn())
<< "Malformed transaction: Invalid UTF8 inside MintURIToken.";
return temMALFORMED;
}
if (mint.isFieldPresent(sfFlags))
{
if (mint.getFieldU32(sfFlags) & tfURITokenMintMask)
return temINVALID_FLAG;
}
}
// sanity check uritokenids
if (ctx.tx.isFieldPresent(sfURITokenIDs))
{
STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs);
if (ids.size() < 1 || ids.size() > 32)
{
JLOG(ctx.j.warn()) << "Malformed transaction: URITokenIDs Invalid.";
return temMALFORMED;
}
std::sort(ids.begin(), ids.end());
if (std::adjacent_find(ids.begin(), ids.end()) != ids.end())
{
JLOG(ctx.j.warn())
<< "Malformed transaction: Duplicate URITokenID.";
return temMALFORMED;
}
}
return preflight2(ctx);
}
TER
Remit::doApply()
{
Sandbox sb(&ctx_.view());
if (!sb.rules().enabled(featureRemit))
return temDISABLED;
beast::Journal const& j = ctx_.journal;
auto const srcAccID = ctx_.tx[sfAccount];
auto sleSrcAcc = sb.peek(keylet::account(srcAccID));
if (!sleSrcAcc)
return terNO_ACCOUNT;
if (ctx_.tx.isFieldPresent(sfInform))
{
auto const informAcc = ctx_.tx.getAccountID(sfInform);
if (!sb.exists(keylet::account(informAcc)))
{
JLOG(j.warn()) << "Remit: sfInform account does not exist.";
return tecNO_TARGET;
}
}
XRPAmount const accountReserve{sb.fees().accountReserve(0)};
XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve};
// sanity check
if (accountReserve < beast::zero || objectReserve < beast::zero ||
objectReserve > sb.fees().accountReserve(1))
{
JLOG(j.warn())
<< "Remit: account or object reserve calculation not sane.";
return tecINTERNAL;
}
// amount of native tokens we will transfer to cover reserves for the
// tls/acc/uritokens created, and native tokens listed in amounts
XRPAmount nativeRemit{0};
AccountID const dstAccID{ctx_.tx[sfDestination]};
auto sleDstAcc = sb.peek(keylet::account(dstAccID));
auto const flags = !sleDstAcc ? 0 : sleDstAcc->getFlags();
// Check if the destination has disallowed incoming
if (sb.rules().enabled(featureDisallowIncoming) &&
(flags & lsfDisallowIncomingRemit))
return tecNO_PERMISSION;
// Check if the destination account requires deposit authorization.
bool const depositAuth{sb.rules().enabled(featureDepositAuth)};
if (depositAuth && sleDstAcc && (flags & lsfDepositAuth))
{
if (!sb.exists(keylet::depositPreauth(dstAccID, srcAccID)))
return tecNO_PERMISSION;
}
// the destination may require a dest tag
if ((flags & lsfRequireDestTag) &&
!ctx_.tx.isFieldPresent(sfDestinationTag))
{
JLOG(j.warn())
<< "Remit: DestinationTag required for this destination.";
return tecDST_TAG_NEEDED;
}
// if the destination doesn't exist, create it.
bool const createDst = !sleDstAcc;
if (createDst)
{
// sender will pay the reserve
if (nativeRemit + accountReserve < nativeRemit)
return tecINTERNAL;
nativeRemit += accountReserve;
// Create the account.
std::uint32_t const seqno{
sb.rules().enabled(featureXahauGenesis)
? sb.info().parentCloseTime.time_since_epoch().count()
: sb.rules().enabled(featureDeletableAccounts) ? sb.seq() : 1};
sleDstAcc = std::make_shared<SLE>(keylet::account(dstAccID));
sleDstAcc->setAccountID(sfAccount, dstAccID);
sleDstAcc->setFieldU32(sfSequence, seqno);
sleDstAcc->setFieldU32(sfOwnerCount, 0);
if (sb.exists(keylet::fees()) &&
sb.rules().enabled(featureXahauGenesis))
{
auto sleFees = sb.peek(keylet::fees());
uint64_t accIdx = sleFees->isFieldPresent(sfAccountCount)
? sleFees->getFieldU64(sfAccountCount)
: 0;
sleDstAcc->setFieldU64(sfAccountIndex, accIdx);
sleFees->setFieldU64(sfAccountCount, accIdx + 1);
sb.update(sleFees);
}
// we'll fix this up at the end
sleDstAcc->setFieldAmount(sfBalance, STAmount{XRPAmount{0}});
sb.insert(sleDstAcc);
}
// if theres a minted uritoken the sender pays for that
if (ctx_.tx.isFieldPresent(sfMintURIToken))
{
if (nativeRemit + objectReserve < nativeRemit)
return tecINTERNAL;
nativeRemit += objectReserve;
STObject const& mint = const_cast<ripple::STTx&>(ctx_.tx)
.getField(sfMintURIToken)
.downcast<STObject>();
Blob const& mintURI = mint.getFieldVL(sfURI);
std::optional<uint256> mintDigest;
if (mint.isFieldPresent(sfDigest))
mintDigest = mint.getFieldH256(sfDigest);
Keylet kl = keylet::uritoken(srcAccID, mintURI);
// check that it doesn't already exist
if (sb.exists(kl))
{
JLOG(j.trace()) << "Remit: tried to creat duplicate URIToken. Tx: "
<< ctx_.tx.getTransactionID();
return tecDUPLICATE;
}
auto sleMint = std::make_shared<SLE>(kl);
sleMint->setAccountID(sfOwner, dstAccID);
sleMint->setAccountID(sfIssuer, srcAccID);
sleMint->setFieldVL(sfURI, mintURI);
if (mint.isFieldPresent(sfDigest))
sleMint->setFieldH256(sfDigest, mint.getFieldH256(sfDigest));
sleMint->setFieldU32(
sfFlags,
mint.isFieldPresent(sfFlags) ? mint.getFieldU32(sfFlags) : 0);
auto const page = sb.dirInsert(
keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID));
JLOG(j_.trace()) << "Adding URIToken to owner directory "
<< to_string(kl.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleMint->setFieldU64(sfOwnerNode, *page);
sb.insert(sleMint);
// ensure there is a deletion blocker against the issuer now
sleSrcAcc->setFieldU32(
sfFlags, sleSrcAcc->getFlags() | lsfURITokenIssuer);
adjustOwnerCount(sb, sleDstAcc, 1, j);
}
// iterate uritokens
if (ctx_.tx.isFieldPresent(sfURITokenIDs))
{
STVector256 ids = ctx_.tx.getFieldV256(sfURITokenIDs);
for (uint256 const klRaw : ids)
{
Keylet kl = keylet::unchecked(klRaw);
auto sleU = sb.peek(kl);
// does it exist
if (!sleU)
{
JLOG(j.warn()) << "Remit: one or more uritokens did not exist "
"on the source account.";
return tecNO_ENTRY;
}
// is it a uritoken?
if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN)
{
JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was "
"not actually a uritoken.";
return tecNO_ENTRY;
}
// is it our uritoken?
if (sleU->getAccountID(sfOwner) != srcAccID)
{
JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was "
"not owned by sender.";
return tecNO_PERMISSION;
}
// erase current sale offers, if any
if (sleU->isFieldPresent(sfAmount))
sleU->makeFieldAbsent(sfAmount);
if (sleU->isFieldPresent(sfDestination))
sleU->makeFieldAbsent(sfDestination);
// pay the reserve
if (nativeRemit + objectReserve < nativeRemit)
return tecINTERNAL;
nativeRemit += objectReserve;
// remove from sender dir
{
auto const page = (*sleU)[sfOwnerNode];
if (!sb.dirRemove(
keylet::ownerDir(srcAccID), page, kl.key, true))
{
JLOG(j.fatal())
<< "Could not remove URIToken from owner directory";
return tefBAD_LEDGER;
}
adjustOwnerCount(sb, sleSrcAcc, -1, j);
}
// add to dest dir
{
auto const page = sb.dirInsert(
keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID));
JLOG(j_.trace()) << "Adding URIToken to owner directory "
<< to_string(kl.key) << ": "
<< (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
sleU->setFieldU64(sfOwnerNode, *page);
adjustOwnerCount(sb, sleDstAcc, 1, j);
}
// change the owner
sleU->setAccountID(sfOwner, dstAccID);
sb.update(sleU);
}
}
// iterate trustlines
if (ctx_.tx.isFieldPresent(sfAmounts))
{
// process trustline remits
STArray const& sEntries(ctx_.tx.getFieldArray(sfAmounts));
for (STObject const& sEntry : sEntries)
{
STAmount const amount = sEntry.getFieldAmount(sfAmount);
if (isXRP(amount))
{
// since we have to pay for all the created objects including
// possibly the account itself this is paid right at the end,
// and only if there is balance enough to cover.
// check for overflow
if (nativeRemit + amount.xrp() < nativeRemit)
return tecINTERNAL;
nativeRemit += amount.xrp();
continue;
}
AccountID const issuerAccID = amount.getIssuer();
// check permissions
if (issuerAccID == srcAccID || issuerAccID == dstAccID)
{
// no permission check needed when the issuer sends out or a
// subscriber sends back RH TODO: move this condition into
// trustTransferAllowed, guarded by an amendment
}
else if (TER canXfer = trustTransferAllowed(
sb,
std::vector<AccountID>{srcAccID, dstAccID},
amount.issue(),
j);
canXfer != tesSUCCESS)
return canXfer;
// compute the amount the source will need to send
// in remit the sender pays all transfer fees, so that
// the destination can always be assured they got the exact amount
// specified. therefore we need to compute the amount + transfer fee
auto const srcAmt =
issuerAccID != srcAccID && issuerAccID != dstAccID
? multiply(amount, transferRate(sb, issuerAccID))
: amount;
// sanity check this calculation
if (srcAmt < amount || srcAmt > amount + amount)
{
JLOG(j.warn()) << "Remit: srcAmt calculation not sane.";
return tecINTERNAL;
}
STAmount availableFunds{
accountFunds(sb, srcAccID, srcAmt, fhZERO_IF_FROZEN, j)};
if (availableFunds < srcAmt)
return tecUNFUNDED_PAYMENT;
// if the target trustline doesn't exist we need to create it and
// pay its reserve
if (!sb.exists(
keylet::line(dstAccID, issuerAccID, amount.getCurrency())))
{
if (nativeRemit + objectReserve < nativeRemit)
return tecINTERNAL;
nativeRemit += objectReserve;
}
// action the transfer
if (TER result =
accountSend(sb, srcAccID, dstAccID, amount, j, true);
result != tesSUCCESS)
return result;
}
}
if (nativeRemit < beast::zero)
return tecINTERNAL;
if (nativeRemit > beast::zero)
{
// ensure the account can cover the native remit
if (mSourceBalance < nativeRemit)
return tecUNFUNDED_PAYMENT;
// subtract the balance from the sender
{
STAmount bal = mSourceBalance;
bal -= nativeRemit;
if (bal < beast::zero || bal > mSourceBalance)
return tecINTERNAL;
sleSrcAcc->setFieldAmount(sfBalance, bal);
}
// add the balance to the destination
{
STAmount bal = sleDstAcc->getFieldAmount(sfBalance);
STAmount prior = bal;
bal += nativeRemit;
if (bal < beast::zero || bal < prior)
return tecINTERNAL;
sleDstAcc->setFieldAmount(sfBalance, bal);
}
}
auto hasSufficientReserve = [&](std::shared_ptr<SLE> const& sle) -> bool {
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
return sle->getFieldAmount(sfBalance) >=
sb.fees().accountReserve(uOwnerCount);
};
// sanity check reserves
if (!hasSufficientReserve(sleSrcAcc))
{
JLOG(j.warn()) << "Remit: sender " << srcAccID
<< " lacks reserves to cover send.";
return tecINSUFFICIENT_RESERVE;
}
// this isn't actually an error but we will print a warning
// this can occur if the destination was already below reserve level at the
// time assets were sent
if (!hasSufficientReserve(sleDstAcc))
{
JLOG(j.warn()) << "Remit: destination has insufficient reserves.";
}
// apply
sb.update(sleSrcAcc);
sb.update(sleDstAcc);
sb.apply(ctx_.rawView());
return tesSUCCESS;
}
XRPAmount
Remit::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount extraFee{0};
if (tx.isFieldPresent(sfBlob))
extraFee +=
XRPAmount{static_cast<XRPAmount>(tx.getFieldVL(sfBlob).size())};
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
} // namespace ripple

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
#define RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class Remit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit Remit(ApplyContext& ctx) : Transactor(ctx)
{
}
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
TER
doApply() override;
};
} // namespace ripple
#endif

View File

@@ -577,6 +577,14 @@ SetAccount::doApply()
uFlagsOut |= lsfDisallowIncomingTrustline;
else if (uClearFlag == asfDisallowIncomingTrustline)
uFlagsOut &= ~lsfDisallowIncomingTrustline;
if (ctx_.view().rules().enabled(featureRemit))
{
if (uSetFlag == asfDisallowIncomingRemit)
uFlagsOut |= lsfDisallowIncomingRemit;
else if (uClearFlag == asfDisallowIncomingRemit)
uFlagsOut &= ~lsfDisallowIncomingRemit;
}
}
if (uFlagsIn != uFlagsOut)

View File

@@ -164,6 +164,8 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<Import>(ctx);
case ttINVOKE:
return invoke_preflight_helper<Invoke>(ctx);
case ttREMIT:
return invoke_preflight_helper<Remit>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -283,6 +285,8 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<Import>(ctx);
case ttINVOKE:
return invoke_preclaim<Invoke>(ctx);
case ttREMIT:
return invoke_preclaim<Remit>(ctx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -364,6 +368,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return Import::calculateBaseFee(view, tx);
case ttINVOKE:
return Invoke::calculateBaseFee(view, tx);
case ttREMIT:
return Remit::calculateBaseFee(view, tx);
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:
@@ -543,6 +549,10 @@ invoke_apply(ApplyContext& ctx)
Invoke p(ctx);
return p();
}
case ttREMIT: {
Remit p(ctx);
return p();
}
case ttURITOKEN_MINT:
case ttURITOKEN_BURN:
case ttURITOKEN_BUY:

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 67;
static constexpr std::size_t numFeatures = 68;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -355,6 +355,7 @@ extern uint256 const featureXahauGenesis;
extern uint256 const featureHooksUpdate1;
extern uint256 const fixXahauV1;
extern uint256 const fixXahauV2;
extern uint256 const featureRemit;
} // namespace ripple

View File

@@ -285,6 +285,8 @@ enum LedgerSpecificFlags {
0x20000000, // True, reject new trustlines (only if no issued assets)
lsfURITokenIssuer =
0x40000000, // True, has minted tokens in the past
lsfDisallowIncomingRemit = // True, no remits allowed to this account
0x80000000,
// ltOFFER
lsfPassive = 0x00010000,

View File

@@ -552,6 +552,7 @@ extern SF_ACCOUNT const sfEmitCallback;
// account (uncommon)
extern SF_ACCOUNT const sfHookAccount;
extern SF_ACCOUNT const sfNFTokenMinter;
extern SF_ACCOUNT const sfInform;
// path set
extern SField const sfPaths;
@@ -562,6 +563,7 @@ extern SF_VECTOR256 const sfHashes;
extern SF_VECTOR256 const sfAmendments;
extern SF_VECTOR256 const sfNFTokenOffers;
extern SF_VECTOR256 const sfHookNamespaces;
extern SF_VECTOR256 const sfURITokenIDs;
// inner object
// OBJECT/1 is reserved for end of object
@@ -590,6 +592,8 @@ extern SField const sfHookGrant;
extern SField const sfActiveValidator;
extern SField const sfImportVLKey;
extern SField const sfHookEmission;
extern SField const sfMintURIToken;
extern SField const sfAmountEntry;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -617,6 +621,7 @@ extern SField const sfGenesisMints;
extern SField const sfActiveValidators;
extern SField const sfImportVLKeys;
extern SField const sfHookEmissions;
extern SField const sfAmounts;
//------------------------------------------------------------------------------

View File

@@ -86,6 +86,7 @@ constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12;
constexpr std::uint32_t asfDisallowIncomingCheck = 13;
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
constexpr std::uint32_t asfDisallowIncomingRemit = 16;
// OfferCreate flags:
constexpr std::uint32_t tfPassive = 0x00010000;

View File

@@ -146,6 +146,10 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
* that the sender pays for. */
ttREMIT = 95,
/** This transaction can only be used by the genesis account, which is controlled exclusively by
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
* according to reward schedule */

View File

@@ -461,6 +461,7 @@ REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixXahauV1, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -143,6 +143,20 @@ InnerObjectFormats::InnerObjectFormats()
{sfPublicKey, soeREQUIRED},
{sfAccount, soeOPTIONAL},
});
add(sfAmountEntry.jsonName.c_str(),
sfAmountEntry.getCode(),
{
{sfAmount, soeREQUIRED},
});
add(sfMintURIToken.jsonName.c_str(),
sfMintURIToken.getCode(),
{
{sfURI, soeREQUIRED},
{sfDigest, soeOPTIONAL},
{sfFlags, soeOPTIONAL},
});
}
InnerObjectFormats const&

View File

@@ -305,6 +305,7 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT,
// account (uncommon)
CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
CONSTRUCT_TYPED_SFIELD(sfInform, "Inform", ACCOUNT, 99);
// vector of 256-bit
CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
@@ -312,6 +313,7 @@ CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR25
CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3);
CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4);
CONSTRUCT_TYPED_SFIELD(sfHookNamespaces, "HookNamespaces", VECTOR256, 5);
CONSTRUCT_TYPED_SFIELD(sfURITokenIDs, "URITokenIDs", VECTOR256, 99);
// path set
CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1);
@@ -346,6 +348,8 @@ CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmission, "HookEmission", OBJECT, 93);
CONSTRUCT_UNTYPED_SFIELD(sfMintURIToken, "MintURIToken", OBJECT, 92);
CONSTRUCT_UNTYPED_SFIELD(sfAmountEntry, "AmountEntry", OBJECT, 91);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -370,6 +374,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
CONSTRUCT_UNTYPED_SFIELD(sfHookEmissions, "HookEmissions", ARRAY, 93);
CONSTRUCT_UNTYPED_SFIELD(sfAmounts, "Amounts", ARRAY, 92);
// clang-format on

View File

@@ -116,6 +116,21 @@ TxFormats::TxFormats()
},
commonFields);
add(jss::Remit,
ttREMIT,
{
{sfDestination, soeREQUIRED},
{sfAmounts, soeOPTIONAL},
{sfURITokenIDs, soeOPTIONAL},
{sfMintURIToken, soeOPTIONAL},
{sfInvoiceID, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfTicketSequence, soeOPTIONAL},
{sfBlob, soeOPTIONAL},
{sfInform, soeOPTIONAL},
},
commonFields);
add(jss::EscrowCreate,
ttESCROW_CREATE,
{

View File

@@ -117,6 +117,7 @@ JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(Remit); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.

View File

@@ -91,13 +91,14 @@ doAccountInfo(RPC::JsonContext& context)
{"requireDestinationTag", lsfRequireDestTag}}};
static constexpr std::
array<std::pair<std::string_view, LedgerSpecificFlags>, 4>
array<std::pair<std::string_view, LedgerSpecificFlags>, 5>
disallowIncomingFlags{
{{"disallowIncomingNFTokenOffer",
lsfDisallowIncomingNFTokenOffer},
{"disallowIncomingCheck", lsfDisallowIncomingCheck},
{"disallowIncomingPayChan", lsfDisallowIncomingPayChan},
{"disallowIncomingTrustline", lsfDisallowIncomingTrustline}}};
{"disallowIncomingTrustline", lsfDisallowIncomingTrustline},
{"disallowIncomingRemit", lsfDisallowIncomingRemit}}};
auto const sleAccepted = ledger->read(keylet::account(accountID));
if (sleAccepted)

2838
src/test/app/Remit_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -292,6 +292,215 @@ private:
0x00U, 0x73U, 0x21U, 0x00U, 0x41U, 0x9BU, 0x0AU, 0x0BU, 0x02U, 0x81U,
0x14U, 0x00U, 0x41U, 0xB1U, 0x0AU, 0x0BU, 0x02U, 0x83U, 0x14U};
const std::vector<uint8_t> EmitTenHook = {
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x35U,
0x08U, 0x60U, 0x05U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU,
0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U,
0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU,
0x01U, 0x7EU, 0x60U, 0x00U, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
0x7FU, 0x01U, 0x7EU, 0x02U, 0xBEU, 0x01U, 0x0CU, 0x03U, 0x65U, 0x6EU,
0x76U, 0x05U, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x00U, 0x00U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U,
0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U,
0x09U, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x6EU, 0x75U, 0x6DU,
0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x75U, 0x74U, 0x69U,
0x6CU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x69U, 0x64U, 0x00U, 0x05U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U,
0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x06U, 0x03U, 0x65U, 0x6EU, 0x76U,
0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU,
0x64U, 0x00U, 0x07U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U,
0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U,
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x65U, 0x74U, 0x78U,
0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U,
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x65U, 0x6DU, 0x69U,
0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U,
0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x04U, 0x03U, 0x02U, 0x01U, 0x02U,
0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x31U, 0x08U, 0x7FU, 0x01U,
0x41U, 0xD0U, 0x8DU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xB0U, 0x0BU,
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U,
0xCBU, 0x0DU, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0xD0U, 0x8DU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x00U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0x01U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U,
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x0CU, 0x0AU, 0x9AU, 0x89U, 0x00U,
0x01U, 0x96U, 0x89U, 0x00U, 0x02U, 0x02U, 0x7FU, 0x01U, 0x7EU, 0x23U,
0x00U, 0x41U, 0x80U, 0x01U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x00U, 0x20U,
0x01U, 0x20U, 0x00U, 0x36U, 0x02U, 0x7CU, 0x41U, 0x9AU, 0x0BU, 0x41U,
0x14U, 0x41U, 0x87U, 0x0BU, 0x41U, 0x13U, 0x41U, 0x00U, 0x10U, 0x00U,
0x1AU, 0x41U, 0xADU, 0x0CU, 0x41U, 0x14U, 0x10U, 0x01U, 0x1AU, 0x41U,
0x0AU, 0x10U, 0x02U, 0x1AU, 0x20U, 0x01U, 0x42U, 0x00U, 0x37U, 0x03U,
0x58U, 0x03U, 0x40U, 0x41U, 0xB9U, 0x80U, 0x80U, 0x80U, 0x78U, 0x41U,
0x0BU, 0x10U, 0x03U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x01U, 0x29U, 0x03U,
0x58U, 0x42U, 0x09U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xD6U, 0x08U, 0x41U,
0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x58U, 0x10U, 0x04U, 0x1AU, 0x20U,
0x01U, 0x29U, 0x03U, 0x58U, 0x22U, 0x03U, 0xA7U, 0x21U, 0x00U, 0x20U,
0x03U, 0x42U, 0x09U, 0x58U, 0x04U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0x01U,
0x6BU, 0x0EU, 0x09U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U,
0x08U, 0x09U, 0x00U, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U,
0xB3U, 0x08U, 0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC3U,
0x0CU, 0x41U, 0x14U, 0x41U, 0xE4U, 0x09U, 0x41U, 0x23U, 0x10U, 0x05U,
0x1AU, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U, 0xFBU, 0x08U,
0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U,
0x14U, 0x41U, 0xCDU, 0x0AU, 0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU,
0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U, 0xD8U, 0x08U, 0x41U, 0x23U,
0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U,
0xAAU, 0x0AU, 0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC3U,
0x0CU, 0x41U, 0x14U, 0x41U, 0x8CU, 0x08U, 0x41U, 0x23U, 0x10U, 0x05U,
0x1AU, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U, 0xC1U, 0x09U,
0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU, 0x41U, 0xC3U, 0x0CU, 0x41U,
0x14U, 0x41U, 0x9EU, 0x09U, 0x41U, 0x23U, 0x10U, 0x05U, 0x1AU, 0x0BU,
0x41U, 0xC3U, 0x0CU, 0x41U, 0x14U, 0x41U, 0x87U, 0x0AU, 0x41U, 0x23U,
0x10U, 0x05U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x10U, 0x06U, 0xA7U, 0x41U,
0x01U, 0x6AU, 0x36U, 0x02U, 0x54U, 0x41U, 0xC4U, 0x0BU, 0x20U, 0x01U,
0x28U, 0x02U, 0x54U, 0x22U, 0x00U, 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U,
0x18U, 0x74U, 0x20U, 0x00U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U, 0x41U,
0x08U, 0x74U, 0x72U, 0x20U, 0x00U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U,
0x71U, 0x41U, 0x08U, 0x76U, 0x72U, 0x20U, 0x00U, 0x41U, 0x80U, 0x80U,
0x80U, 0x78U, 0x71U, 0x41U, 0x18U, 0x76U, 0x72U, 0x36U, 0x02U, 0x00U,
0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x54U, 0x41U, 0x04U, 0x6AU,
0x36U, 0x02U, 0x50U, 0x41U, 0xCAU, 0x0BU, 0x20U, 0x01U, 0x28U, 0x02U,
0x50U, 0x22U, 0x00U, 0x41U, 0xFFU, 0x01U, 0x71U, 0x41U, 0x18U, 0x74U,
0x20U, 0x00U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U, 0x41U, 0x08U, 0x74U,
0x72U, 0x20U, 0x00U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x41U,
0x08U, 0x76U, 0x72U, 0x20U, 0x00U, 0x41U, 0x80U, 0x80U, 0x80U, 0x78U,
0x71U, 0x41U, 0x18U, 0x76U, 0x72U, 0x36U, 0x02U, 0x00U, 0x20U, 0x01U,
0x42U, 0xC0U, 0x84U, 0x3DU, 0x37U, 0x03U, 0x48U, 0x20U, 0x01U, 0x41U,
0xCFU, 0x0BU, 0x36U, 0x02U, 0x44U, 0x20U, 0x01U, 0x29U, 0x03U, 0x48U,
0x42U, 0x38U, 0x88U, 0x42U, 0x3FU, 0x83U, 0x42U, 0x40U, 0x7DU, 0xA7U,
0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x44U, 0x22U,
0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x44U, 0x20U, 0x02U, 0x20U,
0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x48U, 0x42U,
0x30U, 0x88U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U,
0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x44U, 0x22U, 0x02U, 0x41U, 0x01U,
0x6AU, 0x36U, 0x02U, 0x44U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U,
0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x48U, 0x42U, 0x28U, 0x88U, 0x42U,
0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U,
0x28U, 0x02U, 0x44U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U,
0x44U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U,
0x29U, 0x03U, 0x48U, 0x42U, 0x20U, 0x88U, 0x42U, 0xFFU, 0x01U, 0x83U,
0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x44U,
0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x44U, 0x20U, 0x02U,
0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x48U,
0x42U, 0x18U, 0x88U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U,
0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x44U, 0x22U, 0x02U, 0x41U,
0x01U, 0x6AU, 0x36U, 0x02U, 0x44U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU,
0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x48U, 0x42U, 0x10U, 0x88U,
0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U,
0x01U, 0x28U, 0x02U, 0x44U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U,
0x02U, 0x44U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U,
0x01U, 0x29U, 0x03U, 0x48U, 0x42U, 0x08U, 0x88U, 0x42U, 0xFFU, 0x01U,
0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U,
0x44U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x44U, 0x20U,
0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U,
0x48U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U,
0x20U, 0x01U, 0x28U, 0x02U, 0x44U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU,
0x36U, 0x02U, 0x44U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U,
0x41U, 0xBEU, 0x0BU, 0x41U, 0x04U, 0x41U, 0x83U, 0x80U, 0x08U, 0x10U,
0x07U, 0x42U, 0x04U, 0x51U, 0x04U, 0x40U, 0x41U, 0xBDU, 0x0BU, 0x41U,
0x2EU, 0x3AU, 0x00U, 0x00U, 0x0BU, 0x41U, 0xD7U, 0x0CU, 0x41U, 0xF4U,
0x00U, 0x10U, 0x08U, 0x1AU, 0x20U, 0x01U, 0x41U, 0xB0U, 0x0BU, 0x41U,
0x9BU, 0x02U, 0x10U, 0x09U, 0x37U, 0x03U, 0x38U, 0x20U, 0x01U, 0x41U,
0x80U, 0x0CU, 0x36U, 0x02U, 0x34U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U,
0x42U, 0x38U, 0x87U, 0x42U, 0x3FU, 0x83U, 0x42U, 0x40U, 0x7DU, 0xA7U,
0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x34U, 0x22U,
0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x34U, 0x20U, 0x02U, 0x20U,
0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x42U,
0x30U, 0x87U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U,
0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x34U, 0x22U, 0x02U, 0x41U, 0x01U,
0x6AU, 0x36U, 0x02U, 0x34U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U,
0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x42U, 0x28U, 0x87U, 0x42U,
0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U,
0x28U, 0x02U, 0x34U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U,
0x34U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U,
0x29U, 0x03U, 0x38U, 0x42U, 0x20U, 0x87U, 0x42U, 0xFFU, 0x01U, 0x83U,
0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x34U,
0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x34U, 0x20U, 0x02U,
0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U,
0x42U, 0x18U, 0x87U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U,
0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x34U, 0x22U, 0x02U, 0x41U,
0x01U, 0x6AU, 0x36U, 0x02U, 0x34U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU,
0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x38U, 0x42U, 0x10U, 0x87U,
0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U,
0x01U, 0x28U, 0x02U, 0x34U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U,
0x02U, 0x34U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U,
0x01U, 0x29U, 0x03U, 0x38U, 0x42U, 0x08U, 0x87U, 0x42U, 0xFFU, 0x01U,
0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U,
0x34U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU, 0x36U, 0x02U, 0x34U, 0x20U,
0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U,
0x38U, 0x42U, 0xFFU, 0x01U, 0x83U, 0xA7U, 0x21U, 0x00U, 0x20U, 0x01U,
0x20U, 0x01U, 0x28U, 0x02U, 0x34U, 0x22U, 0x02U, 0x41U, 0x01U, 0x6AU,
0x36U, 0x02U, 0x34U, 0x20U, 0x02U, 0x20U, 0x00U, 0x3AU, 0x00U, 0x00U,
0x41U, 0xAFU, 0x08U, 0x41U, 0x03U, 0x41U, 0xB0U, 0x0BU, 0x41U, 0x9BU,
0x02U, 0x41U, 0x01U, 0x10U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x20U, 0x01U,
0x41U, 0x10U, 0x6AU, 0x41U, 0x20U, 0x41U, 0xB0U, 0x0BU, 0x41U, 0x9BU,
0x02U, 0x10U, 0x0AU, 0x37U, 0x03U, 0x08U, 0x41U, 0x80U, 0x08U, 0x41U,
0x0BU, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x10U, 0x04U, 0x1AU, 0x20U,
0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x58U, 0x42U, 0x01U, 0x7CU, 0x37U,
0x03U, 0x58U, 0x0CU, 0x01U, 0x0BU, 0x0BU, 0x41U, 0xF0U, 0x0AU, 0x41U,
0x17U, 0x42U, 0x86U, 0x01U, 0x10U, 0x0BU, 0x1AU, 0x20U, 0x01U, 0x41U,
0x80U, 0x01U, 0x6AU, 0x24U, 0x00U, 0x42U, 0x00U, 0x0BU, 0x0BU, 0xA6U,
0x04U, 0x04U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xAEU, 0x03U, 0x65U,
0x6DU, 0x69U, 0x74U, 0x5FU, 0x72U, 0x65U, 0x73U, 0x75U, 0x6CU, 0x74U,
0x00U, 0x72U, 0x4BU, 0x77U, 0x58U, 0x70U, 0x69U, 0x48U, 0x47U, 0x63U,
0x51U, 0x53U, 0x7AU, 0x4BU, 0x45U, 0x47U, 0x58U, 0x63U, 0x77U, 0x65U,
0x6AU, 0x35U, 0x45U, 0x71U, 0x32U, 0x43U, 0x51U, 0x74U, 0x4EU, 0x36U,
0x48U, 0x6FU, 0x54U, 0x44U, 0x72U, 0x00U, 0x74U, 0x78U, 0x6EU, 0x00U,
0x72U, 0x47U, 0x31U, 0x51U, 0x51U, 0x76U, 0x32U, 0x6EU, 0x68U, 0x32U,
0x67U, 0x72U, 0x37U, 0x52U, 0x43U, 0x5AU, 0x31U, 0x50U, 0x38U, 0x59U,
0x59U, 0x63U, 0x42U, 0x55U, 0x4BU, 0x43U, 0x43U, 0x4EU, 0x36U, 0x33U,
0x33U, 0x6AU, 0x43U, 0x6EU, 0x00U, 0x69U, 0x00U, 0x72U, 0x4CU, 0x38U,
0x78U, 0x65U, 0x51U, 0x53U, 0x47U, 0x78U, 0x54U, 0x6DU, 0x4BU, 0x58U,
0x58U, 0x79U, 0x4CU, 0x55U, 0x46U, 0x52U, 0x75U, 0x41U, 0x41U, 0x4BU,
0x73U, 0x57U, 0x4DU, 0x59U, 0x31U, 0x42U, 0x4DU, 0x70U, 0x51U, 0x62U,
0x65U, 0x00U, 0x72U, 0x48U, 0x34U, 0x4BU, 0x45U, 0x63U, 0x47U, 0x39U,
0x64U, 0x45U, 0x77U, 0x47U, 0x77U, 0x70U, 0x6EU, 0x36U, 0x41U, 0x79U,
0x6FU, 0x57U, 0x4BU, 0x39U, 0x63U, 0x5AU, 0x50U, 0x4CU, 0x4CU, 0x34U,
0x52U, 0x4CU, 0x53U, 0x6DU, 0x57U, 0x57U, 0x00U, 0x72U, 0x68U, 0x57U,
0x35U, 0x68U, 0x67U, 0x32U, 0x78U, 0x45U, 0x37U, 0x77U, 0x32U, 0x65U,
0x77U, 0x65U, 0x70U, 0x62U, 0x68U, 0x57U, 0x74U, 0x67U, 0x5AU, 0x57U,
0x57U, 0x38U, 0x53U, 0x75U, 0x4EU, 0x36U, 0x75U, 0x57U, 0x39U, 0x37U,
0x53U, 0x00U, 0x72U, 0x4DU, 0x63U, 0x58U, 0x75U, 0x59U, 0x73U, 0x51U,
0x33U, 0x4DU, 0x6AU, 0x46U, 0x65U, 0x52U, 0x52U, 0x71U, 0x69U, 0x31U,
0x47U, 0x76U, 0x61U, 0x73U, 0x4AU, 0x7AU, 0x54U, 0x64U, 0x4CU, 0x37U,
0x77U, 0x75U, 0x4CU, 0x33U, 0x68U, 0x4EU, 0x00U, 0x72U, 0x50U, 0x4DU,
0x68U, 0x37U, 0x50U, 0x69U, 0x39U, 0x63U, 0x74U, 0x36U, 0x39U, 0x39U,
0x69U, 0x5AU, 0x55U, 0x54U, 0x57U, 0x61U, 0x79U, 0x74U, 0x4AU, 0x55U,
0x6FU, 0x48U, 0x63U, 0x4AU, 0x37U, 0x63U, 0x67U, 0x79U, 0x7AU, 0x69U,
0x4BU, 0x00U, 0x72U, 0x70U, 0x67U, 0x67U, 0x78U, 0x47U, 0x73U, 0x34U,
0x79U, 0x46U, 0x61U, 0x46U, 0x45U, 0x47U, 0x54U, 0x68U, 0x78U, 0x42U,
0x45U, 0x62U, 0x68U, 0x69U, 0x72U, 0x37U, 0x54U, 0x39U, 0x46U, 0x62U,
0x35U, 0x4CU, 0x51U, 0x4BU, 0x4CU, 0x43U, 0x00U, 0x72U, 0x44U, 0x76U,
0x4AU, 0x64U, 0x79U, 0x4AU, 0x58U, 0x45U, 0x4DU, 0x63U, 0x4CU, 0x42U,
0x79U, 0x59U, 0x37U, 0x79U, 0x38U, 0x66U, 0x50U, 0x58U, 0x4BU, 0x6EU,
0x4BU, 0x36U, 0x67U, 0x39U, 0x42U, 0x70U, 0x41U, 0x79U, 0x68U, 0x62U,
0x39U, 0x00U, 0x72U, 0x47U, 0x57U, 0x43U, 0x31U, 0x34U, 0x79U, 0x54U,
0x33U, 0x55U, 0x38U, 0x62U, 0x6BU, 0x75U, 0x41U, 0x78U, 0x32U, 0x63U,
0x38U, 0x74U, 0x41U, 0x31U, 0x61U, 0x6AU, 0x4CU, 0x55U, 0x4AU, 0x43U,
0x61U, 0x6EU, 0x64U, 0x70U, 0x35U, 0x39U, 0x00U, 0x65U, 0x6DU, 0x69U,
0x74U, 0x74U, 0x65U, 0x6EU, 0x2EU, 0x63U, 0x3AU, 0x20U, 0x53U, 0x75U,
0x63U, 0x63U, 0x65U, 0x73U, 0x73U, 0x66U, 0x75U, 0x6CU, 0x2EU, 0x00U,
0x65U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x65U, 0x6EU, 0x2EU, 0x63U, 0x3AU,
0x20U, 0x43U, 0x61U, 0x6CU, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x22U,
0x65U, 0x6DU, 0x69U, 0x74U, 0x74U, 0x65U, 0x6EU, 0x2EU, 0x63U, 0x3AU,
0x20U, 0x43U, 0x61U, 0x6CU, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x22U, 0x00U,
0x41U, 0xB0U, 0x0BU, 0x0BU, 0x5AU, 0x12U, 0x00U, 0x00U, 0x22U, 0x80U,
0x00U, 0x00U, 0x00U, 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x20U,
0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x61U, 0x99U, 0x99U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U,
0x99U, 0x99U, 0x99U, 0x99U, 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x73U, 0x21U, 0x00U, 0x41U, 0xABU, 0x0CU, 0x0BU,
0x02U, 0x81U, 0x14U, 0x00U, 0x41U, 0xC1U, 0x0CU, 0x0BU, 0x02U, 0x83U,
0x14U};
const std::vector<uint8_t> TshHook = {
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x28U,
0x06U, 0x60U, 0x05U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU,
@@ -4931,6 +5140,347 @@ private:
}
}
// Remit
// | otxn | tsh | remit w/amt |
// | A | A | S |
// | A | D | S |
// | A | I | W |
// | otxn | tsh | burnable | remit w/uri |
// | A | I | F | W |
// | A | I | T | S |
void
testRemitTSH(FeatureBitset features)
{
testcase("remit tsh");
using namespace test::jtx;
using namespace std::literals;
/*
sfAmounts
*/
// otxn: account
// tsh account
// w/s: strong
for (bool const testStrong : {true, false})
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account{"bob"};
env.fund(XRP(1000), account, dest);
env.close();
// set tsh collect
if (!testStrong)
addWeakTSH(env, account);
// set tsh hook
setTSHHook(env, account, testStrong);
// payment
env(remit::remit(account, dest), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
testTSHStrongWeak(env, tshSTRONG, __LINE__);
}
// otxn: account
// tsh dest
// w/s: strong
for (bool const testStrong : {true, false})
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account{"bob"};
env.fund(XRP(1000), account, dest);
env.close();
// set tsh collect
if (!testStrong)
addWeakTSH(env, dest);
// set tsh hook
setTSHHook(env, dest, testStrong);
// payment
env(remit::remit(account, dest), fee(XRP(1)), ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
testTSHStrongWeak(env, tshSTRONG, __LINE__);
}
// otxn: account
// tsh inform
// w/s: weak
for (bool const testStrong : {true, false})
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account("bob");
auto const inform = Account("carol");
env.fund(XRP(1000), account, dest, inform);
env.close();
// set tsh collect
if (!testStrong)
addWeakTSH(env, inform);
// set tsh hook
setTSHHook(env, inform, testStrong);
// payment
env(remit::remit(account, dest),
remit::inform(inform),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
auto const expected = testStrong ? tshNONE : tshWEAK;
testTSHStrongWeak(env, expected, __LINE__);
}
/*
sfURITokenIDs
*/
// otxn: account
// tsh issuer
// burnable: true
// w/s: strong
for (bool const testStrong : {true, false})
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account("bob");
auto const issuer = Account("carol");
env.fund(XRP(1000), account, dest, issuer);
env.close();
// mint uritoken
std::string const uri(maxTokenURILength, '?');
auto const tid = uritoken::tokenid(issuer, uri);
env(uritoken::mint(issuer, uri),
txflags(tfBurnable),
ter(tesSUCCESS));
// sell uritoken
env(uritoken::sell(issuer, strHex(tid)),
uritoken::amt(XRP(1)),
uritoken::dest(account),
ter(tesSUCCESS));
env.close();
// buy uritoken
env(uritoken::buy(account, strHex(tid)),
uritoken::amt(XRP(1)),
ter(tesSUCCESS));
env.close();
// set tsh collect
if (!testStrong)
addWeakTSH(env, issuer);
// set tsh hook
setTSHHook(env, issuer, testStrong);
// payment
env(remit::remit(account, dest),
remit::token_ids({strHex(tid)}),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
testTSHStrongWeak(env, expected, __LINE__);
}
// otxn: account
// tsh issuer
// burnable: false
// w/s: weak
for (bool const testStrong : {true, false})
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const account = Account("alice");
auto const dest = Account("bob");
auto const issuer = Account("carol");
env.fund(XRP(1000), account, dest, issuer);
env.close();
// mint uritoken
std::string const uri(maxTokenURILength, '?');
auto const tid = uritoken::tokenid(issuer, uri);
env(uritoken::mint(issuer, uri), ter(tesSUCCESS));
// sell uritoken
env(uritoken::sell(issuer, strHex(tid)),
uritoken::amt(XRP(1)),
uritoken::dest(account),
ter(tesSUCCESS));
env.close();
// buy uritoken
env(uritoken::buy(account, strHex(tid)),
uritoken::amt(XRP(1)),
ter(tesSUCCESS));
env.close();
// set tsh collect
if (!testStrong)
addWeakTSH(env, issuer);
// set tsh hook
setTSHHook(env, issuer, testStrong);
// payment
env(remit::remit(account, dest),
remit::token_ids({strHex(tid)}),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// verify tsh hook triggered
auto const expected = testStrong ? tshNONE : tshWEAK;
testTSHStrongWeak(env, expected, __LINE__);
}
}
void
testEmissionOrdering(FeatureBitset features)
{
testcase("emission ordering");
using namespace test::jtx;
using namespace std::literals;
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const dave = Account("dave");
auto const elsa = Account("elsa");
auto const frank = Account("frank");
auto const grace = Account("grace");
auto const heidi = Account("heidi");
auto const ivan = Account("ivan");
auto const judy = Account("judy");
env.fund(
XRP(1000),
alice,
bob,
carol,
dave,
elsa,
frank,
grace,
heidi,
ivan,
judy);
env.close();
// set tsh hook
env(hook(alice, {{hso(EmitTenHook, overrideFlag)}}, 0),
fee(XRP(2)),
ter(tesSUCCESS));
env.close();
// invoke
env(invoke::invoke(alice), fee(XRP(2)), ter(tesSUCCESS));
env.close();
// validate the emitted txn ids
std::vector<std::string> const txIds = {
"9610F73CDD6590EB6B3C82E5EC55D4B4C80CD7128B98AA556F7EC9DD96AE7056",
"2F4582A29272390C0C25A80D4A3BCE5A14ACE6D86D8D0CB2C57719EB6FA881AE",
"89A301CFEF0DD781AB9032A6A2DCE0937BC0119D2CDD06033B8B2FD80968E519",
"DD8721B59024E168480B4DF8F8E93778601F0BD2E77FC991F3DA1182F5AD8B1E",
"5D735C2EE3CB8289F8E11621FDC9565F9D6D67F3AE59D65332EACE591D67945F",
"F02470E01731C968881AF4CBDEC90BB9E1F7AB0BE1CC22AF15451FB6D191096D",
"8AD65E541DECD49B1693F8C17DFD8A2B906F49C673C4FD2034FF772E2BE50C30",
"9F225229059CCC6257814D03C107884CF588C1C246A89ADFC16E50DF671B834C",
"13C2A54A14BADF3648CED05175E1CCAD713F7E5EA56D9735CF8813CD5551F281",
"87C60F41A96554587CED289F83F52DEE3CF670EEB189B067E6066B9A06056ADF",
};
Json::Value params;
params[jss::transaction] =
env.tx()->getJson(JsonOptions::none)[jss::hash];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
auto const emissions = meta[sfHookEmissions.jsonName];
for (size_t i = 0; i <= 9; i++)
{
auto const emitted = emissions[i][sfHookEmission.jsonName];
BEAST_EXPECT(emitted[sfEmittedTxnID.jsonName] == txIds[i]);
}
env.close();
// NOTE: emitted txns are emitted in reverse
// LIFO: Last In First Out
std::vector<jtx::Account> const accounts = {
judy,
ivan,
heidi,
grace,
frank,
elsa,
dave,
carol,
bob,
alice,
};
// verify emitted txns
for (size_t i = 0; i <= 9; i++)
{
Json::Value params;
params[jss::transaction] = txIds[i];
auto const jrr = env.rpc("json", "tx", to_string(params));
auto const meta = jrr[jss::result][jss::meta];
for (auto const& node : meta[sfAffectedNodes.jsonName])
{
auto const nodeType = node[sfLedgerEntryType.jsonName];
if (nodeType == ltEMITTED_TXN)
{
auto const& nf = node[sfFinalFields.jsonName];
auto const& et = nf[sfEmittedTxn.jsonName];
BEAST_EXPECT(
et[sfDestination.jsonName] == accounts[i].human());
break;
}
}
}
}
void
testTSH(FeatureBitset features)
{
@@ -4965,6 +5515,7 @@ private:
testURITokenBuyTSH(features);
testURITokenCancelSellOfferTSH(features);
testURITokenCreateSellOfferTSH(features);
testRemitTSH(features);
}
void
@@ -4972,6 +5523,7 @@ private:
{
testEmittedTxnReliability(features);
testEmittedFlags(features);
testEmissionOrdering(features);
}
public:

View File

@@ -362,6 +362,7 @@ struct URIToken_test : public beast::unit_test::suite
// tecNO_PERMISSION - no permission
env(uritoken::burn(carol, hexid), ter(tecNO_PERMISSION));
env.close();
// tefBAD_LEDGER - could not remove object
}
@@ -454,10 +455,7 @@ struct URIToken_test : public beast::unit_test::suite
using namespace std::literals::chrono_literals;
// setup env
Env env{
*this, envconfig(), features, nullptr, beast::severities::kWarning
// beast::severities::kTrace
};
Env env{*this, envconfig(), features, nullptr};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
@@ -508,7 +506,7 @@ struct URIToken_test : public beast::unit_test::suite
ter(temBAD_CURRENCY));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient buy offer amount
// tecINSUFFICIENT_PAYMENT - insufficient buy offer amount
env(uritoken::buy(bob, hexid),
uritoken::amt(USD(9)),
ter(tecINSUFFICIENT_PAYMENT));
@@ -518,10 +516,13 @@ struct URIToken_test : public beast::unit_test::suite
env(uritoken::sell(alice, hexid), uritoken::amt(XRP(10000)));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient xrp - fees
env(uritoken::buy(bob, hexid),
uritoken::amt(XRP(1000)),
ter(tecINSUFFICIENT_PAYMENT));
// tecINSUFFICIENT_FUNDS - insufficient xrp - fees
// fixXahauV1 - fix checking wrong account for insufficient xrp
env(pay(env.master, alice, XRP(10000)));
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? ter(tecINSUFFICIENT_FUNDS)
: ter(tecINTERNAL);
env(uritoken::buy(bob, hexid), uritoken::amt(XRP(10000)), txResult);
env.close();
// clear sell and reset new sell
@@ -529,10 +530,10 @@ struct URIToken_test : public beast::unit_test::suite
env(uritoken::sell(alice, hexid), uritoken::amt(USD(10000)));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient amount
// tecINSUFFICIENT_FUNDS - insufficient amount
env(uritoken::buy(bob, hexid),
uritoken::amt(USD(1000)),
ter(tecINSUFFICIENT_PAYMENT));
uritoken::amt(USD(10000)),
ter(tecINSUFFICIENT_FUNDS));
env.close();
//----------------------------------------------------------------------
@@ -571,15 +572,18 @@ struct URIToken_test : public beast::unit_test::suite
env(uritoken::sell(alice, hexid), uritoken::amt(XRP(1000)));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient xrp sent
// tecINSUFFICIENT_PAYMENT - insufficient xrp sent
env(uritoken::buy(bob, hexid),
uritoken::amt(XRP(900)),
ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient xrp - fees
env(uritoken::buy(bob, hexid),
uritoken::amt(XRP(1000)),
ter(tecINSUFFICIENT_FUNDS));
// tecINSUFFICIENT_FUNDS - insufficient xrp - fees
// fixXahauV1 - fix checking wrong account for insufficient xrp
auto const txResult1 = env.current()->rules().enabled(fixXahauV1)
? ter(tecINSUFFICIENT_FUNDS)
: ter(tecINTERNAL);
env(uritoken::buy(bob, hexid), uritoken::amt(XRP(1000)), txResult1);
env.close();
// clear sell and set usd sell
@@ -587,18 +591,19 @@ struct URIToken_test : public beast::unit_test::suite
env(uritoken::sell(alice, hexid), uritoken::amt(USD(1000)));
env.close();
// tecINSUFFICIENT_PAYMENT - insuficient amount sent
// tecINSUFFICIENT_PAYMENT - insufficient amount sent
env(uritoken::buy(bob, hexid),
uritoken::amt(USD(900)),
ter(tecINSUFFICIENT_PAYMENT));
env.close();
// tecINSUFFICIENT_FUNDS - insuficient amount sent
// tecINSUFFICIENT_FUNDS - insufficient amount sent
env(uritoken::buy(bob, hexid),
uritoken::amt(USD(10000)),
ter(tecINSUFFICIENT_FUNDS));
env.close();
// tecNO_LINE_INSUF_RESERVE - insuficient xrp to create line
// tecNO_LINE_INSUF_RESERVE - insufficient xrp to create line
{
// fund echo 251 xrp (not enough for line reserve)
env.fund(XRP(251), echo);
@@ -617,7 +622,7 @@ struct URIToken_test : public beast::unit_test::suite
env(uritoken::sell(echo, hexid), uritoken::amt(USD(1)));
env.close();
// tecNO_LINE_INSUF_RESERVE - insuficient xrp to create line
// tecNO_LINE_INSUF_RESERVE - insufficient xrp to create line
auto const txResult = env.current()->rules().enabled(fixXahauV1)
? ter(tecINSUF_RESERVE_SELLER)
: ter(tecNO_LINE_INSUF_RESERVE);
@@ -1974,13 +1979,16 @@ struct URIToken_test : public beast::unit_test::suite
std::string multiply;
std::string divide;
};
std::array<TestRateData, 6> testCases = {{
std::array<TestRateData, 9> testCases = {{
{1, USD(100), "1100", "1100"},
{1.1, USD(100), "1110", "1090.909090909091"},
{1.0005, USD(100), "1100.05", "1099.950024987506"},
{1.005, USD(100), "1100.4999999", "1099.502487661197"},
{1.25, USD(100), "1125", "1080"},
{2, USD(100), "1200", "1050"},
{1.25, USD(1), "1001.25", "1000.8"},
{1.25, USD(0.1), "1000.125", "1000.08"},
{1.25, USD(0.0001), "1000.000125", "1000.00008"},
}};
for (auto const& tc : testCases)
@@ -2001,14 +2009,13 @@ struct URIToken_test : public beast::unit_test::suite
// setup mint
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(uritoken::tokenid(alice, uri))};
auto const delta = USD(100);
auto const delta = tc.delta;
env(uritoken::mint(alice, uri));
env(uritoken::sell(alice, id), uritoken::amt(delta));
env.close();
env(uritoken::buy(bob, id), uritoken::amt(delta));
env.close();
auto xferRate = transferRate(*env.current(), gw);
auto const postAlice = env.balance(alice, USD.issue());
if (!env.current()->rules().enabled(fixXahauV1))
{
@@ -2021,6 +2028,52 @@ struct URIToken_test : public beast::unit_test::suite
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - delta);
}
// test dust amount
{
Env env{*this, features};
env.fund(XRP(10000), alice, bob, gw);
env(rate(gw, 1.50));
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 preAlice = env.balance(alice, USD.issue());
auto const preBob = env.balance(bob, USD.issue());
// setup mint
std::string const uri(maxTokenURILength, '?');
std::string const id{strHex(uritoken::tokenid(alice, uri))};
env(uritoken::mint(alice, uri));
auto sellTx = uritoken::sell(alice, id);
sellTx[jss::Amount][jss::issuer] = gw.human();
sellTx[jss::Amount][jss::currency] = "USD";
sellTx[jss::Amount][jss::value] = "1e-81";
env(sellTx);
env.close();
auto buyTx = uritoken::buy(bob, id);
buyTx[jss::Amount][jss::issuer] = gw.human();
buyTx[jss::Amount][jss::currency] = "USD";
buyTx[jss::Amount][jss::value] = "1e-81";
env(buyTx);
env.close();
auto const postAlice = env.balance(alice, USD.issue());
if (!env.current()->rules().enabled(fixXahauV1))
{
BEAST_EXPECT(postAlice.value() == preAlice);
}
else
{
BEAST_EXPECT(postAlice.value() == preAlice);
}
BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob - USD(0));
}
// test rate change
{
Env env{*this, features};

View File

@@ -57,6 +57,7 @@
#include <test/jtx/quality.h>
#include <test/jtx/rate.h>
#include <test/jtx/regkey.h>
#include <test/jtx/remit.h>
#include <test/jtx/require.h>
#include <test/jtx/requires.h>
#include <test/jtx/reward.h>

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 XRPL Labs
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/protocol/jss.h>
#include <test/jtx/remit.h>
namespace ripple {
namespace test {
namespace jtx {
namespace remit {
Json::Value
remit(
jtx::Account const& account,
jtx::Account const& dest,
std::optional<std::uint32_t> const& dstTag)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::Remit;
jv[jss::Account] = account.human();
jv[jss::Destination] = dest.human();
if (dstTag)
jv[sfDestinationTag.jsonName] = *dstTag;
return jv;
}
void
amts::operator()(Env& env, JTx& jt) const
{
auto& ja = jt.jv[sfAmounts.getJsonName()];
for (std::size_t i = 0; i < amts_.size(); ++i)
{
ja[i][sfAmountEntry.jsonName] = Json::Value{};
ja[i][sfAmountEntry.jsonName][jss::Amount] =
amts_[i].getJson(JsonOptions::none);
}
jt.jv[sfAmounts.jsonName] = ja;
}
void
blob::operator()(Env& env, JTx& jt) const
{
jt.jv[sfBlob.jsonName] = blob_;
}
void
inform::operator()(Env& env, JTx& jt) const
{
jt.jv[sfInform.jsonName] = inform_.human();
}
void
token_ids::operator()(Env& env, JTx& jt) const
{
jt.jv[sfURITokenIDs.jsonName] = Json::arrayValue;
for (std::size_t i = 0; i < token_ids_.size(); ++i)
{
jt.jv[sfURITokenIDs.jsonName].append(token_ids_[i]);
}
}
void
uri::operator()(Env& env, JTx& jt) const
{
jt.jv[sfMintURIToken.jsonName] = Json::Value{};
jt.jv[sfMintURIToken.jsonName][sfURI.jsonName] = strHex(uri_);
if (flags_)
{
jt.jv[sfMintURIToken.jsonName][sfFlags.jsonName] = *flags_;
}
if (digest_)
{
jt.jv[sfMintURIToken.jsonName][sfDigest.fieldName] = *digest_;
}
}
} // namespace remit
} // namespace jtx
} // namespace test
} // namespace ripple

128
src/test/jtx/remit.h Normal file
View File

@@ -0,0 +1,128 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 XRPL Labs
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_REMIT_H_INCLUDED
#define RIPPLE_TEST_JTX_REMIT_H_INCLUDED
#include <ripple/protocol/STAmount.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
namespace ripple {
namespace test {
namespace jtx {
namespace remit {
Json::Value
remit(
jtx::Account const& account,
jtx::Account const& dest,
std::optional<std::uint32_t> const& dstTag = std::nullopt);
/** Sets the optional Amount on a JTx. */
class amts
{
private:
std::vector<STAmount> amts_;
public:
explicit amts(std::vector<STAmount> const& amts) : amts_(amts)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Set the optional "Blob" on a JTx */
class blob
{
private:
std::string blob_;
public:
explicit blob(std::string const& blob) : blob_(blob)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional "Inform" on a JTx. */
class inform
{
private:
jtx::Account inform_;
public:
explicit inform(jtx::Account const& inform) : inform_(inform)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional "URITokenIDs" on a JTx. */
class token_ids
{
private:
std::vector<std::string> token_ids_;
public:
explicit token_ids(std::vector<std::string> const& token_ids)
: token_ids_(token_ids)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Set the optional "sfMintURIToken" on a JTx */
class uri
{
private:
std::string uri_;
std::optional<std::uint32_t> flags_;
std::optional<std::string> digest_;
public:
explicit uri(
std::string const& uri,
std::optional<std::uint32_t> const& flags = std::nullopt,
std::optional<std::string> const& digest = std::nullopt)
: uri_(uri), flags_(flags), digest_(digest)
{
}
void
operator()(Env&, JTx& jtx) const;
};
} // namespace remit
} // namespace jtx
} // namespace test
} // namespace ripple
#endif // RIPPLE_TEST_JTX_REMIT_H_INCLUDED

View File

@@ -546,14 +546,15 @@ public:
}
static constexpr std::
array<std::pair<std::string_view, std::uint32_t>, 4>
array<std::pair<std::string_view, std::uint32_t>, 5>
disallowIncomingFlags{
{{"disallowIncomingCheck", asfDisallowIncomingCheck},
{"disallowIncomingNFTokenOffer",
asfDisallowIncomingNFTokenOffer},
{"disallowIncomingPayChan", asfDisallowIncomingPayChan},
{"disallowIncomingTrustline",
asfDisallowIncomingTrustline}}};
asfDisallowIncomingTrustline},
{"disallowIncomingRemit", asfDisallowIncomingRemit}}};
if (features[featureDisallowIncoming])
{

View File

@@ -88,7 +88,7 @@ public:
flag == asfDisallowIncomingPayChan ||
flag == asfDisallowIncomingNFTokenOffer ||
flag == asfDisallowIncomingTrustline ||
flag == asfTshCollect)
flag == asfTshCollect || flag == asfDisallowIncomingRemit)
{
// These flags are part of the DisallowIncoming amendment
// and are tested elsewhere

View File

@@ -571,6 +571,15 @@ class AccountTx_test : public beast::unit_test::suite
env.close();
}
// Remit
{
// Empty
// XAH
// USD
// URIToken Mint
// URIToken Transfer
}
// Setup is done. Look at the transactions returned by account_tx.
Json::Value params;
params[jss::account] = alice.human();