mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 10:45:50 +00:00
Compare commits
7 Commits
remarks
...
patch-ctid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbacb6296d | ||
|
|
372f25d09b | ||
|
|
401395a204 | ||
|
|
eef47da061 | ||
|
|
4221dcf568 | ||
|
|
989532702d | ||
|
|
f9cd2e0d21 |
31
.github/workflows/clang-format.yml
vendored
31
.github/workflows/clang-format.yml
vendored
@@ -4,21 +4,32 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CLANG_VERSION: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install clang-format
|
||||
# - name: Install clang-format
|
||||
# run: |
|
||||
# codename=$( lsb_release --codename --short )
|
||||
# sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <<EOF
|
||||
# deb http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
# deb-src http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
# EOF
|
||||
# wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add
|
||||
# sudo apt-get update -y
|
||||
# sudo apt-get install -y clang-format-${CLANG_VERSION}
|
||||
|
||||
# Temporary fix until this commit is merged
|
||||
# https://github.com/XRPLF/rippled/commit/552377c76f55b403a1c876df873a23d780fcc81c
|
||||
- name: Download and install clang-format
|
||||
run: |
|
||||
codename=$( lsb_release --codename --short )
|
||||
sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <<EOF
|
||||
deb http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
deb-src http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
|
||||
EOF
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-format-${CLANG_VERSION}
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libtinfo5
|
||||
curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
tar -xf clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
||||
sudo mv clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04 /opt/clang-10
|
||||
sudo ln -s /opt/clang-10/bin/clang-format /usr/local/bin/clang-format-10
|
||||
- name: Format src/ripple
|
||||
run: find src/ripple -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format-${CLANG_VERSION} -i
|
||||
- name: Format src/test
|
||||
|
||||
@@ -456,6 +456,7 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/tx/impl/Remit.cpp
|
||||
src/ripple/app/tx/impl/SetAccount.cpp
|
||||
src/ripple/app/tx/impl/SetHook.cpp
|
||||
src/ripple/app/tx/impl/SetRemarks.cpp
|
||||
src/ripple/app/tx/impl/SetRegularKey.cpp
|
||||
src/ripple/app/tx/impl/SetSignerList.cpp
|
||||
src/ripple/app/tx/impl/SetTrust.cpp
|
||||
@@ -752,7 +753,10 @@ if (tests)
|
||||
src/test/app/Remit_test.cpp
|
||||
src/test/app/SHAMapStore_test.cpp
|
||||
src/test/app/SetAuth_test.cpp
|
||||
src/test/app/SetHook_test.cpp
|
||||
src/test/app/SetHookTSH_test.cpp
|
||||
src/test/app/SetRegularKey_test.cpp
|
||||
src/test/app/SetRemarks_test.cpp
|
||||
src/test/app/SetTrust_test.cpp
|
||||
src/test/app/Taker_test.cpp
|
||||
src/test/app/TheoreticalQuality_test.cpp
|
||||
@@ -765,8 +769,6 @@ if (tests)
|
||||
src/test/app/ValidatorKeys_test.cpp
|
||||
src/test/app/ValidatorList_test.cpp
|
||||
src/test/app/ValidatorSite_test.cpp
|
||||
src/test/app/SetHook_test.cpp
|
||||
src/test/app/SetHookTSH_test.cpp
|
||||
src/test/app/Wildcard_test.cpp
|
||||
src/test/app/XahauGenesis_test.cpp
|
||||
src/test/app/tx/apply_test.cpp
|
||||
@@ -900,6 +902,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/remarks.cpp
|
||||
src/test/jtx/impl/remit.cpp
|
||||
src/test/jtx/impl/sendmax.cpp
|
||||
src/test/jtx/impl/seq.cpp
|
||||
|
||||
@@ -498,15 +498,11 @@ RCLConsensus::Adaptor::doAccept(
|
||||
|
||||
for (auto const& item : *result.txns.map_)
|
||||
{
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
retriableTxs.insert(
|
||||
std::make_shared<STTx const>(SerialIter{item.slice()}));
|
||||
JLOG(j_.debug()) << " Tx: " << item.key();
|
||||
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
@@ -514,7 +510,6 @@ RCLConsensus::Adaptor::doAccept(
|
||||
JLOG(j_.warn())
|
||||
<< " Tx: " << item.key() << " throws: " << ex.what();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto built = buildLCL(
|
||||
|
||||
@@ -116,10 +116,8 @@ applyTransactions(
|
||||
{
|
||||
auto const txid = it->first.getTXID();
|
||||
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (pass == 0 && built->txExists(txid))
|
||||
{
|
||||
it = txns.erase(it);
|
||||
@@ -142,7 +140,6 @@ applyTransactions(
|
||||
case ApplyResult::Retry:
|
||||
++it;
|
||||
}
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
@@ -151,7 +148,6 @@ applyTransactions(
|
||||
failed.insert(txid);
|
||||
it = txns.erase(it);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
JLOG(j.debug()) << (certainRetry ? "Pass: " : "Final pass: ") << pass
|
||||
|
||||
@@ -44,8 +44,7 @@ convertBlobsToTxResult(
|
||||
|
||||
auto tr = std::make_shared<Transaction>(txn, reason, app);
|
||||
|
||||
auto metaset =
|
||||
std::make_shared<TxMeta>(tr->getID(), tr->getLedger(), rawMeta);
|
||||
auto metaset = std::make_shared<TxMeta>(tr->getID(), ledger_index, rawMeta);
|
||||
|
||||
// if properly formed meta is available we can use it to generate ctid
|
||||
if (metaset->getAsObject().isFieldPresent(sfTransactionIndex))
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
|
||||
#define RIPPLE_TX_SIMPLE_PAYMENT_H_INCLUDED
|
||||
#ifndef RIPPLE_TX_REMIT_H_INCLUDED
|
||||
#define RIPPLE_TX_REMIT_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
|
||||
450
src/ripple/app/tx/impl/SetRemarks.cpp
Normal file
450
src/ripple/app/tx/impl/SetRemarks.cpp
Normal file
@@ -0,0 +1,450 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/tx/impl/SetRemarks.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/Quality.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
TxConsequences
|
||||
SetRemarks::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, TxConsequences::normal};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
SetRemarks::validateRemarks(STArray const& remarks, beast::Journal const& j)
|
||||
{
|
||||
std::set<Blob> already_seen;
|
||||
|
||||
if (remarks.empty() || remarks.size() > 32)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: Cannot set more than 32 remarks (or "
|
||||
"fewer than 1) in a txn.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
for (auto const& remark : remarks)
|
||||
{
|
||||
if (remark.getFName() != sfRemark)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: contained non-sfRemark field.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// will be checked by template system, extra check for security
|
||||
if (!remark.isFieldPresent(sfRemarkName))
|
||||
return temMALFORMED;
|
||||
|
||||
Blob const& name = remark.getFieldVL(sfRemarkName);
|
||||
if (name.size() == 0 || name.size() > 256)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: RemarkName cannot be empty or "
|
||||
"larger than 256 chars.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (already_seen.find(name) != already_seen.end())
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: duplicate RemarkName entry.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
already_seen.emplace(name);
|
||||
|
||||
uint32_t flags =
|
||||
remark.isFieldPresent(sfFlags) ? remark.getFieldU32(sfFlags) : 0;
|
||||
if (flags != 0 && flags != tfImmutable)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "SetRemarks: Flags must be either tfImmutable or 0";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (!remark.isFieldPresent(sfRemarkValue))
|
||||
{
|
||||
if (flags & tfImmutable)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: A remark deletion cannot be "
|
||||
"marked immutable.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Blob const& val = remark.getFieldVL(sfRemarkValue);
|
||||
if (val.size() == 0 || val.size() > 256)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: RemarkValue cannot be empty or "
|
||||
"larger than 256 chars.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
SetRemarks::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureRemarks))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto& tx = ctx.tx;
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (tx.getFlags() & tfUniversalMask)
|
||||
{
|
||||
JLOG(j.warn()) << "SetRemarks: Invalid flags set.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
auto const& remarks = tx.getFieldArray(sfRemarks);
|
||||
if (NotTEC result = validateRemarks(remarks, j); !isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::optional<AccountID>
|
||||
getRemarksIssuer(T const& sleO)
|
||||
{
|
||||
std::optional<AccountID> issuer;
|
||||
|
||||
// check if it's an allowable object type
|
||||
uint16_t lt = sleO->getFieldU16(sfLedgerEntryType);
|
||||
|
||||
switch (lt)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
case ltOFFER:
|
||||
case ltESCROW:
|
||||
case ltTICKET:
|
||||
case ltPAYCHAN:
|
||||
case ltCHECK:
|
||||
case ltDEPOSIT_PREAUTH: {
|
||||
issuer = sleO->getAccountID(sfAccount);
|
||||
break;
|
||||
}
|
||||
|
||||
case ltNFTOKEN_OFFER: {
|
||||
issuer = sleO->getAccountID(sfOwner);
|
||||
break;
|
||||
}
|
||||
|
||||
case ltURI_TOKEN: {
|
||||
issuer = sleO->getAccountID(sfIssuer);
|
||||
break;
|
||||
}
|
||||
|
||||
case ltRIPPLE_STATE: {
|
||||
// remarks can only be attached to a trustline by the issuer
|
||||
AccountID lowAcc = sleO->getFieldAmount(sfLowLimit).getIssuer();
|
||||
AccountID highAcc = sleO->getFieldAmount(sfHighLimit).getIssuer();
|
||||
|
||||
STAmount bal = sleO->getFieldAmount(sfBalance);
|
||||
|
||||
if (bal < beast::zero)
|
||||
{
|
||||
// low account is issuer
|
||||
issuer = lowAcc;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bal > beast::zero)
|
||||
{
|
||||
// high acccount is issuer
|
||||
issuer = highAcc;
|
||||
break;
|
||||
}
|
||||
|
||||
// if the balance is zero we'll look for the side in default state
|
||||
// and assume this is the issuer
|
||||
uint32_t flags = sleO->getFieldU32(sfFlags);
|
||||
bool const highReserve = (flags & lsfHighReserve);
|
||||
bool const lowReserve = (flags & lsfLowReserve);
|
||||
|
||||
if (!highReserve && !lowReserve)
|
||||
{
|
||||
// error state
|
||||
// do nothing, fallthru.
|
||||
}
|
||||
else if (highReserve && lowReserve)
|
||||
{
|
||||
// in this edge case we don't know who is the issuer, because
|
||||
// there isn't a clear issuer. do nothing, fallthru.
|
||||
}
|
||||
else
|
||||
{
|
||||
issuer = (highReserve ? lowAcc : highAcc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return issuer;
|
||||
}
|
||||
|
||||
TER
|
||||
SetRemarks::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureRemarks))
|
||||
return temDISABLED;
|
||||
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const objID = ctx.tx[sfObjectID];
|
||||
auto const sleO = ctx.view.read(keylet::unchecked(objID));
|
||||
if (!sleO)
|
||||
return tecNO_TARGET;
|
||||
|
||||
std::optional<AccountID> issuer = getRemarksIssuer(sleO);
|
||||
|
||||
if (!issuer || *issuer != id)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// sanity check the remarks merge between txn and obj
|
||||
auto const& remarksTxn = ctx.tx.getFieldArray(sfRemarks);
|
||||
|
||||
std::map<Blob, std::pair<Blob, bool>> keys;
|
||||
if (sleO->isFieldPresent(sfRemarks))
|
||||
{
|
||||
auto const& remarksObj = sleO->getFieldArray(sfRemarks);
|
||||
|
||||
// map the remark name to its value and whether it's immutable
|
||||
for (auto const& remark : remarksObj)
|
||||
keys.emplace(std::make_pair(
|
||||
remark.getFieldVL(sfRemarkName),
|
||||
std::make_pair(
|
||||
remark.getFieldVL(sfRemarkValue),
|
||||
remark.isFieldPresent(sfFlags) &&
|
||||
remark.getFieldU32(sfFlags) & tfImmutable)));
|
||||
}
|
||||
|
||||
int64_t count = keys.size();
|
||||
|
||||
for (auto const& remark : remarksTxn)
|
||||
{
|
||||
std::optional<Blob> valTxn;
|
||||
if (remark.isFieldPresent(sfRemarkValue))
|
||||
valTxn = remark.getFieldVL(sfRemarkValue);
|
||||
bool const isDeletion = !valTxn.has_value();
|
||||
|
||||
Blob name = remark.getFieldVL(sfRemarkName);
|
||||
if (keys.find(name) == keys.end())
|
||||
{
|
||||
// new remark
|
||||
if (isDeletion)
|
||||
{
|
||||
// this could have been an error but deleting something
|
||||
// that doesn't exist is traditionally not an error in xrpl
|
||||
continue;
|
||||
}
|
||||
|
||||
++count;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& [valObj, immutable] = keys[name];
|
||||
|
||||
// even if it's mutable, if we don't mutate it that's a noop so just
|
||||
// pass it
|
||||
if (valTxn.has_value() && *valTxn == valObj)
|
||||
continue;
|
||||
|
||||
if (immutable)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "SetRemarks: attempt to mutate an immutable remark.";
|
||||
return tecIMMUTABLE;
|
||||
}
|
||||
|
||||
if (isDeletion)
|
||||
{
|
||||
if (--count < 0)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "SetRemarks: insane remarks accounting.";
|
||||
return tecCLAIM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 32)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "SetRemarks: an object may have at most 32 remarks.";
|
||||
return tecTOO_MANY_REMARKS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetRemarks::doApply()
|
||||
{
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const sle = sb.read(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const objID = ctx_.tx[sfObjectID];
|
||||
auto sleO = sb.peek(keylet::unchecked(objID));
|
||||
if (!sleO)
|
||||
return tefINTERNAL;
|
||||
|
||||
std::optional<AccountID> issuer = getRemarksIssuer(sleO);
|
||||
|
||||
if (!issuer || *issuer != account_)
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const& remarksTxn = ctx_.tx.getFieldArray(sfRemarks);
|
||||
|
||||
std::map<Blob, std::pair<Blob, bool>> remarksMap;
|
||||
|
||||
if (sleO->isFieldPresent(sfRemarks))
|
||||
{
|
||||
auto const& remarksObj = sleO->getFieldArray(sfRemarks);
|
||||
for (auto const& remark : remarksObj)
|
||||
{
|
||||
uint32_t flags = remark.isFieldPresent(sfFlags)
|
||||
? remark.getFieldU32(sfFlags)
|
||||
: 0;
|
||||
bool const immutable = (flags & tfImmutable) != 0;
|
||||
remarksMap[remark.getFieldVL(sfRemarkName)] = {
|
||||
remark.getFieldVL(sfRemarkValue),
|
||||
remark.isFieldPresent(sfFlags) && immutable};
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& remark : remarksTxn)
|
||||
{
|
||||
std::optional<Blob> val;
|
||||
if (remark.isFieldPresent(sfRemarkValue))
|
||||
val = remark.getFieldVL(sfRemarkValue);
|
||||
Blob name = remark.getFieldVL(sfRemarkName);
|
||||
|
||||
bool const isDeletion = !val.has_value();
|
||||
uint32_t flags =
|
||||
remark.isFieldPresent(sfFlags) ? remark.getFieldU32(sfFlags) : 0;
|
||||
bool const setImmutable = (flags & tfImmutable) != 0;
|
||||
|
||||
if (isDeletion)
|
||||
{
|
||||
if (remarksMap.find(name) != remarksMap.end())
|
||||
remarksMap.erase(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remarksMap.find(name) == remarksMap.end())
|
||||
{
|
||||
remarksMap[name] = std::make_pair(*val, setImmutable);
|
||||
continue;
|
||||
}
|
||||
|
||||
remarksMap[name].first = *val;
|
||||
if (!remarksMap[name].second)
|
||||
remarksMap[name].second = setImmutable;
|
||||
}
|
||||
|
||||
// canonically order
|
||||
std::vector<Blob> keys;
|
||||
for (auto const& [k, _] : remarksMap)
|
||||
keys.push_back(k);
|
||||
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
STArray newRemarks{sfRemarks, static_cast<int>(keys.size())};
|
||||
for (auto const& k : keys)
|
||||
{
|
||||
STObject remark{sfRemark};
|
||||
|
||||
remark.setFieldVL(sfRemarkName, k);
|
||||
remark.setFieldVL(sfRemarkValue, remarksMap[k].first);
|
||||
if (remarksMap[k].second)
|
||||
remark.setFieldU32(sfFlags, lsfImmutable);
|
||||
|
||||
newRemarks.push_back(std::move(remark));
|
||||
}
|
||||
|
||||
if (newRemarks.size() > 32)
|
||||
return tefINTERNAL;
|
||||
|
||||
if (newRemarks.empty() && sleO->isFieldPresent(sfRemarks))
|
||||
sleO->makeFieldAbsent(sfRemarks);
|
||||
else
|
||||
sleO->setFieldArray(sfRemarks, std::move(newRemarks));
|
||||
|
||||
sb.update(sleO);
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
SetRemarks::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount remarkFee{0};
|
||||
if (tx.isFieldPresent(sfRemarks))
|
||||
{
|
||||
int64_t remarkBytes = 0;
|
||||
auto const& remarks = tx.getFieldArray(sfRemarks);
|
||||
for (auto const& remark : remarks)
|
||||
{
|
||||
int64_t entryBytes = 0;
|
||||
if (remark.isFieldPresent(sfRemarkName))
|
||||
{
|
||||
entryBytes += remark.getFieldVL(sfRemarkName).size();
|
||||
}
|
||||
if (remark.isFieldPresent(sfRemarkValue))
|
||||
{
|
||||
entryBytes += remark.getFieldVL(sfRemarkValue).size();
|
||||
}
|
||||
|
||||
// overflow
|
||||
if (remarkBytes + entryBytes < remarkBytes)
|
||||
return INITIAL_XRP;
|
||||
|
||||
remarkBytes += entryBytes;
|
||||
}
|
||||
|
||||
// one drop per byte
|
||||
remarkFee = XRPAmount{remarkBytes};
|
||||
}
|
||||
auto fee = Transactor::calculateBaseFee(view, tx);
|
||||
return fee + remarkFee;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
60
src/ripple/app/tx/impl/SetRemarks.h
Normal file
60
src/ripple/app/tx/impl/SetRemarks.h
Normal file
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
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_SETREMARKS_H_INCLUDED
|
||||
#define RIPPLE_TX_SETREMARKS_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SetRemarks : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit SetRemarks(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&);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
static NotTEC
|
||||
validateRemarks(STArray const& remarks, beast::Journal const& j);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -168,10 +168,8 @@ applyTransaction(
|
||||
JLOG(j.debug()) << "TXN " << txn.getTransactionID()
|
||||
<< (retryAssured ? "/retry" : "/final");
|
||||
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
auto const result = apply(app, view, txn, flags, j);
|
||||
if (result.second)
|
||||
{
|
||||
@@ -191,14 +189,12 @@ applyTransaction(
|
||||
|
||||
JLOG(j.debug()) << "Transaction retry: " << transHuman(result.first);
|
||||
return ApplyResult::Retry;
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
JLOG(j.warn()) << "Throws: " << ex.what();
|
||||
return ApplyResult::Fail;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <ripple/app/tx/impl/SetAccount.h>
|
||||
#include <ripple/app/tx/impl/SetHook.h>
|
||||
#include <ripple/app/tx/impl/SetRegularKey.h>
|
||||
#include <ripple/app/tx/impl/SetRemarks.h>
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/app/tx/impl/URIToken.h>
|
||||
@@ -169,6 +170,8 @@ invoke_preflight(PreflightContext const& ctx)
|
||||
return invoke_preflight_helper<Invoke>(ctx);
|
||||
case ttREMIT:
|
||||
return invoke_preflight_helper<Remit>(ctx);
|
||||
case ttREMARKS_SET:
|
||||
return invoke_preflight_helper<SetRemarks>(ctx);
|
||||
case ttURITOKEN_MINT:
|
||||
case ttURITOKEN_BURN:
|
||||
case ttURITOKEN_BUY:
|
||||
@@ -290,6 +293,8 @@ invoke_preclaim(PreclaimContext const& ctx)
|
||||
return invoke_preclaim<Invoke>(ctx);
|
||||
case ttREMIT:
|
||||
return invoke_preclaim<Remit>(ctx);
|
||||
case ttREMARKS_SET:
|
||||
return invoke_preclaim<SetRemarks>(ctx);
|
||||
case ttURITOKEN_MINT:
|
||||
case ttURITOKEN_BURN:
|
||||
case ttURITOKEN_BUY:
|
||||
@@ -373,6 +378,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return Invoke::calculateBaseFee(view, tx);
|
||||
case ttREMIT:
|
||||
return Remit::calculateBaseFee(view, tx);
|
||||
case ttREMARKS_SET:
|
||||
return SetRemarks::calculateBaseFee(view, tx);
|
||||
case ttURITOKEN_MINT:
|
||||
case ttURITOKEN_BURN:
|
||||
case ttURITOKEN_BUY:
|
||||
@@ -556,6 +563,10 @@ invoke_apply(ApplyContext& ctx)
|
||||
Remit p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttREMARKS_SET: {
|
||||
SetRemarks p(ctx);
|
||||
return p();
|
||||
}
|
||||
case ttURITOKEN_MINT:
|
||||
case ttURITOKEN_BURN:
|
||||
case ttURITOKEN_BUY:
|
||||
@@ -580,19 +591,15 @@ preflight(
|
||||
{
|
||||
PreflightContext const pfctx(app, tx, rules, flags, j);
|
||||
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
return {pfctx, invoke_preflight(pfctx)};
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal()) << "apply: " << e.what();
|
||||
return {pfctx, {tefEXCEPTION, TxConsequences{tx}}};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PreclaimResult
|
||||
@@ -629,21 +636,17 @@ preclaim(
|
||||
preflightResult.j);
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (!isTesSuccess(ctx->preflightResult))
|
||||
return {*ctx, ctx->preflightResult};
|
||||
return {*ctx, invoke_preclaim(*ctx)};
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(ctx->j.fatal()) << "apply: " << e.what();
|
||||
return {*ctx, tefEXCEPTION};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -667,10 +670,8 @@ doApply(PreclaimResult const& preclaimResult, Application& app, OpenView& view)
|
||||
// info to recover.
|
||||
return {tefEXCEPTION, false};
|
||||
}
|
||||
#ifndef DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (!preclaimResult.likelyToClaimFee)
|
||||
return {preclaimResult.ter, false};
|
||||
|
||||
@@ -683,14 +684,12 @@ doApply(PreclaimResult const& preclaimResult, Application& app, OpenView& view)
|
||||
preclaimResult.flags,
|
||||
preclaimResult.j);
|
||||
return invoke_apply(ctx);
|
||||
#ifndef DEBUG
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(preclaimResult.j.fatal()) << "apply: " << e.what();
|
||||
return {tefEXCEPTION, false};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -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 = 80;
|
||||
static constexpr std::size_t numFeatures = 81;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -362,6 +362,7 @@ extern uint256 const fix240819;
|
||||
extern uint256 const fixPageCap;
|
||||
extern uint256 const fix240911;
|
||||
extern uint256 const fixFloatDivide;
|
||||
extern uint256 const featureRemarks;
|
||||
extern uint256 const featureTouch;
|
||||
extern uint256 const fixReduceImport;
|
||||
extern uint256 const fixXahauV3;
|
||||
|
||||
@@ -315,6 +315,9 @@ enum LedgerSpecificFlags {
|
||||
|
||||
// ltURI_TOKEN
|
||||
lsfBurnable = 0x00000001, // True, issuer can burn the token
|
||||
|
||||
// remarks
|
||||
lsfImmutable = 1,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -460,6 +460,7 @@ extern SF_UINT256 const sfNFTokenID;
|
||||
extern SF_UINT256 const sfEmitParentTxnID;
|
||||
extern SF_UINT256 const sfEmitNonce;
|
||||
extern SF_UINT256 const sfEmitHookHash;
|
||||
extern SF_UINT256 const sfObjectID;
|
||||
|
||||
// 256-bit (uncommon)
|
||||
extern SF_UINT256 const sfBookDirectory;
|
||||
@@ -539,6 +540,8 @@ extern SF_VL const sfHookReturnString;
|
||||
extern SF_VL const sfHookParameterName;
|
||||
extern SF_VL const sfHookParameterValue;
|
||||
extern SF_VL const sfBlob;
|
||||
extern SF_VL const sfRemarkName;
|
||||
extern SF_VL const sfRemarkValue;
|
||||
|
||||
// account
|
||||
extern SF_ACCOUNT const sfAccount;
|
||||
@@ -596,6 +599,7 @@ extern SField const sfImportVLKey;
|
||||
extern SField const sfHookEmission;
|
||||
extern SField const sfMintURIToken;
|
||||
extern SField const sfAmountEntry;
|
||||
extern SField const sfRemark;
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
@@ -624,6 +628,7 @@ extern SField const sfActiveValidators;
|
||||
extern SField const sfImportVLKeys;
|
||||
extern SField const sfHookEmissions;
|
||||
extern SField const sfAmounts;
|
||||
extern SField const sfRemarks;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -341,6 +341,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecXCHAIN_SELF_COMMIT = 185, // RESERVED - XCHAIN
|
||||
tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 186, // RESERVED - XCHAIN
|
||||
tecINSUF_RESERVE_SELLER = 187,
|
||||
tecIMMUTABLE = 188,
|
||||
tecTOO_MANY_REMARKS = 189,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -191,6 +191,9 @@ enum ClaimRewardFlags : uint32_t {
|
||||
};
|
||||
constexpr std::uint32_t const tfClaimRewardMask = ~(tfUniversal | tfOptOut);
|
||||
|
||||
// Remarks flags:
|
||||
constexpr std::uint32_t const tfImmutable = 1;
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -146,6 +146,9 @@ enum TxType : std::uint16_t
|
||||
ttURITOKEN_CREATE_SELL_OFFER = 48,
|
||||
ttURITOKEN_CANCEL_SELL_OFFER = 49,
|
||||
|
||||
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
|
||||
ttREMARKS_SET = 94,
|
||||
|
||||
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
|
||||
* that the sender pays for. */
|
||||
ttREMIT = 95,
|
||||
|
||||
@@ -468,6 +468,7 @@ REGISTER_FIX (fix240819, Supported::yes, VoteBehavior::De
|
||||
REGISTER_FIX (fixPageCap, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fix240911, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixFloatDivide, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FEATURE(Remarks, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FEATURE(Touch, Supported::yes, VoteBehavior::DefaultNo);
|
||||
REGISTER_FIX (fixReduceImport, Supported::yes, VoteBehavior::DefaultYes);
|
||||
REGISTER_FIX (fixXahauV3, Supported::yes, VoteBehavior::DefaultYes);
|
||||
|
||||
@@ -159,6 +159,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfDigest, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfRemark.jsonName.c_str(),
|
||||
sfRemark.getCode(),
|
||||
{
|
||||
{sfRemarkName, soeREQUIRED},
|
||||
{sfRemarkValue, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -31,6 +31,7 @@ LedgerFormats::LedgerFormats()
|
||||
{sfLedgerIndex, soeOPTIONAL},
|
||||
{sfLedgerEntryType, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfRemarks, soeOPTIONAL},
|
||||
};
|
||||
|
||||
add(jss::AccountRoot,
|
||||
|
||||
@@ -211,6 +211,7 @@ CONSTRUCT_TYPED_SFIELD(sfNFTokenID, "NFTokenID", UINT256,
|
||||
CONSTRUCT_TYPED_SFIELD(sfEmitParentTxnID, "EmitParentTxnID", UINT256, 11);
|
||||
CONSTRUCT_TYPED_SFIELD(sfEmitNonce, "EmitNonce", UINT256, 12);
|
||||
CONSTRUCT_TYPED_SFIELD(sfEmitHookHash, "EmitHookHash", UINT256, 13);
|
||||
CONSTRUCT_TYPED_SFIELD(sfObjectID, "ObjectID", UINT256, 14);
|
||||
|
||||
// 256-bit (uncommon)
|
||||
CONSTRUCT_TYPED_SFIELD(sfBookDirectory, "BookDirectory", UINT256, 16);
|
||||
@@ -292,6 +293,8 @@ CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL,
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24);
|
||||
CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25);
|
||||
CONSTRUCT_TYPED_SFIELD(sfBlob, "Blob", VL, 26);
|
||||
CONSTRUCT_TYPED_SFIELD(sfRemarkValue, "RemarkValue", VL, 98);
|
||||
CONSTRUCT_TYPED_SFIELD(sfRemarkName, "RemarkName", VL, 99);
|
||||
|
||||
// account
|
||||
CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1);
|
||||
@@ -346,6 +349,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfRemark, "Remark", OBJECT, 97);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMint, "GenesisMint", OBJECT, 96);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidator, "ActiveValidator", OBJECT, 95);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKey, "ImportVLKey", OBJECT, 94);
|
||||
@@ -372,6 +376,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY,
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfRemarks, "Remarks", ARRAY, 97);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfGenesisMints, "GenesisMints", ARRAY, 96);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfActiveValidators, "ActiveValidators", ARRAY, 95);
|
||||
CONSTRUCT_UNTYPED_SFIELD(sfImportVLKeys, "ImportVLKeys", ARRAY, 94);
|
||||
|
||||
@@ -92,6 +92,8 @@ transResults()
|
||||
MAKE_ERROR(tecREQUIRES_FLAG, "The transaction or part-thereof requires a flag that wasn't set."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."),
|
||||
MAKE_ERROR(tecIMMUTABLE, "The remark is marked immutable on the object, and therefore cannot be updated."),
|
||||
MAKE_ERROR(tecTOO_MANY_REMARKS, "The number of remarks on the object would exceed the limit of 32."),
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||
|
||||
@@ -456,6 +456,14 @@ TxFormats::TxFormats()
|
||||
{sfTicketSequence, soeOPTIONAL},
|
||||
},
|
||||
commonFields);
|
||||
|
||||
add(jss::SetRemarks,
|
||||
ttREMARKS_SET,
|
||||
{
|
||||
{sfObjectID, soeREQUIRED},
|
||||
{sfRemarks, soeREQUIRED},
|
||||
},
|
||||
commonFields);
|
||||
}
|
||||
|
||||
TxFormats const&
|
||||
|
||||
@@ -122,6 +122,7 @@ JSS(Remit); // transaction type.
|
||||
JSS(RippleState); // ledger type.
|
||||
JSS(SLE_hit_rate); // out: GetCounts.
|
||||
JSS(SetFee); // transaction type.
|
||||
JSS(SetRemarks); // transaction type
|
||||
JSS(UNLModify); // transaction type.
|
||||
JSS(UNLReport); // transaction type.
|
||||
JSS(SettleDelay); // in: TransactionSign
|
||||
|
||||
@@ -396,6 +396,8 @@ private:
|
||||
return "SetRegularKey";
|
||||
if (inp == "HookSet")
|
||||
return "SetHook";
|
||||
if (inp == "RemarksSet")
|
||||
return "SetRemarks";
|
||||
return inp;
|
||||
};
|
||||
|
||||
|
||||
589
src/test/app/SetRemarks_test.cpp
Normal file
589
src/test/app/SetRemarks_test.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/core/ConfigSections.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <sstream>
|
||||
#include <test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
struct SetRemarks_test : public beast::unit_test::suite
|
||||
{
|
||||
// debugRemarks(env, keylet::account(alice).key);
|
||||
void
|
||||
debugRemarks(jtx::Env& env, uint256 const& id)
|
||||
{
|
||||
Json::Value params;
|
||||
params[jss::index] = strHex(id);
|
||||
auto const info = env.rpc("json", "ledger_entry", to_string(params));
|
||||
std::cout << "INFO: " << info << "\n";
|
||||
}
|
||||
|
||||
void
|
||||
validateRemarks(
|
||||
ReadView const& view,
|
||||
uint256 const& id,
|
||||
std::vector<jtx::remarks::remark> const& marks)
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const slep = view.read(keylet::unchecked(id));
|
||||
if (slep && slep->isFieldPresent(sfRemarks))
|
||||
{
|
||||
auto const& remarksObj = slep->getFieldArray(sfRemarks);
|
||||
BEAST_EXPECT(remarksObj.size() == marks.size());
|
||||
for (int i = 0; i < marks.size(); ++i)
|
||||
{
|
||||
remarks::remark const expectedMark = marks[i];
|
||||
STObject const remark = remarksObj[i];
|
||||
|
||||
Blob name = remark.getFieldVL(sfRemarkName);
|
||||
// BEAST_EXPECT(expectedMark.name == name);
|
||||
|
||||
uint32_t flags = remark.isFieldPresent(sfFlags)
|
||||
? remark.getFieldU32(sfFlags)
|
||||
: 0;
|
||||
BEAST_EXPECT(expectedMark.flags == flags);
|
||||
|
||||
std::optional<Blob> val;
|
||||
if (remark.isFieldPresent(sfRemarkValue))
|
||||
val = remark.getFieldVL(sfRemarkValue);
|
||||
// BEAST_EXPECT(expectedMark.value == val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
testcase("enabled");
|
||||
using namespace jtx;
|
||||
|
||||
// setup env
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
|
||||
for (bool const withRemarks : {false, true})
|
||||
{
|
||||
// If the Remarks amendment is not enabled, you cannot add remarks
|
||||
auto const amend =
|
||||
withRemarks ? features : features - featureRemarks;
|
||||
Env env{*this, amend};
|
||||
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
auto const txResult =
|
||||
withRemarks ? ter(tesSUCCESS) : ter(temDISABLED);
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
txResult);
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPreflightInvalid(FeatureBitset features)
|
||||
{
|
||||
testcase("preflight invalid");
|
||||
using namespace jtx;
|
||||
|
||||
// setup env
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// preflight
|
||||
|
||||
// temDISABLED
|
||||
// DA: testEnabled()
|
||||
|
||||
// temINVALID_FLAG: SetRemarks: Invalid flags set.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
txflags(tfClose),
|
||||
fee(XRP(1)),
|
||||
ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: Cannot set more than 32 remarks (or fewer
|
||||
// than 1) in a txn.
|
||||
{
|
||||
std::vector<remarks::remark> marks;
|
||||
for (int i = 0; i < 0; ++i)
|
||||
{
|
||||
marks.push_back({"CAFE", "DEADBEEF", 0});
|
||||
}
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: Cannot set more than 32 remarks (or fewer
|
||||
// than 1) in a txn.
|
||||
{
|
||||
std::vector<remarks::remark> marks;
|
||||
for (int i = 0; i < 33; ++i)
|
||||
{
|
||||
marks.push_back({"CAFE", "DEADBEEF", 0});
|
||||
}
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: contained non-sfRemark field.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SetRemarks;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[sfObjectID.jsonName] = strHex(keylet::account(alice).key);
|
||||
auto& ja = jv[sfRemarks.getJsonName()];
|
||||
for (std::size_t i = 0; i < 1; ++i)
|
||||
{
|
||||
ja[i][sfGenesisMint.jsonName] = Json::Value{};
|
||||
ja[i][sfGenesisMint.jsonName][jss::Amount] =
|
||||
STAmount(1).getJson(JsonOptions::none);
|
||||
ja[i][sfGenesisMint.jsonName][jss::Destination] = bob.human();
|
||||
}
|
||||
jv[sfRemarks.jsonName] = ja;
|
||||
env(jv, fee(XRP(1)), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: duplicate RemarkName entry.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: RemarkName cannot be empty or larger than
|
||||
// 256 chars.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"", "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: RemarkName cannot be empty or larger than
|
||||
// 256 chars.
|
||||
{
|
||||
std::string const name((256 * 2) + 1, 'A');
|
||||
std::vector<remarks::remark> marks = {
|
||||
{name, "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: Flags must be either tfImmutable or 0
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 2},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: A remark deletion cannot be marked
|
||||
// immutable.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", std::nullopt, 1},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: RemarkValue cannot be empty or larger than
|
||||
// 256 chars.
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
// temMALFORMED: SetRemarks: RemarkValue cannot be empty or larger than
|
||||
// 256 chars.
|
||||
{
|
||||
std::string const value((256 * 2) + 1, 'A');
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", value, 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPreclaimInvalid(FeatureBitset features)
|
||||
{
|
||||
testcase("preclaim invalid");
|
||||
using namespace jtx;
|
||||
|
||||
// setup env
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
env.memoize(carol);
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// preclaim
|
||||
|
||||
// temDISABLED
|
||||
// DA: testEnabled()
|
||||
|
||||
// terNO_ACCOUNT - account doesnt exist
|
||||
{
|
||||
auto const carol = Account("carol");
|
||||
env.memoize(carol);
|
||||
auto tx =
|
||||
remarks::setRemarks(carol, keylet::account(carol).key, marks);
|
||||
tx[jss::Sequence] = 0;
|
||||
env(tx, carol, fee(XRP(1)), ter(terNO_ACCOUNT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_TARGET - object doesnt exist
|
||||
{
|
||||
env(remarks::setRemarks(alice, keylet::account(carol).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_PERMISSION: !issuer
|
||||
{
|
||||
env(deposit::auth(bob, alice));
|
||||
env(remarks::setRemarks(
|
||||
alice, keylet::depositPreauth(bob, alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
// tecNO_PERMISSION: issuer != _account
|
||||
{
|
||||
env(remarks::setRemarks(alice, keylet::account(bob).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
// tecIMMUTABLE: SetRemarks: attempt to mutate an immutable remark.
|
||||
{
|
||||
// alice creates immutable remark
|
||||
std::vector<remarks::remark> immutableMarks = {
|
||||
{"CAFF", "DEAD", tfImmutable},
|
||||
};
|
||||
env(remarks::setRemarks(
|
||||
alice, keylet::account(alice).key, immutableMarks),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// alice cannot update immutable remark
|
||||
std::vector<remarks::remark> badMarks = {
|
||||
{"CAFF", "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(
|
||||
alice, keylet::account(alice).key, badMarks),
|
||||
fee(XRP(1)),
|
||||
ter(tecIMMUTABLE));
|
||||
env.close();
|
||||
}
|
||||
// tecCLAIM: SetRemarks: insane remarks accounting.
|
||||
{} // tecTOO_MANY_REMARKS: SetRemarks: an object may have at most 32
|
||||
// remarks.
|
||||
{
|
||||
std::vector<remarks::remark> _marks;
|
||||
unsigned int hexValue = 0xEFAC;
|
||||
for (int i = 0; i < 31; ++i)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::uppercase << hexValue;
|
||||
_marks.push_back({ss.str(), "DEADBEEF", 0});
|
||||
hexValue++;
|
||||
}
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, _marks),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
env(remarks::setRemarks(alice, keylet::account(alice).key, marks),
|
||||
fee(XRP(1)),
|
||||
ter(tecTOO_MANY_REMARKS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDoApplyInvalid(FeatureBitset features)
|
||||
{
|
||||
testcase("doApply invalid");
|
||||
using namespace jtx;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// doApply
|
||||
|
||||
// All checks in doApply are done in preclaim.
|
||||
BEAST_EXPECT(1);
|
||||
}
|
||||
|
||||
void
|
||||
testDelete(FeatureBitset features)
|
||||
{
|
||||
testcase("delete");
|
||||
using namespace jtx;
|
||||
|
||||
// setup env
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const id = keylet::account(alice).key;
|
||||
|
||||
// Set Remarks
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
|
||||
// Delete Remarks
|
||||
{
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", std::nullopt, 0},
|
||||
};
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, {});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerObjects(FeatureBitset features)
|
||||
{
|
||||
testcase("ledger objects");
|
||||
using namespace jtx;
|
||||
|
||||
// setup env
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice);
|
||||
env.trust(USD(10000), bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env(pay(gw, bob, USD(1000)));
|
||||
env.close();
|
||||
|
||||
std::vector<remarks::remark> marks = {
|
||||
{"CAFE", "DEADBEEF", 0},
|
||||
};
|
||||
|
||||
// ltACCOUNT_ROOT
|
||||
{
|
||||
auto const id = keylet::account(alice).key;
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltOFFER
|
||||
{
|
||||
auto const id = keylet::offer(alice, env.seq(alice)).key;
|
||||
env(offer(alice, XRP(10), USD(10)), fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltESCROW
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
auto const id = keylet::escrow(alice, env.seq(alice)).key;
|
||||
env(escrow::create(alice, bob, XRP(10)),
|
||||
escrow::finish_time(env.now() + 1s),
|
||||
fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltTICKET
|
||||
{
|
||||
auto const id = keylet::ticket(alice, env.seq(alice) + 1).key;
|
||||
env(ticket::create(alice, 10), fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltPAYCHAN
|
||||
{
|
||||
using namespace std::literals::chrono_literals;
|
||||
auto const id = keylet::payChan(alice, bob, env.seq(alice)).key;
|
||||
auto const pk = alice.pk();
|
||||
auto const settleDelay = 100s;
|
||||
env(paychan::create(alice, bob, XRP(10), settleDelay, pk),
|
||||
fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltCHECK
|
||||
{
|
||||
auto const id = keylet::check(alice, env.seq(alice)).key;
|
||||
env(check::create(alice, bob, XRP(10)), fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltDEPOSIT_PREAUTH
|
||||
{
|
||||
env(fset(bob, asfDepositAuth));
|
||||
auto const id = keylet::depositPreauth(alice, bob).key;
|
||||
env(deposit::auth(alice, bob), fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltURI_TOKEN
|
||||
{
|
||||
std::string const uri(256, 'A');
|
||||
auto const id =
|
||||
keylet::uritoken(alice, Blob(uri.begin(), uri.end())).key;
|
||||
env(uritoken::mint(alice, uri), fee(XRP(1)));
|
||||
env(remarks::setRemarks(alice, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltRIPPLE_STATE: bal < 0
|
||||
{
|
||||
auto const alice2 = Account("alice2");
|
||||
env.fund(XRP(1000), alice2);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice2);
|
||||
auto const id = keylet::line(alice2, USD).key;
|
||||
env(pay(gw, alice2, USD(1000)));
|
||||
env(remarks::setRemarks(gw, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltRIPPLE_STATE: bal > 0
|
||||
{
|
||||
auto const carol0 = Account("carol0");
|
||||
env.fund(XRP(1000), carol0);
|
||||
env.close();
|
||||
env.trust(USD(10000), carol0);
|
||||
auto const id = keylet::line(carol0, USD).key;
|
||||
env(pay(gw, carol0, USD(1000)));
|
||||
env(remarks::setRemarks(gw, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltRIPPLE_STATE: highReserve
|
||||
{
|
||||
auto const dan1 = Account("dan1");
|
||||
env.fund(XRP(1000), dan1);
|
||||
env.close();
|
||||
env.trust(USD(1000), dan1);
|
||||
auto const id = keylet::line(dan1, USD).key;
|
||||
env(remarks::setRemarks(gw, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
// ltRIPPLE_STATE: lowReserve
|
||||
{
|
||||
auto const bob0 = Account("bob0");
|
||||
env.fund(XRP(1000), bob0);
|
||||
env.close();
|
||||
env.trust(USD(1000), bob0);
|
||||
auto const id = keylet::line(bob0, USD).key;
|
||||
env(remarks::setRemarks(gw, id, marks), fee(XRP(1)));
|
||||
env.close();
|
||||
validateRemarks(*env.current(), id, marks);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testEnabled(features);
|
||||
testPreflightInvalid(features);
|
||||
testPreclaimInvalid(features);
|
||||
testDoApplyInvalid(features);
|
||||
testDelete(features);
|
||||
testLedgerObjects(features);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SetRemarks, app, ripple);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -57,6 +57,7 @@
|
||||
#include <test/jtx/quality.h>
|
||||
#include <test/jtx/rate.h>
|
||||
#include <test/jtx/regkey.h>
|
||||
#include <test/jtx/remarks.h>
|
||||
#include <test/jtx/remit.h>
|
||||
#include <test/jtx/require.h>
|
||||
#include <test/jtx/requires.h>
|
||||
|
||||
56
src/test/jtx/impl/remarks.cpp
Normal file
56
src/test/jtx/impl/remarks.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx/remarks.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
namespace remarks {
|
||||
|
||||
Json::Value
|
||||
setRemarks(
|
||||
jtx::Account const& account,
|
||||
uint256 const& id,
|
||||
std::vector<remark> const& marks)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SetRemarks;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfObjectID.jsonName] = strHex(id);
|
||||
auto& ja = jv[sfRemarks.getJsonName()];
|
||||
for (std::size_t i = 0; i < marks.size(); ++i)
|
||||
{
|
||||
ja[i][sfRemark.jsonName] = Json::Value{};
|
||||
ja[i][sfRemark.jsonName][sfRemarkName.jsonName] = marks[i].name;
|
||||
if (marks[i].value)
|
||||
ja[i][sfRemark.jsonName][sfRemarkValue.jsonName] = *marks[i].value;
|
||||
if (marks[i].flags)
|
||||
ja[i][sfRemark.jsonName][sfFlags.jsonName] = *marks[i].flags;
|
||||
}
|
||||
jv[sfRemarks.jsonName] = ja;
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace remarks
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
64
src/test/jtx/remarks.h
Normal file
64
src/test/jtx/remarks.h
Normal file
@@ -0,0 +1,64 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
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_REMARKS_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_REMARKS_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
namespace remarks {
|
||||
|
||||
struct remark
|
||||
{
|
||||
std::string name;
|
||||
std::optional<std::string> value;
|
||||
std::optional<std::uint32_t> flags;
|
||||
remark(
|
||||
std::string name_,
|
||||
std::optional<std::string> value_ = std::nullopt,
|
||||
std::optional<std::uint32_t> flags_ = std::nullopt)
|
||||
: name(name_), value(value_), flags(flags_)
|
||||
{
|
||||
if (value_)
|
||||
value = *value_;
|
||||
|
||||
if (flags_)
|
||||
flags = *flags_;
|
||||
}
|
||||
};
|
||||
|
||||
Json::Value
|
||||
setRemarks(
|
||||
jtx::Account const& account,
|
||||
uint256 const& id,
|
||||
std::vector<remark> const& marks);
|
||||
|
||||
} // namespace remarks
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_TEST_JTX_REMARKS_H_INCLUDED
|
||||
Reference in New Issue
Block a user