mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 22:45:52 +00:00
move changes over
This commit is contained in:
@@ -20,8 +20,9 @@
|
||||
#ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/ledger/detail/ApplyViewBase.h>
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
@@ -74,6 +75,18 @@ public:
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
@@ -92,6 +105,8 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -17,147 +17,98 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED
|
||||
#ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
// Helper class that buffers modifications
|
||||
class ApplyStateTable
|
||||
/** Editable, discardable view that can build metadata for one tx.
|
||||
|
||||
Iteration of the tx map is delegated to the base.
|
||||
|
||||
@note Presented as ApplyView to clients.
|
||||
*/
|
||||
class ApplyViewImpl final : public detail::ApplyViewBase
|
||||
{
|
||||
public:
|
||||
using key_type = ReadView::key_type;
|
||||
ApplyViewImpl() = delete;
|
||||
ApplyViewImpl(ApplyViewImpl const&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl&&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl const&) = delete;
|
||||
|
||||
private:
|
||||
enum class Action {
|
||||
cache,
|
||||
erase,
|
||||
insert,
|
||||
modify,
|
||||
};
|
||||
ApplyViewImpl(ApplyViewImpl&&) = default;
|
||||
ApplyViewImpl(ReadView const* base, ApplyFlags flags);
|
||||
|
||||
using items_t = std::map<key_type, std::pair<Action, std::shared_ptr<SLE>>>;
|
||||
|
||||
items_t items_;
|
||||
XRPAmount dropsDestroyed_{0};
|
||||
|
||||
public:
|
||||
ApplyStateTable() = default;
|
||||
ApplyStateTable(ApplyStateTable&&) = default;
|
||||
|
||||
ApplyStateTable(ApplyStateTable const&) = delete;
|
||||
ApplyStateTable&
|
||||
operator=(ApplyStateTable&&) = delete;
|
||||
ApplyStateTable&
|
||||
operator=(ApplyStateTable const&) = delete;
|
||||
|
||||
void
|
||||
apply(RawView& to) const;
|
||||
/** Apply the transaction.
|
||||
|
||||
After a call to `apply`, the only valid
|
||||
operation on this object is to call the
|
||||
destructor.
|
||||
*/
|
||||
std::optional<TxMeta>
|
||||
apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::optional<uint256 const> const& parentBatchId,
|
||||
std::optional<uint256> parentBatchId,
|
||||
bool isDryRun,
|
||||
beast::Journal j);
|
||||
|
||||
bool
|
||||
exists(ReadView const& base, Keylet const& k) const;
|
||||
/** Set the amount of currency delivered.
|
||||
|
||||
std::optional<key_type>
|
||||
succ(
|
||||
ReadView const& base,
|
||||
key_type const& key,
|
||||
std::optional<key_type> const& last) const;
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
read(ReadView const& base, Keylet const& k) const;
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
peek(ReadView const& base, Keylet const& k);
|
||||
|
||||
std::size_t
|
||||
size() const;
|
||||
This value is used when generating metadata
|
||||
for payments, to set the DeliveredAmount field.
|
||||
If the amount is not specified, the field is
|
||||
excluded from the resulting metadata.
|
||||
*/
|
||||
void
|
||||
deliver(STAmount const& amount)
|
||||
{
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
size();
|
||||
|
||||
/** Visit modified entries
|
||||
*/
|
||||
void
|
||||
visit(
|
||||
ReadView const& base,
|
||||
OpenView& target,
|
||||
std::function<void(
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)> const& func) const;
|
||||
|
||||
void
|
||||
erase(ReadView const& base, std::shared_ptr<SLE> const& sle);
|
||||
|
||||
void
|
||||
rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle);
|
||||
|
||||
void
|
||||
insert(ReadView const& base, std::shared_ptr<SLE> const& sle);
|
||||
|
||||
void
|
||||
update(ReadView const& base, std::shared_ptr<SLE> const& sle);
|
||||
|
||||
void
|
||||
replace(ReadView const& base, std::shared_ptr<SLE> const& sle);
|
||||
|
||||
void
|
||||
destroyXRP(XRPAmount const& fee);
|
||||
|
||||
// For debugging
|
||||
XRPAmount const&
|
||||
dropsDestroyed() const
|
||||
{
|
||||
return dropsDestroyed_;
|
||||
}
|
||||
std::shared_ptr<SLE const> const& after)> const& func);
|
||||
|
||||
private:
|
||||
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
|
||||
|
||||
static void
|
||||
threadItem(TxMeta& meta, std::shared_ptr<SLE> const& to);
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
getForMod(
|
||||
ReadView const& base,
|
||||
key_type const& key,
|
||||
Mods& mods,
|
||||
beast::Journal j);
|
||||
|
||||
void
|
||||
threadTx(
|
||||
ReadView const& base,
|
||||
TxMeta& meta,
|
||||
AccountID const& to,
|
||||
Mods& mods,
|
||||
beast::Journal j);
|
||||
|
||||
void
|
||||
threadOwners(
|
||||
ReadView const& base,
|
||||
TxMeta& meta,
|
||||
std::shared_ptr<SLE const> const& sle,
|
||||
Mods& mods,
|
||||
beast::Journal j);
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -34,6 +36,10 @@ struct Fees
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint32_t extensionComputeLimit{
|
||||
0}; // Extension compute limit (instructions)
|
||||
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
|
||||
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -231,6 +231,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
escrow(uint256 const& key) noexcept
|
||||
{
|
||||
return {ltESCROW, key};
|
||||
}
|
||||
|
||||
/** A PaymentChannel */
|
||||
Keylet
|
||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
@@ -287,11 +293,9 @@ delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept;
|
||||
Keylet
|
||||
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
|
||||
|
||||
// `seq` is stored as `sfXChainClaimID` in the object
|
||||
Keylet
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
|
||||
// `seq` is stored as `sfXChainAccountCreateCount` in the object
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/partitioned_unordered_map.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -187,6 +187,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNO_TICKET,
|
||||
tefNFTOKEN_IS_NOT_TRANSFERABLE,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_WASM,
|
||||
tefWASM_FIELD_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -364,6 +366,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecWASM_REJECTED = 199,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,10 +46,7 @@ private:
|
||||
CtorHelper);
|
||||
|
||||
public:
|
||||
TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId = std::nullopt);
|
||||
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
|
||||
@@ -136,7 +133,7 @@ public:
|
||||
void
|
||||
setParentBatchId(uint256 const& parentBatchId)
|
||||
{
|
||||
mParentBatchId = parentBatchId;
|
||||
parentBatchId_ = parentBatchId;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -145,13 +142,55 @@ public:
|
||||
XRPL_ASSERT(
|
||||
hasParentBatchId(),
|
||||
"ripple::TxMeta::getParentBatchId : non-null batch id");
|
||||
return *mParentBatchId;
|
||||
return *parentBatchId_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasParentBatchId() const
|
||||
{
|
||||
return static_cast<bool>(mParentBatchId);
|
||||
return static_cast<bool>(parentBatchId_);
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::uint32_t const& gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getGasUsed() const
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hasGasUsed(),
|
||||
"ripple::TxMeta::getGasUsed : non-null gas used field");
|
||||
return *gasUsed_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasGasUsed() const
|
||||
{
|
||||
return static_cast<bool>(gasUsed_);
|
||||
}
|
||||
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const& wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
std::int32_t
|
||||
getWasmReturnCode() const
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hasWasmReturnCode(),
|
||||
"ripple::TxMeta::getWasmReturnCode : non-null wasm return code");
|
||||
return *wasmReturnCode_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasWasmReturnCode() const
|
||||
{
|
||||
return static_cast<bool>(wasmReturnCode_);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -161,7 +200,9 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<uint256> mParentBatchId;
|
||||
std::optional<uint256> parentBatchId_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -32,9 +32,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (IncludeKeyletFields, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
||||
@@ -47,7 +45,6 @@ XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -120,7 +120,6 @@ LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({
|
||||
// All fields are soeREQUIRED because there is always a SignerEntries.
|
||||
// If there are no SignerEntries the node is deleted.
|
||||
LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
|
||||
{sfOwner, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfSignerQuorum, soeREQUIRED},
|
||||
{sfSignerEntries, soeREQUIRED},
|
||||
@@ -189,7 +188,7 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
|
||||
{sfNFTokenID, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL} // order book directories
|
||||
{sfDomainID, soeOPTIONAL}
|
||||
}))
|
||||
|
||||
/** The ledger object which lists details about amendments on the network.
|
||||
@@ -320,6 +319,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
@@ -344,12 +344,13 @@ LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAc
|
||||
*/
|
||||
LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfSequence, soeOPTIONAL},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
{sfSourceTag, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
@@ -367,7 +368,6 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSequence, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfBalance, soeREQUIRED},
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
@@ -415,7 +415,6 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfMutableFlags, soeDEFAULT},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks MPToken
|
||||
@@ -436,7 +435,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
*/
|
||||
LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, oracle, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOracleDocumentID, soeOPTIONAL},
|
||||
{sfProvider, soeREQUIRED},
|
||||
{sfPriceDataSeries, soeREQUIRED},
|
||||
{sfAssetClass, soeREQUIRED},
|
||||
|
||||
@@ -114,7 +114,11 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 53)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 54)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 55)
|
||||
TYPED_SFIELD(sfComputationAllowance, UINT32, 56)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 57)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -174,8 +178,7 @@ TYPED_SFIELD(sfNFTokenID, UINT256, 10)
|
||||
TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11)
|
||||
TYPED_SFIELD(sfEmitNonce, UINT256, 12)
|
||||
TYPED_SFIELD(sfEmitHookHash, UINT256, 13)
|
||||
TYPED_SFIELD(sfAMMID, UINT256, 14,
|
||||
SField::sMD_PseudoAccount | SField::sMD_Default)
|
||||
TYPED_SFIELD(sfAMMID, UINT256, 14)
|
||||
|
||||
// 256-bit (uncommon)
|
||||
TYPED_SFIELD(sfBookDirectory, UINT256, 16)
|
||||
@@ -197,8 +200,7 @@ TYPED_SFIELD(sfHookHash, UINT256, 31)
|
||||
TYPED_SFIELD(sfHookNamespace, UINT256, 32)
|
||||
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 34)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35,
|
||||
SField::sMD_PseudoAccount | SField::sMD_Default)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35)
|
||||
TYPED_SFIELD(sfParentBatchID, UINT256, 36)
|
||||
|
||||
// number (common)
|
||||
@@ -208,6 +210,9 @@ TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
|
||||
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
|
||||
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
|
||||
|
||||
// 32-bit signed (common)
|
||||
TYPED_SFIELD(sfWasmReturnCode, INT32, 1)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
TYPED_SFIELD(sfBalance, AMOUNT, 2)
|
||||
@@ -236,7 +241,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||
|
||||
// currency amount (AMM)
|
||||
// currency amount (more)
|
||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
@@ -278,6 +283,7 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
|
||||
TYPED_SFIELD(sfProvider, VL, 29)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 31)
|
||||
TYPED_SFIELD(sfFinishFunction, VL, 32)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -22,31 +22,16 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* TRANSACTION(tag, value, name, delegatable, amendments, privileges, fields)
|
||||
*
|
||||
* To ease maintenance, you may replace any unneeded values with "..."
|
||||
* e.g. #define TRANSACTION(tag, value, name, ...)
|
||||
* TRANSACTION(tag, value, name, delegatable, amendments, fields)
|
||||
*
|
||||
* You must define a transactor class in the `ripple` namespace named `name`,
|
||||
* and include its header alongside the TRANSACTOR definition using this
|
||||
* format:
|
||||
* #if TRANSACTION_INCLUDE
|
||||
* # include <xrpld/app/tx/detail/HEADER.h>
|
||||
* #endif
|
||||
*
|
||||
* The `privileges` parameter of the TRANSACTION macro is a bitfield
|
||||
* defining which operations the transaction can perform.
|
||||
* The values are defined and used in InvariantCheck.cpp
|
||||
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
|
||||
*/
|
||||
|
||||
/** This transaction type executes a payment. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Payment.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
createAcct,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
@@ -60,44 +45,38 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
}))
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Escrow.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfComputationAllowance, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
|
||||
/** This transaction type adjusts various account settings. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#endif
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfEmailHash, soeOPTIONAL},
|
||||
{sfWalletLocator, soeOPTIONAL},
|
||||
@@ -112,26 +91,18 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Escrow.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets or clears an account's "regular key". */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#endif
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfRegularKey, soeOPTIONAL},
|
||||
}))
|
||||
@@ -139,13 +110,9 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
|
||||
// 6 deprecated
|
||||
|
||||
/** This transaction type creates an offer to trade one asset for another. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfTakerPays, soeREQUIRED},
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
@@ -155,13 +122,9 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CancelOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
@@ -169,13 +132,9 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
|
||||
// 9 deprecated
|
||||
|
||||
/** This transaction type creates a new set of tickets. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateTicket.h>
|
||||
#endif
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
|
||||
Delegation::delegatable,
|
||||
featureTicketBatch,
|
||||
noPriv,
|
||||
({
|
||||
{sfTicketCount, soeREQUIRED},
|
||||
}))
|
||||
@@ -185,26 +144,18 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
|
||||
/** This transaction type modifies the signer list associated with an account. */
|
||||
// The SignerEntries are optional because a SignerList is deleted by
|
||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#endif
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfSignerQuorum, soeREQUIRED},
|
||||
{sfSignerEntries, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PayChan.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -218,7 +169,6 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -229,7 +179,6 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -240,13 +189,9 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
noPriv,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED},
|
||||
@@ -256,13 +201,9 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type cashes an existing check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CashCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
noPriv,
|
||||
({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -270,25 +211,17 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash,
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CancelCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
noPriv,
|
||||
({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type grants or revokes authorization to transfer funds. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
#endif
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
|
||||
Delegation::delegatable,
|
||||
featureDepositPreauth,
|
||||
noPriv,
|
||||
({
|
||||
{sfAuthorize, soeOPTIONAL},
|
||||
{sfUnauthorize, soeOPTIONAL},
|
||||
@@ -297,13 +230,9 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
|
||||
}))
|
||||
|
||||
/** This transaction type modifies a trustline between two accounts. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#endif
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfLimitAmount, soeOPTIONAL},
|
||||
{sfQualityIn, soeOPTIONAL},
|
||||
@@ -311,13 +240,9 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an existing account. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||
#endif
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
mustDeleteAcct,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
@@ -327,13 +252,9 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
|
||||
// 22 reserved
|
||||
|
||||
/** This transaction mints a new NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenMint.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
changeNFTCounts,
|
||||
({
|
||||
{sfNFTokenTaxon, soeREQUIRED},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
@@ -345,26 +266,18 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
|
||||
}))
|
||||
|
||||
/** This transaction burns (i.e. destroys) an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenBurn.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
changeNFTCounts,
|
||||
({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction creates a new offer to buy or sell an NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
noPriv,
|
||||
({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -374,25 +287,17 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
|
||||
}))
|
||||
|
||||
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
noPriv,
|
||||
({
|
||||
{sfNFTokenOffers, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
noPriv,
|
||||
({
|
||||
{sfNFTokenBuyOffer, soeOPTIONAL},
|
||||
{sfNFTokenSellOffer, soeOPTIONAL},
|
||||
@@ -400,26 +305,18 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
|
||||
}))
|
||||
|
||||
/** This transaction claws back issued tokens. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Clawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback,
|
||||
Delegation::delegatable,
|
||||
featureClawback,
|
||||
noPriv,
|
||||
({
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from an AMM pool. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
|
||||
Delegation::delegatable,
|
||||
featureAMMClawback,
|
||||
mayDeleteAcct | overrideFreeze,
|
||||
({
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED},
|
||||
@@ -428,13 +325,9 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
|
||||
}))
|
||||
|
||||
/** This transaction type creates an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
createPseudoAcct,
|
||||
({
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfAmount2, soeREQUIRED},
|
||||
@@ -442,13 +335,9 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type deposits into an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMDeposit.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
noPriv,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -460,13 +349,9 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
|
||||
}))
|
||||
|
||||
/** This transaction type withdraws from an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMWithdraw.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
mayDeleteAcct,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -477,13 +362,9 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
|
||||
}))
|
||||
|
||||
/** This transaction type votes for the trading fee */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMVote.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
noPriv,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -491,13 +372,9 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote,
|
||||
}))
|
||||
|
||||
/** This transaction type bids for the auction slot */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMBid.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
noPriv,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -507,26 +384,18 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes AMM in the empty state */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
mustDeleteAcct,
|
||||
({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transactions creates a crosschain sequence number */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/XChainBridge.h>
|
||||
#endif
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
@@ -537,7 +406,6 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
@@ -549,7 +417,6 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
@@ -562,7 +429,6 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
@@ -574,7 +440,6 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
createAcct,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
@@ -591,11 +456,9 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to an account */
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46,
|
||||
XChainAddAccountCreateAttestation,
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
createAcct,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
@@ -616,7 +479,6 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46,
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeOPTIONAL},
|
||||
@@ -627,7 +489,6 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
noPriv,
|
||||
({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
@@ -635,13 +496,9 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
}))
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DID.h>
|
||||
#endif
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
Delegation::delegatable,
|
||||
featureDID,
|
||||
noPriv,
|
||||
({
|
||||
{sfDIDDocument, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
@@ -652,17 +509,12 @@ TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete,
|
||||
Delegation::delegatable,
|
||||
featureDID,
|
||||
noPriv,
|
||||
({}))
|
||||
|
||||
/** This transaction type creates an Oracle instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetOracle.h>
|
||||
#endif
|
||||
TRANSACTION(ttORACLE_SET, 51, OracleSet,
|
||||
Delegation::delegatable,
|
||||
featurePriceOracle,
|
||||
noPriv,
|
||||
({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
{sfProvider, soeOPTIONAL},
|
||||
@@ -673,97 +525,65 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an Oracle instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#endif
|
||||
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
|
||||
Delegation::delegatable,
|
||||
featurePriceOracle,
|
||||
noPriv,
|
||||
({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type fixes a problem in the ledger state */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/LedgerStateFix.h>
|
||||
#endif
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
|
||||
Delegation::delegatable,
|
||||
fixNFTokenPageLinks,
|
||||
noPriv,
|
||||
({
|
||||
{sfLedgerFixType, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a MPTokensIssuance instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
createMPTIssuance,
|
||||
({
|
||||
{sfAssetScale, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfMutableFlags, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
destroyMPTIssuance,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfMutableFlags, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type authorizes a MPToken instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
mustAuthorizeMPT,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Credentials.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
noPriv,
|
||||
({
|
||||
{sfSubject, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
@@ -775,7 +595,6 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
noPriv,
|
||||
({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
@@ -785,7 +604,6 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
noPriv,
|
||||
({
|
||||
{sfSubject, soeOPTIONAL},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
@@ -793,13 +611,9 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
|
||||
}))
|
||||
|
||||
/** This transaction type modify a NFToken */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenModify.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
|
||||
Delegation::delegatable,
|
||||
featureDynamicNFT,
|
||||
noPriv,
|
||||
({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
@@ -807,51 +621,35 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
|
||||
}))
|
||||
|
||||
/** This transaction type creates or modifies a Permissioned Domain */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
|
||||
Delegation::delegatable,
|
||||
featurePermissionedDomains,
|
||||
noPriv,
|
||||
({
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfAcceptedCredentials, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a Permissioned Domain */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
|
||||
Delegation::delegatable,
|
||||
featurePermissionedDomains,
|
||||
noPriv,
|
||||
({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type delegates authorized account specified permissions */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DelegateSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
|
||||
Delegation::notDelegatable,
|
||||
featurePermissionDelegation,
|
||||
noPriv,
|
||||
({
|
||||
{sfAuthorize, soeREQUIRED},
|
||||
{sfPermissions, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction creates a single asset vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
createPseudoAcct | createMPTIssuance,
|
||||
({
|
||||
{sfAsset, soeREQUIRED, soeMPTSupported},
|
||||
{sfAssetsMaximum, soeOPTIONAL},
|
||||
@@ -863,13 +661,9 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
}))
|
||||
|
||||
/** This transaction updates a single asset vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
noPriv,
|
||||
({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAssetsMaximum, soeOPTIONAL},
|
||||
@@ -878,38 +672,26 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
}))
|
||||
|
||||
/** This transaction deletes a single asset vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
mustDeleteAcct | destroyMPTIssuance,
|
||||
({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction trades assets for shares with a vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultDeposit.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
mayAuthorizeMPT,
|
||||
({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This transaction trades shares for assets with a vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultWithdraw.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
mayDeleteMPT,
|
||||
({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
@@ -918,13 +700,9 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from a vault. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
mayDeleteMPT,
|
||||
({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfHolder, soeREQUIRED},
|
||||
@@ -932,13 +710,9 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
|
||||
}))
|
||||
|
||||
/** This transaction type batches together transactions. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Batch.h>
|
||||
#endif
|
||||
TRANSACTION(ttBATCH, 71, Batch,
|
||||
Delegation::notDelegatable,
|
||||
featureBatch,
|
||||
noPriv,
|
||||
({
|
||||
{sfRawTransactions, soeREQUIRED},
|
||||
{sfBatchSigners, soeOPTIONAL},
|
||||
@@ -948,13 +722,9 @@ TRANSACTION(ttBATCH, 71, Batch,
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
*/
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Change.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfAmendment, soeREQUIRED},
|
||||
@@ -966,7 +736,6 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
|
||||
TRANSACTION(ttFEE, 101, SetFee,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfLedgerSequence, soeOPTIONAL},
|
||||
// Old version uses raw numbers
|
||||
@@ -987,7 +756,6 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
TRANSACTION(ttUNL_MODIFY, 102, UNLModify,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
noPriv,
|
||||
({
|
||||
{sfUNLModifyDisabling, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
|
||||
@@ -17,681 +17,98 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/detail/ApplyStateTable.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
#ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
void
|
||||
ApplyStateTable::apply(RawView& to) const
|
||||
/** Editable, discardable view that can build metadata for one tx.
|
||||
|
||||
Iteration of the tx map is delegated to the base.
|
||||
|
||||
@note Presented as ApplyView to clients.
|
||||
*/
|
||||
class ApplyViewImpl final : public detail::ApplyViewBase
|
||||
{
|
||||
to.rawDestroyXRP(dropsDestroyed_);
|
||||
for (auto const& item : items_)
|
||||
public:
|
||||
ApplyViewImpl() = delete;
|
||||
ApplyViewImpl(ApplyViewImpl const&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl&&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl const&) = delete;
|
||||
|
||||
ApplyViewImpl(ApplyViewImpl&&) = default;
|
||||
ApplyViewImpl(ReadView const* base, ApplyFlags flags);
|
||||
|
||||
/** Apply the transaction.
|
||||
|
||||
After a call to `apply`, the only valid
|
||||
operation on this object is to call the
|
||||
destructor.
|
||||
*/
|
||||
std::optional<TxMeta>
|
||||
apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<uint256> parentBatchId,
|
||||
bool isDryRun,
|
||||
beast::Journal j);
|
||||
|
||||
/** Set the amount of currency delivered.
|
||||
|
||||
This value is used when generating metadata
|
||||
for payments, to set the DeliveredAmount field.
|
||||
If the amount is not specified, the field is
|
||||
excluded from the resulting metadata.
|
||||
*/
|
||||
void
|
||||
deliver(STAmount const& amount)
|
||||
{
|
||||
auto const& sle = item.second.second;
|
||||
switch (item.second.first)
|
||||
{
|
||||
case Action::cache:
|
||||
break;
|
||||
case Action::erase:
|
||||
to.rawErase(sle);
|
||||
break;
|
||||
case Action::insert:
|
||||
to.rawInsert(sle);
|
||||
break;
|
||||
case Action::modify:
|
||||
to.rawReplace(sle);
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ApplyStateTable::size() const
|
||||
{
|
||||
std::size_t ret = 0;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
switch (item.second.first)
|
||||
{
|
||||
case Action::erase:
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
++ret;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::visit(
|
||||
ReadView const& to,
|
||||
std::function<void(
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)> const& func) const
|
||||
{
|
||||
for (auto& item : items_)
|
||||
{
|
||||
switch (item.second.first)
|
||||
{
|
||||
case Action::erase:
|
||||
func(
|
||||
item.first,
|
||||
true,
|
||||
to.read(keylet::unchecked(item.first)),
|
||||
item.second.second);
|
||||
break;
|
||||
|
||||
case Action::insert:
|
||||
func(item.first, false, nullptr, item.second.second);
|
||||
break;
|
||||
|
||||
case Action::modify:
|
||||
func(
|
||||
item.first,
|
||||
false,
|
||||
to.read(keylet::unchecked(item.first)),
|
||||
item.second.second);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TxMeta>
|
||||
ApplyStateTable::apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::optional<uint256 const> const& parentBatchId,
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Build metadata and insert
|
||||
auto const sTx = std::make_shared<Serializer>();
|
||||
tx.add(*sTx);
|
||||
std::shared_ptr<Serializer> sMeta;
|
||||
std::optional<TxMeta> metadata;
|
||||
if (!to.open() || isDryRun)
|
||||
{
|
||||
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
|
||||
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
SField const* type;
|
||||
switch (item.second.first)
|
||||
{
|
||||
default:
|
||||
case Action::cache:
|
||||
continue;
|
||||
case Action::erase:
|
||||
type = &sfDeletedNode;
|
||||
break;
|
||||
case Action::insert:
|
||||
type = &sfCreatedNode;
|
||||
break;
|
||||
case Action::modify:
|
||||
type = &sfModifiedNode;
|
||||
break;
|
||||
}
|
||||
auto const origNode = to.read(keylet::unchecked(item.first));
|
||||
auto curNode = item.second.second;
|
||||
if ((type == &sfModifiedNode) && (*curNode == *origNode))
|
||||
continue;
|
||||
std::uint16_t nodeType = curNode
|
||||
? curNode->getFieldU16(sfLedgerEntryType)
|
||||
: origNode->getFieldU16(sfLedgerEntryType);
|
||||
meta.setAffectedNode(item.first, *type, nodeType);
|
||||
if (type == &sfDeletedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
origNode && curNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"deletion");
|
||||
threadOwners(to, meta, origNode, newMod, j);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// go through the original node for
|
||||
// modified fields saved on modification
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// go through the final node for final fields
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_DeleteFinal))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfModifiedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"modification");
|
||||
|
||||
if (curNode->isThreadedType(
|
||||
to.rules())) // thread transaction to node
|
||||
// item modified
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// search the original node for values saved on modify
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// search the final node for values saved always
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_ChangeNew))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfCreatedNode) // if created, thread to owner(s)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && !origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"creation");
|
||||
threadOwners(to, meta, curNode, newMod, j);
|
||||
|
||||
if (curNode->isThreadedType(
|
||||
to.rules())) // always thread to self
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject news(sfNewFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// save non-default values
|
||||
if (!obj.isDefault() &&
|
||||
obj.getFName().shouldMeta(
|
||||
SField::sMD_Create | SField::sMD_Always))
|
||||
news.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!news.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(news));
|
||||
}
|
||||
else
|
||||
{
|
||||
UNREACHABLE(
|
||||
"ripple::detail::ApplyStateTable::apply : unsupported "
|
||||
"operation type");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDryRun)
|
||||
{
|
||||
// add any new modified nodes to the modification set
|
||||
for (auto const& mod : newMod)
|
||||
to.rawReplace(mod.second);
|
||||
}
|
||||
|
||||
sMeta = std::make_shared<Serializer>();
|
||||
meta.addRaw(*sMeta, ter, to.txCount());
|
||||
|
||||
// VFALCO For diagnostics do we want to show
|
||||
// metadata even when the base view is open?
|
||||
JLOG(j.trace()) << "metadata " << meta.getJson(JsonOptions::none);
|
||||
|
||||
metadata = meta;
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
if (!isDryRun)
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
to.rawTxInsert(tx.getTransactionID(), sTx, sMeta);
|
||||
apply(to);
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
//---
|
||||
|
||||
bool
|
||||
ApplyStateTable::exists(ReadView const& base, Keylet const& k) const
|
||||
{
|
||||
auto const iter = items_.find(k.key);
|
||||
if (iter == items_.end())
|
||||
return base.exists(k);
|
||||
auto const& item = iter->second;
|
||||
auto const& sle = item.second;
|
||||
switch (item.first)
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
case Action::erase:
|
||||
return false;
|
||||
case Action::cache:
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
if (!k.check(*sle))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto
|
||||
ApplyStateTable::succ(
|
||||
ReadView const& base,
|
||||
key_type const& key,
|
||||
std::optional<key_type> const& last) const -> std::optional<key_type>
|
||||
{
|
||||
std::optional<key_type> next = key;
|
||||
items_t::const_iterator iter;
|
||||
// Find base successor that is
|
||||
// not also deleted in our list
|
||||
do
|
||||
{
|
||||
next = base.succ(*next, last);
|
||||
if (!next)
|
||||
break;
|
||||
iter = items_.find(*next);
|
||||
} while (iter != items_.end() && iter->second.first == Action::erase);
|
||||
// Find non-deleted successor in our list
|
||||
for (iter = items_.upper_bound(key); iter != items_.end(); ++iter)
|
||||
{
|
||||
if (iter->second.first != Action::erase)
|
||||
{
|
||||
// Found both, return the lower key
|
||||
if (!next || next > iter->first)
|
||||
next = iter->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Nothing in our list, return
|
||||
// what we got from the parent.
|
||||
if (last && next >= last)
|
||||
return std::nullopt;
|
||||
return next;
|
||||
}
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
size();
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
ApplyStateTable::read(ReadView const& base, Keylet const& k) const
|
||||
{
|
||||
auto const iter = items_.find(k.key);
|
||||
if (iter == items_.end())
|
||||
return base.read(k);
|
||||
auto const& item = iter->second;
|
||||
auto const& sle = item.second;
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
return nullptr;
|
||||
case Action::cache:
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
};
|
||||
if (!k.check(*sle))
|
||||
return nullptr;
|
||||
return sle;
|
||||
}
|
||||
/** Visit modified entries
|
||||
*/
|
||||
void
|
||||
visit(
|
||||
OpenView& target,
|
||||
std::function<void(
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)> const& func);
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
ApplyStateTable::peek(ReadView const& base, Keylet const& k)
|
||||
{
|
||||
auto iter = items_.lower_bound(k.key);
|
||||
if (iter == items_.end() || iter->first != k.key)
|
||||
{
|
||||
auto const sle = base.read(k);
|
||||
if (!sle)
|
||||
return nullptr;
|
||||
// Make our own copy
|
||||
using namespace std;
|
||||
iter = items_.emplace_hint(
|
||||
iter,
|
||||
piecewise_construct,
|
||||
forward_as_tuple(sle->key()),
|
||||
forward_as_tuple(Action::cache, make_shared<SLE>(*sle)));
|
||||
return iter->second.second;
|
||||
}
|
||||
auto const& item = iter->second;
|
||||
auto const& sle = item.second;
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
return nullptr;
|
||||
case Action::cache:
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
};
|
||||
if (!k.check(*sle))
|
||||
return nullptr;
|
||||
return sle;
|
||||
}
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
void
|
||||
ApplyStateTable::erase(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
LogicError("ApplyStateTable::erase: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
LogicError("ApplyStateTable::erase: unknown SLE");
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("ApplyStateTable::erase: double erase");
|
||||
break;
|
||||
case Action::insert:
|
||||
items_.erase(iter);
|
||||
break;
|
||||
case Action::cache:
|
||||
case Action::modify:
|
||||
item.first = Action::erase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
using namespace std;
|
||||
auto const result = items_.emplace(
|
||||
piecewise_construct,
|
||||
forward_as_tuple(sle->key()),
|
||||
forward_as_tuple(Action::erase, sle));
|
||||
if (result.second)
|
||||
return;
|
||||
auto& item = result.first->second;
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("ApplyStateTable::rawErase: double erase");
|
||||
break;
|
||||
case Action::insert:
|
||||
items_.erase(result.first);
|
||||
break;
|
||||
case Action::cache:
|
||||
case Action::modify:
|
||||
item.first = Action::erase;
|
||||
item.second = sle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::insert(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.lower_bound(sle->key());
|
||||
if (iter == items_.end() || iter->first != sle->key())
|
||||
{
|
||||
using namespace std;
|
||||
items_.emplace_hint(
|
||||
iter,
|
||||
piecewise_construct,
|
||||
forward_as_tuple(sle->key()),
|
||||
forward_as_tuple(Action::insert, sle));
|
||||
return;
|
||||
}
|
||||
auto& item = iter->second;
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::cache:
|
||||
LogicError("ApplyStateTable::insert: already cached");
|
||||
case Action::insert:
|
||||
LogicError("ApplyStateTable::insert: already inserted");
|
||||
case Action::modify:
|
||||
LogicError("ApplyStateTable::insert: already modified");
|
||||
case Action::erase:
|
||||
break;
|
||||
}
|
||||
item.first = Action::modify;
|
||||
item.second = sle;
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::replace(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.lower_bound(sle->key());
|
||||
if (iter == items_.end() || iter->first != sle->key())
|
||||
{
|
||||
using namespace std;
|
||||
items_.emplace_hint(
|
||||
iter,
|
||||
piecewise_construct,
|
||||
forward_as_tuple(sle->key()),
|
||||
forward_as_tuple(Action::modify, sle));
|
||||
return;
|
||||
}
|
||||
auto& item = iter->second;
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("ApplyStateTable::replace: already erased");
|
||||
case Action::cache:
|
||||
item.first = Action::modify;
|
||||
break;
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
}
|
||||
item.second = sle;
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::update(ReadView const& base, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
auto const iter = items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
LogicError("ApplyStateTable::update: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
LogicError("ApplyStateTable::update: unknown SLE");
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("ApplyStateTable::update: erased");
|
||||
break;
|
||||
case Action::cache:
|
||||
item.first = Action::modify;
|
||||
break;
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::destroyXRP(XRPAmount const& fee)
|
||||
{
|
||||
dropsDestroyed_ += fee;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Insert this transaction to the SLE's threading list
|
||||
void
|
||||
ApplyStateTable::threadItem(TxMeta& meta, std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
key_type prevTxID;
|
||||
LedgerIndex prevLgrID;
|
||||
|
||||
if (!sle->thread(meta.getTxID(), meta.getLgrSeq(), prevTxID, prevLgrID))
|
||||
return;
|
||||
|
||||
if (!prevTxID.isZero())
|
||||
{
|
||||
auto& node = meta.getAffectedNode(sle, sfModifiedNode);
|
||||
|
||||
if (node.getFieldIndex(sfPreviousTxnID) == -1)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
node.getFieldIndex(sfPreviousTxnLgrSeq) == -1,
|
||||
"ripple::ApplyStateTable::threadItem : previous ledger is not "
|
||||
"set");
|
||||
node.setFieldH256(sfPreviousTxnID, prevTxID);
|
||||
node.setFieldU32(sfPreviousTxnLgrSeq, prevLgrID);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
node.getFieldH256(sfPreviousTxnID) == prevTxID,
|
||||
"ripple::ApplyStateTable::threadItem : previous transaction is a "
|
||||
"match");
|
||||
XRPL_ASSERT(
|
||||
node.getFieldU32(sfPreviousTxnLgrSeq) == prevLgrID,
|
||||
"ripple::ApplyStateTable::threadItem : previous ledger is a match");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
ApplyStateTable::getForMod(
|
||||
ReadView const& base,
|
||||
key_type const& key,
|
||||
Mods& mods,
|
||||
beast::Journal j)
|
||||
{
|
||||
{
|
||||
auto miter = mods.find(key);
|
||||
if (miter != mods.end())
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
miter->second,
|
||||
"ripple::ApplyStateTable::getForMod : non-null result");
|
||||
return miter->second;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto iter = items_.find(key);
|
||||
if (iter != items_.end())
|
||||
{
|
||||
auto const& item = iter->second;
|
||||
if (item.first == Action::erase)
|
||||
{
|
||||
// The Destination of an Escrow or a PayChannel may have been
|
||||
// deleted. In that case the account we're threading to will
|
||||
// not be found and it is appropriate to return a nullptr.
|
||||
JLOG(j.warn()) << "Trying to thread to deleted node";
|
||||
return nullptr;
|
||||
}
|
||||
if (item.first != Action::cache)
|
||||
return item.second;
|
||||
|
||||
// If it's only cached, then the node is being modified only by
|
||||
// metadata; fall through and track it in the mods table.
|
||||
}
|
||||
}
|
||||
auto c = base.read(keylet::unchecked(key));
|
||||
if (!c)
|
||||
{
|
||||
// The Destination of an Escrow or a PayChannel may have been
|
||||
// deleted. In that case the account we're threading to will
|
||||
// not be found and it is appropriate to return a nullptr.
|
||||
JLOG(j.warn()) << "ApplyStateTable::getForMod: key not found";
|
||||
return nullptr;
|
||||
}
|
||||
auto sle = std::make_shared<SLE>(*c);
|
||||
mods.emplace(key, sle);
|
||||
return sle;
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::threadTx(
|
||||
ReadView const& base,
|
||||
TxMeta& meta,
|
||||
AccountID const& to,
|
||||
Mods& mods,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sle = getForMod(base, keylet::account(to).key, mods, j);
|
||||
if (!sle)
|
||||
{
|
||||
// The Destination of an Escrow or PayChannel may have been deleted.
|
||||
// In that case the account we are threading to will not be found.
|
||||
// So this logging is just a warning.
|
||||
JLOG(j.warn()) << "Threading to non-existent account: " << toBase58(to);
|
||||
return;
|
||||
}
|
||||
// threadItem only applied to AccountRoot
|
||||
XRPL_ASSERT(
|
||||
sle->isThreadedType(base.rules()),
|
||||
"ripple::ApplyStateTable::threadTx : SLE is threaded");
|
||||
threadItem(meta, sle);
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::threadOwners(
|
||||
ReadView const& base,
|
||||
TxMeta& meta,
|
||||
std::shared_ptr<SLE const> const& sle,
|
||||
Mods& mods,
|
||||
beast::Journal j)
|
||||
{
|
||||
LedgerEntryType const ledgerType{sle->getType()};
|
||||
switch (ledgerType)
|
||||
{
|
||||
case ltACCOUNT_ROOT: {
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
case ltRIPPLE_STATE: {
|
||||
threadTx(base, meta, (*sle)[sfLowLimit].getIssuer(), mods, j);
|
||||
threadTx(base, meta, (*sle)[sfHighLimit].getIssuer(), mods, j);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// If sfAccount is present, thread to that account
|
||||
if (auto const optSleAcct{(*sle)[~sfAccount]})
|
||||
threadTx(base, meta, *optSleAcct, mods, j);
|
||||
|
||||
// Don't thread a check's sfDestination unless the amendment is
|
||||
// enabled
|
||||
if (ledgerType == ltCHECK &&
|
||||
!base.rules().enabled(fixCheckThreading))
|
||||
break;
|
||||
|
||||
// If sfDestination is present, thread to that account
|
||||
if (auto const optSleDest{(*sle)[~sfDestination]})
|
||||
threadTx(base, meta, *optSleDest, mods, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,43 +17,98 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/ledger/ApplyViewImpl.h>
|
||||
#ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED
|
||||
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags)
|
||||
: ApplyViewBase(base, flags)
|
||||
{
|
||||
}
|
||||
/** Editable, discardable view that can build metadata for one tx.
|
||||
|
||||
std::optional<TxMeta>
|
||||
ApplyViewImpl::apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<uint256> parentBatchId,
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
|
||||
}
|
||||
Iteration of the tx map is delegated to the base.
|
||||
|
||||
std::size_t
|
||||
ApplyViewImpl::size()
|
||||
@note Presented as ApplyView to clients.
|
||||
*/
|
||||
class ApplyViewImpl final : public detail::ApplyViewBase
|
||||
{
|
||||
return items_.size();
|
||||
}
|
||||
public:
|
||||
ApplyViewImpl() = delete;
|
||||
ApplyViewImpl(ApplyViewImpl const&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl&&) = delete;
|
||||
ApplyViewImpl&
|
||||
operator=(ApplyViewImpl const&) = delete;
|
||||
|
||||
void
|
||||
ApplyViewImpl::visit(
|
||||
OpenView& to,
|
||||
std::function<void(
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)> const& func)
|
||||
{
|
||||
items_.visit(to, func);
|
||||
}
|
||||
ApplyViewImpl(ApplyViewImpl&&) = default;
|
||||
ApplyViewImpl(ReadView const* base, ApplyFlags flags);
|
||||
|
||||
/** Apply the transaction.
|
||||
|
||||
After a call to `apply`, the only valid
|
||||
operation on this object is to call the
|
||||
destructor.
|
||||
*/
|
||||
std::optional<TxMeta>
|
||||
apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<uint256> parentBatchId,
|
||||
bool isDryRun,
|
||||
beast::Journal j);
|
||||
|
||||
/** Set the amount of currency delivered.
|
||||
|
||||
This value is used when generating metadata
|
||||
for payments, to set the DeliveredAmount field.
|
||||
If the amount is not specified, the field is
|
||||
excluded from the resulting metadata.
|
||||
*/
|
||||
void
|
||||
deliver(STAmount const& amount)
|
||||
{
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
size();
|
||||
|
||||
/** Visit modified entries
|
||||
*/
|
||||
void
|
||||
visit(
|
||||
OpenView& target,
|
||||
std::function<void(
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)> const& func);
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -128,6 +128,7 @@ transResults()
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -151,6 +152,8 @@ transResults()
|
||||
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
|
||||
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
|
||||
@@ -56,9 +56,12 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
setParentBatchId(obj.getFieldH256(sfParentBatchID));
|
||||
if (obj.isFieldPresent(sfGasUsed))
|
||||
setGasUsed(obj.getFieldU32(sfGasUsed));
|
||||
if (obj.isFieldPresent(sfWasmReturnCode))
|
||||
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -82,6 +85,12 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
setParentBatchId(obj.getFieldH256(sfParentBatchID));
|
||||
|
||||
if (obj.isFieldPresent(sfGasUsed))
|
||||
setGasUsed(obj.getFieldU32(sfGasUsed));
|
||||
|
||||
if (obj.isFieldPresent(sfWasmReturnCode))
|
||||
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -97,15 +106,11 @@ TxMeta::TxMeta(
|
||||
{
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId)
|
||||
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
|
||||
: mTransactionID(transactionID)
|
||||
, mLedger(ledger)
|
||||
, mIndex(static_cast<std::uint32_t>(-1))
|
||||
, mResult(255)
|
||||
, mParentBatchId(parentBatchId)
|
||||
, mNodes(sfAffectedNodes)
|
||||
{
|
||||
mNodes.reserve(32);
|
||||
@@ -253,9 +258,12 @@ TxMeta::getAsObject() const
|
||||
metaData.emplace_back(mNodes);
|
||||
if (hasDeliveredAmount())
|
||||
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
|
||||
|
||||
if (hasParentBatchId())
|
||||
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
|
||||
if (hasGasUsed())
|
||||
metaData.setFieldU32(sfGasUsed, getGasUsed());
|
||||
if (hasWasmReturnCode())
|
||||
metaData.setFieldI32(sfWasmReturnCode, getWasmReturnCode());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
#include <xrpld/ledger/Dir.h>
|
||||
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -253,14 +255,6 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(sle);
|
||||
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
|
||||
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
|
||||
if (features[fixIncludeKeyletFields])
|
||||
{
|
||||
BEAST_EXPECT((*sle)[sfSequence] == seq);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfSequence));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -302,7 +296,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Implied Finish Time (without fix1571)");
|
||||
|
||||
Env env(*this, testable_amendments() - fix1571);
|
||||
Env env(*this, features - fix1571);
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
env.close();
|
||||
@@ -1559,7 +1553,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
Account const dillon{"dillon "};
|
||||
Account const dillon{"dillon"};
|
||||
Account const zelda{"zelda"};
|
||||
|
||||
char const credType[] = "abcde";
|
||||
@@ -1701,6 +1695,763 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateFinishFunctionPreflight(FeatureBitset features)
|
||||
{
|
||||
testcase("Test preflight checks involving FinishFunction");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
static auto wasmHex = ledgerSqnWasmHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction > max length
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_size_limit = 10; // 10 bytes
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// 11-byte string
|
||||
std::string longWasmHex = "00112233445566778899AA";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(longWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->START_UP = Config::FRESH;
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// Success situations
|
||||
{
|
||||
// FinishFunction + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 20s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 30s),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 40s),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 50s),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Failure situations (i.e. all other combinations)
|
||||
{
|
||||
// only FinishFunction
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction 0 length
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(""),
|
||||
escrow::cancel_time(env.now() + 60s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Not enough fees
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 70s),
|
||||
fee(txnFees - 1),
|
||||
ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction nonexistent host function
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::bad() >= 5 }
|
||||
// }
|
||||
auto const badWasmHex =
|
||||
"0061736d010000000105016000017f02100108686f73745f6c696203626164"
|
||||
"00000302010005030100100611027f00418080c0000b7f00418080c0000b07"
|
||||
"2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64"
|
||||
"03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970"
|
||||
"726f64756365727302086c616e6775616765010452757374000c70726f6365"
|
||||
"737365642d6279010572757374631d312e38352e3120283465623136313235"
|
||||
"3020323032352d30332d31352900490f7461726765745f6665617475726573"
|
||||
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72"
|
||||
"65666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(badWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_WASM));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishWasmFailures(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish Smart Escrow failures");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
static auto wasmHex = ledgerSqnWasmHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(txnFees),
|
||||
escrow::comp_allowance(4),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance > max compute limit
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_compute_limit = 1'000; // in gas
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
auto const allowance = 1'001;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(env.current()->fees().base + allowance),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
// create escrow
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// no ComputationAllowance field
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
ter(tefWASM_FIELD_NOT_INCLUDED));
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance value of 0
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(0),
|
||||
ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough fees
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 3;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(4),
|
||||
ter(telINSUF_FEE_P));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough gas
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 4;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(2),
|
||||
ter(tecFAILED_PROCESSING));
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance field included w/no FinishFunction on
|
||||
// escrow
|
||||
auto const seq2 = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::finish_time(env.now() + 10s),
|
||||
escrow::cancel_time(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const allowance = 100;
|
||||
env(escrow::finish(carol, alice, seq2),
|
||||
fee(env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tefNO_WASM));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishFunction(FeatureBitset features)
|
||||
{
|
||||
testcase("Example escrow function");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
auto const& wasmHex = ledgerSqnWasmHex;
|
||||
std::uint32_t const allowance = 66;
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
auto [createFee, finishFee] = [&]() {
|
||||
Env env(*this, features);
|
||||
auto createFee =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
auto finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
return std::make_pair(createFee, finishFee);
|
||||
}();
|
||||
|
||||
{
|
||||
// basic FinishFunction situation
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
}
|
||||
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto const seq = env.seq(alice);
|
||||
// create escrow
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
auto const conditionFinishFee = finishFee +
|
||||
env.current()->fees().base * (32 + (escrow::fb1.size() / 16));
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// no fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
env.close();
|
||||
// no fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// wrong fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb2),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function succeeds, tx succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 6,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto const ts = env.now() + 97s;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(ts),
|
||||
escrow::cancel_time(env.now() + 1000s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 1),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
// finish time hasn't passed, function succeeds
|
||||
for (; env.now() < ts; env.close())
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 2),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 1),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 13,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter #2
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
// Don't close the ledger here
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// finish time has passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
env.close();
|
||||
// finish time has passed, function succeeds, tx succeeds
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 6,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAllHostFunctions(FeatureBitset features)
|
||||
{
|
||||
testcase("Test all host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// TODO: create wasm module for all host functions
|
||||
static auto wasmHex = allHostFunctionsWasmHex;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 11s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::data("1000000000"), // 1000 XRP in drops
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(
|
||||
env.ownerCount(alice) == (1 + wasmHex.size() / 2 / 500)))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
auto const allowance = 1'000'000;
|
||||
XRPAmount const finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
|
||||
// FinishAfter time hasn't passed
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// reduce the destination balance
|
||||
env(pay(carol, alice, XRP(4500)));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldU32(sfGasUsed) == 38'571,
|
||||
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testKeyletHostFunctions(FeatureBitset features)
|
||||
{
|
||||
testcase("Test all keylet host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// TODO: create wasm module for all host functions
|
||||
static auto wasmHex = allKeyletsWasmHex;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
Env env{*this};
|
||||
env.fund(XRP(10000), alice, carol);
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == 4);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
|
||||
// base objects that need to be created first
|
||||
auto const tokenId =
|
||||
token::getNextID(env, alice, 0, tfTransferable);
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env(trust(alice, carol["USD"](1'000'000)));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == 6);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 2);
|
||||
|
||||
// set up a bunch of objects to check their keylets
|
||||
AMM amm(env, carol, XRP(10), carol["USD"](1000));
|
||||
env(check::create(alice, carol, XRP(100)));
|
||||
env(credentials::create(alice, alice, "termsandconditions"));
|
||||
env(delegate::set(alice, carol, {"TrustSet"}));
|
||||
env(deposit::auth(alice, carol));
|
||||
env(did::set(alice), did::data("alice_did"));
|
||||
env(escrow::create(alice, carol, XRP(100)),
|
||||
escrow::finish_time(env.now() + 100s));
|
||||
MPTTester mptTester{env, alice, {.fund = false}};
|
||||
mptTester.create();
|
||||
mptTester.authorize({.account = carol});
|
||||
env(token::createOffer(carol, tokenId, XRP(100)),
|
||||
token::owner(alice));
|
||||
env(offer(alice, carol["GBP"](0.1), XRP(100)));
|
||||
env(create(alice, carol, XRP(1000), 100s, alice.pk()));
|
||||
pdomain::Credentials credentials{{alice, "first credential"}};
|
||||
env(pdomain::setTx(alice, credentials));
|
||||
env(signers(alice, 1, {{carol, 1}}));
|
||||
env(ticket::create(alice, 1));
|
||||
Vault vault{env};
|
||||
auto [tx, _keylet] =
|
||||
vault.create({.owner = alice, .asset = xrpIssue()});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECTS(
|
||||
env.ownerCount(alice) == 16,
|
||||
std::to_string(env.ownerCount(alice)));
|
||||
if (BEAST_EXPECTS(
|
||||
env.seq(alice) == 20, std::to_string(env.seq(alice))))
|
||||
{
|
||||
auto const seq = env.seq(alice);
|
||||
XRPAmount txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrow::create(alice, carol, XRP(1000)),
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const allowance = 137'596;
|
||||
auto const finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee));
|
||||
env.close();
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
||||
{
|
||||
auto const gasUsed = txMeta->getFieldU32(sfGasUsed);
|
||||
BEAST_EXPECTS(
|
||||
gasUsed == allowance, std::to_string(gasUsed));
|
||||
}
|
||||
BEAST_EXPECTS(
|
||||
env.ownerCount(alice) == 16,
|
||||
std::to_string(env.ownerCount(alice)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -1716,6 +2467,13 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
testConsequences(features);
|
||||
testEscrowWithTickets(features);
|
||||
testCredentials(features);
|
||||
testCreateFinishFunctionPreflight(features);
|
||||
testFinishWasmFailures(features);
|
||||
testFinishFunction(features);
|
||||
|
||||
// TODO: Update module with new host functions
|
||||
testAllHostFunctions(features);
|
||||
testKeyletHostFunctions(features);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -1726,8 +2484,7 @@ public:
|
||||
FeatureBitset const all{testable_amendments()};
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
testTags(all - fixIncludeKeyletFields);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Escrow, app, ripple);
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/app/wasm/HostFunc.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
|
||||
#include <xrpl/ledger/detail/ApplyViewBase.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@ struct Wasm_test : public beast::unit_test::suite
|
||||
Bytes const wasm(wasmStr.begin(), wasmStr.end());
|
||||
TestHostFunctions hfs(env, 0);
|
||||
|
||||
auto const allowance = 148'406;
|
||||
auto const allowance = 121'895;
|
||||
auto re = runEscrowWasm(
|
||||
wasm, ESCROW_FUNCTION_NAME, {}, &hfs, allowance, env.journal);
|
||||
|
||||
@@ -709,7 +709,7 @@ struct Wasm_test : public beast::unit_test::suite
|
||||
testWasmSha();
|
||||
testWasmB58();
|
||||
|
||||
// running too long
|
||||
// runing too long
|
||||
// testWasmSP1Verifier();
|
||||
testWasmBG16Verifier();
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/rate.h>
|
||||
|
||||
@@ -95,14 +94,156 @@ std::array<std::uint8_t, 39> const cb3 = {
|
||||
0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
auto const finish_time = JTxFieldWrapper<timePointField>(sfFinishAfter);
|
||||
struct finish_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit finish_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
auto const cancel_time = JTxFieldWrapper<timePointField>(sfCancelAfter);
|
||||
struct cancel_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
auto const condition = JTxFieldWrapper<blobField>(sfCondition);
|
||||
public:
|
||||
explicit cancel_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
auto const fulfillment = JTxFieldWrapper<blobField>(sfFulfillment);
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
struct condition
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit condition(Slice const& cond) : value_(strHex(cond))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit condition(std::array<std::uint8_t, N> const& c)
|
||||
: condition(makeSlice(c))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct fulfillment
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit fulfillment(Slice condition) : value_(strHex(condition))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit fulfillment(std::array<std::uint8_t, N> f)
|
||||
: fulfillment(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct finish_function
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit finish_function(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit finish_function(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit finish_function(std::array<std::uint8_t, N> const& f)
|
||||
: finish_function(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishFunction.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct data
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit data(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit data(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfData.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct comp_allowance
|
||||
{
|
||||
private:
|
||||
std::uint32_t value_;
|
||||
|
||||
public:
|
||||
explicit comp_allowance(std::uint32_t const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfComputationAllowance.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
|
||||
@@ -33,9 +33,14 @@ setupConfigForUnitTests(Config& cfg)
|
||||
using namespace jtx;
|
||||
// Default fees to old values, so tests don't have to worry about changes in
|
||||
// Config.h
|
||||
// NOTE: For new `FEES` fields, you need to wait for the first flag ledger
|
||||
// to close for the values to be activated.
|
||||
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
||||
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
|
||||
cfg.FEES.extension_compute_limit = 1'000'000;
|
||||
cfg.FEES.extension_size_limit = 100'000;
|
||||
cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.BETA_RPC_API = true;
|
||||
|
||||
@@ -59,6 +59,11 @@ ApplyContext::discard()
|
||||
std::optional<TxMeta>
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
if (wasmReturnCode_.has_value())
|
||||
{
|
||||
view_->setWasmReturnCode(*wasmReturnCode_);
|
||||
}
|
||||
view_->setGasUsed(gasUsed_);
|
||||
return view_->apply(
|
||||
base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/ApplyViewImpl.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyViewImpl.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
@@ -106,6 +106,20 @@ public:
|
||||
view_->deliver(amount);
|
||||
}
|
||||
|
||||
/** Sets the gas used in the metadata */
|
||||
void
|
||||
setGasUsed(std::uint32_t const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
/** Sets the gas used in the metadata */
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
/** Discard changes and start fresh. */
|
||||
void
|
||||
discard();
|
||||
@@ -157,6 +171,8 @@ private:
|
||||
|
||||
// The ID of the batch transaction we are executing under, if seated.
|
||||
std::optional<uint256 const> parentBatchId_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -17,23 +17,26 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/tx/detail/Escrow.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/app/wasm/HostFuncImpl.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
#include <xrpld/conditions/Condition.h>
|
||||
#include <xrpld/conditions/Fulfillment.h>
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <algorithm>
|
||||
namespace ripple {
|
||||
|
||||
// During an EscrowFinish, the transaction must specify both
|
||||
@@ -81,8 +84,8 @@ constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6;
|
||||
TxConsequences
|
||||
EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero};
|
||||
return TxConsequences{
|
||||
ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
@@ -118,9 +121,29 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
|
||||
if (tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
// 10 base fees for the transaction (1 is in
|
||||
// `Transactor::calculateBaseFee`), plus 5 drops per byte
|
||||
txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size();
|
||||
}
|
||||
return txnFees;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
@@ -157,14 +180,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.tx.isFieldPresent(sfCancelAfter))
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.rules.enabled(fix1571))
|
||||
{
|
||||
// In the absence of a FinishAfter, the escrow can be finished
|
||||
// immediately, which can be confusing. When creating an escrow,
|
||||
// we want to ensure that either a FinishAfter time is explicitly
|
||||
// specified or a completion condition is attached.
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
|
||||
!ctx.tx[~sfFinishFunction])
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
|
||||
"Condition, or FinishFunction.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
@@ -189,6 +221,27 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
|
||||
if (code.size() == 0 ||
|
||||
code.size() > ctx.app.config().FEES.extension_size_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "EscrowCreate.FinishFunction bad size " << code.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
HostFunctions mock;
|
||||
auto const re =
|
||||
preflightEscrowWasm(code, ESCROW_FUNCTION_NAME, {}, &mock, ctx.j);
|
||||
if (!isTesSuccess(re))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
|
||||
return re;
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
@@ -451,6 +504,17 @@ escrowLockApplyHelper<MPTIssue>(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static uint32_t
|
||||
calculateAdditionalReserve(T const& finishFunction)
|
||||
{
|
||||
if (!finishFunction)
|
||||
return 1;
|
||||
// First 500 bytes included in the normal reserve
|
||||
// Each additional 500 bytes requires an additional reserve
|
||||
return 1 + (finishFunction->size() / 500);
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::doApply()
|
||||
{
|
||||
@@ -494,9 +558,11 @@ EscrowCreate::doApply()
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
auto const reserveToAdd =
|
||||
calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]);
|
||||
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd);
|
||||
|
||||
if (mSourceBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -537,11 +603,8 @@ EscrowCreate::doApply()
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
}
|
||||
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
|
||||
(*slep)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
{
|
||||
@@ -604,7 +667,8 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
// TODO: determine actual reserve based on FinishFunction size
|
||||
adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -639,6 +703,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
!ctx.rules.enabled(featureCredentials))
|
||||
return temDISABLED;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -648,7 +719,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
// If you specify a condition, then you must also specify
|
||||
// a fulfillment.
|
||||
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// Verify the transaction signature. If it doesn't work
|
||||
// then don't do any more work.
|
||||
@@ -677,6 +751,20 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
if (*allowance == 0)
|
||||
{
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ComputationAllowance too large: " << *allowance;
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
@@ -693,7 +781,14 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
// The extra fee is the allowance in drops, rounded up to the nearest
|
||||
// whole drop.
|
||||
// Integer math rounds down by default, so we add 1 to round up.
|
||||
extraFee +=
|
||||
((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
||||
}
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
@@ -773,25 +868,52 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow) ||
|
||||
ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// this check is done in doApply before this amendment is enabled
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (slep->isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "FinishFunction requires ComputationAllowance";
|
||||
return tefWASM_FIELD_NOT_INCLUDED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "FinishFunction not present, "
|
||||
"ComputationAllowance present";
|
||||
return tefNO_WASM;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(
|
||||
ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -1012,13 +1134,8 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
return rippleUnlockEscrowMPT(
|
||||
view,
|
||||
sender,
|
||||
receiver,
|
||||
finalAmt,
|
||||
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
journal);
|
||||
|
||||
return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -1028,7 +1145,8 @@ EscrowFinish::doApply()
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
|
||||
ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
@@ -1043,11 +1161,17 @@ EscrowFinish::doApply()
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1055,13 +1179,36 @@ EscrowFinish::doApply()
|
||||
if ((*slep)[~sfFinishAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfFinishAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late?
|
||||
if ((*slep)[~sfCancelAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfCancelAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
@@ -1111,18 +1258,65 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
if (!ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute custom release function
|
||||
if ((*slep)[~sfFinishFunction])
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "The escrow has a finish function, running WASM code...";
|
||||
// WASM execution
|
||||
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
|
||||
|
||||
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
// already checked above, this check is just in case
|
||||
return tecINTERNAL;
|
||||
}
|
||||
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
|
||||
auto re = runEscrowWasm(
|
||||
wasm, ESCROW_FUNCTION_NAME, {}, &ledgerDataProvider, allowance);
|
||||
JLOG(j_.trace()) << "Escrow WASM ran";
|
||||
|
||||
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
|
||||
{
|
||||
slep->setFieldVL(sfData, makeSlice(*data));
|
||||
}
|
||||
|
||||
if (re.has_value())
|
||||
{
|
||||
auto reValue = re.value().result;
|
||||
ctx_.setWasmReturnCode(reValue);
|
||||
// TODO: better error handling for this conversion
|
||||
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
|
||||
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
|
||||
<< ", cost: " << re.value().cost;
|
||||
if (reValue <= 0)
|
||||
{
|
||||
return tecWASM_REJECTED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
|
||||
return re.error();
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
@@ -1195,9 +1389,12 @@ EscrowFinish::doApply()
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
auto const reserveToSubtract =
|
||||
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
@@ -1408,7 +1605,9 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
auto const reserveToSubtract =
|
||||
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
|
||||
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/DelegateUtils.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
@@ -25,12 +26,11 @@
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -206,10 +206,7 @@ preflight2(PreflightContext const& ctx)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Transactor::Transactor(ApplyContext& ctx)
|
||||
: ctx_(ctx)
|
||||
, sink_(ctx.journal, to_short_string(ctx.tx.getTransactionID()) + " ")
|
||||
, j_(sink_)
|
||||
, account_(ctx.tx.getAccountID(sfAccount))
|
||||
: ctx_(ctx), j_(ctx.journal), account_(ctx.tx.getAccountID(sfAccount))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -967,6 +964,22 @@ removeExpiredCredentials(
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
modifyWasmDataFields(
|
||||
ApplyView& view,
|
||||
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
|
||||
beast::Journal viewJ)
|
||||
{
|
||||
for (auto const& [index, data] : wasmObjects)
|
||||
{
|
||||
if (auto const sle = view.peek(keylet::escrow(index)))
|
||||
{
|
||||
sle->setFieldVL(sfData, data);
|
||||
view.update(sle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
removeDeletedTrustLines(
|
||||
ApplyView& view,
|
||||
@@ -1124,6 +1137,7 @@ Transactor::operator()()
|
||||
else if (
|
||||
(result == tecOVERSIZE) || (result == tecKILLED) ||
|
||||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
|
||||
(result == tecWASM_REJECTED) ||
|
||||
(isTecClaimHardFail(result, view().flags())))
|
||||
{
|
||||
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
||||
@@ -1136,13 +1150,16 @@ Transactor::operator()()
|
||||
std::vector<uint256> removedTrustLines;
|
||||
std::vector<uint256> expiredNFTokenOffers;
|
||||
std::vector<uint256> expiredCredentials;
|
||||
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
|
||||
|
||||
bool const doOffers =
|
||||
((result == tecOVERSIZE) || (result == tecKILLED));
|
||||
bool const doLines = (result == tecINCOMPLETE);
|
||||
bool const doNFTokenOffers = (result == tecEXPIRED);
|
||||
bool const doCredentials = (result == tecEXPIRED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials)
|
||||
bool const doWasmData = (result == tecWASM_REJECTED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
|
||||
doWasmData)
|
||||
{
|
||||
ctx_.visit([doOffers,
|
||||
&removedOffers,
|
||||
@@ -1151,7 +1168,9 @@ Transactor::operator()()
|
||||
doNFTokenOffers,
|
||||
&expiredNFTokenOffers,
|
||||
doCredentials,
|
||||
&expiredCredentials](
|
||||
&expiredCredentials,
|
||||
doWasmData,
|
||||
&modifiedWasmObjects](
|
||||
uint256 const& index,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
@@ -1186,6 +1205,13 @@ Transactor::operator()()
|
||||
(before->getType() == ltCREDENTIAL))
|
||||
expiredCredentials.push_back(index);
|
||||
}
|
||||
|
||||
if (doWasmData && before && after &&
|
||||
(before->getType() == ltESCROW))
|
||||
{
|
||||
modifiedWasmObjects.push_back(
|
||||
std::make_pair(index, after->getFieldVL(sfData)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1215,6 +1241,10 @@ Transactor::operator()()
|
||||
removeExpiredCredentials(
|
||||
view(), expiredCredentials, ctx_.app.journal("View"));
|
||||
|
||||
if (result == tecWASM_REJECTED)
|
||||
modifyWasmDataFields(
|
||||
view(), modifiedWasmObjects, ctx_.app.journal("View"));
|
||||
|
||||
applied = isTecClaim(result);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user