mirror of
				https://github.com/Xahau/xahaud.git
				synced 2025-11-04 10:45:50 +00:00 
			
		
		
		
	Compare commits
	
		
			44 Commits
		
	
	
		
			849d447a20
			...
			remarks
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6b49032436 | ||
| 
						 | 
					7a62559da9 | ||
| 
						 | 
					d7dd6196e8 | ||
| 
						 | 
					d3cfd46af3 | ||
| 
						 | 
					94fab7d58b | ||
| 
						 | 
					53b3b543a7 | ||
| 
						 | 
					69e72ecb91 | ||
| 
						 | 
					98a33d11e0 | ||
| 
						 | 
					c908018647 | ||
| 
						 | 
					c6ddd6d2c4 | ||
| 
						 | 
					78906ee086 | ||
| 
						 | 
					987247ddc1 | ||
| 
						 | 
					a5e2fd0699 | ||
| 
						 | 
					d92403ce35 | ||
| 
						 | 
					6fb8fef883 | ||
| 
						 | 
					a8a4774232 | ||
| 
						 | 
					eaec08471b | ||
| 
						 | 
					caffeea6fc | ||
| 
						 | 
					23d49d0548 | ||
| 
						 | 
					519ab34e4f | ||
| 
						 | 
					bdc59ac4ec | ||
| 
						 | 
					96bb67bfe5 | ||
| 
						 | 
					798212f87c | ||
| 
						 | 
					a3d61c0fbf | ||
| 
						 | 
					3e926c9946 | ||
| 
						 | 
					4392342c99 | ||
| 
						 | 
					f4fe7b7d9a | ||
| 
						 | 
					d268638a39 | ||
| 
						 | 
					b1447afcc0 | ||
| 
						 | 
					f40621c662 | ||
| 
						 | 
					36ff48474a | ||
| 
						 | 
					2adc234bf1 | ||
| 
						 | 
					89bcacca5b | ||
| 
						 | 
					6d496cc16f | ||
| 
						 | 
					63b0245d06 | ||
| 
						 | 
					fdf02a3853 | ||
| 
						 | 
					9edf7ae67a | ||
| 
						 | 
					533ba7ab75 | ||
| 
						 | 
					4e10d7d61f | ||
| 
						 | 
					01e7caa0d6 | ||
| 
						 | 
					349f4d2d68 | ||
| 
						 | 
					24ac5d5f51 | ||
| 
						 | 
					8522c6684b | ||
| 
						 | 
					7efc26a8b1 | 
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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/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()
 | 
			
		||||
{
 | 
			
		||||
    auto j = ctx_.journal;
 | 
			
		||||
    Sandbox sb(&ctx_.view());
 | 
			
		||||
 | 
			
		||||
    auto const sle = sb.read(keylet::account(account_));
 | 
			
		||||
    if (!sle)
 | 
			
		||||
        return terNO_ACCOUNT;
 | 
			
		||||
 | 
			
		||||
    auto const objID = ctx_.tx[sfObjectID];
 | 
			
		||||
    auto sleO = sb.peek(keylet::unchecked(objID));
 | 
			
		||||
    if (!sleO)
 | 
			
		||||
        return terNO_ACCOUNT;
 | 
			
		||||
 | 
			
		||||
    std::optional<AccountID> issuer = getRemarksIssuer(sleO);
 | 
			
		||||
 | 
			
		||||
    if (!issuer || *issuer != account_)
 | 
			
		||||
        return tecNO_PERMISSION;
 | 
			
		||||
 | 
			
		||||
    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 tecTOO_MANY_REMARKS;
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										591
									
								
								src/test/app/SetRemarks_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										591
									
								
								src/test/app/SetRemarks_test.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,591 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
        // terNO_ACCOUNT
 | 
			
		||||
        // tecNO_TARGET
 | 
			
		||||
        // tecNO_PERMISSION
 | 
			
		||||
        // tecTOO_MANY_REMARKS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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