From 489a47241dab02f4ee9f0c370050cb4c7c45146b Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Wed, 29 Nov 2023 18:47:31 +0000 Subject: [PATCH 01/15] mostly finished remit preflight + preclaim, wip --- src/ripple/app/tx/impl/Remit.cpp | 360 ++++++++++++++++++ src/ripple/app/tx/impl/Remit.h | 57 +++ src/ripple/app/tx/impl/SetAccount.cpp | 8 + src/ripple/app/tx/impl/URIToken.cpp | 52 +-- src/ripple/app/tx/impl/URIToken.h | 57 +++ 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 | 4 + 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 | 4 + src/ripple/protocol/impl/TxFormats.cpp | 15 + 15 files changed, 541 insertions(+), 53 deletions(-) create mode 100644 src/ripple/app/tx/impl/Remit.cpp create mode 100644 src/ripple/app/tx/impl/Remit.h diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp new file mode 100644 index 000000000..a7151fcf9 --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -0,0 +1,360 @@ +//------------------------------------------------------------------------------ +/* + 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; + } + if (ctx.tx[sfAccount] == ctx.tx[sfDestination]) + { + // They wrote a check to themselves. + 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(obj.getFieldArray(sfAmounts)); + for (STObject const& sEntry : sEntries) + { + // Validate the AmountEntry. + if (sEntry.getFName() != sfAmountEntry) + { + JLOG(ctx.j.warn()) + << "Malformed " << annotation << ": Expected AmountEntry."; + return temMALFORMED; + } + + STAmount const amt = sEntry.getFieldAmount(sfAmount); + if (!isLegalNet(amt) || amt.signum() <= 0) + { + JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: " + << amt.getFullText(); + return temBAD_AMOUNT; + } + + if (isBadCurrency(amt.getCurrency())) + { + JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency."; + return temBAD_CURRENCY; + } + + if (isXRP(amt)) + { + if (nativeAlready) + { + JLOG(ctx.j.warn()) << "Malformed transaction: Native Currency appears more than once."; + return temMALFORMED; + } + + nativeAlready = true; + continue; + } + + + auto& found = already.find(amt.getCurrency()); + if (found == already.end()) + { + already.emplace(amt.getCurrency(), {amt.getIssuer()}); + continue; + } + + if (found->second.find(amt.getIssuer())) + { + JLOG(ctx.j.warn()) << "Malformed transaction: Issued Currency appears more than once."; + return temMALFORMED; + } + + found->second.emplace(amt.getIssuer()); + } + } + + // sanity check minturitoken + if (ctx.tx.isFieldPresent(sfMintURIToken)) + { + STObject const& mint = const_cast(tx) + .getField(sfMintURIToken) + .downcast(); + if (!URToken::validateUTF(mint.getFieldVL(sfURI))) + { + JLOG(ctx.j.warn()) + << "Malformed transaction: Invalid UTF8 inside MintURIToken."; + return temMALFORMED; + } + } + + // 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::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureRemit)) + return temDISABLED; + + auto const id = ctx.tx[sfAccount]; + + auto const sle = ctx.view.read(keylet::account(id)); + if (!sle) + return terNO_ACCOUNT; + + + // 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 }; + + XRPAmount const accountReserve { ctx.view.fees().accountReserve(0) }; + XRPAmount const objectReserve { ctx.view.fees().accountReserve(1) - accountReserve }; + AccountID const dstId{ctx.tx[sfDestination]}; + auto const sleDst = ctx.view.read(keylet::account(dstId)); + + // the sender must pay for the destination acc if it doesn't exist + if (!sleDst) + nativeRemit += accountReserve; + else + { + auto const flags = sleDst->getFlags(); + + // Check if the destination has disallowed incoming + if (ctx.view.rules().enabled(featureDisallowIncoming) && + (flags & lsfDisallowIncomingRemit)) + return tecNO_PERMISSION; + } + + // the destination may require a dest tag + if ((flags & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) + { + JLOG(ctx.j.warn()) << "Remit: DestinationTag required for this destination."; + return tecDST_TAG_NEEDED; + } + + // if theres a minted uritoken the sender pays for that + std::optional mintKL; + std::optional mintURI; + std::optional mintDigest; + if (ctx.tx.isFieldPresent(sfMintURIToken)) + { + nativeRemit += objectReserve; + + STObject const& mint = const_cast(tx) + .getField(sfMintURIToken) + .downcast(); + mintURI = mint.getFieldVL(sfURI); + + if (mint.isFieldPresent(sfDigest)) + mintDigest = mint.getFieldH256(sfDigest); + + // check that it doesn't already exist + mintKL = keylet::uritoken(id, *mintURI); + + if (ctx.view.exists(*mintKL)) + { + JLOG(ctx.j.trace()) + << "Remit: tried to creat duplicate URIToken. Tx: " + << ctx.tx.getTransactionID(); + return tecDUPLICATE; + } + } + + // iterate trustlines to see if these exist + if (ctx.tx.isFieldPresent(sfAmounts)) + { + STArray const& sEntries(obj.getFieldArray(sfAmounts)); + for (STObject const& sEntry : sEntries) + { + STAmount const amt = sEntry.getFieldAmount(sfAmount); + if (isXRP(amt)) + { + nativeRemit = amt.xrp(); + continue; + } + + Keylet destLineKL = keylet::line(dstID, amt.getIssuer(), amt.getCurrency()); + if (!ctx.view.exists(destLineKL)) + nativeRemit += objectReserve; + + // if the sender is the issuer we'll just assume they're allowed to send it + // the trustline might be frozen but since its their asset they can do what they want + if (id == amt.getIssuer()) + continue; + + // check there's a source line and the amount is sufficient + Keylet srcLineKL = keylet::line(id, amt.getIssuer(), amt.getCurrency()); + if (!ctx.view.exists(srcLineKL)) + { + JLOG(ctx.j.trace()) + << "Remit: sender lacked one or more trustlines for remitted currencies."; + return tecUNFUNDED_PAYMENT; + } + + // check permissions + if (TER canXfer = + trustTransferAllowed( + ctx.view, + {id, dstID}, + ctx.j); canXfer != tesSUCCESS) + return canXfer; + + //bool srcHigh = id > amt.getIssuer(); + + auto const srcLine = ctx.view.read(srcLineKL); + STAmount bal = srcLine.getFieldAmount(sfBalance); + + /* + A = src acc + B = amt.getIssuer() + + A > B | bal > 0 | issuer is | low acc | high acc + ---------------------------- + false | false | A A B + false | true | B A B + true | false | B B A + true | true | A B A + + A cannot be the issuer, this would mean you specify someone who holds a trustline with you + and has a balance issued by you, as the issuer on a transfer to someone else. + + */ + + + bool const srcHigh = id > amt.getIssuer(); + bool const balPos = bal >= beast::zero; + + if (srcHigh && balPos || !srcHigh && !balPos) + { + JLOG(ctx.j.warn()) + << "Remit: specified issuer is not the issuer in the trustline between source and that account."; + return tecUNFUNDED_PAYMENT; + } + + if (bal < beast::zero) + bal = -bal; + + if (bal.iou() < amt.iou()) + { + JLOG(ctx.j.warn()) + << "Remit: one or more currencies were not adequately covered by available funds in account."; + return tecUNFUNDED_PAYMENT; + } + } + } + + + // iterate uritokens + if (ctx.tx.isFieldPresent(sfURITokenIDs)) + { + STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); + for (uint256 const utKL : ids) + { + auto ut = ctx.view.read(utKL); + + // does it exist + if (!ut) + { + JLOG(ctx.j.warn()) + << "Remit: one or more uritokens did not exist on the source account."; + return tecUNFUNDED_PAYMENT; + } + + // is it a uritoken? + if (ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN) + { + JLOG(ctx.j.warn()) + << "Remit: one or more supplied URITokenIDs was not actually a uritoken."; + return tecNO_ENTRY; + } + + // is it our uritoken? + if (ut->getAccounntID(sfOwner) != id) + { + JLOG(ctx.j.warn()) + << "Remit: one or more supplied URITokenIDs was not owned by sender."; + return tecNO_PERMISSION; + } + } + } + + // ensure the account can cover the native remit + + // RH UPTO + + return tesSUCCESS; +} + +TER +Remit::doApply() +{ + // RH TODO + + 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())}; + + 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..157381922 --- /dev/null +++ b/src/ripple/app/tx/impl/Remit.h @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------ +/* + 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); + + static TER + preclaim(PreclaimContext 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/URIToken.cpp b/src/ripple/app/tx/impl/URIToken.cpp index 391af6be2..2c1f0772f 100644 --- a/src/ripple/app/tx/impl/URIToken.cpp +++ b/src/ripple/app/tx/impl/URIToken.cpp @@ -103,57 +103,7 @@ URIToken::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (!([](std::vector const& u) -> bool { - // this code is from - // https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c - uint8_t const* s = (uint8_t const*)u.data(); - uint8_t const* end = s + u.size(); - while (s < end) - { - if (*s < 0x80) - /* 0xxxxxxx */ - s++; - else if ((s[0] & 0xe0) == 0xc0) - { - /* 110XXXXx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || - (s[0] & 0xfe) == 0xc0) /* overlong? */ - return false; - else - s += 2; - } - else if ((s[0] & 0xf0) == 0xe0) - { - /* 1110XXXX 10Xxxxxx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || - (s[0] == 0xe0 && - (s[1] & 0xe0) == 0x80) || /* overlong? */ - (s[0] == 0xed && - (s[1] & 0xe0) == 0xa0) || /* surrogate? */ - (s[0] == 0xef && s[1] == 0xbf && - (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ - return false; - else - s += 3; - } - else if ((s[0] & 0xf8) == 0xf0) - { - /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ - if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || - (s[3] & 0xc0) != 0x80 || - (s[0] == 0xf0 && - (s[1] & 0xf0) == 0x80) || /* overlong? */ - (s[0] == 0xf4 && s[1] > 0x8f) || - s[0] > 0xf4) /* > U+10FFFF? */ - return false; - else - s += 4; - } - else - return false; - } - return true; - })(uri)) + if (!validUTF8(uri)) { JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a " "valid utf-8 string."; diff --git a/src/ripple/app/tx/impl/URIToken.h b/src/ripple/app/tx/impl/URIToken.h index 9b9742712..159f7fb95 100644 --- a/src/ripple/app/tx/impl/URIToken.h +++ b/src/ripple/app/tx/impl/URIToken.h @@ -30,6 +30,63 @@ namespace ripple { class URIToken : public Transactor { public: + + bool + inline + static + validateUTF8(std::vector const& u) + { + // this code is from + // https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c + uint8_t const* s = (uint8_t const*)u.data(); + uint8_t const* end = s + u.size(); + while (s < end) + { + if (*s < 0x80) + /* 0xxxxxxx */ + s++; + else if ((s[0] & 0xe0) == 0xc0) + { + /* 110XXXXx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[0] & 0xfe) == 0xc0) /* overlong? */ + return false; + else + s += 2; + } + else if ((s[0] & 0xf0) == 0xe0) + { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || + (s[0] == 0xe0 && + (s[1] & 0xe0) == 0x80) || /* overlong? */ + (s[0] == 0xed && + (s[1] & 0xe0) == 0xa0) || /* surrogate? */ + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ + return false; + else + s += 3; + } + else if ((s[0] & 0xf8) == 0xf0) + { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + (s[0] == 0xf0 && + (s[1] & 0xf0) == 0x80) || /* overlong? */ + (s[0] == 0xf4 && s[1] > 0x8f) || + s[0] > 0xf4) /* > U+10FFFF? */ + return false; + else + s += 4; + } + else + return false; + } + return true; + } + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; explicit URIToken(ApplyContext& ctx) : Transactor(ctx) diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index fa3a17c36..9849f226f 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: @@ -544,6 +550,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 14cb3c8a0..5b6c03342 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 fixURITokenV1; +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..10adbaa8c 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -590,6 +590,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 +619,8 @@ extern SField const sfGenesisMints; extern SField const sfActiveValidators; extern SField const sfImportVLKeys; extern SField const sfHookEmissions; +extern SField const sfAmounts; +extern SField const sfURITokenIDs; //------------------------------------------------------------------------------ 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..9650e964c 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 8ccadef8e..1d143efa0 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 (fixURITokenV1, 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..72edf7f84 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}, + }); + } InnerObjectFormats const& diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 889885442..dc19769f2 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -312,6 +312,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 +347,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 +373,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 23c39e3cd..4f29b0bbf 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, { From 5168b2684ae3d7060cdf8c9d869ce80c6ec425ab Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Fri, 1 Dec 2023 16:28:39 +0000 Subject: [PATCH 02/15] revamp remit, untested --- src/ripple/app/tx/impl/Remit.cpp | 423 ++++++++++++------ src/ripple/protocol/TER.h | 1 + .../protocol/impl/InnerObjectFormats.cpp | 1 + 3 files changed, 277 insertions(+), 148 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index a7151fcf9..70a55bdc4 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -44,9 +44,12 @@ Remit::preflight(PreflightContext const& ctx) JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } - if (ctx.tx[sfAccount] == ctx.tx[sfDestination]) + + AccountID const dstID = ctx.tx.getAccountID(sfDestination); + AccountID const srcID = ctx.tx.getAccountID(sfAccount); + + if (dstID == srcID) { - // They wrote a check to themselves. JLOG(ctx.j.warn()) << "Malformed transaction: Remit to self."; return temREDUNDANT; } @@ -68,21 +71,21 @@ Remit::preflight(PreflightContext const& ctx) return temMALFORMED; } - STAmount const amt = sEntry.getFieldAmount(sfAmount); - if (!isLegalNet(amt) || amt.signum() <= 0) + STAmount const amount = sEntry.getFieldAmount(sfAmount); + if (!isLegalNet(amount) || amount.signum() <= 0) { JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: " - << amt.getFullText(); + << amount.getFullText(); return temBAD_AMOUNT; } - if (isBadCurrency(amt.getCurrency())) + if (isBadCurrency(amount.getCurrency())) { JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } - if (isXRP(amt)) + if (isXRP(amount)) { if (nativeAlready) { @@ -94,21 +97,20 @@ Remit::preflight(PreflightContext const& ctx) continue; } - - auto& found = already.find(amt.getCurrency()); + auto& found = already.find(amount.getCurrency()); if (found == already.end()) { - already.emplace(amt.getCurrency(), {amt.getIssuer()}); + already.emplace(amount.getCurrency(), {issuerAccID}); continue; } - if (found->second.find(amt.getIssuer())) + if (found->second.find(issuerAccID)) { JLOG(ctx.j.warn()) << "Malformed transaction: Issued Currency appears more than once."; return temMALFORMED; } - found->second.emplace(amt.getIssuer()); + found->second.emplace(issuerAccID); } } @@ -118,12 +120,28 @@ Remit::preflight(PreflightContext const& ctx) STObject const& mint = const_cast(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 (!URToken::validateUTF(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 @@ -142,207 +160,314 @@ Remit::preflight(PreflightContext const& ctx) return preflight2(ctx); } -TER -Remit::preclaim(PreclaimContext const& ctx) +Remit::doApply() { - if (!ctx.view.rules().enabled(featureRemit)) + if (!sb.rules().enabled(featureRemit)) return temDISABLED; - auto const id = ctx.tx[sfAccount]; + Sandbox sb(&ctx_.view()); - auto const sle = ctx.view.read(keylet::account(id)); - if (!sle) + beast::Journal& j = 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 }; - XRPAmount const accountReserve { ctx.view.fees().accountReserve(0) }; - XRPAmount const objectReserve { ctx.view.fees().accountReserve(1) - accountReserve }; - AccountID const dstId{ctx.tx[sfDestination]}; - auto const sleDst = ctx.view.read(keylet::account(dstId)); + AccountID const dstAccID{ctx_.tx[sfDestination]}; + auto sleDstAcc = sb.peek(keylet::account(dstAccID)); + auto const flags = !sleDstAcc ? 0 : sleDstAcc->getFlags(); - // the sender must pay for the destination acc if it doesn't exist - if (!sleDst) - nativeRemit += accountReserve; - else - { - auto const flags = sleDst->getFlags(); - - // Check if the destination has disallowed incoming - if (ctx.view.rules().enabled(featureDisallowIncoming) && - (flags & lsfDisallowIncomingRemit)) + // 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)) + if ((flags & lsfRequireDestTag) && !ctx_.tx.isFieldPresent(sfDestinationTag)) { - JLOG(ctx.j.warn()) << "Remit: DestinationTag required for this destination."; + JLOG(j.warn()) << "Remit: DestinationTag required for this destination."; return tecDST_TAG_NEEDED; } + + // if the destination doesn't exist, create it. + if (!sleDstAcc) + { + // 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(srcAccID)); + sleDstAcc->setAccountID(sfAccount, srcAccID); + + sleDstAcc->setFieldU32(sfSequence, seqno); + sleDstAcc->setFieldU32(sfOwnerCount, 0); + if (sleDstAccFees && view().rules().enabled(featureXahauGenesis)) + { + uint64_t accIdx = sleDstAccFees->isFieldPresent(sfAccountCount) + ? sleDstAccFees->getFieldU64(sfAccountCount) + : 0; + sleDstAcc->setFieldU64(sfAccountIndex, accIdx); + sleDstAccFees->setFieldU64(sfAccountCount, accIdx + 1); + } + + // we'll fix this up at the end + sleDstAcc->setFieldAmount(sfBalance, 0); + sb.insert(sleDstAcc); + sleDstAcc = sb.peek(sleDstAcc); + } + + // if theres a minted uritoken the sender pays for that - std::optional mintKL; - std::optional mintURI; - std::optional mintDigest; - if (ctx.tx.isFieldPresent(sfMintURIToken)) + if (ctx_.tx.isFieldPresent(sfMintURIToken)) { nativeRemit += objectReserve; - STObject const& mint = const_cast(tx) .getField(sfMintURIToken) .downcast(); - mintURI = mint.getFieldVL(sfURI); + + Blob const& mintURI = mint.getFieldVL(sfURI); + std::optional mintDigest; if (mint.isFieldPresent(sfDigest)) mintDigest = mint.getFieldH256(sfDigest); - // check that it doesn't already exist - mintKL = keylet::uritoken(id, *mintURI); + Keylet kl = keylet::uritoken(srcAccID, mintURI); - if (ctx.view.exists(*mintKL)) + // check that it doesn't already exist + if (sb.exists(kl)) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "Remit: tried to creat duplicate URIToken. Tx: " - << ctx.tx.getTransactionID(); + << ctx_.tx.getTransactionID(); return tecDUPLICATE; } + + + 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(sleDstAcc), kl, describeOwnerDir(sleDstAcc)); + + 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(sleU); + + // ensure there is a deletion blocker against the issuer now + sleSrcAcc->setFieldU32(sfFlags, sle->getFlags() | lsfURITokenIssuer); + + adjustOwnerCount(sb, sleSrcAcc, 1, j); } - // iterate trustlines to see if these exist - if (ctx.tx.isFieldPresent(sfAmounts)) - { - STArray const& sEntries(obj.getFieldArray(sfAmounts)); - for (STObject const& sEntry : sEntries) - { - STAmount const amt = sEntry.getFieldAmount(sfAmount); - if (isXRP(amt)) - { - nativeRemit = amt.xrp(); - continue; - } - - Keylet destLineKL = keylet::line(dstID, amt.getIssuer(), amt.getCurrency()); - if (!ctx.view.exists(destLineKL)) - nativeRemit += objectReserve; - - // if the sender is the issuer we'll just assume they're allowed to send it - // the trustline might be frozen but since its their asset they can do what they want - if (id == amt.getIssuer()) - continue; - - // check there's a source line and the amount is sufficient - Keylet srcLineKL = keylet::line(id, amt.getIssuer(), amt.getCurrency()); - if (!ctx.view.exists(srcLineKL)) - { - JLOG(ctx.j.trace()) - << "Remit: sender lacked one or more trustlines for remitted currencies."; - return tecUNFUNDED_PAYMENT; - } - - // check permissions - if (TER canXfer = - trustTransferAllowed( - ctx.view, - {id, dstID}, - ctx.j); canXfer != tesSUCCESS) - return canXfer; - - //bool srcHigh = id > amt.getIssuer(); - - auto const srcLine = ctx.view.read(srcLineKL); - STAmount bal = srcLine.getFieldAmount(sfBalance); - - /* - A = src acc - B = amt.getIssuer() - - A > B | bal > 0 | issuer is | low acc | high acc - ---------------------------- - false | false | A A B - false | true | B A B - true | false | B B A - true | true | A B A - - A cannot be the issuer, this would mean you specify someone who holds a trustline with you - and has a balance issued by you, as the issuer on a transfer to someone else. - - */ - - - bool const srcHigh = id > amt.getIssuer(); - bool const balPos = bal >= beast::zero; - - if (srcHigh && balPos || !srcHigh && !balPos) - { - JLOG(ctx.j.warn()) - << "Remit: specified issuer is not the issuer in the trustline between source and that account."; - return tecUNFUNDED_PAYMENT; - } - - if (bal < beast::zero) - bal = -bal; - - if (bal.iou() < amt.iou()) - { - JLOG(ctx.j.warn()) - << "Remit: one or more currencies were not adequately covered by available funds in account."; - return tecUNFUNDED_PAYMENT; - } - } - } - // iterate uritokens - if (ctx.tx.isFieldPresent(sfURITokenIDs)) + if (ctx_.tx.isFieldPresent(sfURITokenIDs)) { - STVector256 ids = ctx.tx.getFieldV256(sfURITokenIDs); - for (uint256 const utKL : ids) + STVector256 ids = ctx_.tx.getFieldV256(sfURITokenIDs); + for (uint256 const kl : ids) { - auto ut = ctx.view.read(utKL); + auto sleU = sb.peek(kl); // does it exist - if (!ut) + if (!sleU) { - JLOG(ctx.j.warn()) + JLOG(j.warn()) << "Remit: one or more uritokens did not exist on the source account."; return tecUNFUNDED_PAYMENT; } // is it a uritoken? - if (ut->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN) + if (sleU->getFieldU16(sfLedgerEntryType) != ltURI_TOKEN) { - JLOG(ctx.j.warn()) + JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was not actually a uritoken."; return tecNO_ENTRY; } // is it our uritoken? - if (ut->getAccounntID(sfOwner) != id) + if (sleU->getAccounntID(sfOwner) != srcAccID) { - JLOG(ctx.j.warn()) + 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, sleDstAcc); } } - // ensure the account can cover the native remit - - // RH UPTO + + // iterate trustlines + if (ctx_.tx.isFieldPresent(sfAmounts)) + { + + // process trustline remits + STArray const& sEntries(obj.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; + } + + // check permissions + if (TER canXfer = + trustTransferAllowed( + sb, + {srcAccID, dstID}, + j); canXfer != tesSUCCESS) + return canXfer; + + AccountID const issuerAccID = amount.getIssuer(); + + // 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 && xferRate != parityRate + ? 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.setField(sfBalance, bal); + } + } + + // apply + sb.apply(ctx_.rawView()); return tesSUCCESS; } -TER -Remit::doApply() { - // RH TODO - - return tesSUCCESS; } XRPAmount @@ -354,6 +479,8 @@ 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; } diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 2a91b4465..54ce761b1 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -337,6 +337,7 @@ enum TECcodes : TERUnderlyingType { tecXCHAIN_PAYMENT_FAILED = 184, // RESERVED - XCHAIN tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN + tecBAD_LINE_DIRECTIONALITY = 187, tecLAST_POSSIBLE_ENTRY = 255, }; diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index 72edf7f84..d788e06fa 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -154,6 +154,7 @@ InnerObjectFormats::InnerObjectFormats() { {sfURI, soeREQUIRED}, {sfDigest, soeOPTIONAL}, + {sfFlags, soeOPTIONAL}, }); } From 16553b3778cf7aac5b22a52898f5e2294e098df0 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 2 Dec 2023 18:00:23 +0000 Subject: [PATCH 03/15] remit compiling, not tested --- Builds/CMake/RippledCore.cmake | 1 + src/ripple/app/tx/impl/InvariantCheck.cpp | 2 +- src/ripple/app/tx/impl/Remit.cpp | 77 ++++++++------- src/ripple/app/tx/impl/Remit.h | 3 - src/ripple/app/tx/impl/URIToken.cpp | 2 +- src/ripple/ledger/View.h | 22 ++++- src/ripple/ledger/impl/View.cpp | 2 +- src/ripple/protocol/SField.h | 3 +- src/ripple/protocol/TER.h | 1 - src/ripple/protocol/TxFormats.h | 2 +- src/ripple/protocol/impl/SField.cpp | 1 + src/ripple/protocol/jss.h | 1 + src/test/consensus/NegativeUNL_test.cpp | 47 ++++----- src/test/consensus/UNLReport_test.cpp | 114 ++++++++++------------ src/test/jtx/utility.h | 1 + 15 files changed, 141 insertions(+), 138 deletions(-) diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 63311c0b1..d90165715 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 diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 84a5ff582..8e38f80c2 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -597,7 +597,7 @@ 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 index 70a55bdc4..0339f583c 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -23,7 +23,6 @@ #include #include #include - namespace ripple { TxConsequences @@ -60,14 +59,14 @@ Remit::preflight(PreflightContext const& ctx) std::map> already; bool nativeAlready = false; - STArray const& sEntries(obj.getFieldArray(sfAmounts)); + STArray const& sEntries(ctx.tx.getFieldArray(sfAmounts)); for (STObject const& sEntry : sEntries) { // Validate the AmountEntry. if (sEntry.getFName() != sfAmountEntry) { JLOG(ctx.j.warn()) - << "Malformed " << annotation << ": Expected AmountEntry."; + << "Malformed: Expected AmountEntry."; return temMALFORMED; } @@ -97,27 +96,27 @@ Remit::preflight(PreflightContext const& ctx) continue; } - auto& found = already.find(amount.getCurrency()); + auto found = already.find(amount.getCurrency()); if (found == already.end()) { - already.emplace(amount.getCurrency(), {issuerAccID}); + already.emplace(amount.getCurrency(), std::set{amount.getIssuer()}); continue; } - if (found->second.find(issuerAccID)) + 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(issuerAccID); + found->second.emplace(amount.getIssuer()); } } // sanity check minturitoken if (ctx.tx.isFieldPresent(sfMintURIToken)) { - STObject const& mint = const_cast(tx) + STObject const& mint = const_cast(ctx.tx) .getField(sfMintURIToken) .downcast(); // RH TODO: iterate mint fields detect any that shouldnt be there @@ -130,7 +129,7 @@ Remit::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (!URToken::validateUTF(mint.getFieldVL(sfURI))) + if (!URIToken::validateUTF8(mint.getFieldVL(sfURI))) { JLOG(ctx.j.warn()) << "Malformed transaction: Invalid UTF8 inside MintURIToken."; @@ -160,14 +159,15 @@ Remit::preflight(PreflightContext const& ctx) return preflight2(ctx); } +TER Remit::doApply() { - if (!sb.rules().enabled(featureRemit)) + if (!view().rules().enabled(featureRemit)) return temDISABLED; Sandbox sb(&ctx_.view()); - beast::Journal& j = journal; + beast::Journal const& j = ctx_.journal; auto const srcAccID = ctx_.tx[sfAccount]; @@ -218,19 +218,23 @@ Remit::doApply() sleDstAcc->setFieldU32(sfSequence, seqno); sleDstAcc->setFieldU32(sfOwnerCount, 0); - if (sleDstAccFees && view().rules().enabled(featureXahauGenesis)) + auto sleFees = view().peek(keylet::fees()); + if (sleFees && view().rules().enabled(featureXahauGenesis)) { - uint64_t accIdx = sleDstAccFees->isFieldPresent(sfAccountCount) - ? sleDstAccFees->getFieldU64(sfAccountCount) + uint64_t accIdx = sleFees->isFieldPresent(sfAccountCount) + ? sleFees->getFieldU64(sfAccountCount) : 0; sleDstAcc->setFieldU64(sfAccountIndex, accIdx); - sleDstAccFees->setFieldU64(sfAccountCount, accIdx + 1); + sleFees->setFieldU64(sfAccountCount, accIdx + 1); + sb.update(sleFees); } // we'll fix this up at the end - sleDstAcc->setFieldAmount(sfBalance, 0); + sleDstAcc->setFieldAmount(sfBalance, STAmount{XRPAmount{0}}); sb.insert(sleDstAcc); - sleDstAcc = sb.peek(sleDstAcc); + + // pull a new shrptr so we can update as we go + sleDstAcc = sb.peek(keylet::account(dstAccID)); } @@ -238,7 +242,7 @@ Remit::doApply() if (ctx_.tx.isFieldPresent(sfMintURIToken)) { nativeRemit += objectReserve; - STObject const& mint = const_cast(tx) + STObject const& mint = const_cast(ctx_.tx) .getField(sfMintURIToken) .downcast(); @@ -260,7 +264,7 @@ Remit::doApply() } - sleMint = std::make_shared(kl); + auto sleMint = std::make_shared(kl); sleMint->setAccountID(sfOwner, dstAccID); sleMint->setAccountID(sfIssuer, srcAccID); @@ -274,20 +278,20 @@ Remit::doApply() auto const page = view().dirInsert( - keylet::ownerDir(sleDstAcc), kl, describeOwnerDir(sleDstAcc)); + keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID)); JLOG(j_.trace()) - << "Adding URIToken to owner directory " << to_string(kl->key) + << "Adding URIToken to owner directory " << to_string(kl.key) << ": " << (page ? "success" : "failure"); if (!page) return tecDIR_FULL; sleMint->setFieldU64(sfOwnerNode, *page); - sb.insert(sleU); + sb.insert(sleMint); // ensure there is a deletion blocker against the issuer now - sleSrcAcc->setFieldU32(sfFlags, sle->getFlags() | lsfURITokenIssuer); + sleSrcAcc->setFieldU32(sfFlags, sleSrcAcc->getFlags() | lsfURITokenIssuer); adjustOwnerCount(sb, sleSrcAcc, 1, j); } @@ -297,8 +301,9 @@ Remit::doApply() if (ctx_.tx.isFieldPresent(sfURITokenIDs)) { STVector256 ids = ctx_.tx.getFieldV256(sfURITokenIDs); - for (uint256 const kl : ids) + for (uint256 const klRaw : ids) { + Keylet kl = keylet::unchecked(klRaw); auto sleU = sb.peek(kl); // does it exist @@ -318,7 +323,7 @@ Remit::doApply() } // is it our uritoken? - if (sleU->getAccounntID(sfOwner) != srcAccID) + if (sleU->getAccountID(sfOwner) != srcAccID) { JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was not owned by sender."; @@ -337,7 +342,7 @@ Remit::doApply() // remove from sender dir { auto const page = (*sleU)[sfOwnerNode]; - if (!sb.dirRemove(keylet::ownerDir(srcAccID), page, kl->key, true)) + if (!sb.dirRemove(keylet::ownerDir(srcAccID), page, kl.key, true)) { JLOG(j.fatal()) << "Could not remove URIToken from owner directory"; @@ -353,7 +358,7 @@ Remit::doApply() sb.dirInsert(keylet::ownerDir(dstAccID), kl, describeOwnerDir(dstAccID)); JLOG(j_.trace()) - << "Adding URIToken to owner directory " << to_string(kl->key) + << "Adding URIToken to owner directory " << to_string(kl.key) << ": " << (page ? "success" : "failure"); if (!page) @@ -365,7 +370,7 @@ Remit::doApply() } // change the owner - sleU->setAccountID(sfOwner, sleDstAcc); + sleU->setAccountID(sfOwner, dstAccID); } } @@ -375,7 +380,7 @@ Remit::doApply() { // process trustline remits - STArray const& sEntries(obj.getFieldArray(sfAmounts)); + STArray const& sEntries(ctx_.tx.getFieldArray(sfAmounts)); for (STObject const& sEntry : sEntries) { STAmount const amount = sEntry.getFieldAmount(sfAmount); @@ -391,7 +396,8 @@ Remit::doApply() if (TER canXfer = trustTransferAllowed( sb, - {srcAccID, dstID}, + std::vector{srcAccID, dstAccID}, + amount.issue(), j); canXfer != tesSUCCESS) return canXfer; @@ -402,7 +408,7 @@ Remit::doApply() // 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 && xferRate != parityRate + issuerAccID != srcAccID && issuerAccID != dstAccID ? multiply(amount, transferRate(sb, issuerAccID)) : amount; @@ -447,17 +453,17 @@ Remit::doApply() bal -= nativeRemit; if (bal < beast::zero || bal > mSourceBalance) return tecINTERNAL; - sleSrcAcc.setFieldAmount(sfBalance, bal); + sleSrcAcc->setFieldAmount(sfBalance, bal); } // add the balance to the destination { - STAmount bal = sleDstAcc.getFieldAmount(sfBalance); + STAmount bal = sleDstAcc->getFieldAmount(sfBalance); STAmount prior = bal; bal += nativeRemit; if (bal < beast::zero || bal < prior) return tecINTERNAL; - sleDstAcc.setField(sfBalance, bal); + sleDstAcc->setFieldAmount(sfBalance, bal); } } @@ -467,9 +473,6 @@ Remit::doApply() return tesSUCCESS; } -{ -} - XRPAmount Remit::calculateBaseFee(ReadView const& view, STTx const& tx) { diff --git a/src/ripple/app/tx/impl/Remit.h b/src/ripple/app/tx/impl/Remit.h index 157381922..56ac494be 100644 --- a/src/ripple/app/tx/impl/Remit.h +++ b/src/ripple/app/tx/impl/Remit.h @@ -45,9 +45,6 @@ public: static NotTEC preflight(PreflightContext const& ctx); - static TER - preclaim(PreclaimContext const& ctx); - TER doApply() override; }; diff --git a/src/ripple/app/tx/impl/URIToken.cpp b/src/ripple/app/tx/impl/URIToken.cpp index 2c1f0772f..c82849214 100644 --- a/src/ripple/app/tx/impl/URIToken.cpp +++ b/src/ripple/app/tx/impl/URIToken.cpp @@ -103,7 +103,7 @@ URIToken::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (!validUTF8(uri)) + if (!validateUTF8(uri)) { JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a " "valid utf-8 string."; diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 9dd497558..dced77af9 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -388,6 +389,18 @@ rippleCredit( bool bCheckIssuer, beast::Journal j); +// Send regardless of limits. +// --> saAmount: Amount/currency/issuer to deliver to receiver. +// <-- saActual: Amount actually cost. Sender pays fees. +TER +rippleSend( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + STAmount& saActual, + beast::Journal j); + [[nodiscard]] TER accountSend( ApplyView& view, @@ -627,12 +640,13 @@ trustTransferAllowed( { static_assert( std::is_same::value || - std::is_same::value); + std::is_same::value || + std::is_same::value); typedef typename std::conditional< - std::is_same::value, - std::shared_ptr, - std::shared_ptr>::type SLEPtr; + std::is_same::value, + std::shared_ptr, + std::shared_ptr>::type SLEPtr; if (isBadCurrency(issue.currency)) return tecNO_PERMISSION; diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 42bd3fe6a..b3e948ca1 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -1150,7 +1150,7 @@ rippleCredit( // Send regardless of limits. // --> saAmount: Amount/currency/issuer to deliver to receiver. // <-- saActual: Amount actually cost. Sender pays fees. -static TER +TER rippleSend( ApplyView& view, AccountID const& uSenderID, diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 10adbaa8c..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 @@ -620,7 +622,6 @@ extern SField const sfActiveValidators; extern SField const sfImportVLKeys; extern SField const sfHookEmissions; extern SField const sfAmounts; -extern SField const sfURITokenIDs; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 54ce761b1..2a91b4465 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -337,7 +337,6 @@ enum TECcodes : TERUnderlyingType { tecXCHAIN_PAYMENT_FAILED = 184, // RESERVED - XCHAIN tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN - tecBAD_LINE_DIRECTIONALITY = 187, tecLAST_POSSIBLE_ENTRY = 255, }; diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index 9650e964c..2f287efe5 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -148,7 +148,7 @@ enum TxType : std::uint16_t /* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed * that the sender pays for. */ - ttREMIT = 95; + 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, diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index dc19769f2..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); diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 9671a8399..88a8bca29 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/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 013f279d1..0648cf95d 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -53,12 +53,21 @@ namespace test { * @return true if meet all three expectation */ bool +inline negUnlSizeTest( - std::shared_ptr const& l, + std::shared_ptr const& l, size_t size, bool hasToDisable, - bool hasToReEnable); + bool hasToReEnable) +{ + bool sameSize = l->negativeUNL().size() == size; + bool sameToDisable = + (l->validatorToDisable() != std::nullopt) == hasToDisable; + bool sameToReEnable = + (l->validatorToReEnable() != std::nullopt) == hasToReEnable; + return sameSize && sameToDisable && sameToReEnable; +} /** * Try to apply a ttUNL_MODIFY Tx, and test the apply result * @@ -69,7 +78,15 @@ negUnlSizeTest( * @return true if meet the expectation of apply result */ bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass); +inline +applyAndTestResult(jtx::Env& env, ripple::OpenView& view, ripple::STTx const& tx, bool pass) +{ + auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); + if (pass) + return res.first == tesSUCCESS; + else + return res.first == tefFAILURE || res.first == temDISABLED; +} /** * Verify the content of negative UNL entries (public key and ledger sequence) @@ -1912,31 +1929,7 @@ BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// -bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable) -{ - bool sameSize = l->negativeUNL().size() == size; - bool sameToDisable = - (l->validatorToDisable() != std::nullopt) == hasToDisable; - bool sameToReEnable = - (l->validatorToReEnable() != std::nullopt) == hasToReEnable; - return sameSize && sameToDisable && sameToReEnable; -} - -bool -applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED; -} bool VerifyPubKeyAndSeq( diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp index a580f1845..f7ea93d1e 100644 --- a/src/test/consensus/UNLReport_test.cpp +++ b/src/test/consensus/UNLReport_test.cpp @@ -61,28 +61,57 @@ namespace test { // * @param hasToReEnable if expect ToDisable in ledger // * @return true if meet all three expectation // */ -inline bool +/* +bool +inline negUnlSizeTest( - std::shared_ptr const& l, + std::shared_ptr const& l, size_t size, bool hasToDisable, - bool hasToReEnable); + bool hasToReEnable) +{ + bool sameSize = l->negativeUNL().size() == size; + bool sameToDisable = + (l->validatorToDisable() != std::nullopt) == hasToDisable; + bool sameToReEnable = + (l->validatorToReEnable() != std::nullopt) == hasToReEnable; -// /** -// * Try to apply a ttUNL_MODIFY Tx, and test the apply result -// * -// * @param env the test environment -// * @param view the OpenView of the ledger -// * @param tx the ttUNL_MODIFY Tx -// * @param pass if the Tx should be applied successfully -// * @return true if meet the expectation of apply result -// */ + return sameSize && sameToDisable && sameToReEnable; +} +*/ +/** + * Try to apply a ttUNL_MODIFY Tx, and test the apply result + * + * @param env the test environment + * @param view the OpenView of the ledger + * @param tx the ttUNL_MODIFY Tx + * @param pass if the Tx should be applied successfully + * @return true if meet the expectation of apply result + */ +/* bool -applyAndTestUNLRResult( - jtx::Env& env, - OpenView& view, - STTx const& tx, - bool pass); +inline +applyAndTestResult(jtx::Env& env, ripple::OpenView& view, ripple::STTx const& +tx, bool pass) +{ + auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); + if (pass) + return res.first == tesSUCCESS; + else + return res.first == tefFAILURE || res.first == temDISABLED; +} +*/ +inline bool +applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) +{ + auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); + if (pass) + return res.first == tesSUCCESS; + else + return res.first == tefFAILURE || res.first == temDISABLED || + res.first == temMALFORMED || + res.first == telIMPORT_VL_KEY_NOT_RECOGNISED; +} /** * Verify the content of UNL Report entries (public key and ledger sequence) @@ -1235,34 +1264,6 @@ BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// -inline bool -negUnlSizeTest( - std::shared_ptr const& l, - size_t size, - bool hasToDisable, - bool hasToReEnable) -{ - bool sameSize = l->negativeUNL().size() == size; - bool sameToDisable = - (l->validatorToDisable() != std::nullopt) == hasToDisable; - bool sameToReEnable = - (l->validatorToReEnable() != std::nullopt) == hasToReEnable; - - return sameSize && sameToDisable && sameToReEnable; -} - -bool -applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED || - res.first == temMALFORMED || - res.first == telIMPORT_VL_KEY_NOT_RECOGNISED; -} - bool VerifyUNLRPubKeyAndSeq( std::shared_ptr const& l, @@ -1412,6 +1413,7 @@ createUNLRTx( return STTx(ttUNL_REPORT, fill); } +/* inline STTx createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) { @@ -1422,35 +1424,24 @@ createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) }; return STTx(ttUNL_MODIFY, fill); } +*/ +/* inline std::size_t countTx(std::shared_ptr const& txSet) { - /*uint64_t counter = 0; - if (txSet) - for (auto const& item : *txSet) - { - - SerialIter sit(item.slice()); - auto tx = std::make_shared(SerialIter{sit.getSlice(sit.getVLDataLength())}); - - if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY) - counter++; - } - */ - std::size_t count = 0; for (auto i = txSet->begin(); i != txSet->end(); ++i) { - // RH TODO: why does the above parse?? auto raw = i->slice(); if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x66U) count++; } return count; }; +*/ +/* inline bool applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) { @@ -1460,6 +1451,7 @@ applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) else return res.first == tefFAILURE || res.first == temDISABLED; } +*/ } // namespace test -} // namespace ripple \ No newline at end of file +} // namespace ripple diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index cb013a684..cfcdf676f 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -65,4 +65,5 @@ fill_seq(Json::Value& jv, ReadView const& view); } // namespace test } // namespace ripple + #endif From af952a945f874c910325bbd0c9faf1e3c0a87486 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 2 Dec 2023 19:31:29 +0000 Subject: [PATCH 04/15] fix crashbug --- src/ripple/app/tx/impl/Remit.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 0339f583c..2f88c4967 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -200,7 +200,8 @@ Remit::doApply() // if the destination doesn't exist, create it. - if (!sleDstAcc) + bool const createDst = !sleDstAcc; + if (createDst) { // sender will pay the reserve nativeRemit += accountReserve; @@ -213,8 +214,8 @@ Remit::doApply() ? sb.seq() : 1}; - sleDstAcc = std::make_shared(keylet::account(srcAccID)); - sleDstAcc->setAccountID(sfAccount, srcAccID); + sleDstAcc = std::make_shared(keylet::account(dstAccID)); + sleDstAcc->setAccountID(sfAccount, dstAccID); sleDstAcc->setFieldU32(sfSequence, seqno); sleDstAcc->setFieldU32(sfOwnerCount, 0); @@ -232,11 +233,8 @@ Remit::doApply() // we'll fix this up at the end sleDstAcc->setFieldAmount(sfBalance, STAmount{XRPAmount{0}}); sb.insert(sleDstAcc); - - // pull a new shrptr so we can update as we go - sleDstAcc = sb.peek(keylet::account(dstAccID)); } - + // if theres a minted uritoken the sender pays for that if (ctx_.tx.isFieldPresent(sfMintURIToken)) @@ -467,7 +465,8 @@ Remit::doApply() } } - // apply + // apply + sb.update(sleSrcAcc); sb.apply(ctx_.rawView()); return tesSUCCESS; From bba4281f52a68095a328fa006eab9d0b4dd276c5 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 2 Dec 2023 21:05:24 +0000 Subject: [PATCH 05/15] issuer is src or dst fix --- src/ripple/app/tx/impl/Remit.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index 2f88c4967..cdd11dfdb 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -390,7 +390,15 @@ Remit::doApply() 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, @@ -399,7 +407,6 @@ Remit::doApply() j); canXfer != tesSUCCESS) return canXfer; - AccountID const issuerAccID = amount.getIssuer(); // compute the amount the source will need to send // in remit the sender pays all transfer fees, so that From 6854055f7050c631713db5b2345a1c7ab40e1f52 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 2 Dec 2023 21:28:25 +0000 Subject: [PATCH 06/15] ensure uritokens are updated --- src/ripple/app/tx/impl/Remit.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index cdd11dfdb..84c0feace 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -369,6 +369,8 @@ Remit::doApply() // change the owner sleU->setAccountID(sfOwner, dstAccID); + + sb.update(sleU); } } From 43d90e4905f073deac4884b3ee51ebf8c8d8bda9 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 10:19:36 +0000 Subject: [PATCH 07/15] bug fix + clang format --- src/ripple/app/tx/impl/InvariantCheck.cpp | 3 +- src/ripple/app/tx/impl/Remit.cpp | 173 +++++++++--------- src/ripple/app/tx/impl/URIToken.h | 15 +- src/ripple/ledger/View.h | 3 +- .../protocol/impl/InnerObjectFormats.cpp | 3 +- src/test/consensus/NegativeUNL_test.cpp | 13 +- src/test/jtx/utility.h | 1 - 7 files changed, 101 insertions(+), 110 deletions(-) diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 8e38f80c2..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 || tt == ttREMIT) && + 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 index 84c0feace..fb1e8d9d3 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -18,11 +18,11 @@ //============================================================================== #include +#include #include #include #include #include -#include namespace ripple { TxConsequences @@ -43,7 +43,7 @@ Remit::preflight(PreflightContext const& ctx) 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); @@ -65,8 +65,7 @@ Remit::preflight(PreflightContext const& ctx) // Validate the AmountEntry. if (sEntry.getFName() != sfAmountEntry) { - JLOG(ctx.j.warn()) - << "Malformed: Expected AmountEntry."; + JLOG(ctx.j.warn()) << "Malformed: Expected AmountEntry."; return temMALFORMED; } @@ -88,7 +87,8 @@ Remit::preflight(PreflightContext const& ctx) { if (nativeAlready) { - JLOG(ctx.j.warn()) << "Malformed transaction: Native Currency appears more than once."; + JLOG(ctx.j.warn()) << "Malformed transaction: Native " + "Currency appears more than once."; return temMALFORMED; } @@ -99,13 +99,16 @@ Remit::preflight(PreflightContext const& ctx) auto found = already.find(amount.getCurrency()); if (found == already.end()) { - already.emplace(amount.getCurrency(), std::set{amount.getIssuer()}); + 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."; + JLOG(ctx.j.warn()) << "Malformed transaction: Issued Currency " + "appears more than once."; return temMALFORMED; } @@ -117,8 +120,8 @@ Remit::preflight(PreflightContext const& ctx) if (ctx.tx.isFieldPresent(sfMintURIToken)) { STObject const& mint = const_cast(ctx.tx) - .getField(sfMintURIToken) - .downcast(); + .getField(sfMintURIToken) + .downcast(); // RH TODO: iterate mint fields detect any that shouldnt be there Blob const uri = mint.getFieldVL(sfURI); @@ -175,12 +178,12 @@ Remit::doApply() 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 }; + 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)); @@ -189,30 +192,29 @@ Remit::doApply() // Check if the destination has disallowed incoming if (sb.rules().enabled(featureDisallowIncoming) && (flags & lsfDisallowIncomingRemit)) - return tecNO_PERMISSION; - + return tecNO_PERMISSION; + // the destination may require a dest tag - if ((flags & lsfRequireDestTag) && !ctx_.tx.isFieldPresent(sfDestinationTag)) + if ((flags & lsfRequireDestTag) && + !ctx_.tx.isFieldPresent(sfDestinationTag)) { - JLOG(j.warn()) << "Remit: DestinationTag required for this destination."; + 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}; + : sb.rules().enabled(featureDeletableAccounts) ? sb.seq() : 1}; sleDstAcc = std::make_shared(keylet::account(dstAccID)); sleDstAcc->setAccountID(sfAccount, dstAccID); @@ -235,15 +237,14 @@ Remit::doApply() 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(); - + .getField(sfMintURIToken) + .downcast(); + Blob const& mintURI = mint.getFieldVL(sfURI); std::optional mintDigest; @@ -251,17 +252,15 @@ Remit::doApply() 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(); + 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); @@ -272,15 +271,16 @@ Remit::doApply() if (mint.isFieldPresent(sfDigest)) sleMint->setFieldH256(sfDigest, mint.getFieldH256(sfDigest)); - sleMint->setFieldU32(sfFlags, mint.isFieldPresent(sfFlags) ? mint.getFieldU32(sfFlags) : 0); - + 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"); + JLOG(j_.trace()) << "Adding URIToken to owner directory " + << to_string(kl.key) << ": " + << (page ? "success" : "failure"); if (!page) return tecDIR_FULL; @@ -289,12 +289,12 @@ Remit::doApply() sb.insert(sleMint); // ensure there is a deletion blocker against the issuer now - sleSrcAcc->setFieldU32(sfFlags, sleSrcAcc->getFlags() | lsfURITokenIssuer); + sleSrcAcc->setFieldU32( + sfFlags, sleSrcAcc->getFlags() | lsfURITokenIssuer); - adjustOwnerCount(sb, sleSrcAcc, 1, j); + adjustOwnerCount(sb, sleDstAcc, 1, j); } - // iterate uritokens if (ctx_.tx.isFieldPresent(sfURITokenIDs)) { @@ -303,28 +303,28 @@ Remit::doApply() { 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."; + 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."; + 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."; + JLOG(j.warn()) << "Remit: one or more supplied URITokenIDs was " + "not owned by sender."; return tecNO_PERMISSION; } @@ -340,24 +340,25 @@ Remit::doApply() // remove from sender dir { auto const page = (*sleU)[sfOwnerNode]; - if (!sb.dirRemove(keylet::ownerDir(srcAccID), page, kl.key, true)) + 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)); + 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"); + JLOG(j_.trace()) << "Adding URIToken to owner directory " + << to_string(kl.key) << ": " + << (page ? "success" : "failure"); if (!page) return tecDIR_FULL; @@ -366,7 +367,7 @@ Remit::doApply() adjustOwnerCount(sb, sleDstAcc, 1, j); } - + // change the owner sleU->setAccountID(sfOwner, dstAccID); @@ -374,11 +375,9 @@ Remit::doApply() } } - // iterate trustlines if (ctx_.tx.isFieldPresent(sfAmounts)) { - // process trustline remits STArray const& sEntries(ctx_.tx.getFieldArray(sfAmounts)); for (STObject const& sEntry : sEntries) @@ -386,68 +385,70 @@ Remit::doApply() 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. + // 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 + // 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) + 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 = + // 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; + ? multiply(amount, transferRate(sb, issuerAccID)) + : amount; auto const dstAmt = amount; - STAmount availableFunds{accountFunds( - sb, srcAccID, srcAmt, fhZERO_IF_FROZEN, j)}; + 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 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()))) + 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) + 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 @@ -475,7 +476,7 @@ Remit::doApply() } // apply - sb.update(sleSrcAcc); + sb.update(sleSrcAcc); sb.apply(ctx_.rawView()); return tesSUCCESS; diff --git a/src/ripple/app/tx/impl/URIToken.h b/src/ripple/app/tx/impl/URIToken.h index 159f7fb95..ebea4c187 100644 --- a/src/ripple/app/tx/impl/URIToken.h +++ b/src/ripple/app/tx/impl/URIToken.h @@ -30,11 +30,7 @@ namespace ripple { class URIToken : public Transactor { public: - - bool - inline - static - validateUTF8(std::vector const& u) + bool inline static validateUTF8(std::vector const& u) { // this code is from // https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c @@ -58,10 +54,8 @@ public: { /* 1110XXXX 10Xxxxxx 10xxxxxx */ if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || - (s[0] == 0xe0 && - (s[1] & 0xe0) == 0x80) || /* overlong? */ - (s[0] == 0xed && - (s[1] & 0xe0) == 0xa0) || /* surrogate? */ + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */ + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */ (s[0] == 0xef && s[1] == 0xbf && (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ return false; @@ -73,8 +67,7 @@ public: /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || (s[3] & 0xc0) != 0x80 || - (s[0] == 0xf0 && - (s[1] & 0xf0) == 0x80) || /* overlong? */ + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */ (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */ return false; diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index dced77af9..96abe4ce2 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -640,8 +640,7 @@ trustTransferAllowed( { static_assert( std::is_same::value || - std::is_same::value || - std::is_same::value); + std::is_same::value || std::is_same::value); typedef typename std::conditional< std::is_same::value, diff --git a/src/ripple/protocol/impl/InnerObjectFormats.cpp b/src/ripple/protocol/impl/InnerObjectFormats.cpp index d788e06fa..c6d0340c1 100644 --- a/src/ripple/protocol/impl/InnerObjectFormats.cpp +++ b/src/ripple/protocol/impl/InnerObjectFormats.cpp @@ -141,7 +141,7 @@ InnerObjectFormats::InnerObjectFormats() {sfPublicKey, soeREQUIRED}, {sfAccount, soeOPTIONAL}, }); - + add(sfAmountEntry.jsonName.c_str(), sfAmountEntry.getCode(), { @@ -156,7 +156,6 @@ InnerObjectFormats::InnerObjectFormats() {sfDigest, soeOPTIONAL}, {sfFlags, soeOPTIONAL}, }); - } InnerObjectFormats const& diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 0648cf95d..7e18e28b8 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -52,9 +52,7 @@ namespace test { * @param hasToReEnable if expect ToDisable in ledger * @return true if meet all three expectation */ -bool -inline -negUnlSizeTest( +bool inline negUnlSizeTest( std::shared_ptr const& l, size_t size, bool hasToDisable, @@ -77,9 +75,11 @@ negUnlSizeTest( * @param pass if the Tx should be applied successfully * @return true if meet the expectation of apply result */ -bool -inline -applyAndTestResult(jtx::Env& env, ripple::OpenView& view, ripple::STTx const& tx, bool pass) +bool inline applyAndTestResult( + jtx::Env& env, + ripple::OpenView& view, + ripple::STTx const& tx, + bool pass) { auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); if (pass) @@ -1930,7 +1930,6 @@ BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// - bool VerifyPubKeyAndSeq( std::shared_ptr const& l, diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index cfcdf676f..cb013a684 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -65,5 +65,4 @@ fill_seq(Json::Value& jv, ReadView const& view); } // namespace test } // namespace ripple - #endif From ee33c979509d58ce1d14074021e7402eab2898e6 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 11:13:14 +0000 Subject: [PATCH 08/15] . --- src/test/consensus/UNLReport_test.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp index 6eb5d0e0f..59cd3437f 100644 --- a/src/test/consensus/UNLReport_test.cpp +++ b/src/test/consensus/UNLReport_test.cpp @@ -60,10 +60,11 @@ namespace test { // * @param pass if the Tx should be applied successfully // * @return true if meet the expectation of apply result // */ -bool -inline -applyAndTestResult(jtx::Env& env, ripple::OpenView& view, ripple::STTx const& -tx, bool pass) +bool inline applyAndTestResult( + jtx::Env& env, + ripple::OpenView& view, + ripple::STTx const& tx, + bool pass) { auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); if (pass) @@ -71,9 +72,13 @@ tx, bool pass) else return res.first == tefFAILURE || res.first == temDISABLED; } -*/ -inline bool -applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass) +* / + inline bool + applyAndTestUNLRResult( + jtx::Env& env, + OpenView& view, + STTx const& tx, + bool pass) { auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); if (pass) From 9f650f4258ddc27435a183e312f709cca423c1ab Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 11:34:45 +0000 Subject: [PATCH 09/15] . --- src/test/consensus/UNLReport_test.cpp | 32 +++++---------------------- src/test/jtx/impl/acctdelete.cpp | 1 + 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp index 59cd3437f..5be39a236 100644 --- a/src/test/consensus/UNLReport_test.cpp +++ b/src/test/consensus/UNLReport_test.cpp @@ -60,34 +60,12 @@ namespace test { // * @param pass if the Tx should be applied successfully // * @return true if meet the expectation of apply result // */ -bool inline applyAndTestResult( +inline bool +applyAndTestUNLRResult( jtx::Env& env, - ripple::OpenView& view, - ripple::STTx const& tx, - bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED; -} -* / - inline bool - applyAndTestUNLRResult( - jtx::Env& env, - OpenView& view, - STTx const& tx, - bool pass) -{ - auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); - if (pass) - return res.first == tesSUCCESS; - else - return res.first == tefFAILURE || res.first == temDISABLED || - res.first == temMALFORMED || - res.first == telIMPORT_VL_KEY_NOT_RECOGNISED; -} + OpenView& view, + STTx const& tx, + bool pass); /** * Verify the content of UNL Report entries (public key and ledger sequence) diff --git a/src/test/jtx/impl/acctdelete.cpp b/src/test/jtx/impl/acctdelete.cpp index f7d9aa9b6..ea104e22b 100644 --- a/src/test/jtx/impl/acctdelete.cpp +++ b/src/test/jtx/impl/acctdelete.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include From 226574665b201ee75002d39cef690dd4791cf2ff Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 13:18:57 +0000 Subject: [PATCH 10/15] bug --- src/ripple/app/tx/impl/Remit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index fb1e8d9d3..e96d2d786 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -477,6 +477,7 @@ Remit::doApply() // apply sb.update(sleSrcAcc); + sb.update(sleDstAcc); sb.apply(ctx_.rawView()); return tesSUCCESS; From 215b7e82606cedb86b046b0741cc4a312693e295 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 14:03:04 +0000 Subject: [PATCH 11/15] defensively program fees --- src/ripple/app/tx/impl/Remit.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index e96d2d786..b0dbf8a68 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -221,9 +221,10 @@ Remit::doApply() sleDstAcc->setFieldU32(sfSequence, seqno); sleDstAcc->setFieldU32(sfOwnerCount, 0); - auto sleFees = view().peek(keylet::fees()); - if (sleFees && view().rules().enabled(featureXahauGenesis)) + + if (view().exists(keylet::fees()) && view().rules().enabled(featureXahauGenesis)) { + auto sleFees = view().peek(keylet::fees()); uint64_t accIdx = sleFees->isFieldPresent(sfAccountCount) ? sleFees->getFieldU64(sfAccountCount) : 0; From ff6293c955b6bab1b3fd7cb89290f02feebf766f Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Mon, 4 Dec 2023 16:31:12 +0000 Subject: [PATCH 12/15] bug --- src/ripple/app/tx/impl/Remit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ripple/app/tx/impl/Remit.cpp b/src/ripple/app/tx/impl/Remit.cpp index b0dbf8a68..987e03a47 100644 --- a/src/ripple/app/tx/impl/Remit.cpp +++ b/src/ripple/app/tx/impl/Remit.cpp @@ -222,9 +222,9 @@ Remit::doApply() sleDstAcc->setFieldU32(sfSequence, seqno); sleDstAcc->setFieldU32(sfOwnerCount, 0); - if (view().exists(keylet::fees()) && view().rules().enabled(featureXahauGenesis)) + if (sb.exists(keylet::fees()) && sb.rules().enabled(featureXahauGenesis)) { - auto sleFees = view().peek(keylet::fees()); + auto sleFees = sb.peek(keylet::fees()); uint64_t accIdx = sleFees->isFieldPresent(sfAccountCount) ? sleFees->getFieldU64(sfAccountCount) : 0; From a13e3425dcd8f07deb225eefb16b2fa9c8de58e5 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 5 Dec 2023 11:31:59 +0100 Subject: [PATCH 13/15] add remit test --- Builds/CMake/RippledCore.cmake | 2 + src/test/app/Remit_test.cpp | 380 +++++++++++++++++++++++++++++++++ src/test/jtx.h | 1 + src/test/jtx/impl/remit.cpp | 84 ++++++++ src/test/jtx/remit.h | 118 ++++++++++ 5 files changed, 585 insertions(+) create mode 100644 src/test/app/Remit_test.cpp create mode 100644 src/test/jtx/impl/remit.cpp create mode 100644 src/test/jtx/remit.h diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 9cdd2f0d4..c3fd51bc7 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -741,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 @@ -890,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/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp new file mode 100644 index 000000000..e16f6b48c --- /dev/null +++ b/src/test/app/Remit_test.cpp @@ -0,0 +1,380 @@ +//------------------------------------------------------------------------------ +/* + 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 | +*/ + +namespace ripple { +namespace test { +struct Remit_test : public beast::unit_test::suite +{ + + 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; + + // 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 + // }; + + 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(); + + // REMIT + { + env(remit::remit(alice, bob), ter(tesSUCCESS)); + env.close(); + // auto const preAlice = env.balance(alice, USD.issue()); + } + + // REMIT: XAH + { + auto const preAlice = env.balance(alice); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + ter(tesSUCCESS)); + env.close(); + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + XRP(1) - feeDrops); + } + + // REMIT: XAH + USD + { + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'A'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit with uritoken id + env(remit::remit(alice, bob), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: URITOKEN MINT + { + std::string const uri(maxTokenURILength, 'B'); + env(remit::remit(alice, bob), remit::uri(uri), ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'C'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit xah + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + { + // mint uri token + std::string const uri(maxTokenURILength, 'D'); + std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + env(uritoken::mint(alice, uri), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({tid}), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH + URITOKEN XFER + URITOKEN MINT + { + // mint uri token + std::string const uri1(maxTokenURILength, 'E'); + std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, 'E'); + env(remit::remit(alice, bob), + remit::amts({XRP(1)}), + remit::token_ids({tid}), + remit::uri(uri2), + ter(tesSUCCESS)); + env.close(); + } + + // REMIT: XAH/USD + URITOKEN XFER + URITOKEN MINT + { + // mint uri token + std::string const uri1(maxTokenURILength, 'F'); + std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + env(uritoken::mint(alice, uri1), ter(tesSUCCESS)); + env.close(); + + // remit xah/usd + uritoken id + uritoken mint + std::string const uri2(maxTokenURILength - 1, 'F'); + env(remit::remit(alice, bob), + remit::amts({XRP(1), USD(1)}), + remit::token_ids({tid}), + remit::uri(uri2), + ter(tesSUCCESS)); + 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); + } + +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..0fcc62239 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -60,6 +60,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..8ae158eff --- /dev/null +++ b/src/test/jtx/impl/remit.cpp @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + 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/jtx/remit.h b/src/test/jtx/remit.h new file mode 100644 index 000000000..4ad4bafbf --- /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 From 827dc07965d5232a5d7c52840952bb237699555f Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Tue, 5 Dec 2023 13:47:54 +0000 Subject: [PATCH 14/15] make server_definitions caching thread_local --- src/ripple/rpc/handlers/ServerDefinitions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ripple/rpc/handlers/ServerDefinitions.cpp b/src/ripple/rpc/handlers/ServerDefinitions.cpp index f9a443c47..622163e98 100644 --- a/src/ripple/rpc/handlers/ServerDefinitions.cpp +++ b/src/ripple/rpc/handlers/ServerDefinitions.cpp @@ -422,11 +422,11 @@ doServerDefinitions(RPC::JsonContext& context) uint32_t curLgrSeq = context.ledgerMaster.getValidatedLedger()->info().seq; // static values used for cache - static uint32_t lastGenerated = 0; // last ledger seq it was generated - static Json::Value lastFeatures{ - Json::objectValue}; // the actual features JSON last generated - static uint256 lastFeatureHash; // the hash of the features JSON last time - // it was generated + static thread_local uint32_t lastGenerated = 0; // last ledger seq it was generated + static thread_local Json::Value lastFeatures{ + Json::objectValue}; // the actual features JSON last generated + static thread_local uint256 lastFeatureHash; // the hash of the features JSON last time + // it was generated // if a flag ledger has passed since it was last generated, regenerate it, // update the cache above From 32b73c071fcde9a35263fd339027d72ad43f48ae Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 5 Dec 2023 16:29:05 +0100 Subject: [PATCH 15/15] Update Remit_test.cpp --- src/test/app/Remit_test.cpp | 924 ++++++++++++++++++++++++++++++++++-- 1 file changed, 883 insertions(+), 41 deletions(-) diff --git a/src/test/app/Remit_test.cpp b/src/test/app/Remit_test.cpp index e16f6b48c..443f3721e 100644 --- a/src/test/app/Remit_test.cpp +++ b/src/test/app/Remit_test.cpp @@ -70,11 +70,74 @@ // 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) { @@ -113,30 +176,23 @@ struct Remit_test : public beast::unit_test::suite 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 - // }; - - 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(); - // 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()); @@ -144,111 +200,896 @@ 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; + + 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); - BEAST_EXPECT(postAlice == preAlice + XRP(1) - feeDrops); + 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, 'A'); - std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + 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({tid}), + 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 { - std::string const uri(maxTokenURILength, 'B'); + // 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, 'C'); - std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + 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({tid}), + 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, 'D'); - std::string const tid{strHex(uritoken::tokenid(alice, uri))}; + 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({tid}), + 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, 'E'); - std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + 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, 'E'); + 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({tid}), + 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, 'F'); - std::string const tid{strHex(uritoken::tokenid(alice, uri1))}; + 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, 'F'); + 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({tid}), + 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(); } } @@ -363,6 +1204,7 @@ struct Remit_test : public beast::unit_test::suite { testEnabled(features); testDestExistsTLExists(features); + testDestExistsTLDNE(features); } public: