From 8b11a10cbb1ef5e5fbeaf8dd860b04deede62819 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Thu, 28 Dec 2023 10:35:54 +0000 Subject: [PATCH 01/47] Remit squashed (not compiling) Co-authored-by: Denis Angell --- Builds/CMake/RippledCore.cmake | 3 + src/ripple/app/tx/impl/InvariantCheck.cpp | 3 +- src/ripple/app/tx/impl/Remit.cpp | 502 +++++++ src/ripple/app/tx/impl/Remit.h | 54 + src/ripple/app/tx/impl/SetAccount.cpp | 8 + src/ripple/app/tx/impl/applySteps.cpp | 10 + src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/LedgerFormats.h | 2 + src/ripple/protocol/SField.h | 5 + src/ripple/protocol/TxFlags.h | 1 + src/ripple/protocol/TxFormats.h | 4 + src/ripple/protocol/impl/Feature.cpp | 2 +- .../protocol/impl/InnerObjectFormats.cpp | 15 + src/ripple/protocol/impl/SField.cpp | 5 + src/ripple/protocol/impl/TxFormats.cpp | 15 + src/ripple/protocol/jss.h | 1 + src/test/app/Remit_test.cpp | 1229 +++++++++++++++++ src/test/jtx.h | 1 + src/test/jtx/impl/remit.cpp | 85 ++ 19 files changed, 1945 insertions(+), 3 deletions(-) create mode 100644 src/ripple/app/tx/impl/Remit.cpp create mode 100644 src/ripple/app/tx/impl/Remit.h create mode 100644 src/test/app/Remit_test.cpp create mode 100644 src/test/jtx/impl/remit.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 8e6ed5765..c3fd51bc7 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -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 diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 84a5ff582..3cdf14bc6 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -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{ diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp new file mode 100644 index 000000000..e6cb77421 --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -0,0 +1,502 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +namespace ripple { + +TxConsequences +Remit::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +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; + } + + // sanity check amounts + if (ctx.tx.isFieldPresent(sfAmounts)) + { + std::map> 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{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(ctx.tx) + .getField(sfMintURIToken) + .downcast(); + // RH TODO: iterate mint fields detect any that shouldnt be there + + 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(mint.getFieldVL(sfURI))) + { + JLOG(ctx.j.warn()) + << "Malformed transaction: Invalid UTF8 inside MintURIToken."; + return temMALFORMED; + } + + if (mint.isFieldPresent(sfFlags)) + { + if (mint.getFieldU32(sfFlags) & tfURITokenMintMask) + return temINVALID_FLAG; + } + } + + // check uritokenids for duplicates + if (ctx.tx.isFieldPresent(sfURITokenIDs)) + { + STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); + 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() +{ + if (!view().rules().enabled(featureRemit)) + return temDISABLED; + + Sandbox sb(&ctx_.view()); + + beast::Journal const& j = ctx_.journal; + + auto const srcAccID = ctx_.tx[sfAccount]; + + auto sleSrcAcc = sb.peek(keylet::account(srcAccID)); + if (!sleSrcAcc) + return terNO_ACCOUNT; + + XRPAmount const accountReserve{sb.fees().accountReserve(0)}; + XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve}; + + // 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; + + // 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 + 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(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)) + { + nativeRemit += objectReserve; + STObject const& mint = const_cast(ctx_.tx) + .getField(sfMintURIToken) + .downcast(); + + Blob const& mintURI = mint.getFieldVL(sfURI); + + std::optional 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(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 = view().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 tecUNFUNDED_PAYMENT; + } + + // 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 + 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. + 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{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; + + auto const dstAmt = amount; + + 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( + issuerAccID == dstAccID ? srcAccID : dstAccID, + issuerAccID, + amount.getCurrency()))) + nativeRemit += objectReserve; + + // action the transfer + STAmount sentAmt; + if (TER result = + rippleSend(sb, srcAccID, dstAccID, dstAmt, sentAmt, j); + result != tesSUCCESS) + return result; + + if (sentAmt != srcAmt) + return tecINTERNAL; + } + } + + 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); + } + } + + // 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(tx.getFieldVL(sfBlob).size())}; + + // RH TODO: add fees + + return Transactor::calculateBaseFee(view, tx) + extraFee; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/Remit.h b/src/ripple/app/tx/impl/Remit.h new file mode 100644 index 000000000..56ac494be --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.h @@ -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 +#include +#include +#include + +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 diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 9702212a3..085e36422 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -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) diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index ae2d35463..d94d2c0dc 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -164,6 +164,8 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttINVOKE: return invoke_preflight_helper(ctx); + case ttREMIT: + return invoke_preflight_helper(ctx); case ttURITOKEN_MINT: case ttURITOKEN_BURN: case ttURITOKEN_BUY: @@ -283,6 +285,8 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttINVOKE: return invoke_preclaim(ctx); + case ttREMIT: + return invoke_preclaim(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: diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index e3ddeb632..6fc556a08 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -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 = 66; +static constexpr std::size_t numFeatures = 67; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -354,6 +354,7 @@ extern uint256 const featureImport; extern uint256 const featureXahauGenesis; extern uint256 const featureHooksUpdate1; extern uint256 const fixXahauV1; +extern uint256 const featureRemit; } // namespace ripple diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 0a34a84e6..6134a8f33 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -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, diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index cd5dda504..1f9d15368 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -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; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 23f57e4cb..b27104a67 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -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; diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 7999ee5b4..2f287efe5 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -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 */ diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 6603685fb..bac11e0cb 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -460,7 +460,7 @@ REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::De REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixXahauV1, 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. diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index d98ad2056..c6d0340c1 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -141,6 +141,21 @@ InnerObjectFormats::InnerObjectFormats() {sfPublicKey, soeREQUIRED}, {sfAccount, soeOPTIONAL}, }); + + add(sfAmountEntry.jsonName.c_str(), + sfAmountEntry.getCode(), + { + {sfAmount, soeREQUIRED}, + {sfFlags, soeOPTIONAL}, + }); + + add(sfMintURIToken.jsonName.c_str(), + sfMintURIToken.getCode(), + { + {sfURI, soeREQUIRED}, + {sfDigest, soeOPTIONAL}, + {sfFlags, soeOPTIONAL}, + }); } InnerObjectFormats const& diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 889885442..a72208607 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -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 diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 61eec4762..6c38711ad 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -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, { diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 699928f7e..3bcd4fd0f 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -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. diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp new file mode 100644 index 000000000..47b1f4c6d --- /dev/null +++ b/src/test/app/Remit_test.cpp @@ -0,0 +1,1229 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +// Destination Exists - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | N | N | N | N | +// A | B | Y | Y | N | N | N | N | +// A | B | Y | N | Y | Y | N | N | +// A | B | Y | Y | Y | Y | N | N | +// A | B | Y | N | N | N | Y | N | +// A | B | Y | N | N | N | N | Y | +// A | B | Y | Y | N | N | Y | N | +// A | B | Y | Y | Y | Y | Y | N | +// A | B | Y | Y | N | N | Y | Y | +// A | B | Y | Y | Y | Y | Y | Y | + +/* +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | Y | N | N | N | +// A | B | Y | Y | Y | N | N | N | +// A | B | Y | Y | Y | N | Y | N | +// A | B | Y | Y | Y | N | Y | Y | + +// Destination Does Not Exist - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | N | N | N | N | +// A | B | N | Y | N | N | N | N | +// A | B | N | N | Y | Y | N | N | +// A | B | N | Y | Y | Y | N | N | +// A | B | N | N | N | N | Y | N | +// A | B | N | N | N | N | N | Y | +// A | B | N | Y | N | N | Y | N | +// A | B | N | Y | Y | Y | Y | N | +// A | B | N | Y | N | N | Y | Y | +// A | B | N | Y | Y | Y | Y | Y | + +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | Y | N | N | N | +// A | B | N | Y | Y | N | N | N | +// A | B | N | Y | Y | N | Y | N | +// A | B | N | Y | Y | N | Y | Y | +*/ + +// Round Robin Test +// Multiple in one Ledger Test +// Forward and Backward remits +// Compute a uri token id inline with the send (a token that is being minted) + +namespace ripple { +namespace test { +struct Remit_test : public beast::unit_test::suite +{ + // testDebug("PRE", env, { alice, bob }, {}); + void + testDebug( + std::string const& testNumber, + jtx::Env const& env, + std::vector const& accounts, + std::vector const& ious) + { + std::cout << "DEBUG: " << testNumber << "\n"; + for (std::size_t a = 0; a < accounts.size(); ++a) + { + auto const bal = env.balance(accounts[a]); + std::cout << "account: " << accounts[a].human() << "BAL: " << bal + << "\n"; + for (std::size_t i = 0; i < ious.size(); ++i) + { + auto const iouBal = env.balance(accounts[a], ious[i]); + std::cout << "account: " << accounts[a].human() + << "IOU: " << iouBal << "\n"; + } + } + } + + 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 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 AccountID + tokenIsser(ReadView const& view, uint256 const& id) + { + auto const slep = view.read({ltURI_TOKEN, id}); + if (!slep) + return AccountID(); + return slep->getAccountID(sfIssuer); + } + + void + testEnabled(FeatureBitset features) + { + // 0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA + + 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 withRemit : {true, false}) + { + auto const amend = withRemit ? features : features - featureRemit; + + Env env{*this, amend}; + + env.fund(XRP(1000), alice, bob); + + auto const txResult = + withRemit ? ter(tesSUCCESS) : ter(temDISABLED); + + // REMIT + env(remit::remit(alice, bob), txResult); + env.close(); + } + } + + void + testDestExistsTLExists(FeatureBitset features) + { + testcase("dest exists and trustline exists"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // REMIT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + // auto const preAlice = env.balance(alice, USD.issue()); + } + + // REMIT: XAH + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + env.close(); + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(postBob == preBob + XRP(1)); + } + + // REMIT: USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, gw); + 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 + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops); + BEAST_EXPECT(postBob == preBob); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: XAH + USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, gw); + 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 + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(postBob == preBob + XRP(1)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); + + // remit with uritoken id + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + (feeReserve * 2)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + } + + void + testDestExistsTLDNE(FeatureBitset features) + { + testcase("dest exists and trustline does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // REMIT: USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(preAlice == XRP(1000)); + BEAST_EXPECT(preAliceUSD == USD(10000)); + BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBobUSD == USD(0)); + + // remit + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: XAH + USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(preAlice == XRP(1000)); + BEAST_EXPECT(preAliceUSD == USD(10000)); + BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBobUSD == USD(0)); + + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (2 * feeReserve)); + BEAST_EXPECT(postBob == preBob + (2 * feeReserve)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (2 * feeReserve)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (2 * feeReserve)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 3)); + BEAST_EXPECT(postBob == preBob + (feeReserve * 3)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 3)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 3)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + } + + void + testDestDoesNotExists(FeatureBitset features) + { + testcase("dest does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), + // txResult); env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testTLDoesNotExists(FeatureBitset features) + { + testcase("trust line does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), + // txResult); env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testWithFeats(FeatureBitset features) + { + testEnabled(features); + testDestExistsTLExists(features); + testDestExistsTLDNE(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Remit, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/jtx.h b/src/test/jtx.h index 39d4a9662..a91ae3b2f 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/impl/remit.cpp b/src/test/jtx/impl/remit.cpp new file mode 100644 index 000000000..f743848ac --- /dev/null +++ b/src/test/jtx/impl/remit.cpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { +namespace test { +namespace jtx { +namespace remit { + +Json::Value +remit(jtx::Account const& account, jtx::Account const& dest) +{ + using namespace jtx; + Json::Value jv; + jv[jss::TransactionType] = jss::Remit; + jv[jss::Account] = account.human(); + jv[jss::Destination] = dest.human(); + 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 +{ + for (std::size_t i = 0; i < token_ids_.size(); ++i) + { + jt.jv[sfURITokenIDs.jsonName] = Json::arrayValue; + jt.jv[sfURITokenIDs.jsonName][i] = 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_); + ; +} + +} // namespace remit +} // namespace jtx +} // namespace test +} // namespace ripple From 1f14b4092b535cf7129024cdb3889837b02d14db Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Thu, 28 Dec 2023 10:59:53 +0000 Subject: [PATCH 02/47] change remit to use accountsend (more changes needed) --- src/ripple/app/tx/impl/Remit.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index e6cb77421..b7537da6e 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -420,8 +420,6 @@ Remit::doApply() ? multiply(amount, transferRate(sb, issuerAccID)) : amount; - auto const dstAmt = amount; - STAmount availableFunds{ accountFunds(sb, srcAccID, srcAmt, fhZERO_IF_FROZEN, j)}; @@ -437,14 +435,10 @@ Remit::doApply() nativeRemit += objectReserve; // action the transfer - STAmount sentAmt; if (TER result = - rippleSend(sb, srcAccID, dstAccID, dstAmt, sentAmt, j); + accountSend(sb, srcAccID, dstAccID, amount, j, false); result != tesSUCCESS) return result; - - if (sentAmt != srcAmt) - return tecINTERNAL; } } @@ -477,6 +471,28 @@ Remit::doApply() } } + auto hasSufficientReserve = [&](std::shared_ptr 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); From 3ca9f4d0ec600b6f32bb1496ad273cc405618cc2 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Thu, 28 Dec 2023 10:35:54 +0000 Subject: [PATCH 03/47] Remit squashed Co-authored-by: Denis Angell --- Builds/CMake/RippledCore.cmake | 3 + src/ripple/app/tx/impl/InvariantCheck.cpp | 3 +- src/ripple/app/tx/impl/Remit.cpp | 518 +++++++ src/ripple/app/tx/impl/Remit.h | 54 + src/ripple/app/tx/impl/SetAccount.cpp | 8 + src/ripple/app/tx/impl/applySteps.cpp | 10 + src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/LedgerFormats.h | 2 + src/ripple/protocol/SField.h | 5 + src/ripple/protocol/TxFlags.h | 1 + src/ripple/protocol/TxFormats.h | 4 + src/ripple/protocol/impl/Feature.cpp | 2 +- .../protocol/impl/InnerObjectFormats.cpp | 15 + src/ripple/protocol/impl/SField.cpp | 5 + src/ripple/protocol/impl/TxFormats.cpp | 15 + src/ripple/protocol/jss.h | 1 + src/test/app/Remit_test.cpp | 1229 +++++++++++++++++ src/test/jtx.h | 1 + src/test/jtx/impl/remit.cpp | 85 ++ src/test/rpc/AccountSet_test.cpp | 3 +- 20 files changed, 1963 insertions(+), 4 deletions(-) create mode 100644 src/ripple/app/tx/impl/Remit.cpp create mode 100644 src/ripple/app/tx/impl/Remit.h create mode 100644 src/test/app/Remit_test.cpp create mode 100644 src/test/jtx/impl/remit.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 8e6ed5765..c3fd51bc7 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -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 diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 84a5ff582..3cdf14bc6 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -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{ diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp new file mode 100644 index 000000000..b7537da6e --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -0,0 +1,518 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +namespace ripple { + +TxConsequences +Remit::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +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; + } + + // sanity check amounts + if (ctx.tx.isFieldPresent(sfAmounts)) + { + std::map> 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{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(ctx.tx) + .getField(sfMintURIToken) + .downcast(); + // RH TODO: iterate mint fields detect any that shouldnt be there + + 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(mint.getFieldVL(sfURI))) + { + JLOG(ctx.j.warn()) + << "Malformed transaction: Invalid UTF8 inside MintURIToken."; + return temMALFORMED; + } + + if (mint.isFieldPresent(sfFlags)) + { + if (mint.getFieldU32(sfFlags) & tfURITokenMintMask) + return temINVALID_FLAG; + } + } + + // check uritokenids for duplicates + if (ctx.tx.isFieldPresent(sfURITokenIDs)) + { + STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); + 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() +{ + if (!view().rules().enabled(featureRemit)) + return temDISABLED; + + Sandbox sb(&ctx_.view()); + + beast::Journal const& j = ctx_.journal; + + auto const srcAccID = ctx_.tx[sfAccount]; + + auto sleSrcAcc = sb.peek(keylet::account(srcAccID)); + if (!sleSrcAcc) + return terNO_ACCOUNT; + + XRPAmount const accountReserve{sb.fees().accountReserve(0)}; + XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve}; + + // 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; + + // 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 + 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(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)) + { + nativeRemit += objectReserve; + STObject const& mint = const_cast(ctx_.tx) + .getField(sfMintURIToken) + .downcast(); + + Blob const& mintURI = mint.getFieldVL(sfURI); + + std::optional 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(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 = view().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 tecUNFUNDED_PAYMENT; + } + + // 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 + 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. + 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{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; + + 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( + issuerAccID == dstAccID ? srcAccID : dstAccID, + issuerAccID, + amount.getCurrency()))) + nativeRemit += objectReserve; + + // action the transfer + if (TER result = + accountSend(sb, srcAccID, dstAccID, amount, j, false); + 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 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(tx.getFieldVL(sfBlob).size())}; + + // RH TODO: add fees + + return Transactor::calculateBaseFee(view, tx) + extraFee; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/Remit.h b/src/ripple/app/tx/impl/Remit.h new file mode 100644 index 000000000..56ac494be --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.h @@ -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 +#include +#include +#include + +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 diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 9702212a3..085e36422 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -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) diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index ae2d35463..d94d2c0dc 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -164,6 +164,8 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttINVOKE: return invoke_preflight_helper(ctx); + case ttREMIT: + return invoke_preflight_helper(ctx); case ttURITOKEN_MINT: case ttURITOKEN_BURN: case ttURITOKEN_BUY: @@ -283,6 +285,8 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttINVOKE: return invoke_preclaim(ctx); + case ttREMIT: + return invoke_preclaim(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: diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index e3ddeb632..6fc556a08 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -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 = 66; +static constexpr std::size_t numFeatures = 67; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -354,6 +354,7 @@ extern uint256 const featureImport; extern uint256 const featureXahauGenesis; extern uint256 const featureHooksUpdate1; extern uint256 const fixXahauV1; +extern uint256 const featureRemit; } // namespace ripple diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 0a34a84e6..6134a8f33 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -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, diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index cd5dda504..1f9d15368 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -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; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 23f57e4cb..b27104a67 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -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; diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 7999ee5b4..2f287efe5 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -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 */ diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 6603685fb..bac11e0cb 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -460,7 +460,7 @@ REGISTER_FEATURE(Import, Supported::yes, VoteBehavior::De REGISTER_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixXahauV1, 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. diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index d98ad2056..c6d0340c1 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -141,6 +141,21 @@ InnerObjectFormats::InnerObjectFormats() {sfPublicKey, soeREQUIRED}, {sfAccount, soeOPTIONAL}, }); + + add(sfAmountEntry.jsonName.c_str(), + sfAmountEntry.getCode(), + { + {sfAmount, soeREQUIRED}, + {sfFlags, soeOPTIONAL}, + }); + + add(sfMintURIToken.jsonName.c_str(), + sfMintURIToken.getCode(), + { + {sfURI, soeREQUIRED}, + {sfDigest, soeOPTIONAL}, + {sfFlags, soeOPTIONAL}, + }); } InnerObjectFormats const& diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 889885442..a72208607 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -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 diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 61eec4762..6c38711ad 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -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, { diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 699928f7e..3bcd4fd0f 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -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. diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp new file mode 100644 index 000000000..47b1f4c6d --- /dev/null +++ b/src/test/app/Remit_test.cpp @@ -0,0 +1,1229 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +// Destination Exists - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | N | N | N | N | +// A | B | Y | Y | N | N | N | N | +// A | B | Y | N | Y | Y | N | N | +// A | B | Y | Y | Y | Y | N | N | +// A | B | Y | N | N | N | Y | N | +// A | B | Y | N | N | N | N | Y | +// A | B | Y | Y | N | N | Y | N | +// A | B | Y | Y | Y | Y | Y | N | +// A | B | Y | Y | N | N | Y | Y | +// A | B | Y | Y | Y | Y | Y | Y | + +/* +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | Y | N | Y | N | N | N | +// A | B | Y | Y | Y | N | N | N | +// A | B | Y | Y | Y | N | Y | N | +// A | B | Y | Y | Y | N | Y | Y | + +// Destination Does Not Exist - Trust Line Exists +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | N | N | N | N | +// A | B | N | Y | N | N | N | N | +// A | B | N | N | Y | Y | N | N | +// A | B | N | Y | Y | Y | N | N | +// A | B | N | N | N | N | Y | N | +// A | B | N | N | N | N | N | Y | +// A | B | N | Y | N | N | Y | N | +// A | B | N | Y | Y | Y | Y | N | +// A | B | N | Y | N | N | Y | Y | +// A | B | N | Y | Y | Y | Y | Y | + +// Destination Exists - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | +// A | B | N | N | Y | N | N | N | +// A | B | N | Y | Y | N | N | N | +// A | B | N | Y | Y | N | Y | N | +// A | B | N | Y | Y | N | Y | Y | +*/ + +// Round Robin Test +// Multiple in one Ledger Test +// Forward and Backward remits +// Compute a uri token id inline with the send (a token that is being minted) + +namespace ripple { +namespace test { +struct Remit_test : public beast::unit_test::suite +{ + // testDebug("PRE", env, { alice, bob }, {}); + void + testDebug( + std::string const& testNumber, + jtx::Env const& env, + std::vector const& accounts, + std::vector const& ious) + { + std::cout << "DEBUG: " << testNumber << "\n"; + for (std::size_t a = 0; a < accounts.size(); ++a) + { + auto const bal = env.balance(accounts[a]); + std::cout << "account: " << accounts[a].human() << "BAL: " << bal + << "\n"; + for (std::size_t i = 0; i < ious.size(); ++i) + { + auto const iouBal = env.balance(accounts[a], ious[i]); + std::cout << "account: " << accounts[a].human() + << "IOU: " << iouBal << "\n"; + } + } + } + + 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 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 AccountID + tokenIsser(ReadView const& view, uint256 const& id) + { + auto const slep = view.read({ltURI_TOKEN, id}); + if (!slep) + return AccountID(); + return slep->getAccountID(sfIssuer); + } + + void + testEnabled(FeatureBitset features) + { + // 0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA + + 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 withRemit : {true, false}) + { + auto const amend = withRemit ? features : features - featureRemit; + + Env env{*this, amend}; + + env.fund(XRP(1000), alice, bob); + + auto const txResult = + withRemit ? ter(tesSUCCESS) : ter(temDISABLED); + + // REMIT + env(remit::remit(alice, bob), txResult); + env.close(); + } + } + + void + testDestExistsTLExists(FeatureBitset features) + { + testcase("dest exists and trustline exists"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // REMIT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + // auto const preAlice = env.balance(alice, USD.issue()); + } + + // REMIT: XAH + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + env.close(); + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(postBob == preBob + XRP(1)); + } + + // REMIT: USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, gw); + 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 + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops); + BEAST_EXPECT(postBob == preBob); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: XAH + USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, gw); + 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 + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(postBob == preBob + XRP(1)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); + + // remit with uritoken id + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + (feeReserve * 2)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + } + + void + testDestExistsTLDNE(FeatureBitset features) + { + testcase("dest exists and trustline does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // REMIT: USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(preAlice == XRP(1000)); + BEAST_EXPECT(preAliceUSD == USD(10000)); + BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBobUSD == USD(0)); + + // remit + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: XAH + USD + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(preAlice == XRP(1000)); + BEAST_EXPECT(preAliceUSD == USD(10000)); + BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBobUSD == USD(0)); + + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (2 * feeReserve)); + BEAST_EXPECT(postBob == preBob + (2 * feeReserve)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (2 * feeReserve)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (2 * feeReserve)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 3)); + BEAST_EXPECT(postBob == preBob + (feeReserve * 3)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid2) == alice.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); + + // verify xah & usd + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 3)); + BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 3)); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + } + + void + testDestDoesNotExists(FeatureBitset features) + { + testcase("dest does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), + // txResult); env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testTLDoesNotExists(FeatureBitset features) + { + testcase("trust line does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + // Env env{*this, envconfig(), amend, nullptr, + // // beast::severities::kWarning + // beast::severities::kTrace + // }; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // REMIT No Amounts No URI Tokens + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + // REMIT XAH + env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); + env.close(); + + // // REMIT XAH + USD + // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), + // txResult); env.close(); + + // // MINT + // std::string const uri(maxTokenURILength, '?'); + // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + // env(uritoken::mint(alice, uri), txResult); + // env.close(); + + // // REMIT URI XFER + // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); + // env.close(); + + // // REMIT 2 amount XAH + // env(remit::remit(alice, bob), txResult); + // env.close(); + } + + void + testWithFeats(FeatureBitset features) + { + testEnabled(features); + testDestExistsTLExists(features); + testDestExistsTLDNE(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Remit, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/jtx.h b/src/test/jtx.h index 39d4a9662..a91ae3b2f 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/impl/remit.cpp b/src/test/jtx/impl/remit.cpp new file mode 100644 index 000000000..f743848ac --- /dev/null +++ b/src/test/jtx/impl/remit.cpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { +namespace test { +namespace jtx { +namespace remit { + +Json::Value +remit(jtx::Account const& account, jtx::Account const& dest) +{ + using namespace jtx; + Json::Value jv; + jv[jss::TransactionType] = jss::Remit; + jv[jss::Account] = account.human(); + jv[jss::Destination] = dest.human(); + 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 +{ + for (std::size_t i = 0; i < token_ids_.size(); ++i) + { + jt.jv[sfURITokenIDs.jsonName] = Json::arrayValue; + jt.jv[sfURITokenIDs.jsonName][i] = 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_); + ; +} + +} // namespace remit +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index e8de02602..af5ee0412 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -88,7 +88,8 @@ class AccountSet_test : public beast::unit_test::suite flag == asfDisallowIncomingPayChan || flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline || - flag == asfTshCollect) + flag == asfTshCollect || + flag == asfDisallowIncomingRemit) { // These flags are part of the DisallowIncoming amendment // and are tested elsewhere From d97d1c75615b5c487be9a8bc42c27e8d03ee66a2 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 9 Jan 2024 07:02:22 +0100 Subject: [PATCH 04/47] nit: log --- src/ripple/app/tx/impl/Remit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index b7537da6e..766d4d915 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -72,7 +72,7 @@ Remit::preflight(PreflightContext const& ctx) STAmount const amount = sEntry.getFieldAmount(sfAmount); if (!isLegalNet(amount) || amount.signum() <= 0) { - JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: " + JLOG(ctx.j.warn()) << "Malformed transaction: Bad amount: " << amount.getFullText(); return temBAD_AMOUNT; } From 4ee8a9dd0d88523b1d6f17930584022c6efa4502 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 9 Jan 2024 07:02:33 +0100 Subject: [PATCH 05/47] add: remit.h --- src/test/jtx/remit.h | 118 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/test/jtx/remit.h diff --git a/src/test/jtx/remit.h b/src/test/jtx/remit.h new file mode 100644 index 000000000..c3a171ea1 --- /dev/null +++ b/src/test/jtx/remit.h @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace remit { + +Json::Value +remit(jtx::Account const& account, jtx::Account const& dest); + +/** Sets the optional Amount on a JTx. */ +class amts +{ +private: + std::vector amts_; + +public: + explicit amts(std::vector 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 token_ids_; + +public: + explicit token_ids(std::vector 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_; + +public: + explicit uri(std::string const& uri) : uri_(uri) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +} // namespace remit + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif // RIPPLE_TEST_JTX_REMIT_H_INCLUDED \ No newline at end of file From aee66c276da5e9dacd9f70a63b85c8681de26328 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 9 Jan 2024 07:03:16 +0100 Subject: [PATCH 06/47] add test: preflight / doapply --- src/test/app/Remit_test.cpp | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 47b1f4c6d..885eede4f 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -169,6 +169,66 @@ struct Remit_test : public beast::unit_test::suite } } + void + testPreflightInvalid(FeatureBitset features) + { + testcase("preflight invalid"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + //---------------------------------------------------------------------- + // preflight + + // temINVALID_FLAG - Invalid flags set. + // temREDUNDANT - Remit to self. + // temMALFORMED - Expected AmountEntry. + // temBAD_AMOUNT - Bad amount + // temBAD_CURRENCY - Bad currency. + // temMALFORMED - Native Currency appears more than once + // temMALFORMED - Issued Currency appears more than once. + // temMALFORMED - URI was too short/long. + // temMALFORMED - Invalid UTF8 inside MintURIToken. + // temINVALID_FLAG - Invalid URI Mint Flag + // temMALFORMED - Duplicate URITokenID. + } + + void + testDoApplyInvalid(FeatureBitset features) + { + testcase("doApply invalid"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + //---------------------------------------------------------------------- + // doApply + + // terNO_ACCOUNT + // tecNO_PERMISSION + // tecDST_TAG_NEEDED + // tecDUPLICATE + // tecDIR_FULL + // tecUNFUNDED_PAYMENT + // tecNO_ENTRY + // tecNO_PERMISSION + // tefBAD_LEDGER + // tecDIR_FULL + // tecUNFUNDED_PAYMENT + // tecINTERNAL + // tecUNFUNDED_PAYMENT + // tecINTERNAL + // tecINTERNAL + // tecINSUFFICIENT_RESERVE + + } + void testDestExistsTLExists(FeatureBitset features) { From f23c11890bca66a797b0908ed9338c50ced21697 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 9 Jan 2024 07:03:20 +0100 Subject: [PATCH 07/47] Update tts.h --- hook/tts.h | 1 + 1 file changed, 1 insertion(+) diff --git a/hook/tts.h b/hook/tts.h index 304685c31..0973fa189 100644 --- a/hook/tts.h +++ b/hook/tts.h @@ -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 From 2de6eb9e406b4117bf486f223ee92de622151fc8 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 9 Jan 2024 09:01:59 +0100 Subject: [PATCH 08/47] add ttREMIT to tsh --- src/ripple/app/hook/applyHook.h | 1 + src/ripple/app/hook/impl/applyHook.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index ade62b792..5bc721aaa 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -46,6 +46,7 @@ class HookStateMap : public std::map< using namespace ripple; static const std::map TSHAllowances = { + {ttREMIT, tshROLLBACK}, {ttPAYMENT, tshROLLBACK}, {ttESCROW_CREATE, tshROLLBACK}, {ttESCROW_FINISH, tshROLLBACK}, diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 67f97572f..69210b336 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -295,6 +295,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) } // simple two party transactions + case ttREMIT: case ttPAYMENT: case ttESCROW_CREATE: case ttCHECK_CREATE: From 1e5c896503cacd3118a99b751b15871be743f290 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 15 Jan 2024 14:08:53 +0100 Subject: [PATCH 09/47] add dest tag --- src/test/jtx/impl/remit.cpp | 12 ++++++++++-- src/test/jtx/remit.h | 14 +++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/test/jtx/impl/remit.cpp b/src/test/jtx/impl/remit.cpp index f743848ac..b11bb0bfa 100644 --- a/src/test/jtx/impl/remit.cpp +++ b/src/test/jtx/impl/remit.cpp @@ -26,13 +26,18 @@ namespace jtx { namespace remit { Json::Value -remit(jtx::Account const& account, jtx::Account const& dest) +remit( + jtx::Account const& account, + jtx::Account const& dest, + std::optional 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; } @@ -76,7 +81,10 @@ 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_; + } } } // namespace remit diff --git a/src/test/jtx/remit.h b/src/test/jtx/remit.h index c3a171ea1..34fa11f4e 100644 --- a/src/test/jtx/remit.h +++ b/src/test/jtx/remit.h @@ -31,7 +31,10 @@ namespace jtx { namespace remit { Json::Value -remit(jtx::Account const& account, jtx::Account const& dest); +remit( + jtx::Account const& account, + jtx::Account const& dest, + std::optional const& dstTag = std::nullopt); /** Sets the optional Amount on a JTx. */ class amts @@ -85,7 +88,8 @@ class token_ids std::vector token_ids_; public: - explicit token_ids(std::vector const& token_ids) : token_ids_(token_ids) + explicit token_ids(std::vector const& token_ids) + : token_ids_(token_ids) { } @@ -98,9 +102,13 @@ class uri { private: std::string uri_; + std::optional flags_; public: - explicit uri(std::string const& uri) : uri_(uri) + explicit uri( + std::string const& uri, + std::optional const& flags = std::nullopt) + : uri_(uri), flags_(flags) { } From 4babe0eb1687cd14cabe9c9ba2221107adeff8c1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 15 Jan 2024 14:09:03 +0100 Subject: [PATCH 10/47] add remit tests --- src/test/app/Remit_test.cpp | 1300 +++++++++++++++++++++++++++++------ 1 file changed, 1071 insertions(+), 229 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 885eede4f..eb40ae731 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2023 XRPL-Labs + Copyright (c) 2024 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 @@ -41,13 +41,10 @@ // A | B | Y | Y | N | N | Y | Y | // A | B | Y | Y | Y | Y | Y | Y | -/* // Destination Exists - Trust Line DNE -// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | -// A | B | Y | N | Y | N | N | N | -// A | B | Y | Y | Y | N | N | N | -// A | B | Y | Y | Y | N | Y | N | -// A | B | Y | Y | Y | N | Y | Y | +// otxn | dest | exists | native | token | tl exists | +// A | B | Y | N | Y | N | +// A | B | Y | Y | Y | N | // Destination Does Not Exist - Trust Line Exists // otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | @@ -62,18 +59,18 @@ // A | B | N | Y | N | N | Y | Y | // A | B | N | Y | Y | Y | Y | Y | -// Destination Exists - Trust Line DNE -// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | -// A | B | N | N | Y | N | N | N | -// A | B | N | Y | Y | N | N | N | -// A | B | N | Y | Y | N | Y | N | -// A | B | N | Y | Y | N | Y | Y | -*/ +// Destination DNE - Trust Line DNE +// otxn | dest | exists | native | token | tl exists | +// A | B | Y | N | Y | N | +// A | B | Y | Y | Y | N | + -// Round Robin Test -// Multiple in one Ledger Test -// Forward and Backward remits +// Round Robin Test - A -> B -> C +// - Multiple in one Ledger Test +// - Forward and Backward remits // Compute a uri token id inline with the send (a token that is being minted) +// URITokenMint: sfDigest +// URITokenMint: sfFlags namespace ripple { namespace test { @@ -139,6 +136,19 @@ struct Remit_test : public beast::unit_test::suite return slep->getAccountID(sfIssuer); } + 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); + } + void testEnabled(FeatureBitset features) { @@ -179,21 +189,116 @@ struct Remit_test : public beast::unit_test::suite // setup env auto const alice = Account("alice"); auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + Env env{*this, features}; + env.fund(XRP(1000), alice, bob, gw); + env.close(); //---------------------------------------------------------------------- // preflight // temINVALID_FLAG - Invalid flags set. + { + env(remit::remit(alice, bob), + txflags(tfBurnable), + ter(temINVALID_FLAG)); + env.close(); + } + // temREDUNDANT - Remit to self. + { + env(remit::remit(alice, alice), ter(temREDUNDANT)); + env.close(); + } + // temMALFORMED - Expected AmountEntry. + { + auto tx = remit::remit(alice, bob); + std::vector const amts_ = {XRP(1)}; + auto& ja = tx[sfAmounts.getJsonName()]; + for (std::size_t i = 0; i < amts_.size(); ++i) + { + ja[i][sfGenesisMint.jsonName] = Json::Value{}; + ja[i][sfGenesisMint.jsonName][jss::Amount] = + amts_[i].getJson(JsonOptions::none); + ja[i][sfGenesisMint.jsonName][jss::Destination] = bob.human(); + } + tx[sfAmounts.jsonName] = ja; + env(tx, ter(temMALFORMED)); + env.close(); + } + // temBAD_AMOUNT - Bad amount + { + env(remit::remit(alice, bob), + remit::amts({XRP(-1)}), + ter(temBAD_AMOUNT)); + env.close(); + } + // temBAD_CURRENCY - Bad currency. + { + auto const bXAH = gw["XaH"]; + env(remit::remit(alice, bob), + remit::amts({bXAH(100)}), + ter(temBAD_CURRENCY)); + env.close(); + } + // temMALFORMED - Native Currency appears more than once + { + env(remit::remit(alice, bob), + remit::amts({XRP(1), XRP(1)}), + ter(temMALFORMED)); + env.close(); + } + // temMALFORMED - Issued Currency appears more than once. - // temMALFORMED - URI was too short/long. + { + env(remit::remit(alice, bob), + remit::amts({USD(1), USD(1)}), + ter(temMALFORMED)); + env.close(); + } + + // temMALFORMED - URI was too short/long. (short) + { + std::string const uri(0, '?'); + env(remit::remit(alice, bob), remit::uri(uri), ter(temMALFORMED)); + env.close(); + } + + // temMALFORMED - URI was too short/long. (long) + { + std::string const uri(maxTokenURILength + 1, '?'); + env(remit::remit(alice, bob), remit::uri(uri), ter(temMALFORMED)); + env.close(); + } + // temMALFORMED - Invalid UTF8 inside MintURIToken. + // temINVALID_FLAG - Invalid URI Mint Flag + { + std::string const uri(maxTokenURILength, '?'); + env(remit::remit(alice, bob), + remit::uri(uri, tfAllowXRP), + ter(temINVALID_FLAG)); + env.close(); + } + // temMALFORMED - Duplicate URITokenID. + { + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid1), strHex(tid2)}), + ter(temMALFORMED)); + env.close(); + } } void @@ -206,27 +311,270 @@ struct Remit_test : public beast::unit_test::suite // setup env auto const alice = Account("alice"); auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; //---------------------------------------------------------------------- // doApply - // terNO_ACCOUNT - // tecNO_PERMISSION + // terNO_ACCOUNT - account doesnt exist + // { + // auto const carol = Account("carol"); + // env.memoize(carol); + // env(remit::remit(carol, bob), ter(terNO_ACCOUNT)); + // env.close(); + // } + + // tecNO_PERMISSION - lsfDisallowIncomingRemit + // see testDisallowIncoming + // tecDST_TAG_NEEDED + // see testDstTag + // tecDUPLICATE - // tecDIR_FULL - // tecUNFUNDED_PAYMENT + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env(remit::remit(alice, bob), + remit::uri(uri), + ter(tecDUPLICATE)); + env.close(); + } + + // tecDIR_FULL - account directory full + // DA: impossible test + // tecNO_ENTRY - // tecNO_PERMISSION - // tefBAD_LEDGER - // tecDIR_FULL + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tecNO_ENTRY)); + env.close(); + } + + // tecINTERNAL - not actually a uritoken. + // DA: impossible test + + // tecNO_PERMISSION - not owned by sender. + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(bob, uri); + env(uritoken::mint(bob, uri), ter(tesSUCCESS)); + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tecNO_PERMISSION)); + env.close(); + } + + // tefBAD_LEDGER - invalid owner directory + { + + } + + // tecDIR_FULL - destination directory full + // DA: impossible test + // tecUNFUNDED_PAYMENT - // tecINTERNAL + { + 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(remit::remit(alice, bob), + remit::amts({USD(1001)}), + ter(tecUNFUNDED_PAYMENT)); + env.close(); + } + + // tecINTERNAL - negative XRP + // DA: impossible test + // tecUNFUNDED_PAYMENT + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + env(remit::remit(alice, bob), + remit::amts({XRP(1001)}), + ter(tecUNFUNDED_PAYMENT)); + env.close(); + } + // tecINTERNAL + // DA: impossible test + // tecINTERNAL + // DA: impossible test + // tecINSUFFICIENT_RESERVE + { + Env env{*this, features}; + env.fund(XRP(250), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + env(remit::remit(alice, bob), + remit::uri(uri), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + } + } + + 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"); + { + // Make a remit where dst disallows XRP + Env env(*this, features - featureDepositAuth); + env.fund(XRP(10000), alice, bob); + env(fset(bob, asfDisallowXRP)); + + env(remit::remit(alice, bob), + remit::amts({ XRP(1) }), + ter(tesSUCCESS)); + + } + { + // Make a remit where dst disallows XRP. Ignore that flag, + // since it's just advisory. + Env env{*this, features}; + env.fund(XRP(10000), alice, bob); + env(fset(bob, asfDisallowXRP)); + env(remit::remit(alice, bob), + remit::amts({ XRP(1) }), + ter(tesSUCCESS)); + } + } + + void + testDstTag(FeatureBitset features) + { + // auth amount defaults to balance if not present + testcase("Dst Tag"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(10000), alice, bob); + env(fset(bob, asfRequireDest)); + { + env(remit::remit(alice, bob), + remit::amts({ XRP(1) }), + ter(tecDST_TAG_NEEDED)); + } + { + env(remit::remit(alice, bob, 1), + remit::amts({ XRP(1) }), + ter(tesSUCCESS)); + } + } + + void + testDisallowIncoming(FeatureBitset features) + { + testcase("disallow incoming"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + { + Env env{*this, features - featureRemit}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env(fset(alice, asfDisallowIncomingRemit)); + env.close(); + auto const sle = env.le(alice); + uint32_t flags = sle->getFlags(); + BEAST_EXPECT(!(flags & lsfDisallowIncomingRemit)); + } + + // setup env + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + Env env{*this, features}; + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + // set flag on bob only + env(fset(bob, asfDisallowIncomingPayChan)); + env.close(); + + // remit from alice to bob is disallowed + { + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tecNO_PERMISSION)); + } + + // set flag on alice also + env(fset(alice, asfDisallowIncomingPayChan)); + env.close(); + + // remit from bob to alice is now disallowed + { + env(remit::remit(bob, alice), + remit::amts({XRP(1)}), + ter(tecNO_PERMISSION)); + } + + // remove flag from bob + env(fclear(bob, asfDisallowIncomingPayChan)); + env.close(); + + // now remit between alice and bob allowed + { + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + } + // a remit from carol to alice isn't allowed + { + env(remit::remit(carol, alice), + remit::amts({XRP(1)}), + ter(tecNO_PERMISSION)); + } + + // remove flag from alice + env(fclear(alice, asfDisallowIncomingPayChan)); + env.close(); + + // now a remit from carol to alice is allowed + { + env(remit::remit(carol, alice), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + } } void @@ -239,17 +587,12 @@ struct Remit_test : public beast::unit_test::suite // REMIT { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - // Env env{*this, envconfig(), amend, nullptr, - // // beast::severities::kWarning - // beast::severities::kTrace - // }; - auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -261,13 +604,12 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -286,15 +628,14 @@ struct Remit_test : public beast::unit_test::suite // REMIT: USD { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -329,15 +670,14 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH + USD { // setup env - auto const alice = Account("alice"); + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -372,13 +712,12 @@ struct Remit_test : public beast::unit_test::suite // REMIT: URITOKEN XFER { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -409,13 +748,12 @@ struct Remit_test : public beast::unit_test::suite // REMIT: URITOKEN MINT { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -437,14 +775,13 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH + URITOKEN XFER { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -485,16 +822,15 @@ struct Remit_test : public beast::unit_test::suite // REMIT: USD + URITOKEN XFER { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -545,16 +881,15 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH/USD + URITOKEN XFER { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -606,14 +941,13 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH + URITOKEN XFER + URITOKEN MINT { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); env.close(); @@ -664,16 +998,15 @@ struct Remit_test : public beast::unit_test::suite // REMIT: USD + URITOKEN XFER + URITOKEN MINT { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -734,16 +1067,15 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice, bob); @@ -814,16 +1146,61 @@ struct Remit_test : public beast::unit_test::suite // REMIT: USD { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; - Env env{*this, features}; + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(preAlice == XRP(1000)); + BEAST_EXPECT(preAliceUSD == USD(10000)); + BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBobUSD == USD(0)); + + // remit + env(remit::remit(alice, bob), + remit::amts({USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + // REMIT: XAH + USD + { + // setup env + Env env{*this, features}; auto const feeDrops = env.current()->fees().base; auto const feeReserve = env.current()->fees().increment; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.fund(XRP(1000), alice, bob, gw); env.close(); env.trust(USD(100000), alice); @@ -841,6 +1218,111 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(preBob == XRP(1000)); BEAST_EXPECT(preBobUSD == USD(0)); + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + } + + void + testDestDNETLDNE(FeatureBitset features) + { + testcase("dest does not exist and trustline does not exist"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // REMIT + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(bob); + + env.fund(XRP(1000), alice); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve); + BEAST_EXPECT(postBob == preBob + accountReserve); + } + + // REMIT: XAH + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(bob); + + env.fund(XRP(1000), alice); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + env.close(); + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve); + } + + // REMIT: USD + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.memoize(bob); + + env.fund(XRP(1000), alice, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + // remit env(remit::remit(alice, bob), remit::amts({USD(1)}), @@ -852,74 +1334,209 @@ struct Remit_test : public beast::unit_test::suite auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + feeReserve); + + BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(postBob == preBob + accountReserve + increment); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); } - // REMIT: XAH + USD + // REMIT: XAH + USD + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.memoize(bob); + + env.fund(XRP(1000), alice, gw); + env.close(); + env.trust(USD(100000), alice); + env.close(); + env(pay(gw, alice, USD(10000))); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); + auto const postBob = env.balance(bob); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve - increment); + BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve + increment); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + } + + // REMIT: URITOKEN XFER + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(bob); + + env.fund(XRP(1000), alice); + env.close(); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); + + // setup + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit with uritoken id + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // validate + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(postBob == preBob + accountReserve + increment); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: URITOKEN MINT + { + // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(bob); + + env.fund(XRP(1000), alice); + env.close(); + + // setup + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); + env.close(); + + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(postBob == preBob + accountReserve + increment); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER { // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; - Env env{*this, features}; - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); - env.fund(XRP(1000), alice, bob, gw); - env.close(); - env.trust(USD(100000), alice); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.memoize(bob); + + env.fund(XRP(1000), alice); env.close(); - env(pay(gw, alice, USD(10000))); + + // mint uri token + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env.close(); - // setup auto const preAlice = env.balance(alice); - auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); - auto const preBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(preAlice == XRP(1000)); - BEAST_EXPECT(preAliceUSD == USD(10000)); - BEAST_EXPECT(preBob == XRP(1000)); - BEAST_EXPECT(preBobUSD == USD(0)); - // remit + // remit xah + uritoken id env(remit::remit(alice, bob), - remit::amts({XRP(1), USD(1)}), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid)}), ter(tesSUCCESS)); env.close(); - // validate + // verify uri transfer + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // verify xah auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve - increment); + BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve + increment); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); } // REMIT: USD + URITOKEN XFER { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; + env.memoize(bob); - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - - env.fund(XRP(1000), alice, bob, gw); + env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); @@ -950,13 +1567,13 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); - // verify xah & usd + // verify usd auto const postAlice = env.balance(alice); auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - (2 * feeReserve)); - BEAST_EXPECT(postBob == preBob + (2 * feeReserve)); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (increment * 2) - accountReserve); + BEAST_EXPECT(postBob == preBob + (increment * 2) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -968,17 +1585,18 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH/USD + URITOKEN XFER { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; + env.memoize(bob); - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - - env.fund(XRP(1000), alice, bob, gw); + env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); @@ -1015,8 +1633,8 @@ struct Remit_test : public beast::unit_test::suite auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (2 * feeReserve)); - BEAST_EXPECT(postBob == preBob + XRP(1) + (2 * feeReserve)); + postAlice == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 2) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1025,20 +1643,83 @@ struct Remit_test : public beast::unit_test::suite env.close(); } - // REMIT: USD + URITOKEN XFER + URITOKEN MINT + // REMIT: XAH + URITOKEN XFER + URITOKEN MINT { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + auto const alice = Account("alice"); auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; + env.memoize(bob); - Env env{*this, features}; + env.fund(XRP(1000), alice); + env.close(); + + // mint uri token + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({strHex(tid1)}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + + // verify uri mint + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // verify uri transfer + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + std::cout << "alice owner: " << ownerDirCount(*env.current(), alice) << "\n"; + std::cout << "bob owner: " << ownerDirCount(*env.current(), bob) << "\n"; + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + + // verify xah + testDebug("POST", env, { alice, bob }, {}); + auto const postAlice = env.balance(alice); + auto const postBob = env.balance(bob); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 2) + accountReserve); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // REMIT: USD + URITOKEN XFER + URITOKEN MINT + { + // setup env + Env env{*this, features}; auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); - env.fund(XRP(1000), alice, bob, gw); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + env.memoize(bob); + + env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); @@ -1052,7 +1733,9 @@ struct Remit_test : public beast::unit_test::suite env.close(); auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); // remit xah/usd + uritoken id + uritoken mint std::string const uri2(maxTokenURILength - 1, '?'); @@ -1073,14 +1756,19 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); - // verify xah + // verify usd auto const postAlice = env.balance(alice); + auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 3)); - BEAST_EXPECT(postBob == preBob + (feeReserve * 3)); + auto const postBobUSD = env.balance(bob, USD.issue()); + BEAST_EXPECT(postAlice == preAlice - feeDrops - (increment * 3) - accountReserve); + BEAST_EXPECT(postBob == preBob + (increment * 3) + accountReserve); + BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); + BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1091,17 +1779,18 @@ struct Remit_test : public beast::unit_test::suite // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT { // setup env + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + auto const increment = env.current()->fees().increment; + auto const accountReserve = env.current()->fees().accountReserve(0); + auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); auto const USD = gw["USD"]; + env.memoize(bob); - Env env{*this, features}; - - auto const feeDrops = env.current()->fees().base; - auto const feeReserve = env.current()->fees().increment; - - env.fund(XRP(1000), alice, bob, gw); + env.fund(XRP(1000), alice, gw); env.close(); env.trust(USD(100000), alice); env.close(); @@ -1148,8 +1837,8 @@ struct Remit_test : public beast::unit_test::suite auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 3)); - BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 3)); + postAlice == preAlice - XRP(1) - feeDrops - (increment * 3) - accountReserve); + BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 3) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1161,117 +1850,270 @@ struct Remit_test : public beast::unit_test::suite } void - testDestDoesNotExists(FeatureBitset features) + testGateway(FeatureBitset features) { - testcase("dest does not exist"); - using namespace jtx; - using namespace std::literals::chrono_literals; + testcase("Gateway"); + using namespace test::jtx; + using namespace std::literals; - // setup env - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; + struct TestAccountData + { + Account src; + Account dst; + bool hasTrustline; + bool negative; + }; + + std::array gwSrcTests = {{ + // src > dst && src > issuer && dst no trustline + {Account("gw0"), Account{"alice2"}, false, true}, + // // src < dst && src < issuer && dst no trustline + {Account("gw1"), Account{"carol0"}, false, false}, + // // // // dst > src && dst > issuer && dst no trustline + {Account("gw0"), Account{"dan1"}, false, true}, + // // // // dst < src && dst < issuer && dst no trustline + {Account("gw1"), Account{"bob0"}, false, false}, + // // // src > dst && src > issuer && dst has trustline + {Account("gw0"), Account{"alice2"}, true, true}, + // // // src < dst && src < issuer && dst has trustline + {Account("gw1"), Account{"carol0"}, true, false}, + // // // dst > src && dst > issuer && dst has trustline + {Account("gw0"), Account{"dan1"}, true, true}, + // // // dst < src && dst < issuer && dst has trustline + {Account("gw1"), Account{"bob0"}, true, false}, + }}; + + for (auto const& t : gwSrcTests) + { + Env env{*this, features}; + auto const USD = t.src["USD"]; + env.fund(XRP(5000), t.dst, t.src); + env.close(); - Env env{*this, features}; - // Env env{*this, envconfig(), amend, nullptr, - // // beast::severities::kWarning - // beast::severities::kTrace - // }; + if (t.hasTrustline) + env.trust(USD(100000), t.dst); - env.fund(XRP(1000), alice, bob, gw); - env.close(); - env.trust(USD(100000), alice); - env.close(); - env(pay(gw, alice, USD(10000))); - env.close(); + env.close(); - // REMIT No Amounts No URI Tokens - env(remit::remit(alice, bob), ter(tesSUCCESS)); - env.close(); + if (t.hasTrustline) + env(pay(t.src, t.dst, USD(10000))); - // REMIT XAH - env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); - env.close(); + env.close(); - // // REMIT XAH + USD - // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), - // txResult); env.close(); + auto const preAmount = t.hasTrustline ? 10000 : 0; + auto const preDst = lineBalance(env, t.dst, t.src, USD); - // // MINT - // std::string const uri(maxTokenURILength, '?'); - // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; - // env(uritoken::mint(alice, uri), txResult); - // env.close(); + // issuer can remit + env(remit::remit(t.src, t.dst), + remit::amts({ USD(100) }), + ter(tesSUCCESS)); + env.close(); - // // REMIT URI XFER - // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); - // env.close(); + auto const postAmount = t.hasTrustline ? 10100 : 100; + BEAST_EXPECT(preDst == (t.negative ? -USD(preAmount) : USD(preAmount))); + BEAST_EXPECT( + lineBalance(env, t.dst, t.src, USD) == + (t.negative ? -USD(postAmount) : USD(postAmount))); + BEAST_EXPECT(lineBalance(env, t.src, t.src, USD) == USD(0)); + } - // // REMIT 2 amount XAH - // env(remit::remit(alice, bob), txResult); - // env.close(); + // std::array gwDstTests = {{ + // // // // src > dst && src > issuer && dst has trustline + // {Account("alice2"), Account{"gw0"}, true, true}, + // // // // src < dst && src < issuer && dst has trustline + // {Account("carol0"), Account{"gw1"}, true, false}, + // // // // // dst > src && dst > issuer && dst has trustline + // {Account("dan1"), Account{"gw0"}, true, true}, + // // // // // dst < src && dst < issuer && dst has trustline + // {Account("bob0"), Account{"gw1"}, true, false}, + // }}; + + // for (auto const& t : gwDstTests) + // { + // Env env{*this, features}; + // auto const USD = t.dst["USD"]; + // env.fund(XRP(5000), t.src, t.dst); + // env.close(); + + // env.trust(USD(100000), t.src); + // env.close(); + + // env(pay(t.dst, t.src, USD(10000))); + // env.close(); + + // // src can create paychan to dst/issuer + // auto const pk = t.src.pk(); + // auto const settleDelay = 100s; + // auto const chan = channel(t.src, t.dst, env.seq(t.src)); + // env(paychan::create(t.src, t.dst, USD(1000), settleDelay, pk)); + // env.close(); + + // // dst/gw can claim paychan + // auto const preSrc = lineBalance(env, t.src, t.dst, USD); + // auto chanBal = channelBalance(*env.current(), chan); + // auto chanAmt = channelAmount(*env.current(), chan); + // auto const delta = USD(500); + // auto const reqBal = chanBal + delta; + // auto const authAmt = reqBal + USD(100); + // auto const sig = + // signClaimIOUAuth(t.src.pk(), t.src.sk(), chan, authAmt); + // env(paychan::claim( + // t.dst, chan, reqBal, authAmt, Slice(sig), t.src.pk())); + // env.close(); + // auto const preAmount = 10000; + // BEAST_EXPECT( + // preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); + // auto const postAmount = 9500; + // BEAST_EXPECT( + // lineBalance(env, t.src, t.dst, USD) == + // (t.negative ? -USD(postAmount) : USD(postAmount))); + // BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); + // } } void - testTLDoesNotExists(FeatureBitset features) + testRequireAuth(FeatureBitset features) { - testcase("trust line does not exist"); - using namespace jtx; - using namespace std::literals::chrono_literals; + testcase("Require Auth"); + using namespace test::jtx; + using namespace std::literals; - // setup env auto const alice = Account("alice"); auto const bob = Account("bob"); - auto const gw = Account("gw"); + auto const carol = Account("carol"); + auto const gw = Account{"gateway"}; auto const USD = gw["USD"]; - Env env{*this, features}; - // Env env{*this, envconfig(), amend, nullptr, - // // beast::severities::kWarning - // beast::severities::kTrace - // }; + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; - env.fund(XRP(1000), alice, bob, gw); - env.close(); - env.trust(USD(100000), alice); - env.close(); - env(pay(gw, alice, USD(10000))); - env.close(); + // test asfRequireAuth + { + Env env{*this, features}; + env.fund(XRP(1000), alice, bob, gw); + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(gw, aliceUSD(10000)), txflags(tfSetfAuth)); + env(trust(alice, USD(10000))); + env.close(); + env(pay(gw, alice, USD(1000))); + env.close(); - // REMIT No Amounts No URI Tokens - env(remit::remit(alice, bob), ter(tesSUCCESS)); - env.close(); + // alice cannot remit because bob's trustline is not authorized + // all parties must be authorized + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tecNO_AUTH)); + env.close(); - // REMIT XAH - env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); - env.close(); + env(trust(gw, bobUSD(10000)), txflags(tfSetfAuth)); + env(trust(bob, USD(10000))); + env.close(); + env(pay(gw, bob, USD(1000))); + env.close(); + + // alice can now remit because bob's trustline is authorized + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + } + } + + void + testTLFreeze(FeatureBitset features) + { + testcase("Trustline 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"]; + + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + // test Global Freeze + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice); + env.trust(USD(100000), bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + env(fset(gw, asfGlobalFreeze)); + env.close(); + + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tecFROZEN)); + env.close(); + + env(fclear(gw, asfGlobalFreeze)); + env.close(); + + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + } + + // test Individual Freeze + { + // Env Setup + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env(trust(alice, USD(100000))); + env(trust(bob, USD(100000))); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); - // // REMIT XAH + USD - // env(remit::remit(alice, bob), remit::amts({ XRP(1), USD(1) }), - // txResult); env.close(); + // set freeze on alice trustline + env(trust(gw, USD(100000), alice, tfSetFreeze)); + env.close(); - // // MINT - // std::string const uri(maxTokenURILength, '?'); - // std::string const tid{strHex(uritoken::tokenid(alice, uri))}; - // env(uritoken::mint(alice, uri), txResult); - // env.close(); + // remit fails - frozen trustline + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tecFROZEN)); + env.close(); - // // REMIT URI XFER - // env(remit::remit(alice, bob), remit::token_ids({ tid }), txResult); - // env.close(); + // clear freeze on alice trustline + env(trust(gw, USD(100000), alice, tfClearFreeze)); + env.close(); - // // REMIT 2 amount XAH - // env(remit::remit(alice, bob), txResult); - // env.close(); + // remit success + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + } } void testWithFeats(FeatureBitset features) { - testEnabled(features); - testDestExistsTLExists(features); - testDestExistsTLDNE(features); + // testEnabled(features); + // testPreflightInvalid(features); + // testDoApplyInvalid(features); + // testDisallowXRP(features); + // testDstTag(features); + // testDisallowIncoming(features); + // testDestExistsTLExists(features); + // testDestExistsTLDNE(features); + // testDestDNETLDNE(features); + testGateway(features); + // testRequireAuth(features); + // testTLFreeze(features); } public: From 241e27ebaef018f7825fca1f6ef363d4abd6bd5d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 1 Feb 2024 11:50:36 +0100 Subject: [PATCH 11/47] dump --- src/ripple/app/tx/impl/Remit.cpp | 8 +- src/test/app/Remit_test.cpp | 369 +++++++++++++++++++++++++++---- src/test/rpc/AccountTx_test.cpp | 9 + 3 files changed, 334 insertions(+), 52 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index b7537da6e..bdace0911 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -311,7 +311,7 @@ Remit::doApply() { JLOG(j.warn()) << "Remit: one or more uritokens did not exist " "on the source account."; - return tecUNFUNDED_PAYMENT; + return tecNO_ENTRY; } // is it a uritoken? @@ -319,7 +319,7 @@ Remit::doApply() { JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was " "not actually a uritoken."; - return tecNO_ENTRY; + return tecINTERNAL; } // is it our uritoken? @@ -428,8 +428,8 @@ Remit::doApply() // if the target trustline doesn't exist we need to create it and // pay its reserve - if (!sb.exists(keylet::line( - issuerAccID == dstAccID ? srcAccID : dstAccID, + if (issuerAccID != dstAccID && !sb.exists(keylet::line( + dstAccID, issuerAccID, amount.getCurrency()))) nativeRemit += objectReserve; diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 3caa3f2cf..795291b3f 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -72,6 +72,44 @@ // URITokenMint: sfDigest // URITokenMint: sfFlags + +// Empty +// 0. Dest Tag? or Dest Disallow Remit? +// 1. Does Dest Exist +// 1.1 Create w/ featureXahauGenesis +// 1.2 Create w/out featureXahauGenesis + +// XAH +// 1. Available Funds + +// Token +// 1. Is Issuer Send/Recv +// 2. Is Trust Allowed +// 3. Transfer Rate +// 4. Available Funds +// 5. Dest Has Trustline + +// Finish All +// 1. Has enough XAH +// 2. Subtract From Sender +// 3. Add to Dest +// 4. Check Reserves + + +// URITokenMint +// 1. Check for duplicate +// 2. Update Fields +// 3. Add to Dest +// 3. Add Deletion Blocker on Account + +// URITokenTransfer +// 1. Erase Sell Offers +// 2. Pay Reserve +// 3. Remove from Seller Dir +// 4. Add to Dest Dir +// 5. Update Owner + + namespace ripple { namespace test { struct Remit_test : public beast::unit_test::suite @@ -1918,58 +1956,47 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(lineBalance(env, t.src, t.src, USD) == USD(0)); } - // std::array gwDstTests = {{ - // // // // src > dst && src > issuer && dst has trustline - // {Account("alice2"), Account{"gw0"}, true, true}, - // // // // src < dst && src < issuer && dst has trustline - // {Account("carol0"), Account{"gw1"}, true, false}, - // // // // // dst > src && dst > issuer && dst has trustline - // {Account("dan1"), Account{"gw0"}, true, true}, - // // // // // dst < src && dst < issuer && dst has trustline - // {Account("bob0"), Account{"gw1"}, true, false}, - // }}; - - // for (auto const& t : gwDstTests) - // { - // Env env{*this, features}; - // auto const USD = t.dst["USD"]; - // env.fund(XRP(5000), t.src, t.dst); - // env.close(); + std::array gwDstTests = {{ + // // // src > dst && src > issuer && dst has trustline + {Account("alice2"), Account{"gw0"}, true, true}, + // // // src < dst && src < issuer && dst has trustline + {Account("carol0"), Account{"gw1"}, true, false}, + // // // // dst > src && dst > issuer && dst has trustline + {Account("dan1"), Account{"gw0"}, true, true}, + // // // // dst < src && dst < issuer && dst has trustline + {Account("bob0"), Account{"gw1"}, true, false}, + }}; - // env.trust(USD(100000), t.src); - // env.close(); + for (auto const& t : gwDstTests) + { + Env env{*this, features}; + auto const USD = t.dst["USD"]; + env.fund(XRP(5000), t.src, t.dst); + env.close(); - // env(pay(t.dst, t.src, USD(10000))); - // env.close(); + env.trust(USD(100000), t.src); + env.close(); - // // src can create paychan to dst/issuer - // auto const pk = t.src.pk(); - // auto const settleDelay = 100s; - // auto const chan = channel(t.src, t.dst, env.seq(t.src)); - // env(paychan::create(t.src, t.dst, USD(1000), settleDelay, pk)); - // env.close(); + env(pay(t.dst, t.src, USD(10000))); + env.close(); - // // dst/gw can claim paychan - // auto const preSrc = lineBalance(env, t.src, t.dst, USD); - // auto chanBal = channelBalance(*env.current(), chan); - // auto chanAmt = channelAmount(*env.current(), chan); - // auto const delta = USD(500); - // auto const reqBal = chanBal + delta; - // auto const authAmt = reqBal + USD(100); - // auto const sig = - // signClaimIOUAuth(t.src.pk(), t.src.sk(), chan, authAmt); - // env(paychan::claim( - // t.dst, chan, reqBal, authAmt, Slice(sig), t.src.pk())); - // env.close(); - // auto const preAmount = 10000; - // BEAST_EXPECT( - // preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); - // auto const postAmount = 9500; - // BEAST_EXPECT( - // lineBalance(env, t.src, t.dst, USD) == - // (t.negative ? -USD(postAmount) : USD(postAmount))); - // BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); - // } + auto const preSrc = lineBalance(env, t.src, t.dst, USD); + + // issuer can remit to issuer + env(remit::remit(t.src, t.dst), + remit::amts({ USD(100) }), + ter(tesSUCCESS)); + env.close(); + + auto const preAmount = 10000; + BEAST_EXPECT( + preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); + auto const postAmount = 9900; + BEAST_EXPECT( + lineBalance(env, t.src, t.dst, USD) == + (t.negative ? -USD(postAmount) : USD(postAmount))); + BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); + } } void @@ -2100,6 +2127,250 @@ struct Remit_test : public beast::unit_test::suite } } + void + validateNoRipple( + jtx::Env& env, + jtx::Account const& acct, + jtx::Account const& peer, + bool const& result) + { + Json::Value params; + params[jss::account] = acct.human(); + params[jss::peer] = peer.human(); + + auto lines = env.rpc("json", "account_lines", to_string(params)); + std::cout << "RESULT: " << lines << "\n"; + auto const& line = lines[jss::result][jss::lines][0u]; + BEAST_EXPECT(line[jss::no_ripple_peer].asBool() == result); + } + + void + testRippling(FeatureBitset features) + { + testcase("rippling"); + using namespace test::jtx; + using namespace std::literals; + + // // rippling enabled + // { + // Env env{*this, features}; + // auto const alice = Account("alice"); + // auto const bob = Account("bob"); + // auto const carol = Account("carol"); + // auto const USDA = alice["USD"]; + // auto const USDB = bob["USD"]; + // auto const USDC = carol["USD"]; + // env.fund(XRP(10000), alice, bob, carol); + // env.close(); + + // // alice trusts USD bob & carol + // env(trust(alice, USDB(100))); + // env(trust(alice, USDC(100))); + // // bob trusts USD alice & carol + // env(trust(bob, USDA(100))); + // env(trust(bob, USDC(100))); + // // carol trusts USD alice & bob + // env(trust(carol, USDA(100))); + // env(trust(carol, USDB(100))); + // env.close(); + // // alice pays bob USDA + // env(pay(alice, bob, USDA(10))); + // // carol pays alice USDC + // env(pay(carol, alice, USDC(10))); + // env.close(); + + // /* + // aliceUSDABal: 0/USD(alice) + // aliceUSDBBal: -10/USD(bob) + // aliceUSDCBal: 10/USD(carol) + // bobUSDABal: 10/USD(alice) + // bobUSDBBal: 0/USD(bob) + // bobUSDCBal: 0/USD(carol) + // carolUSDABal: -10/USD(alice) + // carolUSDBBal: 0/USD(bob) + // carolUSDCBal: 0/USD(carol) + // */ + + // testDebug("PRE", env, { alice, bob, carol }, { USDA, USDB, USDC }); + // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); + // BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); + // BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); + // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); + // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + + // validateNoRipple(env, alice, bob, false); + // validateNoRipple(env, alice, carol, false); + // validateNoRipple(env, bob, alice, false); + // validateNoRipple(env, bob, carol, false); + // validateNoRipple(env, carol, alice, false); + // validateNoRipple(env, carol, bob, false); + + // // alice cannot create to carol with USDB + // env(remit::remit(alice, carol), + // remit::amts({USDB(10)}), + // ter(tecUNFUNDED_PAYMENT)); + // env.close(); + + // // negative direction destination + // // bob can remit to carol with USDA + // env(remit::remit(bob, carol), + // remit::amts({USDA(10)}), + // ter(tesSUCCESS)); + // env.close(); + + // testDebug("POST", env, { alice, bob, carol }, { USDA, USDB, USDC }); + // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + // } + + // rippling not enabled + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const USDA = alice["USD"]; + auto const USDB = bob["USD"]; + auto const USDC = carol["USD"]; + env.fund(XRP(10000), alice, bob, carol); + env.close(); + + // alice trusts USD bob & carol + env(trust(alice, USDB(100), bob, tfSetNoRipple)); + env(trust(alice, USDC(100), carol, tfSetNoRipple)); + // bob trusts USD alice & carol + env(trust(bob, USDA(100), alice, tfSetNoRipple)); + env(trust(bob, USDC(100), carol, tfSetNoRipple)); + // carol trusts USD alice & bob + env(trust(carol, USDA(100), alice, tfSetNoRipple)); + env(trust(carol, USDB(100), bob, tfSetNoRipple)); + env.close(); + // alice pays bob USDA + env(pay(alice, bob, USDA(10))); + // carol pays alice USDC + env(pay(carol, alice, USDC(10))); + env.close(); + + + testDebug("PRE", env, { alice, bob, carol }, { USDA, USDB, USDC }); + // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); + // BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); + // BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); + // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); + // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + + validateNoRipple(env, alice, bob, true); + validateNoRipple(env, alice, carol, true); + validateNoRipple(env, bob, alice, true); + validateNoRipple(env, bob, carol, true); + validateNoRipple(env, carol, alice, true); + validateNoRipple(env, carol, bob, true); + + // alice cannot create to carol with USDB + env(remit::remit(alice, carol), + remit::amts({USDB(10)}), + ter(tecPATH_DRY)); + env.close(); + + // negative direction destination + // bob can remit to carol with USDA + env(remit::remit(bob, carol), + remit::amts({USDA(10)}), + ter(tecPATH_DRY)); + env.close(); + + testDebug("POST", env, { alice, bob, carol }, { USDA, USDB, USDC }); + // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + // BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); + // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + } + + // // rippling + // { + // Env env{*this, features}; + // auto const alice = Account("alice"); + // auto const bob = Account("bob"); + // auto const carol = Account("carol"); + // auto const gw = Account("gw"); + // auto const USD = gw["USD"]; + // env.fund(XRP(10000), gw, alice, bob, carol); + // env.close(); + + // env.trust(USD(100), alice, bob, carol); + // env.close(); + // // alice pays bob USD + // env(pay(gw, alice, USD(10))); + // env(pay(gw, bob, USD(10))); + // env.close(); + + + // testDebug("PRE", env, { alice, bob, carol }, { USD }); + + // validateNoRipple(env, alice, gw, false); + // validateNoRipple(env, gw, alice, false); + + // // negative direction destination + // // bob can remit to carol with USDA + // env(remit::remit(bob, alice), + // remit::amts({USD(10)}), + // ter(tesSUCCESS)); + // env.close(); + + // env(remit::remit(bob, alice), + // remit::amts({USD(10)}), + // ter(tecUNFUNDED_PAYMENT)); + // env.close(); + + // testDebug("POST", env, { alice, bob, carol }, { USD }); + // } + } + + void + testURITokenMintTransferSame(FeatureBitset features) + { + testcase("uritoken mint transfer same"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + + env(remit::remit(alice, bob), + remit::uri(uri), + remit::token_ids({strHex(tid)}), + ter(tecNO_PERMISSION)); + env.close(); + } + void testWithFeats(FeatureBitset features) { @@ -2115,6 +2386,8 @@ struct Remit_test : public beast::unit_test::suite testGateway(features); // testRequireAuth(features); // testTLFreeze(features); + // testRippling(features); + // testURITokenMintTransferSame(features); } public: diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index b4e34e6c9..85dd2978d 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -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(); From b2217c14dbf35318b7a3d362c075bbe30fe880da Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 2 Feb 2024 15:27:59 +0100 Subject: [PATCH 12/47] Update Remit_test.cpp --- src/test/app/Remit_test.cpp | 548 +++++++++++++++++++----------------- src/test/jtx/impl/remit.cpp | 8 +- src/test/jtx/remit.h | 6 +- 3 files changed, 301 insertions(+), 261 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 795291b3f..ec11b2447 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -28,87 +28,6 @@ #include -// Destination Exists - Trust Line Exists -// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | -// A | B | Y | N | N | N | N | N | -// A | B | Y | Y | N | N | N | N | -// A | B | Y | N | Y | Y | N | N | -// A | B | Y | Y | Y | Y | N | N | -// A | B | Y | N | N | N | Y | N | -// A | B | Y | N | N | N | N | Y | -// A | B | Y | Y | N | N | Y | N | -// A | B | Y | Y | Y | Y | Y | N | -// A | B | Y | Y | N | N | Y | Y | -// A | B | Y | Y | Y | Y | Y | Y | - -// Destination Exists - Trust Line DNE -// otxn | dest | exists | native | token | tl exists | -// A | B | Y | N | Y | N | -// A | B | Y | Y | Y | N | - -// Destination Does Not Exist - Trust Line Exists -// otxn | dest | exists | native | token | tl exists | uris xfr | uri mint | -// A | B | N | N | N | N | N | N | -// A | B | N | Y | N | N | N | N | -// A | B | N | N | Y | Y | N | N | -// A | B | N | Y | Y | Y | N | N | -// A | B | N | N | N | N | Y | N | -// A | B | N | N | N | N | N | Y | -// A | B | N | Y | N | N | Y | N | -// A | B | N | Y | Y | Y | Y | N | -// A | B | N | Y | N | N | Y | Y | -// A | B | N | Y | Y | Y | Y | Y | - -// Destination DNE - Trust Line DNE -// otxn | dest | exists | native | token | tl exists | -// A | B | Y | N | Y | N | -// A | B | Y | Y | Y | N | - - -// Round Robin Test - A -> B -> C -// - Multiple in one Ledger Test -// - Forward and Backward remits -// Compute a uri token id inline with the send (a token that is being minted) -// URITokenMint: sfDigest -// URITokenMint: sfFlags - - -// Empty -// 0. Dest Tag? or Dest Disallow Remit? -// 1. Does Dest Exist -// 1.1 Create w/ featureXahauGenesis -// 1.2 Create w/out featureXahauGenesis - -// XAH -// 1. Available Funds - -// Token -// 1. Is Issuer Send/Recv -// 2. Is Trust Allowed -// 3. Transfer Rate -// 4. Available Funds -// 5. Dest Has Trustline - -// Finish All -// 1. Has enough XAH -// 2. Subtract From Sender -// 3. Add to Dest -// 4. Check Reserves - - -// URITokenMint -// 1. Check for duplicate -// 2. Update Fields -// 3. Add to Dest -// 3. Add Deletion Blocker on Account - -// URITokenTransfer -// 1. Erase Sell Offers -// 2. Pay Reserve -// 3. Remove from Seller Dir -// 4. Add to Dest Dir -// 5. Update Owner - namespace ripple { namespace test { @@ -187,11 +106,31 @@ struct Remit_test : public beast::unit_test::suite return STAmount(iou, 0); } + static bool + validateSequence( + jtx::Env const& env, + jtx::Account const& account, + std::uint32_t const& sequence) + { + auto const sle = env.le(keylet::account(account)); + if (sle && sle->isFieldPresent(sfSequence)) + return (*sle)[sfSequence] == sequence; + return false; + } + + static std::pair> + 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)}; + } + void testEnabled(FeatureBitset features) { - // 0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA - testcase("enabled"); using namespace jtx; using namespace std::literals::chrono_literals; @@ -331,10 +270,8 @@ struct Remit_test : public beast::unit_test::suite { std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); - std::string const uri2(maxTokenURILength - 1, '?'); - auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), - remit::token_ids({strHex(tid1), strHex(tid2)}), + remit::token_ids({strHex(tid1), strHex(tid1)}), ter(temMALFORMED)); env.close(); } @@ -565,7 +502,7 @@ struct Remit_test : public beast::unit_test::suite env.close(); // set flag on bob only - env(fset(bob, asfDisallowIncomingPayChan)); + env(fset(bob, asfDisallowIncomingRemit)); env.close(); // remit from alice to bob is disallowed @@ -576,7 +513,7 @@ struct Remit_test : public beast::unit_test::suite } // set flag on alice also - env(fset(alice, asfDisallowIncomingPayChan)); + env(fset(alice, asfDisallowIncomingRemit)); env.close(); // remit from bob to alice is now disallowed @@ -587,7 +524,7 @@ struct Remit_test : public beast::unit_test::suite } // remove flag from bob - env(fclear(bob, asfDisallowIncomingPayChan)); + env(fclear(bob, asfDisallowIncomingRemit)); env.close(); // now remit between alice and bob allowed @@ -605,7 +542,7 @@ struct Remit_test : public beast::unit_test::suite } // remove flag from alice - env(fclear(alice, asfDisallowIncomingPayChan)); + env(fclear(alice, asfDisallowIncomingRemit)); env.close(); // now a remit from carol to alice is allowed @@ -1176,7 +1113,7 @@ struct Remit_test : public beast::unit_test::suite } void - testDestExistsTLDNE(FeatureBitset features) + testDestExistsTLNotExist(FeatureBitset features) { testcase("dest exists and trustline does not exist"); using namespace jtx; @@ -1277,7 +1214,7 @@ struct Remit_test : public beast::unit_test::suite } void - testDestDNETLDNE(FeatureBitset features) + testDestNotExistTLNotExist(FeatureBitset features) { testcase("dest does not exist and trustline does not exist"); using namespace jtx; @@ -1303,6 +1240,9 @@ struct Remit_test : public beast::unit_test::suite env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); + bool const withXahau = env.current()->rules().enabled(featureXahauGenesis); + BEAST_EXPECT(validateSequence(env, bob, withXahau ? 10 : env.closed()->info().seq)); + auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve); @@ -1725,13 +1665,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); - std::cout << "alice owner: " << ownerDirCount(*env.current(), alice) << "\n"; - std::cout << "bob owner: " << ownerDirCount(*env.current(), bob) << "\n"; BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); // verify xah - testDebug("POST", env, { alice, bob }, {}); auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); BEAST_EXPECT( @@ -2139,7 +2076,6 @@ struct Remit_test : public beast::unit_test::suite params[jss::peer] = peer.human(); auto lines = env.rpc("json", "account_lines", to_string(params)); - std::cout << "RESULT: " << lines << "\n"; auto const& line = lines[jss::result][jss::lines][0u]; BEAST_EXPECT(line[jss::no_ripple_peer].asBool() == result); } @@ -2151,88 +2087,74 @@ struct Remit_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // // rippling enabled - // { - // Env env{*this, features}; - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // auto const carol = Account("carol"); - // auto const USDA = alice["USD"]; - // auto const USDB = bob["USD"]; - // auto const USDC = carol["USD"]; - // env.fund(XRP(10000), alice, bob, carol); - // env.close(); + // rippling enabled + { + Env env{*this, features}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const USDA = alice["USD"]; + auto const USDB = bob["USD"]; + auto const USDC = carol["USD"]; + env.fund(XRP(10000), alice, bob, carol); + env.close(); - // // alice trusts USD bob & carol - // env(trust(alice, USDB(100))); - // env(trust(alice, USDC(100))); - // // bob trusts USD alice & carol - // env(trust(bob, USDA(100))); - // env(trust(bob, USDC(100))); - // // carol trusts USD alice & bob - // env(trust(carol, USDA(100))); - // env(trust(carol, USDB(100))); - // env.close(); - // // alice pays bob USDA - // env(pay(alice, bob, USDA(10))); - // // carol pays alice USDC - // env(pay(carol, alice, USDC(10))); - // env.close(); + // alice trusts USD bob & carol + env(trust(alice, USDB(100))); + env(trust(alice, USDC(100))); + // bob trusts USD alice & carol + env(trust(bob, USDA(100))); + env(trust(bob, USDC(100))); + // carol trusts USD alice & bob + env(trust(carol, USDA(100))); + env(trust(carol, USDB(100))); + env.close(); + // alice pays bob USDA + env(pay(alice, bob, USDA(10))); + // carol pays alice USDC + env(pay(carol, alice, USDC(10))); + env.close(); - // /* - // aliceUSDABal: 0/USD(alice) - // aliceUSDBBal: -10/USD(bob) - // aliceUSDCBal: 10/USD(carol) - // bobUSDABal: 10/USD(alice) - // bobUSDBBal: 0/USD(bob) - // bobUSDCBal: 0/USD(carol) - // carolUSDABal: -10/USD(alice) - // carolUSDBBal: 0/USD(bob) - // carolUSDCBal: 0/USD(carol) - // */ - - // testDebug("PRE", env, { alice, bob, carol }, { USDA, USDB, USDC }); - // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); - // BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); - // BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); - // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); - // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); - - // validateNoRipple(env, alice, bob, false); - // validateNoRipple(env, alice, carol, false); - // validateNoRipple(env, bob, alice, false); - // validateNoRipple(env, bob, carol, false); - // validateNoRipple(env, carol, alice, false); - // validateNoRipple(env, carol, bob, false); - - // // alice cannot create to carol with USDB - // env(remit::remit(alice, carol), - // remit::amts({USDB(10)}), - // ter(tecUNFUNDED_PAYMENT)); - // env.close(); + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); + BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); + BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); + BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); + BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); - // // negative direction destination - // // bob can remit to carol with USDA - // env(remit::remit(bob, carol), - // remit::amts({USDA(10)}), - // ter(tesSUCCESS)); - // env.close(); + validateNoRipple(env, alice, bob, false); + validateNoRipple(env, alice, carol, false); + validateNoRipple(env, bob, alice, false); + validateNoRipple(env, bob, carol, false); + validateNoRipple(env, carol, alice, false); + validateNoRipple(env, carol, bob, false); - // testDebug("POST", env, { alice, bob, carol }, { USDA, USDB, USDC }); - // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); - // } + // alice cannot create to carol with USDB + env(remit::remit(alice, carol), + remit::amts({USDB(10)}), + ter(tecUNFUNDED_PAYMENT)); + env.close(); + + // negative direction destination + // bob can remit to carol with USDA + env(remit::remit(bob, carol), + remit::amts({USDA(10)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + } // rippling not enabled { @@ -2262,17 +2184,15 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); - - testDebug("PRE", env, { alice, bob, carol }, { USDA, USDB, USDC }); - // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); - // BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); - // BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); - // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); - // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); + BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); + BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); + BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); + BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); validateNoRipple(env, alice, bob, true); validateNoRipple(env, alice, carol, true); @@ -2288,68 +2208,28 @@ struct Remit_test : public beast::unit_test::suite env.close(); // negative direction destination - // bob can remit to carol with USDA + // bob can not remit to carol with USDA env(remit::remit(bob, carol), remit::amts({USDA(10)}), ter(tecPATH_DRY)); env.close(); - testDebug("POST", env, { alice, bob, carol }, { USDA, USDB, USDC }); - // BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(alice, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(alice, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(bob, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); - // BEAST_EXPECT(env.balance(carol, USDA) == USDA(0)); - // BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); - // BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); + BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); + BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); + BEAST_EXPECT(env.balance(bob, USDA) == USDA(10)); + BEAST_EXPECT(env.balance(bob, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(bob, USDC) == USDC(0)); + BEAST_EXPECT(env.balance(carol, USDA) == USDA(-10)); + BEAST_EXPECT(env.balance(carol, USDB) == USDB(0)); + BEAST_EXPECT(env.balance(carol, USDC) == USDC(0)); } - - // // rippling - // { - // Env env{*this, features}; - // auto const alice = Account("alice"); - // auto const bob = Account("bob"); - // auto const carol = Account("carol"); - // auto const gw = Account("gw"); - // auto const USD = gw["USD"]; - // env.fund(XRP(10000), gw, alice, bob, carol); - // env.close(); - - // env.trust(USD(100), alice, bob, carol); - // env.close(); - // // alice pays bob USD - // env(pay(gw, alice, USD(10))); - // env(pay(gw, bob, USD(10))); - // env.close(); - - - // testDebug("PRE", env, { alice, bob, carol }, { USD }); - - // validateNoRipple(env, alice, gw, false); - // validateNoRipple(env, gw, alice, false); - - // // negative direction destination - // // bob can remit to carol with USDA - // env(remit::remit(bob, alice), - // remit::amts({USD(10)}), - // ter(tesSUCCESS)); - // env.close(); - - // env(remit::remit(bob, alice), - // remit::amts({USD(10)}), - // ter(tecUNFUNDED_PAYMENT)); - // env.close(); - - // testDebug("POST", env, { alice, bob, carol }, { USD }); - // } } void - testURITokenMintTransferSame(FeatureBitset features) + testURIToken(FeatureBitset features) { - testcase("uritoken mint transfer same"); + testcase("uritoken"); using namespace test::jtx; using namespace std::literals; @@ -2361,33 +2241,186 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - std::string const uri(maxTokenURILength, '?'); - auto const tid = uritoken::tokenid(alice, uri); + // cannot mint and transfer same token + { + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); - env(remit::remit(alice, bob), - remit::uri(uri), - remit::token_ids({strHex(tid)}), - ter(tecNO_PERMISSION)); - env.close(); + env(remit::remit(alice, bob), + remit::uri(uri), + remit::token_ids({strHex(tid)}), + ter(tecNO_PERMISSION)); + env.close(); + } + + // mint and xfer in same ledger + { + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + + // mint uritoken + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + + // remit + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); + BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // confirm offer (amount/dest) is removed on xfer + { + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + + // mint uritoken + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + + // sell offer + env(uritoken::sell(alice, strHex(tid)), + uritoken::amt(XRP(1)), + uritoken::dest(bob), + ter(tesSUCCESS)); + env.close(); + + // verify amount and destination + auto const [urikey1, uriSle1] = uriTokenKeyAndSle(*env.current(), alice, uri); + BEAST_EXPECT(uriSle1->getAccountID(sfDestination) == bob.id()); + BEAST_EXPECT((*uriSle1)[sfAmount] == XRP(1)); + + // xfer the uritoken + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid)}), + ter(tesSUCCESS)); + env.close(); + + // verify amount and destination was removed + auto const [urikey2, uriSle2] = uriTokenKeyAndSle(*env.current(), alice, uri); + BEAST_EXPECT(uriSle2->isFieldPresent(sfDestination) == false); + BEAST_EXPECT(uriSle2->isFieldPresent(sfAmount) == false); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // test digest + { + std::string const uri(maxTokenURILength, '?'); + auto const tid = uritoken::tokenid(alice, uri); + + std::string const digestval = + "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5CCC"; + + // mint the uritoken w/ digest + env(remit::remit(alice, bob), + remit::uri(uri, 0, digestval), + ter(tesSUCCESS)); + env.close(); + + auto const [urikey, uriSle] = uriTokenKeyAndSle(*env.current(), alice, uri); + BEAST_EXPECT(to_string(uriSle->getFieldH256(sfDigest)) == digestval); + + // clean up test + env(uritoken::burn(bob, strHex(tid))); + env.close(); + } + + // test xfer multiple + { + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + std::string const uri2(maxTokenURILength - 1, '?'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(uritoken::mint(alice, uri2), ter(tesSUCCESS)); + env.close(); + + // xfer multiple tokens + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid1), strHex(tid2)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid2)); + BEAST_EXPECT(tokenOwner(*env.current(), tid2) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env(uritoken::burn(bob, strHex(tid2))); + env.close(); + } + + // test sell/xfer in same ledger + { + std::string const uri1(maxTokenURILength, '?'); + auto const tid1 = uritoken::tokenid(alice, uri1); + + // mint uritoken + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + // sell uritoken + env(uritoken::sell(alice, strHex(tid1)), + uritoken::amt(XRP(1)), + uritoken::dest(bob), + ter(tesSUCCESS)); + + // buy uritoken + env(uritoken::buy(bob, strHex(tid1)), + uritoken::amt(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // xfer uritoken + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid1)}), + ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); + BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); + BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid1)); + BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); + + // clean up test + env(uritoken::burn(bob, strHex(tid1))); + env.close(); + } } void testWithFeats(FeatureBitset features) { - // testEnabled(features); - // testPreflightInvalid(features); - // testDoApplyInvalid(features); - // testDisallowXRP(features); - // testDstTag(features); - // testDisallowIncoming(features); - // testDestExistsTLExists(features); - // testDestExistsTLDNE(features); - // testDestDNETLDNE(features); + testEnabled(features); + testPreflightInvalid(features); + testDoApplyInvalid(features); + testDisallowXRP(features); + testDstTag(features); + testDisallowIncoming(features); + testDestExistsTLExists(features); + testDestExistsTLNotExist(features); + testDestNotExistTLNotExist(features); testGateway(features); - // testRequireAuth(features); - // testTLFreeze(features); - // testRippling(features); - // testURITokenMintTransferSame(features); + testRequireAuth(features); + testTLFreeze(features); + testRippling(features); + testURIToken(features); } public: @@ -2396,6 +2429,7 @@ struct Remit_test : public beast::unit_test::suite { using namespace test::jtx; auto const sa = supported_amendments(); + testWithFeats(sa - featureXahauGenesis); testWithFeats(sa); } }; diff --git a/src/test/jtx/impl/remit.cpp b/src/test/jtx/impl/remit.cpp index b11bb0bfa..7a2704eaa 100644 --- a/src/test/jtx/impl/remit.cpp +++ b/src/test/jtx/impl/remit.cpp @@ -69,10 +69,10 @@ inform::operator()(Env& env, JTx& jt) const 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] = Json::arrayValue; - jt.jv[sfURITokenIDs.jsonName][i] = token_ids_[i]; + jt.jv[sfURITokenIDs.jsonName].append(token_ids_[i]); } } @@ -85,6 +85,10 @@ uri::operator()(Env& env, JTx& jt) const { jt.jv[sfMintURIToken.jsonName][sfFlags.jsonName] = *flags_; } + if (digest_) + { + jt.jv[sfMintURIToken.jsonName][sfDigest.fieldName] = *digest_; + } } } // namespace remit diff --git a/src/test/jtx/remit.h b/src/test/jtx/remit.h index 34fa11f4e..0ac5bc750 100644 --- a/src/test/jtx/remit.h +++ b/src/test/jtx/remit.h @@ -103,12 +103,14 @@ class uri private: std::string uri_; std::optional flags_; + std::optional digest_; public: explicit uri( std::string const& uri, - std::optional const& flags = std::nullopt) - : uri_(uri), flags_(flags) + std::optional const& flags = std::nullopt, + std::optional const& digest = std::nullopt) + : uri_(uri), flags_(flags), digest_(digest) { } From 2bbf2264fd29e521969a1079fbe456ce913ce8b8 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 2 Feb 2024 15:32:31 +0100 Subject: [PATCH 13/47] clang-format --- src/ripple/app/tx/impl/Remit.cpp | 7 +- src/test/app/Remit_test.cpp | 114 +++++++++++++++++++------------ src/test/rpc/AccountSet_test.cpp | 3 +- 3 files changed, 74 insertions(+), 50 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index bdace0911..c0028e838 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -428,10 +428,9 @@ Remit::doApply() // if the target trustline doesn't exist we need to create it and // pay its reserve - if (issuerAccID != dstAccID && !sb.exists(keylet::line( - dstAccID, - issuerAccID, - amount.getCurrency()))) + if (issuerAccID != dstAccID && + !sb.exists( + keylet::line(dstAccID, issuerAccID, amount.getCurrency()))) nativeRemit += objectReserve; // action the transfer diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index ec11b2447..bf0f6251d 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -28,7 +28,6 @@ #include - namespace ripple { namespace test { struct Remit_test : public beast::unit_test::suite @@ -303,7 +302,7 @@ struct Remit_test : public beast::unit_test::suite // tecNO_PERMISSION - lsfDisallowIncomingRemit // see testDisallowIncoming - + // tecDST_TAG_NEEDED // see testDstTag @@ -315,9 +314,7 @@ struct Remit_test : public beast::unit_test::suite std::string const uri(maxTokenURILength, '?'); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); - env(remit::remit(alice, bob), - remit::uri(uri), - ter(tecDUPLICATE)); + env(remit::remit(alice, bob), remit::uri(uri), ter(tecDUPLICATE)); env.close(); } @@ -380,7 +377,7 @@ struct Remit_test : public beast::unit_test::suite ter(tecUNFUNDED_PAYMENT)); env.close(); } - + // tecINTERNAL - negative XRP // DA: impossible test @@ -433,9 +430,8 @@ struct Remit_test : public beast::unit_test::suite env(fset(bob, asfDisallowXRP)); env(remit::remit(alice, bob), - remit::amts({ XRP(1) }), + remit::amts({XRP(1)}), ter(tesSUCCESS)); - } { // Make a remit where dst disallows XRP. Ignore that flag, @@ -444,7 +440,7 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); env(remit::remit(alice, bob), - remit::amts({ XRP(1) }), + remit::amts({XRP(1)}), ter(tesSUCCESS)); } } @@ -464,12 +460,12 @@ struct Remit_test : public beast::unit_test::suite env(fset(bob, asfRequireDest)); { env(remit::remit(alice, bob), - remit::amts({ XRP(1) }), + remit::amts({XRP(1)}), ter(tecDST_TAG_NEEDED)); } { env(remit::remit(alice, bob, 1), - remit::amts({ XRP(1) }), + remit::amts({XRP(1)}), ter(tesSUCCESS)); } } @@ -1240,8 +1236,10 @@ struct Remit_test : public beast::unit_test::suite env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); - bool const withXahau = env.current()->rules().enabled(featureXahauGenesis); - BEAST_EXPECT(validateSequence(env, bob, withXahau ? 10 : env.closed()->info().seq)); + bool const withXahau = + env.current()->rules().enabled(featureXahauGenesis); + BEAST_EXPECT(validateSequence( + env, bob, withXahau ? 10 : env.closed()->info().seq)); auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); @@ -1271,7 +1269,8 @@ struct Remit_test : public beast::unit_test::suite env.close(); auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve); + BEAST_EXPECT( + postAlice == preAlice - XRP(1) - feeDrops - accountReserve); BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve); } @@ -1314,7 +1313,8 @@ struct Remit_test : public beast::unit_test::suite auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + postAlice == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT(postBob == preBob + accountReserve + increment); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1358,8 +1358,11 @@ struct Remit_test : public beast::unit_test::suite auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve - increment); - BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve + increment); + BEAST_EXPECT( + postAlice == + preAlice - XRP(1) - feeDrops - accountReserve - increment); + BEAST_EXPECT( + postBob == preBob + XRP(1) + accountReserve + increment); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); } @@ -1400,7 +1403,8 @@ struct Remit_test : public beast::unit_test::suite // validate auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + postAlice == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT(postBob == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); @@ -1439,9 +1443,10 @@ struct Remit_test : public beast::unit_test::suite auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + postAlice == preAlice - feeDrops - accountReserve - increment); BEAST_EXPECT(postBob == preBob + accountReserve + increment); - + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); @@ -1493,8 +1498,11 @@ struct Remit_test : public beast::unit_test::suite // verify xah auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops - accountReserve - increment); - BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve + increment); + BEAST_EXPECT( + postAlice == + preAlice - XRP(1) - feeDrops - accountReserve - increment); + BEAST_EXPECT( + postBob == preBob + XRP(1) + accountReserve + increment); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1551,7 +1559,9 @@ struct Remit_test : public beast::unit_test::suite auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - (increment * 2) - accountReserve); + BEAST_EXPECT( + postAlice == + preAlice - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT(postBob == preBob + (increment * 2) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1612,8 +1622,11 @@ struct Remit_test : public beast::unit_test::suite auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 2) + accountReserve); + postAlice == + preAlice - XRP(1) - feeDrops - (increment * 2) - + accountReserve); + BEAST_EXPECT( + postBob == preBob + XRP(1) + (increment * 2) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1672,8 +1685,11 @@ struct Remit_test : public beast::unit_test::suite auto const postAlice = env.balance(alice); auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 2) + accountReserve); + postAlice == + preAlice - XRP(1) - feeDrops - (increment * 2) - + accountReserve); + BEAST_EXPECT( + postBob == preBob + XRP(1) + (increment * 2) + accountReserve); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1732,7 +1748,6 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid1) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid1) == alice.id()); - BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); @@ -1741,7 +1756,9 @@ struct Remit_test : public beast::unit_test::suite auto const postAliceUSD = env.balance(alice, USD.issue()); auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - (increment * 3) - accountReserve); + BEAST_EXPECT( + postAlice == + preAlice - feeDrops - (increment * 3) - accountReserve); BEAST_EXPECT(postBob == preBob + (increment * 3) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1813,8 +1830,11 @@ struct Remit_test : public beast::unit_test::suite auto const postBob = env.balance(bob); auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (increment * 3) - accountReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + (increment * 3) + accountReserve); + postAlice == + preAlice - XRP(1) - feeDrops - (increment * 3) - + accountReserve); + BEAST_EXPECT( + postBob == preBob + XRP(1) + (increment * 3) + accountReserve); BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); @@ -1881,12 +1901,13 @@ struct Remit_test : public beast::unit_test::suite // issuer can remit env(remit::remit(t.src, t.dst), - remit::amts({ USD(100) }), + remit::amts({USD(100)}), ter(tesSUCCESS)); env.close(); auto const postAmount = t.hasTrustline ? 10100 : 100; - BEAST_EXPECT(preDst == (t.negative ? -USD(preAmount) : USD(preAmount))); + BEAST_EXPECT( + preDst == (t.negative ? -USD(preAmount) : USD(preAmount))); BEAST_EXPECT( lineBalance(env, t.dst, t.src, USD) == (t.negative ? -USD(postAmount) : USD(postAmount))); @@ -1921,7 +1942,7 @@ struct Remit_test : public beast::unit_test::suite // issuer can remit to issuer env(remit::remit(t.src, t.dst), - remit::amts({ USD(100) }), + remit::amts({USD(100)}), ter(tesSUCCESS)); env.close(); @@ -2011,7 +2032,7 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env(pay(gw, bob, USD(10000))); env.close(); - + env(fset(gw, asfGlobalFreeze)); env.close(); @@ -2019,10 +2040,10 @@ struct Remit_test : public beast::unit_test::suite remit::amts({XRP(1), USD(1)}), ter(tecFROZEN)); env.close(); - + env(fclear(gw, asfGlobalFreeze)); env.close(); - + env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1)}), ter(tesSUCCESS)); @@ -2183,7 +2204,7 @@ struct Remit_test : public beast::unit_test::suite // carol pays alice USDC env(pay(carol, alice, USDC(10))); env.close(); - + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); @@ -2293,7 +2314,8 @@ struct Remit_test : public beast::unit_test::suite env.close(); // verify amount and destination - auto const [urikey1, uriSle1] = uriTokenKeyAndSle(*env.current(), alice, uri); + auto const [urikey1, uriSle1] = + uriTokenKeyAndSle(*env.current(), alice, uri); BEAST_EXPECT(uriSle1->getAccountID(sfDestination) == bob.id()); BEAST_EXPECT((*uriSle1)[sfAmount] == XRP(1)); @@ -2304,7 +2326,8 @@ struct Remit_test : public beast::unit_test::suite env.close(); // verify amount and destination was removed - auto const [urikey2, uriSle2] = uriTokenKeyAndSle(*env.current(), alice, uri); + auto const [urikey2, uriSle2] = + uriTokenKeyAndSle(*env.current(), alice, uri); BEAST_EXPECT(uriSle2->isFieldPresent(sfDestination) == false); BEAST_EXPECT(uriSle2->isFieldPresent(sfAmount) == false); @@ -2319,7 +2342,8 @@ struct Remit_test : public beast::unit_test::suite auto const tid = uritoken::tokenid(alice, uri); std::string const digestval = - "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5CCC"; + "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5C" + "CC"; // mint the uritoken w/ digest env(remit::remit(alice, bob), @@ -2327,8 +2351,10 @@ struct Remit_test : public beast::unit_test::suite ter(tesSUCCESS)); env.close(); - auto const [urikey, uriSle] = uriTokenKeyAndSle(*env.current(), alice, uri); - BEAST_EXPECT(to_string(uriSle->getFieldH256(sfDigest)) == digestval); + auto const [urikey, uriSle] = + uriTokenKeyAndSle(*env.current(), alice, uri); + BEAST_EXPECT( + to_string(uriSle->getFieldH256(sfDigest)) == digestval); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -2370,7 +2396,7 @@ struct Remit_test : public beast::unit_test::suite { std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); - + // mint uritoken env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); env.close(); diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index af5ee0412..afcf227ec 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -88,8 +88,7 @@ class AccountSet_test : public beast::unit_test::suite flag == asfDisallowIncomingPayChan || flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline || - flag == asfTshCollect || - flag == asfDisallowIncomingRemit) + flag == asfTshCollect || flag == asfDisallowIncomingRemit) { // These flags are part of the DisallowIncoming amendment // and are tested elsewhere From 0c4561373d3b42ecdcb86381dd84171da24a0e36 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 2 Feb 2024 15:49:18 +0100 Subject: [PATCH 14/47] fix bad merge --- src/ripple/protocol/Feature.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index d479dd2c8..d6e3c3eea 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -354,6 +354,7 @@ extern uint256 const featureImport; extern uint256 const featureXahauGenesis; extern uint256 const featureHooksUpdate1; extern uint256 const fixXahauV1; +extern uint256 const fixXahauV2; extern uint256 const featureRemit; } // namespace ripple From c0e576303ee20e961dcba99b6d3a7edbdaf8945a Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Feb 2024 14:57:50 +0100 Subject: [PATCH 15/47] update remit tsh --- src/ripple/app/hook/impl/applyHook.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 6d56ea17f..fd806dd7d 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -70,6 +70,17 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) switch (tt) { + + case ttREMIT: { + if (destAcc) + ADD_TSH(*destAcc, tshSTRONG); + + if (tx.isFieldPresent(sfInform)) + ADD_TSH(tx.getAccountID(sfInform), tshWEAK); + + break; + } + case ttIMPORT: { if (tx.isFieldPresent(sfIssuer)) ADD_TSH(tx.getAccountID(sfIssuer), fixV2 ? tshWEAK : tshSTRONG); @@ -318,7 +329,6 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) } // simple two party transactions - case ttREMIT: case ttPAYMENT: case ttESCROW_CREATE: case ttCHECK_CREATE: From 5029ff0cb01ebd7c5fe41181b01cbe9a1c242ba5 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Feb 2024 14:58:54 +0100 Subject: [PATCH 16/47] add remit tsh test --- src/test/app/SetHookTSH_test.cpp | 108 +++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 1024164da..f2b964d29 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4931,6 +4931,113 @@ struct SetHookTSH_test : public beast::unit_test::suite } } + // Remit + // | otxn | tsh | remit | + // | A | A | S | + // | A | D | S | + // | A | I | W | + + void + testRemitTSH(FeatureBitset features) + { + testcase("remit tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // 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__); + } + } + void testTSH(FeatureBitset features) { @@ -4965,6 +5072,7 @@ struct SetHookTSH_test : public beast::unit_test::suite testURITokenBuyTSH(features); testURITokenCancelSellOfferTSH(features); testURITokenCreateSellOfferTSH(features); + testRemitTSH(features); } void From 2c4152a41cc0ce6ed5ec6c70bc093c2d0cb16661 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 6 Feb 2024 14:59:21 +0100 Subject: [PATCH 17/47] add remit tests --- src/ripple/app/tx/impl/Remit.cpp | 7 + src/test/app/Remit_test.cpp | 218 ++++++++++++++++++++++++++++--- 2 files changed, 207 insertions(+), 18 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index c0028e838..468dc57ff 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -53,6 +53,13 @@ Remit::preflight(PreflightContext const& ctx) return temREDUNDANT; } + if (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)) { diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index bf0f6251d..7d67b63d8 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -190,6 +190,13 @@ struct Remit_test : public beast::unit_test::suite env.close(); } + // temMALFORMED - blob was more than 128kib + { + ripple::Blob blob; + blob.resize(129 * 1024); + env(remit::remit(alice, bob), remit::blob(strHex(blob)), ter(temMALFORMED)); + } + // temMALFORMED - Expected AmountEntry. { auto tx = remit::remit(alice, bob); @@ -413,6 +420,107 @@ struct Remit_test : public beast::unit_test::suite } } + 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"]; + + struct TestRateData + { + double rate; + STAmount delta; + std::string multiply; + std::string divide; + }; + std::array 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"}, + }}; + + for (auto const& tc : testCases) + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, tc.rate)); + 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()); + + auto const delta = USD(100); + env(remit::remit(alice, bob), remit::amts({ delta })); + env.close(); + auto xferRate = transferRate(*env.current(), gw); + auto const postBob = env.balance(bob, USD.issue()); + BEAST_EXPECT(to_string(postBob.value()) == tc.divide); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - 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 + auto const delta = USD(100); + auto preAlice = env.balance(alice, USD.issue()); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // remit at higher rate + env(remit::remit(alice, bob), remit::amts({ delta })); + env.close(); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + + // 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(); + + auto const delta = USD(100); + auto const preAlice = env.balance(alice, USD.issue()); + + // alice sells + env(remit::remit(gw, alice), remit::amts({ delta })); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); + } + } + void testDisallowXRP(FeatureBitset features) { @@ -1887,14 +1995,16 @@ struct Remit_test : public beast::unit_test::suite env.close(); if (t.hasTrustline) + { env.trust(USD(100000), t.dst); - - env.close(); + env.close(); + } if (t.hasTrustline) + { env(pay(t.src, t.dst, USD(10000))); - - env.close(); + env.close(); + } auto const preAmount = t.hasTrustline ? 10000 : 0; auto const preDst = lineBalance(env, t.dst, t.src, USD); @@ -1914,15 +2024,23 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(lineBalance(env, t.src, t.src, USD) == USD(0)); } - std::array gwDstTests = {{ + std::array gwDstTests = {{ // // // src > dst && src > issuer && dst has trustline {Account("alice2"), Account{"gw0"}, true, true}, + // // // src > dst && src > issuer && dst has trustline + {Account("alice2"), Account{"gw0"}, false, true}, // // // src < dst && src < issuer && dst has trustline {Account("carol0"), Account{"gw1"}, true, false}, + // // // src < dst && src < issuer && dst has trustline + {Account("carol0"), Account{"gw1"}, false, false}, // // // // dst > src && dst > issuer && dst has trustline {Account("dan1"), Account{"gw0"}, true, true}, + // // // // dst > src && dst > issuer && dst has trustline + {Account("dan1"), Account{"gw0"}, false, true}, // // // // dst < src && dst < issuer && dst has trustline {Account("bob0"), Account{"gw1"}, true, false}, + // // // // dst < src && dst < issuer && dst has trustline + {Account("bob0"), Account{"gw1"}, false, false}, }}; for (auto const& t : gwDstTests) @@ -1932,28 +2050,43 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(5000), t.src, t.dst); env.close(); - env.trust(USD(100000), t.src); - env.close(); + if (t.hasTrustline) + { + env.trust(USD(100000), t.src); + env.close(); + } - env(pay(t.dst, t.src, USD(10000))); - env.close(); + if (t.hasTrustline) + { + env(pay(t.dst, t.src, USD(10000))); + env.close(); + } auto const preSrc = lineBalance(env, t.src, t.dst, USD); // issuer can remit to issuer env(remit::remit(t.src, t.dst), remit::amts({USD(100)}), - ter(tesSUCCESS)); + t.hasTrustline ? ter(tesSUCCESS) : ter(tecUNFUNDED_PAYMENT)); env.close(); - auto const preAmount = 10000; - BEAST_EXPECT( - preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); - auto const postAmount = 9900; - BEAST_EXPECT( - lineBalance(env, t.src, t.dst, USD) == - (t.negative ? -USD(postAmount) : USD(postAmount))); - BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); + if (t.hasTrustline) + { + auto const preAmount = 10000; + BEAST_EXPECT( + preSrc == (t.negative ? -USD(preAmount) : USD(preAmount))); + auto const postAmount = 9900; + BEAST_EXPECT( + lineBalance(env, t.src, t.dst, USD) == + (t.negative ? -USD(postAmount) : USD(postAmount))); + BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); + } + else + { + BEAST_EXPECT(preSrc == USD(0)); + BEAST_EXPECT(lineBalance(env, t.src, t.dst, USD) == USD(0)); + BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); + } } } @@ -2430,12 +2563,60 @@ struct Remit_test : public beast::unit_test::suite } } + void + testOptionals(FeatureBitset features) + { + testcase("optionals"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + + // inform + { + env(remit::remit(alice, bob), + remit::inform(carol), + ter(tesSUCCESS)); + env.close(); + } + + // blob + { + ripple::Blob blob; + blob.resize(128 * 1024); + XRPAmount const extraFee = XRPAmount{static_cast(blob.size())}; + env(remit::remit(alice, bob), + remit::blob(strHex(blob)), + fee(feeDrops + extraFee), + ter(tesSUCCESS)); + env.close(); + } + + // invoice + { + env(remit::remit(alice, bob), + invoice_id(uint256{4}), + ter(tesSUCCESS)); + env.close(); + } + } + void testWithFeats(FeatureBitset features) { testEnabled(features); testPreflightInvalid(features); testDoApplyInvalid(features); + testTransferRate(features); testDisallowXRP(features); testDstTag(features); testDisallowIncoming(features); @@ -2447,6 +2628,7 @@ struct Remit_test : public beast::unit_test::suite testTLFreeze(features); testRippling(features); testURIToken(features); + testOptionals(features); } public: From 410278c1c6d3841ea48694b5c731e80c431040f7 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 7 Feb 2024 12:28:30 +0100 Subject: [PATCH 18/47] add limit to amounts & uritokenids --- src/ripple/app/tx/impl/Remit.cpp | 15 ++++++++++++++- src/test/app/Remit_test.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 468dc57ff..d7d18da61 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -63,6 +63,12 @@ Remit::preflight(PreflightContext const& ctx) // sanity check amounts if (ctx.tx.isFieldPresent(sfAmounts)) { + if (ctx.tx.getFieldArray(sfAmounts).size() > 32) + { + JLOG(ctx.j.warn()) << "Malformed: AmountEntrys Exceed Limit `32`."; + return temMALFORMED; + } + std::map> already; bool nativeAlready = false; @@ -153,9 +159,16 @@ Remit::preflight(PreflightContext const& ctx) } } - // check uritokenids for duplicates + // sanity check uritokenids if (ctx.tx.isFieldPresent(sfURITokenIDs)) { + if (ctx.tx.getFieldV256(sfURITokenIDs).size() > 32) + { + JLOG(ctx.j.warn()) + << "Malformed transaction: URITokenIDs Exceed Limit `32`."; + return temMALFORMED; + } + STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); std::sort(ids.begin(), ids.end()); if (std::adjacent_find(ids.begin(), ids.end()) != ids.end()) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 7d67b63d8..d608c2d38 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -197,6 +197,21 @@ struct Remit_test : public beast::unit_test::suite env(remit::remit(alice, bob), remit::blob(strHex(blob)), ter(temMALFORMED)); } + // temMALFORMED - AmountEntrys Exceeds Limit + { + std::vector amts_; // Remove the const qualifier + for (size_t i = 0; i < 33; i++) + { + auto const USD = gw["USD"]; + amts_.emplace_back(USD(1)); + } + + env(remit::remit(alice, bob), + remit::amts(amts_), + ter(temMALFORMED)); + env.close(); + } + // temMALFORMED - Expected AmountEntry. { auto tx = remit::remit(alice, bob); @@ -272,6 +287,22 @@ struct Remit_test : public beast::unit_test::suite env.close(); } + // temMALFORMED - URITokenIDs Exceeds Limit + { + std::vector token_ids; + for (size_t i = 0; i < 33; i++) + { + std::string const uri(i, '?'); + auto const tid = uritoken::tokenid(alice, uri); + token_ids.emplace_back(strHex(tid)); + } + + env(remit::remit(alice, bob), + remit::token_ids(token_ids), + ter(temMALFORMED)); + env.close(); + } + // temMALFORMED - Duplicate URITokenID. { std::string const uri1(maxTokenURILength, '?'); From 6b53f5bea5ae37682d9e46670a2421092cd6ece7 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 7 Feb 2024 12:29:18 +0100 Subject: [PATCH 19/47] update remit tsh --- src/ripple/app/hook/impl/applyHook.cpp | 19 +++ src/ripple/app/tx/impl/Remit.cpp | 1 + src/test/app/SetHookTSH_test.cpp | 181 ++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 5 deletions(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index fd806dd7d..e34158a96 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -78,6 +78,25 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) if (tx.isFieldPresent(sfInform)) ADD_TSH(tx.getAccountID(sfInform), 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) + ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK); + } + } break; } diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index d7d18da61..55c46853c 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -516,6 +516,7 @@ Remit::doApply() sb.update(sleSrcAcc); sb.update(sleDstAcc); sb.apply(ctx_.rawView()); + addWeakTSHFromSandbox(sb); return tesSUCCESS; } diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index f2b964d29..35767c11c 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4932,10 +4932,15 @@ struct SetHookTSH_test : public beast::unit_test::suite } // Remit - // | otxn | tsh | remit | - // | A | A | S | - // | A | D | S | - // | A | I | W | + // | otxn | tsh | remit w/amt | + // | A | A | S | + // | A | D | S | + // | A | I | W | + // | A | C | W | + + // | otxn | tsh | burnable | remit w/uri | + // | A | I | F | W | + // | A | I | T | S | void testRemitTSH(FeatureBitset features) @@ -4944,6 +4949,9 @@ struct SetHookTSH_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; + /* + sfAmounts + */ // otxn: account // tsh account @@ -5029,7 +5037,170 @@ struct SetHookTSH_test : public beast::unit_test::suite setTSHHook(env, inform, testStrong); // payment - env(remit::remit(account, dest), remit::inform(inform), fee(XRP(1)), ter(tesSUCCESS)); + 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__); + } + + // otxn: account + // tsh cross + // 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 cross = Account("bob"); + auto const dest = Account("carol"); + auto const gw = Account{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(1000), account, cross, dest, gw); + env.close(); + + // setup rippling + auto const USDA = account["USD"]; + auto const USDB = cross["USD"]; + auto const USDC = dest["USD"]; + env.trust(USDA(10), cross); + env.trust(USDB(10), dest); + + // set tsh collect + if (!testStrong) + addWeakTSH(env, cross); + + // set tsh hook + setTSHHook(env, cross, testStrong); + + // payment + env(remit::remit(account, dest), + remit::amts({USDB(10)}), + paths(USDA), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + 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 From 6274d2fde811b17ae0896f908329a1c78e653d53 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 7 Feb 2024 12:31:11 +0100 Subject: [PATCH 20/47] clang-format --- src/ripple/app/hook/impl/applyHook.cpp | 9 ++++++--- src/test/app/Remit_test.cpp | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index e34158a96..3915b3483 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -70,7 +70,6 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) switch (tt) { - case ttREMIT: { if (destAcc) ADD_TSH(*destAcc, tshSTRONG); @@ -88,13 +87,17 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) continue; auto const ut = rv.read(id); - if (!ut || ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN) + if (!ut || + ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN) continue; auto const owner = ut->getAccountID(sfOwner); auto const issuer = ut->getAccountID(sfIssuer); if (issuer != owner) - ADD_TSH(issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK); + ADD_TSH( + issuer, + (ut->getFlags() & lsfBurnable) ? tshSTRONG + : tshWEAK); } } break; diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index d608c2d38..419a3b960 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -194,12 +194,14 @@ struct Remit_test : public beast::unit_test::suite { ripple::Blob blob; blob.resize(129 * 1024); - env(remit::remit(alice, bob), remit::blob(strHex(blob)), ter(temMALFORMED)); + env(remit::remit(alice, bob), + remit::blob(strHex(blob)), + ter(temMALFORMED)); } // temMALFORMED - AmountEntrys Exceeds Limit { - std::vector amts_; // Remove the const qualifier + std::vector amts_; // Remove the const qualifier for (size_t i = 0; i < 33; i++) { auto const USD = gw["USD"]; @@ -495,7 +497,7 @@ struct Remit_test : public beast::unit_test::suite auto const preBob = env.balance(bob, USD.issue()); auto const delta = USD(100); - env(remit::remit(alice, bob), remit::amts({ delta })); + env(remit::remit(alice, bob), remit::amts({delta})); env.close(); auto xferRate = transferRate(*env.current(), gw); auto const postBob = env.balance(bob, USD.issue()); @@ -524,7 +526,7 @@ struct Remit_test : public beast::unit_test::suite env.close(); // remit at higher rate - env(remit::remit(alice, bob), remit::amts({ delta })); + env(remit::remit(alice, bob), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); @@ -543,9 +545,9 @@ struct Remit_test : public beast::unit_test::suite auto const delta = USD(100); auto const preAlice = env.balance(alice, USD.issue()); - + // alice sells - env(remit::remit(gw, alice), remit::amts({ delta })); + env(remit::remit(gw, alice), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); @@ -2111,7 +2113,7 @@ struct Remit_test : public beast::unit_test::suite lineBalance(env, t.src, t.dst, USD) == (t.negative ? -USD(postAmount) : USD(postAmount))); BEAST_EXPECT(lineBalance(env, t.dst, t.dst, USD) == USD(0)); - } + } else { BEAST_EXPECT(preSrc == USD(0)); @@ -2610,7 +2612,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, carol); env.close(); - // inform { @@ -2624,7 +2625,8 @@ struct Remit_test : public beast::unit_test::suite { ripple::Blob blob; blob.resize(128 * 1024); - XRPAmount const extraFee = XRPAmount{static_cast(blob.size())}; + XRPAmount const extraFee = + XRPAmount{static_cast(blob.size())}; env(remit::remit(alice, bob), remit::blob(strHex(blob)), fee(feeDrops + extraFee), From 18eced5186a966611a772e63b5c095f66bf5b34b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 7 Feb 2024 13:07:52 +0100 Subject: [PATCH 21/47] remove cross tsh --- src/ripple/app/tx/impl/Remit.cpp | 1 - src/test/app/SetHookTSH_test.cpp | 47 -------------------------------- 2 files changed, 48 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 55c46853c..d7d18da61 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -516,7 +516,6 @@ Remit::doApply() sb.update(sleSrcAcc); sb.update(sleDstAcc); sb.apply(ctx_.rawView()); - addWeakTSHFromSandbox(sb); return tesSUCCESS; } diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 35767c11c..93f5251dc 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4936,7 +4936,6 @@ struct SetHookTSH_test : public beast::unit_test::suite // | A | A | S | // | A | D | S | // | A | I | W | - // | A | C | W | // | otxn | tsh | burnable | remit w/uri | // | A | I | F | W | @@ -5048,52 +5047,6 @@ struct SetHookTSH_test : public beast::unit_test::suite testTSHStrongWeak(env, expected, __LINE__); } - // otxn: account - // tsh cross - // 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 cross = Account("bob"); - auto const dest = Account("carol"); - auto const gw = Account{"gateway"}; - auto const USD = gw["USD"]; - env.fund(XRP(1000), account, cross, dest, gw); - env.close(); - - // setup rippling - auto const USDA = account["USD"]; - auto const USDB = cross["USD"]; - auto const USDC = dest["USD"]; - env.trust(USDA(10), cross); - env.trust(USDB(10), dest); - - // set tsh collect - if (!testStrong) - addWeakTSH(env, cross); - - // set tsh hook - setTSHHook(env, cross, testStrong); - - // payment - env(remit::remit(account, dest), - remit::amts({USDB(10)}), - paths(USDA), - fee(XRP(1)), - ter(tesSUCCESS)); - env.close(); - env.close(); - - // verify tsh hook triggered - auto const expected = testStrong ? tshNONE : tshWEAK; - testTSHStrongWeak(env, expected, __LINE__); - } - /* sfURITokenIDs */ From a334a777f5bca3b00ab50e0a74eb50694db3a67e Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 19 Feb 2024 12:47:46 +0100 Subject: [PATCH 22/47] `asfAllowIncomingRemit` --- src/ripple/app/tx/impl/Remit.cpp | 4 +- src/ripple/app/tx/impl/SetAccount.cpp | 8 +- src/ripple/protocol/LedgerFormats.h | 2 +- src/ripple/protocol/TxFlags.h | 2 +- src/test/app/Remit_test.cpp | 358 +++++++++++++++++--------- src/test/rpc/AccountSet_test.cpp | 2 +- 6 files changed, 242 insertions(+), 134 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index d7d18da61..0ea61b652 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -207,11 +207,11 @@ Remit::doApply() AccountID const dstAccID{ctx_.tx[sfDestination]}; auto sleDstAcc = sb.peek(keylet::account(dstAccID)); - auto const flags = !sleDstAcc ? 0 : sleDstAcc->getFlags(); + auto const flags = !sleDstAcc ? 0x80000000 : sleDstAcc->getFlags(); // Check if the destination has disallowed incoming if (sb.rules().enabled(featureDisallowIncoming) && - (flags & lsfDisallowIncomingRemit)) + !(flags & lsfAllowIncomingRemit)) return tecNO_PERMISSION; // the destination may require a dest tag diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 085e36422..0b42ab839 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -580,10 +580,10 @@ SetAccount::doApply() if (ctx_.view().rules().enabled(featureRemit)) { - if (uSetFlag == asfDisallowIncomingRemit) - uFlagsOut |= lsfDisallowIncomingRemit; - else if (uClearFlag == asfDisallowIncomingRemit) - uFlagsOut &= ~lsfDisallowIncomingRemit; + if (uSetFlag == asfAllowIncomingRemit) + uFlagsOut |= lsfAllowIncomingRemit; + else if (uClearFlag == asfAllowIncomingRemit) + uFlagsOut &= ~lsfAllowIncomingRemit; } } diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 6134a8f33..0d17b1484 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -285,7 +285,7 @@ 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 + lsfAllowIncomingRemit = // True, remits allowed to this account 0x80000000, // ltOFFER diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index b27104a67..b3aabde30 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -86,7 +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; +constexpr std::uint32_t asfAllowIncomingRemit = 16; // OfferCreate flags: constexpr std::uint32_t tfPassive = 0x00010000; diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 419a3b960..400a98d32 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -146,6 +146,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + auto const txResult = withRemit ? ter(tesSUCCESS) : ter(temDISABLED); @@ -173,6 +176,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, gw); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + //---------------------------------------------------------------------- // preflight @@ -340,8 +346,8 @@ struct Remit_test : public beast::unit_test::suite // env.close(); // } - // tecNO_PERMISSION - lsfDisallowIncomingRemit - // see testDisallowIncoming + // tecNO_PERMISSION - lsfAllowIncomingRemit + // see testAllowIncoming // tecDST_TAG_NEEDED // see testDstTag @@ -352,6 +358,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + std::string const uri(maxTokenURILength, '?'); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env(remit::remit(alice, bob), remit::uri(uri), ter(tecDUPLICATE)); @@ -367,6 +376,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), @@ -384,6 +396,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(bob, uri); env(uritoken::mint(bob, uri), ter(tesSUCCESS)); @@ -412,6 +427,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(1000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + env(remit::remit(alice, bob), remit::amts({USD(1001)}), ter(tecUNFUNDED_PAYMENT)); @@ -427,6 +445,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + env(remit::remit(alice, bob), remit::amts({XRP(1001)}), ter(tecUNFUNDED_PAYMENT)); @@ -445,6 +466,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(250), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + std::string const uri(maxTokenURILength, '?'); env(remit::remit(alice, bob), remit::uri(uri), @@ -453,107 +477,6 @@ struct Remit_test : public beast::unit_test::suite } } - 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"]; - - struct TestRateData - { - double rate; - STAmount delta; - std::string multiply; - std::string divide; - }; - std::array 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"}, - }}; - - for (auto const& tc : testCases) - { - Env env{*this, features}; - env.fund(XRP(10000), alice, bob, gw); - env(rate(gw, tc.rate)); - 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()); - - auto const delta = USD(100); - env(remit::remit(alice, bob), remit::amts({delta})); - env.close(); - auto xferRate = transferRate(*env.current(), gw); - auto const postBob = env.balance(bob, USD.issue()); - BEAST_EXPECT(to_string(postBob.value()) == tc.divide); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - 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 - auto const delta = USD(100); - auto preAlice = env.balance(alice, USD.issue()); - - // issuer changes rate lower - env(rate(gw, 1.00)); - env.close(); - - // remit at higher rate - env(remit::remit(alice, bob), remit::amts({delta})); - env.close(); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); - BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); - } - - // 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(); - - auto const delta = USD(100); - auto const preAlice = env.balance(alice, USD.issue()); - - // alice sells - env(remit::remit(gw, alice), remit::amts({delta})); - env.close(); - - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); - } - } - void testDisallowXRP(FeatureBitset features) { @@ -569,6 +492,8 @@ struct Remit_test : public beast::unit_test::suite Env env(*this, features - featureDepositAuth); env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1)}), @@ -580,6 +505,8 @@ struct Remit_test : public beast::unit_test::suite Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); @@ -599,6 +526,8 @@ struct Remit_test : public beast::unit_test::suite auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); env(fset(bob, asfRequireDest)); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); { env(remit::remit(alice, bob), remit::amts({XRP(1)}), @@ -612,9 +541,9 @@ struct Remit_test : public beast::unit_test::suite } void - testDisallowIncoming(FeatureBitset features) + testAllowIncoming(FeatureBitset features) { - testcase("disallow incoming"); + testcase("allow incoming"); using namespace jtx; using namespace std::literals::chrono_literals; @@ -622,11 +551,11 @@ struct Remit_test : public beast::unit_test::suite Env env{*this, features - featureRemit}; Account const alice{"alice"}; env.fund(XRP(10000), alice); - env(fset(alice, asfDisallowIncomingRemit)); + env(fset(alice, asfAllowIncomingRemit)); env.close(); auto const sle = env.le(alice); uint32_t flags = sle->getFlags(); - BEAST_EXPECT(!(flags & lsfDisallowIncomingRemit)); + BEAST_EXPECT(!(flags & lsfAllowIncomingRemit)); } // setup env @@ -639,54 +568,54 @@ struct Remit_test : public beast::unit_test::suite env.close(); // set flag on bob only - env(fset(bob, asfDisallowIncomingRemit)); + env(fset(bob, asfAllowIncomingRemit)); env.close(); - // remit from alice to bob is disallowed + // remit from alice to bob is allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), - ter(tecNO_PERMISSION)); + ter(tesSUCCESS)); } // set flag on alice also - env(fset(alice, asfDisallowIncomingRemit)); + env(fset(alice, asfAllowIncomingRemit)); env.close(); - // remit from bob to alice is now disallowed + // remit from bob to alice is allowed { env(remit::remit(bob, alice), remit::amts({XRP(1)}), - ter(tecNO_PERMISSION)); + ter(tesSUCCESS)); } // remove flag from bob - env(fclear(bob, asfDisallowIncomingRemit)); + env(fclear(bob, asfAllowIncomingRemit)); env.close(); - // now remit between alice and bob allowed + // now remit between alice and bob not allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), - ter(tesSUCCESS)); + ter(tecNO_PERMISSION)); } - // a remit from carol to alice isn't allowed + // a remit from carol to alice is allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), - ter(tecNO_PERMISSION)); + ter(tesSUCCESS)); } // remove flag from alice - env(fclear(alice, asfDisallowIncomingRemit)); + env(fclear(alice, asfAllowIncomingRemit)); env.close(); - // now a remit from carol to alice is allowed + // now a remit from carol to alice is not allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), - ter(tesSUCCESS)); + ter(tecNO_PERMISSION)); } } @@ -709,6 +638,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); // auto const preAlice = env.balance(alice, USD.issue()); @@ -726,6 +658,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), @@ -757,6 +692,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -799,6 +737,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -834,6 +775,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -870,6 +814,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); @@ -898,6 +845,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -952,6 +902,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -1011,6 +964,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -1064,6 +1020,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1128,6 +1087,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1197,6 +1159,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1275,6 +1240,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -1282,7 +1250,7 @@ struct Remit_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); - BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBob == XRP(1000) - feeDrops); BEAST_EXPECT(preBobUSD == USD(0)); // remit @@ -1321,6 +1289,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -1328,7 +1299,7 @@ struct Remit_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); - BEAST_EXPECT(preBob == XRP(1000)); + BEAST_EXPECT(preBob == XRP(1000) - feeDrops); BEAST_EXPECT(preBobUSD == USD(0)); // remit @@ -2027,6 +1998,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(5000), t.dst, t.src); env.close(); + env(fset(t.dst, asfAllowIncomingRemit)); + env.close(); + if (t.hasTrustline) { env.trust(USD(100000), t.dst); @@ -2095,6 +2069,9 @@ struct Remit_test : public beast::unit_test::suite env.close(); } + env(fset(t.dst, asfAllowIncomingRemit)); + env.close(); + auto const preSrc = lineBalance(env, t.src, t.dst, USD); // issuer can remit to issuer @@ -2123,6 +2100,116 @@ struct Remit_test : public beast::unit_test::suite } } + 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"]; + + struct TestRateData + { + double rate; + STAmount delta; + std::string multiply; + std::string divide; + }; + std::array 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"}, + }}; + + for (auto const& tc : testCases) + { + Env env{*this, features}; + env.fund(XRP(10000), alice, bob, gw); + env(rate(gw, tc.rate)); + 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(bob, asfAllowIncomingRemit)); + env.close(); + + auto const preAlice = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob, USD.issue()); + + auto const delta = USD(100); + env(remit::remit(alice, bob), remit::amts({delta})); + env.close(); + auto xferRate = transferRate(*env.current(), gw); + auto const postBob = env.balance(bob, USD.issue()); + BEAST_EXPECT(to_string(postBob.value()) == tc.divide); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - 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(); + + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + + // setup + auto const delta = USD(100); + auto preAlice = env.balance(alice, USD.issue()); + + // issuer changes rate lower + env(rate(gw, 1.00)); + env.close(); + + // remit at higher rate + env(remit::remit(alice, bob), remit::amts({delta})); + env.close(); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + } + + // 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(); + + env(fset(alice, asfAllowIncomingRemit)); + env.close(); + + auto const delta = USD(100); + auto const preAlice = env.balance(alice, USD.issue()); + + // alice sells + env(remit::remit(gw, alice), remit::amts({delta})); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice + delta); + } + } + void testRequireAuth(FeatureBitset features) { @@ -2151,6 +2238,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(1000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // alice cannot remit because bob's trustline is not authorized // all parties must be authorized env(remit::remit(alice, bob), @@ -2199,6 +2289,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + env(fset(gw, asfGlobalFreeze)); env.close(); @@ -2229,6 +2322,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // set freeze on alice trustline env(trust(gw, USD(100000), alice, tfSetFreeze)); env.close(); @@ -2302,6 +2398,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); + env(fset(carol, asfAllowIncomingRemit)); + env.close(); + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); @@ -2371,6 +2470,9 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); + env(fset(carol, asfAllowIncomingRemit)); + env.close(); + BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); @@ -2428,6 +2530,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // cannot mint and transfer same token { std::string const uri(maxTokenURILength, '?'); @@ -2613,6 +2718,9 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, carol); env.close(); + env(fset(bob, asfAllowIncomingRemit)); + env.close(); + // inform { env(remit::remit(alice, bob), @@ -2649,14 +2757,14 @@ struct Remit_test : public beast::unit_test::suite testEnabled(features); testPreflightInvalid(features); testDoApplyInvalid(features); - testTransferRate(features); testDisallowXRP(features); testDstTag(features); - testDisallowIncoming(features); + testAllowIncoming(features); testDestExistsTLExists(features); testDestExistsTLNotExist(features); testDestNotExistTLNotExist(features); testGateway(features); + testTransferRate(features); testRequireAuth(features); testTLFreeze(features); testRippling(features); diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index afcf227ec..d1b571897 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -88,7 +88,7 @@ class AccountSet_test : public beast::unit_test::suite flag == asfDisallowIncomingPayChan || flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline || - flag == asfTshCollect || flag == asfDisallowIncomingRemit) + flag == asfTshCollect || flag == asfAllowIncomingRemit) { // These flags are part of the DisallowIncoming amendment // and are tested elsewhere From 264f70098e6d227d1ae16d97e9ccb88be419e000 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 19 Feb 2024 13:37:51 +0100 Subject: [PATCH 23/47] add `lsfAllowIncoming` to AccountInfo --- src/ripple/rpc/handlers/AccountInfo.cpp | 5 +++-- src/test/rpc/AccountInfo_test.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index ef130ef18..70b78a66f 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -91,13 +91,14 @@ doAccountInfo(RPC::JsonContext& context) {"requireDestinationTag", lsfRequireDestTag}}}; static constexpr std:: - array, 4> + array, 5> disallowIncomingFlags{ {{"disallowIncomingNFTokenOffer", lsfDisallowIncomingNFTokenOffer}, {"disallowIncomingCheck", lsfDisallowIncomingCheck}, {"disallowIncomingPayChan", lsfDisallowIncomingPayChan}, - {"disallowIncomingTrustline", lsfDisallowIncomingTrustline}}}; + {"disallowIncomingTrustline", lsfDisallowIncomingTrustline}, + {"allowIncomingRemit", lsfAllowIncomingRemit}}}; auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 0cda0632e..5d24d0b9c 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -546,14 +546,16 @@ class AccountInfo_test : public beast::unit_test::suite } static constexpr std:: - array, 4> + array, 5> disallowIncomingFlags{ {{"disallowIncomingCheck", asfDisallowIncomingCheck}, {"disallowIncomingNFTokenOffer", asfDisallowIncomingNFTokenOffer}, {"disallowIncomingPayChan", asfDisallowIncomingPayChan}, {"disallowIncomingTrustline", - asfDisallowIncomingTrustline}}}; + asfDisallowIncomingTrustline}, + {"allowIncomingRemit", + asfAllowIncomingRemit}}}; if (features[featureDisallowIncoming]) { From 0ec024e77dc00834b4ec259d56a41614b1c82743 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 19 Feb 2024 13:48:59 +0100 Subject: [PATCH 24/47] Update SetHookTSH_test.cpp --- src/test/app/SetHookTSH_test.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 93f5251dc..04bea3b7f 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4967,6 +4967,9 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest); env.close(); + env(fset(dest, asfAllowIncomingRemit)); + env.close(); + // set tsh collect if (!testStrong) addWeakTSH(env, account); @@ -4997,6 +5000,9 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest); env.close(); + env(fset(dest, asfAllowIncomingRemit)); + env.close(); + // set tsh collect if (!testStrong) addWeakTSH(env, dest); @@ -5028,6 +5034,9 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, inform); env.close(); + env(fset(dest, asfAllowIncomingRemit)); + env.close(); + // set tsh collect if (!testStrong) addWeakTSH(env, inform); @@ -5068,6 +5077,9 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, issuer); env.close(); + env(fset(dest, asfAllowIncomingRemit)); + env.close(); + // mint uritoken std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(issuer, uri); @@ -5124,6 +5136,9 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, issuer); env.close(); + env(fset(dest, asfAllowIncomingRemit)); + env.close(); + // mint uritoken std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(issuer, uri); From cb28b4ffa37da711036a7f380f772289ef048051 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 19 Feb 2024 13:52:54 +0100 Subject: [PATCH 25/47] clang-format --- src/test/rpc/AccountInfo_test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 5d24d0b9c..0f1bc8471 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -554,8 +554,7 @@ class AccountInfo_test : public beast::unit_test::suite {"disallowIncomingPayChan", asfDisallowIncomingPayChan}, {"disallowIncomingTrustline", asfDisallowIncomingTrustline}, - {"allowIncomingRemit", - asfAllowIncomingRemit}}}; + {"allowIncomingRemit", asfAllowIncomingRemit}}}; if (features[featureDisallowIncoming]) { From 0712a922ae3823af0a4e7105437cc83c87eef8b2 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 24 Feb 2024 10:34:16 +0100 Subject: [PATCH 26/47] `lsfDisallowIncomingRemit` --- src/ripple/app/tx/impl/Remit.cpp | 4 +- src/ripple/app/tx/impl/SetAccount.cpp | 8 +- src/ripple/protocol/LedgerFormats.h | 2 +- src/ripple/protocol/TxFlags.h | 2 +- src/ripple/rpc/handlers/AccountInfo.cpp | 2 +- src/test/app/Remit_test.cpp | 133 ++++-------------------- src/test/app/SetHookTSH_test.cpp | 15 --- src/test/rpc/AccountInfo_test.cpp | 2 +- src/test/rpc/AccountSet_test.cpp | 2 +- 9 files changed, 34 insertions(+), 136 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 0ea61b652..d7d18da61 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -207,11 +207,11 @@ Remit::doApply() AccountID const dstAccID{ctx_.tx[sfDestination]}; auto sleDstAcc = sb.peek(keylet::account(dstAccID)); - auto const flags = !sleDstAcc ? 0x80000000 : sleDstAcc->getFlags(); + auto const flags = !sleDstAcc ? 0 : sleDstAcc->getFlags(); // Check if the destination has disallowed incoming if (sb.rules().enabled(featureDisallowIncoming) && - !(flags & lsfAllowIncomingRemit)) + (flags & lsfDisallowIncomingRemit)) return tecNO_PERMISSION; // the destination may require a dest tag diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 0b42ab839..085e36422 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -580,10 +580,10 @@ SetAccount::doApply() if (ctx_.view().rules().enabled(featureRemit)) { - if (uSetFlag == asfAllowIncomingRemit) - uFlagsOut |= lsfAllowIncomingRemit; - else if (uClearFlag == asfAllowIncomingRemit) - uFlagsOut &= ~lsfAllowIncomingRemit; + if (uSetFlag == asfDisallowIncomingRemit) + uFlagsOut |= lsfDisallowIncomingRemit; + else if (uClearFlag == asfDisallowIncomingRemit) + uFlagsOut &= ~lsfDisallowIncomingRemit; } } diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 0d17b1484..6134a8f33 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -285,7 +285,7 @@ enum LedgerSpecificFlags { 0x20000000, // True, reject new trustlines (only if no issued assets) lsfURITokenIssuer = 0x40000000, // True, has minted tokens in the past - lsfAllowIncomingRemit = // True, remits allowed to this account + lsfDisallowIncomingRemit = // True, no remits allowed to this account 0x80000000, // ltOFFER diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index b3aabde30..0cb815236 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -86,7 +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 asfAllowIncomingRemit = 16; +constexpr std::uint32_t asfDisallowIncomingRemit = 16; // OfferCreate flags: constexpr std::uint32_t tfPassive = 0x00010000; diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index 70b78a66f..175db4282 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -98,7 +98,7 @@ doAccountInfo(RPC::JsonContext& context) {"disallowIncomingCheck", lsfDisallowIncomingCheck}, {"disallowIncomingPayChan", lsfDisallowIncomingPayChan}, {"disallowIncomingTrustline", lsfDisallowIncomingTrustline}, - {"allowIncomingRemit", lsfAllowIncomingRemit}}}; + {"allowIncomingRemit", lsfDisallowIncomingRemit}}}; auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 400a98d32..8393b6bd7 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -146,9 +146,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - auto const txResult = withRemit ? ter(tesSUCCESS) : ter(temDISABLED); @@ -176,9 +173,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, gw); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - //---------------------------------------------------------------------- // preflight @@ -346,7 +340,7 @@ struct Remit_test : public beast::unit_test::suite // env.close(); // } - // tecNO_PERMISSION - lsfAllowIncomingRemit + // tecNO_PERMISSION - lsfDisallowIncomingRemit // see testAllowIncoming // tecDST_TAG_NEEDED @@ -358,9 +352,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - std::string const uri(maxTokenURILength, '?'); env(uritoken::mint(alice, uri), ter(tesSUCCESS)); env(remit::remit(alice, bob), remit::uri(uri), ter(tecDUPLICATE)); @@ -376,9 +367,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), @@ -396,9 +384,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(bob, uri); env(uritoken::mint(bob, uri), ter(tesSUCCESS)); @@ -427,9 +412,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(1000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - env(remit::remit(alice, bob), remit::amts({USD(1001)}), ter(tecUNFUNDED_PAYMENT)); @@ -445,9 +427,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - env(remit::remit(alice, bob), remit::amts({XRP(1001)}), ter(tecUNFUNDED_PAYMENT)); @@ -466,9 +445,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(250), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - std::string const uri(maxTokenURILength, '?'); env(remit::remit(alice, bob), remit::uri(uri), @@ -492,7 +468,6 @@ struct Remit_test : public beast::unit_test::suite Env env(*this, features - featureDepositAuth); env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); - env(fset(bob, asfAllowIncomingRemit)); env.close(); env(remit::remit(alice, bob), @@ -505,7 +480,6 @@ struct Remit_test : public beast::unit_test::suite Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); - env(fset(bob, asfAllowIncomingRemit)); env.close(); env(remit::remit(alice, bob), remit::amts({XRP(1)}), @@ -526,7 +500,6 @@ struct Remit_test : public beast::unit_test::suite auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); env(fset(bob, asfRequireDest)); - env(fset(bob, asfAllowIncomingRemit)); env.close(); { env(remit::remit(alice, bob), @@ -551,11 +524,11 @@ struct Remit_test : public beast::unit_test::suite Env env{*this, features - featureRemit}; Account const alice{"alice"}; env.fund(XRP(10000), alice); - env(fset(alice, asfAllowIncomingRemit)); + env(fset(alice, asfDisallowIncomingRemit)); env.close(); auto const sle = env.le(alice); uint32_t flags = sle->getFlags(); - BEAST_EXPECT(!(flags & lsfAllowIncomingRemit)); + BEAST_EXPECT(!(flags & lsfDisallowIncomingRemit)); } // setup env @@ -568,54 +541,54 @@ struct Remit_test : public beast::unit_test::suite env.close(); // set flag on bob only - env(fset(bob, asfAllowIncomingRemit)); + env(fset(bob, asfDisallowIncomingRemit)); env.close(); - // remit from alice to bob is allowed + // remit from alice to bob is not allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), - ter(tesSUCCESS)); + ter(tecNO_PERMISSION)); } // set flag on alice also - env(fset(alice, asfAllowIncomingRemit)); + env(fset(alice, asfDisallowIncomingRemit)); env.close(); - // remit from bob to alice is allowed + // remit from bob to alice is not allowed { env(remit::remit(bob, alice), remit::amts({XRP(1)}), - ter(tesSUCCESS)); + ter(tecNO_PERMISSION)); } // remove flag from bob - env(fclear(bob, asfAllowIncomingRemit)); + env(fclear(bob, asfDisallowIncomingRemit)); env.close(); - // now remit between alice and bob not allowed + // now remit between alice and bob allowed { env(remit::remit(alice, bob), remit::amts({XRP(1)}), - ter(tecNO_PERMISSION)); + ter(tesSUCCESS)); } - // a remit from carol to alice is allowed + // a remit from carol to alice is not allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), - ter(tesSUCCESS)); + ter(tecNO_PERMISSION)); } // remove flag from alice - env(fclear(alice, asfAllowIncomingRemit)); + env(fclear(alice, asfDisallowIncomingRemit)); env.close(); - // now a remit from carol to alice is not allowed + // now a remit from carol to alice is allowed { env(remit::remit(carol, alice), remit::amts({XRP(1)}), - ter(tecNO_PERMISSION)); + ter(tesSUCCESS)); } } @@ -638,9 +611,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); // auto const preAlice = env.balance(alice, USD.issue()); @@ -658,7 +628,7 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); + env(fset(bob, asfDisallowIncomingRemit)); env.close(); auto const preAlice = env.balance(alice); @@ -692,9 +662,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -737,9 +704,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -775,9 +739,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -814,9 +775,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); @@ -845,9 +803,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -902,9 +857,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -964,9 +916,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); @@ -1020,9 +969,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1087,9 +1033,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1159,9 +1102,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // mint uri token std::string const uri1(maxTokenURILength, '?'); auto const tid1 = uritoken::tokenid(alice, uri1); @@ -1240,9 +1180,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -1289,9 +1226,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // setup auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); @@ -1998,7 +1932,7 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(5000), t.dst, t.src); env.close(); - env(fset(t.dst, asfAllowIncomingRemit)); + env(fset(t.dst, asfDisallowIncomingRemit)); env.close(); if (t.hasTrustline) @@ -2069,7 +2003,7 @@ struct Remit_test : public beast::unit_test::suite env.close(); } - env(fset(t.dst, asfAllowIncomingRemit)); + env(fset(t.dst, asfDisallowIncomingRemit)); env.close(); auto const preSrc = lineBalance(env, t.src, t.dst, USD); @@ -2140,9 +2074,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(1000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - auto const preAlice = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob, USD.issue()); @@ -2167,9 +2098,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // setup auto const delta = USD(100); auto preAlice = env.balance(alice, USD.issue()); @@ -2196,7 +2124,7 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); - env(fset(alice, asfAllowIncomingRemit)); + env(fset(alice, asfDisallowIncomingRemit)); env.close(); auto const delta = USD(100); @@ -2238,9 +2166,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(1000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // alice cannot remit because bob's trustline is not authorized // all parties must be authorized env(remit::remit(alice, bob), @@ -2289,9 +2214,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - env(fset(gw, asfGlobalFreeze)); env.close(); @@ -2322,9 +2244,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(10000))); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // set freeze on alice trustline env(trust(gw, USD(100000), alice, tfSetFreeze)); env.close(); @@ -2398,7 +2317,7 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); - env(fset(carol, asfAllowIncomingRemit)); + env(fset(carol, asfDisallowIncomingRemit)); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); @@ -2470,7 +2389,7 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); - env(fset(carol, asfAllowIncomingRemit)); + env(fset(carol, asfDisallowIncomingRemit)); env.close(); BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); @@ -2530,9 +2449,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // cannot mint and transfer same token { std::string const uri(maxTokenURILength, '?'); @@ -2718,9 +2634,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, carol); env.close(); - env(fset(bob, asfAllowIncomingRemit)); - env.close(); - // inform { env(remit::remit(alice, bob), diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index 04bea3b7f..93f5251dc 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -4967,9 +4967,6 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest); env.close(); - env(fset(dest, asfAllowIncomingRemit)); - env.close(); - // set tsh collect if (!testStrong) addWeakTSH(env, account); @@ -5000,9 +4997,6 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest); env.close(); - env(fset(dest, asfAllowIncomingRemit)); - env.close(); - // set tsh collect if (!testStrong) addWeakTSH(env, dest); @@ -5034,9 +5028,6 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, inform); env.close(); - env(fset(dest, asfAllowIncomingRemit)); - env.close(); - // set tsh collect if (!testStrong) addWeakTSH(env, inform); @@ -5077,9 +5068,6 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, issuer); env.close(); - env(fset(dest, asfAllowIncomingRemit)); - env.close(); - // mint uritoken std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(issuer, uri); @@ -5136,9 +5124,6 @@ struct SetHookTSH_test : public beast::unit_test::suite env.fund(XRP(1000), account, dest, issuer); env.close(); - env(fset(dest, asfAllowIncomingRemit)); - env.close(); - // mint uritoken std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(issuer, uri); diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 0f1bc8471..808579e6e 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -554,7 +554,7 @@ class AccountInfo_test : public beast::unit_test::suite {"disallowIncomingPayChan", asfDisallowIncomingPayChan}, {"disallowIncomingTrustline", asfDisallowIncomingTrustline}, - {"allowIncomingRemit", asfAllowIncomingRemit}}}; + {"allowIncomingRemit", asfDisallowIncomingRemit}}}; if (features[featureDisallowIncoming]) { diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index d1b571897..afcf227ec 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -88,7 +88,7 @@ class AccountSet_test : public beast::unit_test::suite flag == asfDisallowIncomingPayChan || flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline || - flag == asfTshCollect || flag == asfAllowIncomingRemit) + flag == asfTshCollect || flag == asfDisallowIncomingRemit) { // These flags are part of the DisallowIncoming amendment // and are tested elsewhere From ab73ae09823381dd612dbbef9c255e43b9c535b4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 24 Feb 2024 11:30:11 +0100 Subject: [PATCH 27/47] cleanup `lsfDisallowIncomingRemit` --- src/ripple/protocol/TxFlags.h | 2 +- src/ripple/rpc/handlers/AccountInfo.cpp | 2 +- src/test/app/Remit_test.cpp | 285 +++++++++--------------- src/test/rpc/AccountInfo_test.cpp | 2 +- 4 files changed, 110 insertions(+), 181 deletions(-) diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 0cb815236..b27104a67 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -86,7 +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; +constexpr std::uint32_t asfDisallowIncomingRemit = 16; // OfferCreate flags: constexpr std::uint32_t tfPassive = 0x00010000; diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index 175db4282..524777e38 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -98,7 +98,7 @@ doAccountInfo(RPC::JsonContext& context) {"disallowIncomingCheck", lsfDisallowIncomingCheck}, {"disallowIncomingPayChan", lsfDisallowIncomingPayChan}, {"disallowIncomingTrustline", lsfDisallowIncomingTrustline}, - {"allowIncomingRemit", lsfDisallowIncomingRemit}}}; + {"disallowIncomingRemit", lsfDisallowIncomingRemit}}}; auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 8393b6bd7..47ec93f63 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -457,7 +457,7 @@ struct Remit_test : public beast::unit_test::suite testDisallowXRP(FeatureBitset features) { // auth amount defaults to balance if not present - testcase("Disallow XRP"); + testcase("disallow xrp"); using namespace jtx; using namespace std::literals::chrono_literals; @@ -491,7 +491,7 @@ struct Remit_test : public beast::unit_test::suite testDstTag(FeatureBitset features) { // auth amount defaults to balance if not present - testcase("Dst Tag"); + testcase("dest tag"); using namespace jtx; using namespace std::literals::chrono_literals; @@ -514,9 +514,9 @@ struct Remit_test : public beast::unit_test::suite } void - testAllowIncoming(FeatureBitset features) + testDisallowIncoming(FeatureBitset features) { - testcase("allow incoming"); + testcase("disallow incoming"); using namespace jtx; using namespace std::literals::chrono_literals; @@ -611,9 +611,14 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + env(remit::remit(alice, bob), ter(tesSUCCESS)); env.close(); - // auto const preAlice = env.balance(alice, USD.issue()); + + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBob); } // REMIT: XAH @@ -628,19 +633,14 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); - env(fset(bob, asfDisallowIncomingRemit)); - env.close(); - auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); env(remit::remit(alice, bob), remit::amts({XRP(1)}), ter(tesSUCCESS)); env.close(); - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); - BEAST_EXPECT(postBob == preBob + XRP(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } // REMIT: USD @@ -675,14 +675,10 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops); - BEAST_EXPECT(postBob == preBob); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD @@ -717,14 +713,10 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - XRP(1) - feeDrops); - BEAST_EXPECT(postBob == preBob + XRP(1)); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - feeDrops); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: URITOKEN XFER @@ -732,6 +724,7 @@ struct Remit_test : public beast::unit_test::suite // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -747,6 +740,9 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), alice, tid)); + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + // remit with uritoken id env(remit::remit(alice, bob), remit::token_ids({strHex(tid)}), @@ -758,6 +754,9 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); + // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); @@ -768,6 +767,7 @@ struct Remit_test : public beast::unit_test::suite // setup env Env env{*this, features}; auto const feeDrops = env.current()->fees().base; + auto const feeReserve = env.current()->fees().increment; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -775,16 +775,24 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob); env.close(); + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); env.close(); + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); + // clean up test env(uritoken::burn(bob, strHex(tid))); env.close(); @@ -826,11 +834,9 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); + env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -882,14 +888,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + feeReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -941,15 +943,11 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah & usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1001,11 +999,9 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); // verify xah - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); - BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); + env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1067,14 +1063,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - (feeReserve * 2)); - BEAST_EXPECT(postBob == preBob + (feeReserve * 2)); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + (feeReserve * 2)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1137,15 +1129,11 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify xah & usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); - BEAST_EXPECT(postBob == preBob + XRP(1) + (feeReserve * 2)); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1187,7 +1175,7 @@ struct Remit_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); - BEAST_EXPECT(preBob == XRP(1000) - feeDrops); + BEAST_EXPECT(preBob == XRP(1000)); BEAST_EXPECT(preBobUSD == USD(0)); // remit @@ -1197,14 +1185,10 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT(postAlice == preAlice - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + feeReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD @@ -1233,7 +1217,7 @@ struct Remit_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT(preAlice == XRP(1000)); BEAST_EXPECT(preAliceUSD == USD(10000)); - BEAST_EXPECT(preBob == XRP(1000) - feeDrops); + BEAST_EXPECT(preBob == XRP(1000)); BEAST_EXPECT(preBobUSD == USD(0)); // remit @@ -1243,15 +1227,11 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - feeReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + feeReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } } @@ -1287,10 +1267,8 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(validateSequence( env, bob, withXahau ? 10 : env.closed()->info().seq)); - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); - BEAST_EXPECT(postAlice == preAlice - feeDrops - accountReserve); - BEAST_EXPECT(postBob == preBob + accountReserve); + BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - accountReserve); + BEAST_EXPECT(env.balance(bob) == preBob + accountReserve); } // REMIT: XAH @@ -1313,11 +1291,10 @@ struct Remit_test : public beast::unit_test::suite remit::amts({XRP(1)}), ter(tesSUCCESS)); env.close(); - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); + BEAST_EXPECT( - postAlice == preAlice - XRP(1) - feeDrops - accountReserve); - BEAST_EXPECT(postBob == preBob + XRP(1) + accountReserve); + env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + accountReserve); } // REMIT: USD @@ -1354,16 +1331,11 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); - BEAST_EXPECT( - postAlice == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(postBob == preBob + accountReserve + increment); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(alice) == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: XAH + USD @@ -1400,17 +1372,13 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( - postBob == preBob + XRP(1) + accountReserve + increment); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(bob) == preBob + XRP(1) + accountReserve + increment); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } // REMIT: URITOKEN XFER @@ -1447,11 +1415,9 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(postBob == preBob + accountReserve + increment); + env.balance(alice) == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); @@ -1487,11 +1453,9 @@ struct Remit_test : public beast::unit_test::suite env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); env.close(); - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(postBob == preBob + accountReserve + increment); + env.balance(alice) == preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); @@ -1542,13 +1506,11 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( - postBob == preBob + XRP(1) + accountReserve + increment); + env.balance(bob) == preBob + XRP(1) + accountReserve + increment); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1601,16 +1563,12 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - feeDrops - (increment * 2) - accountReserve); - BEAST_EXPECT(postBob == preBob + (increment * 2) + accountReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(bob) == preBob + (increment * 2) + accountReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1663,18 +1621,14 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify xah & usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( - postBob == preBob + XRP(1) + (increment * 2) + accountReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1728,14 +1682,12 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 2); // verify xah - auto const postAlice = env.balance(alice); - auto const postBob = env.balance(bob); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( - postBob == preBob + XRP(1) + (increment * 2) + accountReserve); + env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1798,16 +1750,12 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - feeDrops - (increment * 3) - accountReserve); - BEAST_EXPECT(postBob == preBob + (increment * 3) + accountReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + BEAST_EXPECT(env.balance(bob) == preBob + (increment * 3) + accountReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1871,18 +1819,14 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify xah & usd - auto const postAlice = env.balance(alice); - auto const postAliceUSD = env.balance(alice, USD.issue()); - auto const postBob = env.balance(bob); - auto const postBobUSD = env.balance(bob, USD.issue()); BEAST_EXPECT( - postAlice == + env.balance(alice) == preAlice - XRP(1) - feeDrops - (increment * 3) - accountReserve); BEAST_EXPECT( - postBob == preBob + XRP(1) + (increment * 3) + accountReserve); - BEAST_EXPECT(postAliceUSD == preAliceUSD - USD(1)); - BEAST_EXPECT(postBobUSD == preBobUSD + USD(1)); + env.balance(bob) == preBob + XRP(1) + (increment * 3) + accountReserve); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1894,7 +1838,7 @@ struct Remit_test : public beast::unit_test::suite void testGateway(FeatureBitset features) { - testcase("Gateway"); + testcase("gateway"); using namespace test::jtx; using namespace std::literals; @@ -1932,9 +1876,6 @@ struct Remit_test : public beast::unit_test::suite env.fund(XRP(5000), t.dst, t.src); env.close(); - env(fset(t.dst, asfDisallowIncomingRemit)); - env.close(); - if (t.hasTrustline) { env.trust(USD(100000), t.dst); @@ -2003,9 +1944,6 @@ struct Remit_test : public beast::unit_test::suite env.close(); } - env(fset(t.dst, asfDisallowIncomingRemit)); - env.close(); - auto const preSrc = lineBalance(env, t.src, t.dst, USD); // issuer can remit to issuer @@ -2124,9 +2062,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, alice, USD(10000))); env.close(); - env(fset(alice, asfDisallowIncomingRemit)); - env.close(); - auto const delta = USD(100); auto const preAlice = env.balance(alice, USD.issue()); @@ -2141,7 +2076,7 @@ struct Remit_test : public beast::unit_test::suite void testRequireAuth(FeatureBitset features) { - testcase("Require Auth"); + testcase("require auth"); using namespace test::jtx; using namespace std::literals; @@ -2190,7 +2125,7 @@ struct Remit_test : public beast::unit_test::suite void testTLFreeze(FeatureBitset features) { - testcase("Trustline Freeze"); + testcase("trustline freeze"); using namespace test::jtx; using namespace std::literals; @@ -2317,9 +2252,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); - env(fset(carol, asfDisallowIncomingRemit)); - env.close(); - BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); @@ -2389,9 +2321,6 @@ struct Remit_test : public beast::unit_test::suite env(pay(carol, alice, USDC(10))); env.close(); - env(fset(carol, asfDisallowIncomingRemit)); - env.close(); - BEAST_EXPECT(env.balance(alice, USDA) == USDA(0)); BEAST_EXPECT(env.balance(alice, USDB) == USDB(-10)); BEAST_EXPECT(env.balance(alice, USDC) == USDC(10)); @@ -2672,7 +2601,7 @@ struct Remit_test : public beast::unit_test::suite testDoApplyInvalid(features); testDisallowXRP(features); testDstTag(features); - testAllowIncoming(features); + testDisallowIncoming(features); testDestExistsTLExists(features); testDestExistsTLNotExist(features); testDestNotExistTLNotExist(features); diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 808579e6e..78aa9e646 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -554,7 +554,7 @@ class AccountInfo_test : public beast::unit_test::suite {"disallowIncomingPayChan", asfDisallowIncomingPayChan}, {"disallowIncomingTrustline", asfDisallowIncomingTrustline}, - {"allowIncomingRemit", asfDisallowIncomingRemit}}}; + {"disallowIncomingRemit", asfDisallowIncomingRemit}}}; if (features[featureDisallowIncoming]) { From ffdabc9fcd692cd169c9912564ba559941135710 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 24 Feb 2024 11:46:00 +0100 Subject: [PATCH 28/47] add `DepositAuth` --- src/ripple/app/tx/impl/Remit.cpp | 8 ++++ src/test/app/Remit_test.cpp | 81 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index d7d18da61..1e6742b31 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -214,6 +214,14 @@ Remit::doApply() (flags & lsfDisallowIncomingRemit)) return tecNO_PERMISSION; + // Check if the destination account requires deposit authorization. + bool const depositAuth{view().rules().enabled(featureDepositAuth)}; + if (depositAuth && sleDstAcc && (sleDstAcc->getFlags() & lsfDepositAuth)) + { + if (!view().exists(keylet::depositPreauth(dstAccID, srcAccID))) + return tecNO_PERMISSION; + } + // the destination may require a dest tag if ((flags & lsfRequireDestTag) && !ctx_.tx.isFieldPresent(sfDestinationTag)) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 47ec93f63..61f7c48f7 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -2122,6 +2122,86 @@ struct Remit_test : public beast::unit_test::suite } } + void + testDepositAuth(FeatureBitset features) + { + testcase("deposit authorization"); + 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"]; + + { + Env env{*this, features}; + auto const feeDrops = env.current()->fees().base; + + env.fund(XRP(10000), alice, bob, gw); + env.close(); + env.trust(USD(100000), alice, bob); + env.close(); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env.close(); + + auto const preBobXrp = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + auto const preAliceXrp = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + + env(fset(bob, asfDepositAuth)); + env.close(); + + // Since alice is not preauthorized, remit fails. + env(remit::remit(alice, bob), + remit::amts({USD(100)}), + ter(tecNO_PERMISSION)); + env.close(); + + // Bob preauthorizes alice for deposit, remit success. + env(deposit::auth(bob, alice)); + env.close(); + + env(remit::remit(alice, bob), + remit::amts({USD(100)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops)); + BEAST_EXPECT(env.balance(bob) == preBobXrp - (2 * feeDrops)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(100)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(100)); + + // bob removes preauthorization of alice. + env(deposit::unauth(bob, alice)); + env.close(); + + // alice remits and fails since she is no longer preauthorized. + env(remit::remit(alice, bob), + remit::amts({USD(100)}), + ter(tecNO_PERMISSION)); + env.close(); + + // bob clears lsfDepositAuth. + env(fclear(bob, asfDepositAuth)); + env.close(); + + // alice remits successfully. + env(remit::remit(alice, bob), + remit::amts({USD(100)}), + ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == preAliceXrp - (4 * feeDrops)); + BEAST_EXPECT(env.balance(bob) == preBobXrp - (4 * feeDrops)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(200)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(200)); + } + } + void testTLFreeze(FeatureBitset features) { @@ -2608,6 +2688,7 @@ struct Remit_test : public beast::unit_test::suite testGateway(features); testTransferRate(features); testRequireAuth(features); + testDepositAuth(features); testTLFreeze(features); testRippling(features); testURIToken(features); From 5e5590bd3e9ca4e030784d4dfa58aab4d856834d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 24 Feb 2024 11:49:42 +0100 Subject: [PATCH 29/47] clang-format --- src/test/app/Remit_test.cpp | 135 ++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 46 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 61f7c48f7..1eca1ecff 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -677,7 +677,8 @@ struct Remit_test : public beast::unit_test::suite // validate BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } @@ -715,7 +716,8 @@ struct Remit_test : public beast::unit_test::suite // validate BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - feeDrops); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } @@ -754,7 +756,8 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); // clean up test @@ -780,17 +783,18 @@ struct Remit_test : public beast::unit_test::suite std::string const uri(maxTokenURILength, '?'); auto const tid = uritoken::tokenid(alice, uri); - + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); env.close(); - + BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); BEAST_EXPECT(inOwnerDir(*env.current(), bob, tid)); BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); BEAST_EXPECT(tokenIsser(*env.current(), tid) == alice.id()); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); // clean up test @@ -835,7 +839,8 @@ struct Remit_test : public beast::unit_test::suite // verify xah BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); // clean up test @@ -888,9 +893,11 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(tokenOwner(*env.current(), tid) == bob.id()); // verify usd - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -944,9 +951,11 @@ struct Remit_test : public beast::unit_test::suite // verify xah & usd BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1000,8 +1009,10 @@ struct Remit_test : public beast::unit_test::suite // verify xah BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); - BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT( + env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1063,9 +1074,11 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 3); // verify usd - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - (feeReserve * 2)); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - (feeReserve * 2)); BEAST_EXPECT(env.balance(bob) == preBob + (feeReserve * 2)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1130,9 +1143,12 @@ struct Remit_test : public beast::unit_test::suite // verify xah & usd BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - (feeReserve * 2)); - BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - (feeReserve * 2)); + BEAST_EXPECT( + env.balance(bob) == preBob + XRP(1) + (feeReserve * 2)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1185,9 +1201,11 @@ struct Remit_test : public beast::unit_test::suite env.close(); // validate - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - feeReserve); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + feeReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } @@ -1228,9 +1246,11 @@ struct Remit_test : public beast::unit_test::suite // validate BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - feeReserve); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - feeReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + feeReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } } @@ -1267,7 +1287,8 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(validateSequence( env, bob, withXahau ? 10 : env.closed()->info().seq)); - BEAST_EXPECT(env.balance(alice) == preAlice - feeDrops - accountReserve); + BEAST_EXPECT( + env.balance(alice) == preAlice - feeDrops - accountReserve); BEAST_EXPECT(env.balance(bob) == preBob + accountReserve); } @@ -1293,7 +1314,8 @@ struct Remit_test : public beast::unit_test::suite env.close(); BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve); + env.balance(alice) == + preAlice - XRP(1) - feeDrops - accountReserve); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1) + accountReserve); } @@ -1332,9 +1354,12 @@ struct Remit_test : public beast::unit_test::suite // validate BEAST_EXPECT( - env.balance(alice) == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + env.balance(alice) == + preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + env.balance(bob) == preBob + accountReserve + increment); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } @@ -1376,8 +1401,10 @@ struct Remit_test : public beast::unit_test::suite env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( - env.balance(bob) == preBob + XRP(1) + accountReserve + increment); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + env.balance(bob) == + preBob + XRP(1) + accountReserve + increment); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); } @@ -1416,8 +1443,10 @@ struct Remit_test : public beast::unit_test::suite // validate BEAST_EXPECT( - env.balance(alice) == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); + env.balance(alice) == + preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); @@ -1454,8 +1483,10 @@ struct Remit_test : public beast::unit_test::suite env.close(); BEAST_EXPECT( - env.balance(alice) == preAlice - feeDrops - accountReserve - increment); - BEAST_EXPECT(env.balance(bob) == preBob + accountReserve + increment); + env.balance(alice) == + preAlice - feeDrops - accountReserve - increment); + BEAST_EXPECT( + env.balance(bob) == preBob + accountReserve + increment); BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 0); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 1); @@ -1510,7 +1541,8 @@ struct Remit_test : public beast::unit_test::suite env.balance(alice) == preAlice - XRP(1) - feeDrops - accountReserve - increment); BEAST_EXPECT( - env.balance(bob) == preBob + XRP(1) + accountReserve + increment); + env.balance(bob) == + preBob + XRP(1) + accountReserve + increment); // clean up test env(uritoken::burn(bob, strHex(tid))); @@ -1566,8 +1598,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - (increment * 2) - accountReserve); - BEAST_EXPECT(env.balance(bob) == preBob + (increment * 2) + accountReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(bob) == preBob + (increment * 2) + accountReserve); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1626,8 +1660,10 @@ struct Remit_test : public beast::unit_test::suite preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( - env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + env.balance(bob) == + preBob + XRP(1) + (increment * 2) + accountReserve); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1687,7 +1723,8 @@ struct Remit_test : public beast::unit_test::suite preAlice - XRP(1) - feeDrops - (increment * 2) - accountReserve); BEAST_EXPECT( - env.balance(bob) == preBob + XRP(1) + (increment * 2) + accountReserve); + env.balance(bob) == + preBob + XRP(1) + (increment * 2) + accountReserve); // clean up test env(uritoken::burn(bob, strHex(tid1))); @@ -1753,8 +1790,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT( env.balance(alice) == preAlice - feeDrops - (increment * 3) - accountReserve); - BEAST_EXPECT(env.balance(bob) == preBob + (increment * 3) + accountReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + BEAST_EXPECT( + env.balance(bob) == preBob + (increment * 3) + accountReserve); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -1824,8 +1863,10 @@ struct Remit_test : public beast::unit_test::suite preAlice - XRP(1) - feeDrops - (increment * 3) - accountReserve); BEAST_EXPECT( - env.balance(bob) == preBob + XRP(1) + (increment * 3) + accountReserve); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); + env.balance(bob) == + preBob + XRP(1) + (increment * 3) + accountReserve); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(1)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(1)); // clean up test @@ -2172,9 +2213,10 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice) == preAliceXrp - (2 * feeDrops)); BEAST_EXPECT(env.balance(bob) == preBobXrp - (2 * feeDrops)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(100)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(100)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(100)); - + // bob removes preauthorization of alice. env(deposit::unauth(bob, alice)); env.close(); @@ -2197,7 +2239,8 @@ struct Remit_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice) == preAliceXrp - (4 * feeDrops)); BEAST_EXPECT(env.balance(bob) == preBobXrp - (4 * feeDrops)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(200)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAliceUSD - USD(200)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(200)); } } From 4e8c0b4388eaac006e71dd7dd9bca4a0c73e5d5c Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 29 Feb 2024 10:53:21 +0100 Subject: [PATCH 30/47] remove `sfFlags` from `sfAmountEntry` --- src/ripple/protocol/impl/InnerObjectFormats.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index 145d07236..e52d6ff8f 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -148,7 +148,6 @@ InnerObjectFormats::InnerObjectFormats() sfAmountEntry.getCode(), { {sfAmount, soeREQUIRED}, - {sfFlags, soeOPTIONAL}, }); add(sfMintURIToken.jsonName.c_str(), From 2c381d4e1fe553f03dd71696f78a644269243ab9 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 4 Mar 2024 16:41:32 +0100 Subject: [PATCH 31/47] address review comments --- src/ripple/app/tx/impl/Remit.cpp | 43 +++++++++++++++++++++----------- src/test/app/Remit_test.cpp | 41 +++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 1e6742b31..c301b4829 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -53,7 +53,8 @@ Remit::preflight(PreflightContext const& ctx) return temREDUNDANT; } - if (ctx.tx.getFieldVL(sfBlob).size() > (128 * 1024)) + if (ctx.tx.isFieldPresent(sfBlob) && + ctx.tx.getFieldVL(sfBlob).size() > (128 * 1024)) { JLOG(ctx.j.warn()) << "Blob was more than 128kib " << ctx.tx.getTransactionID(); @@ -65,7 +66,7 @@ Remit::preflight(PreflightContext const& ctx) { if (ctx.tx.getFieldArray(sfAmounts).size() > 32) { - JLOG(ctx.j.warn()) << "Malformed: AmountEntrys Exceed Limit `32`."; + JLOG(ctx.j.warn()) << "Malformed: AmountEntry count exceeds `32`."; return temMALFORMED; } @@ -135,8 +136,23 @@ Remit::preflight(PreflightContext const& ctx) STObject const& mint = const_cast(ctx.tx) .getField(sfMintURIToken) .downcast(); - // RH TODO: iterate mint fields detect any that shouldnt be there + 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) { @@ -145,7 +161,7 @@ Remit::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (!URIToken::validateUTF8(mint.getFieldVL(sfURI))) + if (!URIToken::validateUTF8(uri)) { JLOG(ctx.j.warn()) << "Malformed transaction: Invalid UTF8 inside MintURIToken."; @@ -162,14 +178,13 @@ Remit::preflight(PreflightContext const& ctx) // sanity check uritokenids if (ctx.tx.isFieldPresent(sfURITokenIDs)) { - if (ctx.tx.getFieldV256(sfURITokenIDs).size() > 32) + STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); + if (ids.size() < 1 || ids.size() > 32) { - JLOG(ctx.j.warn()) - << "Malformed transaction: URITokenIDs Exceed Limit `32`."; + JLOG(ctx.j.warn()) << "Malformed transaction: URITokenIDs Invalid."; return temMALFORMED; } - STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); std::sort(ids.begin(), ids.end()); if (std::adjacent_find(ids.begin(), ids.end()) != ids.end()) { @@ -185,11 +200,11 @@ Remit::preflight(PreflightContext const& ctx) TER Remit::doApply() { - if (!view().rules().enabled(featureRemit)) - return temDISABLED; - Sandbox sb(&ctx_.view()); + if (!sb.rules().enabled(featureRemit)) + return temDISABLED; + beast::Journal const& j = ctx_.journal; auto const srcAccID = ctx_.tx[sfAccount]; @@ -215,10 +230,10 @@ Remit::doApply() return tecNO_PERMISSION; // Check if the destination account requires deposit authorization. - bool const depositAuth{view().rules().enabled(featureDepositAuth)}; + bool const depositAuth{sb.rules().enabled(featureDepositAuth)}; if (depositAuth && sleDstAcc && (sleDstAcc->getFlags() & lsfDepositAuth)) { - if (!view().exists(keylet::depositPreauth(dstAccID, srcAccID))) + if (!sb.exists(keylet::depositPreauth(dstAccID, srcAccID))) return tecNO_PERMISSION; } @@ -305,7 +320,7 @@ Remit::doApply() sfFlags, mint.isFieldPresent(sfFlags) ? mint.getFieldU32(sfFlags) : 0); - auto const page = view().dirInsert( + auto const page = sb.dirInsert( keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID)); JLOG(j_.trace()) << "Adding URIToken to owner directory " diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 1eca1ecff..a5491b034 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -264,6 +264,30 @@ struct Remit_test : public beast::unit_test::suite env.close(); } + // temMALFORMED - URI field was not provided. + // DA: Template/Submit Failure + // { + // auto tx = remit::remit(alice, bob); + // tx[sfMintURIToken.jsonName] = Json::Value{}; + // tx[sfMintURIToken.jsonName][sfFlags.fieldName] = 1; + // env(tx, ter(temMALFORMED)); + // env.close(); + // } + + // temMALFORMED - Field found in disallowed location. + // DA: Template/Submit Failure + // { + // std::string const uri(0, '?'); + // auto tx = remit::remit(alice, bob); + // tx[sfMintURIToken.jsonName] = Json::Value{}; + // std::string const digestval = + // "C16E7263F07AA41261DCC955660AF4646ADBA414E37B6F5A5BA50F75153F5CCC"; + // tx[sfMintURIToken.jsonName][sfURI.fieldName] = strHex(uri); + // tx[sfMintURIToken.jsonName][sfHookOn.fieldName] = digestval; + // env(tx, ter(temMALFORMED)); + // env.close(); + // } + // temMALFORMED - URI was too short/long. (short) { std::string const uri(0, '?'); @@ -289,6 +313,15 @@ struct Remit_test : public beast::unit_test::suite env.close(); } + // temMALFORMED - URITokenIDs < 1 + { + std::vector token_ids; + env(remit::remit(alice, bob), + remit::token_ids(token_ids), + ter(temMALFORMED)); + env.close(); + } + // temMALFORMED - URITokenIDs Exceeds Limit { std::vector token_ids; @@ -2032,7 +2065,9 @@ struct Remit_test : public beast::unit_test::suite std::string multiply; std::string divide; }; - std::array testCases = {{ + std::array testCases = {{ + {0, USD(100), "1100", "1100"}, + {-1, USD(100), "1100", "1100"}, {1, USD(100), "1100", "1100"}, {1.1, USD(100), "1110", "1090.909090909091"}, {1.0005, USD(100), "1100.05", "1099.950024987506"}, @@ -2085,7 +2120,7 @@ struct Remit_test : public beast::unit_test::suite env(rate(gw, 1.00)); env.close(); - // remit at higher rate + // remit env(remit::remit(alice, bob), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); @@ -2106,7 +2141,7 @@ struct Remit_test : public beast::unit_test::suite auto const delta = USD(100); auto const preAlice = env.balance(alice, USD.issue()); - // alice sells + // remit env(remit::remit(gw, alice), remit::amts({delta})); env.close(); From 0a7e637a05ed96ded5cf06748a391831726e6cf9 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 4 Mar 2024 21:55:36 +0100 Subject: [PATCH 32/47] Update Remit_test.cpp --- src/test/app/Remit_test.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index a5491b034..1bb778b0d 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -2064,23 +2064,26 @@ struct Remit_test : public beast::unit_test::suite STAmount delta; std::string multiply; std::string divide; + TER code; }; - std::array testCases = {{ - {0, USD(100), "1100", "1100"}, - {-1, USD(100), "1100", "1100"}, - {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"}, + std::array testCases = {{ + {0.0, USD(100), "1100", "1100", tesSUCCESS}, + {-1.0, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, + {0.9, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, + {1.0, USD(100), "1100", "1100", tesSUCCESS}, + {1.1, USD(100), "1110", "1090.909090909091", tesSUCCESS}, + {1.0005, USD(100), "1100.05", "1099.950024987506", tesSUCCESS}, + {1.005, USD(100), "1100.4999999", "1099.502487661197", tesSUCCESS}, + {1.25, USD(100), "1125", "1080", tesSUCCESS}, + {2.0, USD(100), "1200", "1050", tesSUCCESS}, + {2.1, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, }}; for (auto const& tc : testCases) { Env env{*this, features}; env.fund(XRP(10000), alice, bob, gw); - env(rate(gw, tc.rate)); + env(rate(gw, tc.rate), ter(tc.code)); env.close(); env.trust(USD(100000), alice, bob); env.close(); From 86d45ad1820c68d89445c16e4891092500f87857 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Mar 2024 23:51:05 +0100 Subject: [PATCH 33/47] tx consequences, flags --- src/ripple/app/tx/impl/Remit.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index c301b4829..440978b17 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -28,7 +28,25 @@ namespace ripple { TxConsequences Remit::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ctx.tx, TxConsequences::normal}; + 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 @@ -231,7 +249,7 @@ Remit::doApply() // Check if the destination account requires deposit authorization. bool const depositAuth{sb.rules().enabled(featureDepositAuth)}; - if (depositAuth && sleDstAcc && (sleDstAcc->getFlags() & lsfDepositAuth)) + if (depositAuth && sleDstAcc && (flags & lsfDepositAuth)) { if (!sb.exists(keylet::depositPreauth(dstAccID, srcAccID))) return tecNO_PERMISSION; @@ -552,8 +570,6 @@ Remit::calculateBaseFee(ReadView const& view, STTx const& tx) extraFee += XRPAmount{static_cast(tx.getFieldVL(sfBlob).size())}; - // RH TODO: add fees - return Transactor::calculateBaseFee(view, tx) + extraFee; } From 66aa3117eac804292a4c09622724d6bb5878f1f8 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Mar 2024 23:53:26 +0100 Subject: [PATCH 34/47] clang --- src/ripple/app/tx/impl/Remit.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 440978b17..6224093d8 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -28,9 +28,7 @@ namespace ripple { TxConsequences Remit::makeTxConsequences(PreflightContext const& ctx) { - XRPAmount native = - ([&ctx]() -> XRPAmount - { + XRPAmount native = ([&ctx]() -> XRPAmount { if (!ctx.tx.isFieldPresent(sfAmounts)) return beast::zero; From f6023e1840e9a7f1096a1533d361d297ca9028e1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 8 Mar 2024 00:04:40 +0100 Subject: [PATCH 35/47] add inform account guard --- src/ripple/app/tx/impl/Remit.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 6224093d8..b5181e909 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -229,6 +229,14 @@ Remit::doApply() if (!sleSrcAcc) return terNO_ACCOUNT; + if (ctx_.tx.isFieldPresent(sfInform)) + { + auto const informAcc = tx.getAccountID(sfInform); + auto sleInformAcc = sb.peek(keylet::account(informAcc)); + if (!sleInformAcc) + return tecNO_TARGET; + } + XRPAmount const accountReserve{sb.fees().accountReserve(0)}; XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve}; From b872d44874278410cdbf0404e773eb07d51260fd Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 8 Mar 2024 00:05:40 +0100 Subject: [PATCH 36/47] update tsh --- src/ripple/app/hook/impl/applyHook.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 3915b3483..271d8a34c 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -75,7 +75,11 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) ADD_TSH(*destAcc, tshSTRONG); if (tx.isFieldPresent(sfInform)) - ADD_TSH(tx.getAccountID(sfInform), tshWEAK); + { + auto const inform = tx.getAccountID(sfInform); + if (*otxnAcc != inform && *destAcc != inform) + ADD_TSH(inform, tshWEAK); + } if (tx.isFieldPresent(sfURITokenIDs)) { @@ -93,11 +97,13 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) auto const owner = ut->getAccountID(sfOwner); auto const issuer = ut->getAccountID(sfIssuer); - if (issuer != owner) + if (issuer != owner && issuer != *destAcc) + { ADD_TSH( issuer, (ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK); + } } } break; From c8952d0e4bc5cbfe67494cb71974b5cdfa08220d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 8 Mar 2024 00:12:27 +0100 Subject: [PATCH 37/47] fixup --- src/ripple/app/tx/impl/Remit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index b5181e909..326d9dcf3 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -231,7 +231,7 @@ Remit::doApply() if (ctx_.tx.isFieldPresent(sfInform)) { - auto const informAcc = tx.getAccountID(sfInform); + auto const informAcc = ctx_.tx.getAccountID(sfInform); auto sleInformAcc = sb.peek(keylet::account(informAcc)); if (!sleInformAcc) return tecNO_TARGET; From 1f852bd821cdcc8772c12238447caebd2f3f3c42 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 8 Mar 2024 00:37:48 +0100 Subject: [PATCH 38/47] add inform test --- src/test/app/Remit_test.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 1bb778b0d..14d19f9ad 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -366,18 +366,36 @@ struct Remit_test : public beast::unit_test::suite // doApply // terNO_ACCOUNT - account doesnt exist - // { - // auto const carol = Account("carol"); - // env.memoize(carol); - // env(remit::remit(carol, bob), ter(terNO_ACCOUNT)); - // env.close(); - // } + { + auto const carol = Account("carol"); + Env env{*this, features}; + env.memoize(carol); + auto tx = remit::remit(carol, bob); + tx[jss::Sequence] = 0; + env(tx, carol, ter(terNO_ACCOUNT)); + env.close(); + } + + // tecNO_TARGET - inform acct doesnt exist + { + auto const carol = Account("carol"); + Env env{*this, features}; + env.fund(XRP(1000), alice, bob); + env.close(); + + env.memoize(carol); + auto tx = remit::remit(alice, bob); + tx[sfInform.jsonName] = carol.human(); + tx[jss::Sequence] = 0; + env(tx, alice, ter(tecNO_TARGET)); + env.close(); + } // tecNO_PERMISSION - lsfDisallowIncomingRemit - // see testAllowIncoming + // DA: see testAllowIncoming // tecDST_TAG_NEEDED - // see testDstTag + // DA: see testDstTag // tecDUPLICATE { From 384999bf22fa198fc07b3a63fed3f9206b5dbc3c Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 8 Mar 2024 00:40:46 +0100 Subject: [PATCH 39/47] fix test bad sequence --- src/test/app/Remit_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 14d19f9ad..8db743670 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -386,7 +386,6 @@ struct Remit_test : public beast::unit_test::suite env.memoize(carol); auto tx = remit::remit(alice, bob); tx[sfInform.jsonName] = carol.human(); - tx[jss::Sequence] = 0; env(tx, alice, ter(tecNO_TARGET)); env.close(); } From 8a7308145f4ed27d902dbbe75b2c29d61cb032f2 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sun, 10 Mar 2024 01:15:18 +0000 Subject: [PATCH 40/47] ensure sfInform is not the same as source or destination --- src/ripple/app/tx/impl/Remit.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 326d9dcf3..b6dc2b9a0 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -69,6 +69,16 @@ Remit::preflight(PreflightContext const& ctx) 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)) { From c5e086d08ff83556dddb47c579b3380f5118554b Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sun, 10 Mar 2024 01:56:43 +0000 Subject: [PATCH 41/47] add extra sanity checks --- src/ripple/app/tx/impl/Remit.cpp | 27 ++++++++++++++++++++++++--- src/test/app/Remit_test.cpp | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index b6dc2b9a0..7b7c1a6bb 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -242,14 +242,27 @@ Remit::doApply() if (ctx_.tx.isFieldPresent(sfInform)) { auto const informAcc = ctx_.tx.getAccountID(sfInform); - auto sleInformAcc = sb.peek(keylet::account(informAcc)); - if (!sleInformAcc) + 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}; @@ -396,7 +409,7 @@ Remit::doApply() { JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was " "not actually a uritoken."; - return tecINTERNAL; + return tecNO_ENTRY; } // is it our uritoken? @@ -496,10 +509,18 @@ Remit::doApply() 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; diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 8db743670..77d1c36fa 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -254,6 +254,12 @@ struct Remit_test : public beast::unit_test::suite remit::amts({XRP(1), XRP(1)}), ter(temMALFORMED)); env.close(); + + // not adjacent + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1), XRP(1)}), + ter(temMALFORMED)); + env.close(); } // temMALFORMED - Issued Currency appears more than once. @@ -262,6 +268,12 @@ struct Remit_test : public beast::unit_test::suite remit::amts({USD(1), USD(1)}), ter(temMALFORMED)); env.close(); + + // not adjacent + env(remit::remit(alice, bob), + remit::amts({USD(1), XRP(1), USD(1)}), + ter(temMALFORMED)); + env.close(); } // temMALFORMED - URI field was not provided. @@ -346,6 +358,14 @@ struct Remit_test : public beast::unit_test::suite remit::token_ids({strHex(tid1), strHex(tid1)}), ter(temMALFORMED)); env.close(); + + // not adj + std::string const uri2(maxTokenURILength, 'a'); + auto const tid2 = uritoken::tokenid(alice, uri2); + env(remit::remit(alice, bob), + remit::token_ids({strHex(tid1), strHex(tid2), strHex(tid1)}), + ter(temMALFORMED)); + env.close(); } } From 393d95b67985d071f5ed610b70128629510d9893 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sun, 10 Mar 2024 04:52:20 +0000 Subject: [PATCH 42/47] fix sender pays fee for remit + clang --- src/ripple/app/tx/impl/Remit.cpp | 14 ++++++-------- src/test/app/Remit_test.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 7b7c1a6bb..998d0c1b8 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -74,7 +74,8 @@ Remit::preflight(PreflightContext const& ctx) 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."; + JLOG(ctx.j.warn()) << "Malformed transaction: sfInform is same as " + "source or destination."; return temMALFORMED; } } @@ -244,8 +245,7 @@ Remit::doApply() auto const informAcc = ctx_.tx.getAccountID(sfInform); if (!sb.exists(keylet::account(informAcc))) { - JLOG(j.warn()) - << "Remit: sfInform account does not exist."; + JLOG(j.warn()) << "Remit: sfInform account does not exist."; return tecNO_TARGET; } } @@ -254,8 +254,7 @@ Remit::doApply() XRPAmount const objectReserve{sb.fees().accountReserve(1) - accountReserve}; // sanity check - if (accountReserve < beast::zero || - objectReserve < beast::zero || + if (accountReserve < beast::zero || objectReserve < beast::zero || objectReserve > sb.fees().accountReserve(1)) { JLOG(j.warn()) @@ -509,7 +508,7 @@ Remit::doApply() issuerAccID != srcAccID && issuerAccID != dstAccID ? multiply(amount, transferRate(sb, issuerAccID)) : amount; - + // sanity check this calculation if (srcAmt < amount || srcAmt > amount + amount) { @@ -520,7 +519,6 @@ Remit::doApply() STAmount availableFunds{ accountFunds(sb, srcAccID, srcAmt, fhZERO_IF_FROZEN, j)}; - if (availableFunds < srcAmt) return tecUNFUNDED_PAYMENT; @@ -533,7 +531,7 @@ Remit::doApply() // action the transfer if (TER result = - accountSend(sb, srcAccID, dstAccID, amount, j, false); + accountSend(sb, srcAccID, dstAccID, amount, j, true); result != tesSUCCESS) return result; } diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 77d1c36fa..dfeb4a2d0 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -254,7 +254,7 @@ struct Remit_test : public beast::unit_test::suite remit::amts({XRP(1), XRP(1)}), ter(temMALFORMED)); env.close(); - + // not adjacent env(remit::remit(alice, bob), remit::amts({XRP(1), USD(1), XRP(1)}), @@ -268,7 +268,7 @@ struct Remit_test : public beast::unit_test::suite remit::amts({USD(1), USD(1)}), ter(temMALFORMED)); env.close(); - + // not adjacent env(remit::remit(alice, bob), remit::amts({USD(1), XRP(1), USD(1)}), @@ -358,8 +358,8 @@ struct Remit_test : public beast::unit_test::suite remit::token_ids({strHex(tid1), strHex(tid1)}), ter(temMALFORMED)); env.close(); - - // not adj + + // not adj std::string const uri2(maxTokenURILength, 'a'); auto const tid2 = uritoken::tokenid(alice, uri2); env(remit::remit(alice, bob), From 713d81ab8a72471a5b52646d56994faff71ed20d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 Mar 2024 08:57:06 +0100 Subject: [PATCH 43/47] fix transfer rate tests --- src/test/app/Remit_test.cpp | 42 ++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index dfeb4a2d0..15bc20ce3 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -2099,21 +2099,20 @@ struct Remit_test : public beast::unit_test::suite { double rate; STAmount delta; - std::string multiply; - std::string divide; + std::string result; TER code; }; std::array testCases = {{ - {0.0, USD(100), "1100", "1100", tesSUCCESS}, - {-1.0, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, - {0.9, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, - {1.0, USD(100), "1100", "1100", tesSUCCESS}, - {1.1, USD(100), "1110", "1090.909090909091", tesSUCCESS}, - {1.0005, USD(100), "1100.05", "1099.950024987506", tesSUCCESS}, - {1.005, USD(100), "1100.4999999", "1099.502487661197", tesSUCCESS}, - {1.25, USD(100), "1125", "1080", tesSUCCESS}, - {2.0, USD(100), "1200", "1050", tesSUCCESS}, - {2.1, USD(100), "1100", "1100", temBAD_TRANSFER_RATE}, + {0.0, USD(100), "900", tesSUCCESS}, + {-1.0, USD(100), "900", temBAD_TRANSFER_RATE}, + {0.9, USD(100), "900", temBAD_TRANSFER_RATE}, + {1.0, USD(100), "900", tesSUCCESS}, + {1.1, USD(100), "890", tesSUCCESS}, + {1.0005, USD(100), "899.95", tesSUCCESS}, + {1.005, USD(100), "899.5000001", tesSUCCESS}, + {1.25, USD(100), "875", tesSUCCESS}, + {2.0, USD(100), "800", tesSUCCESS}, + {2.1, USD(100), "900", temBAD_TRANSFER_RATE}, }}; for (auto const& tc : testCases) @@ -2128,16 +2127,15 @@ struct Remit_test : public beast::unit_test::suite env(pay(gw, bob, USD(1000))); env.close(); - auto const preAlice = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob, USD.issue()); auto const delta = USD(100); env(remit::remit(alice, bob), remit::amts({delta})); env.close(); auto xferRate = transferRate(*env.current(), gw); - auto const postBob = env.balance(bob, USD.issue()); - BEAST_EXPECT(to_string(postBob.value()) == tc.divide); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); + auto const postAlice = env.balance(alice, USD.issue()); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); + BEAST_EXPECT(to_string(postAlice.value()) == tc.result); } // test rate change @@ -2155,16 +2153,26 @@ struct Remit_test : public beast::unit_test::suite // setup auto const delta = USD(100); auto preAlice = env.balance(alice, USD.issue()); + auto preBob = env.balance(bob, USD.issue()); + + // remit + env(remit::remit(alice, bob), remit::amts({delta})); + env.close(); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta - USD(25)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); // issuer changes rate lower env(rate(gw, 1.00)); env.close(); + preAlice = env.balance(alice, USD.issue()); + preBob = env.balance(bob, USD.issue()); + // remit env(remit::remit(alice, bob), remit::amts({delta})); env.close(); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta); - BEAST_EXPECT(env.balance(bob, USD.issue()) == USD(10100)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); } // test issuer doesnt pay own rate From 40ef57bc1a57b56377e9129cc4ce28fb92ccace3 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 Mar 2024 09:03:57 +0100 Subject: [PATCH 44/47] clang-format --- src/test/app/Remit_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index 15bc20ce3..b1530575d 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -2158,7 +2158,8 @@ struct Remit_test : public beast::unit_test::suite // remit env(remit::remit(alice, bob), remit::amts({delta})); env.close(); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAlice - delta - USD(25)); + BEAST_EXPECT( + env.balance(alice, USD.issue()) == preAlice - delta - USD(25)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBob + delta); // issuer changes rate lower From e01d9ad8270bf930ca1368bb9bf99161528cd4f1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 Mar 2024 09:54:08 +0100 Subject: [PATCH 45/47] remove unnecessary check --- src/ripple/app/tx/impl/Remit.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 998d0c1b8..f324fcbae 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -524,8 +524,7 @@ Remit::doApply() // if the target trustline doesn't exist we need to create it and // pay its reserve - if (issuerAccID != dstAccID && - !sb.exists( + if (!sb.exists( keylet::line(dstAccID, issuerAccID, amount.getCurrency()))) nativeRemit += objectReserve; From 9b4b37ad6e9a419145afd66eb6daf98f3e221759 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 10 Mar 2024 09:54:14 +0100 Subject: [PATCH 46/47] nit --- src/test/app/Remit_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index b1530575d..d71b4091e 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -2057,7 +2057,7 @@ struct Remit_test : public beast::unit_test::suite auto const preSrc = lineBalance(env, t.src, t.dst, USD); - // issuer can remit to issuer + // src can remit to issuer env(remit::remit(t.src, t.dst), remit::amts({USD(100)}), t.hasTrustline ? ter(tesSUCCESS) : ter(tecUNFUNDED_PAYMENT)); From 4f122347be67a19d8c11c842fc7875d375603437 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sun, 10 Mar 2024 22:04:43 +0000 Subject: [PATCH 47/47] overflow checks --- src/ripple/app/tx/impl/Remit.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index f324fcbae..e8c527df3 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -297,6 +297,9 @@ Remit::doApply() if (createDst) { // sender will pay the reserve + if (nativeRemit + accountReserve < nativeRemit) + return tecINTERNAL; + nativeRemit += accountReserve; // Create the account. @@ -331,6 +334,9 @@ Remit::doApply() // 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(ctx_.tx) .getField(sfMintURIToken) @@ -426,6 +432,9 @@ Remit::doApply() sleU->makeFieldAbsent(sfDestination); // pay the reserve + if (nativeRemit + objectReserve < nativeRemit) + return tecINTERNAL; + nativeRemit += objectReserve; // remove from sender dir @@ -479,6 +488,11 @@ Remit::doApply() // 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; } @@ -526,7 +540,12 @@ Remit::doApply() // 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 =