mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 11:37:07 +00:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
025aa71930 | ||
|
|
ef5064eeb9 | ||
|
|
c2e739f670 | ||
|
|
15fac6250d | ||
|
|
3608433514 | ||
|
|
7939339a65 | ||
|
|
4ea2db2eda | ||
|
|
d4ffe32534 | ||
|
|
7354816f65 | ||
|
|
12cb3932f3 | ||
|
|
dc64a8daac | ||
|
|
dc524e9cc6 | ||
|
|
4060b1bcd4 | ||
|
|
bfbb440fc2 |
No files matched your search
@@ -318,7 +318,6 @@ words:
|
||||
- sttx
|
||||
- stvar
|
||||
- stvector
|
||||
- stxchainattestations
|
||||
- summands
|
||||
- superpeer
|
||||
- superpeers
|
||||
@@ -377,7 +376,6 @@ words:
|
||||
- writeme
|
||||
- wsrch
|
||||
- wthread
|
||||
- xbridge
|
||||
- xchain
|
||||
- xcrun
|
||||
- ximinez
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
- should-run
|
||||
- build-test
|
||||
# Only run when committing to a PR that targets a release branch.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release/') }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
# This workflow runs all workflows to build and test the code on various Linux
|
||||
# flavors, as well as on MacOS and Windows, on a scheduled basis, on merge into
|
||||
# the 'develop' or 'release*' branches, or when requested manually. Upon pushes
|
||||
# to the develop branch it also uploads the libxrpl recipe to the Conan remote.
|
||||
# the 'develop', 'release/*' or 'staging/*' branches, or when requested
|
||||
# manually. Upon pushes to the develop branch, it also uploads the libxrpl
|
||||
# recipe to the Conan remote.
|
||||
name: Trigger
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
paths:
|
||||
# These paths are unique to `on-trigger.yml`.
|
||||
- ".github/workflows/on-trigger.yml"
|
||||
@@ -62,9 +64,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
# When a PR is merged into the develop branch it will be assigned a unique
|
||||
# When a PR is merged into the develop branch, it will be assigned a unique
|
||||
# group identifier, so execution will continue even if another PR is merged
|
||||
# while it is still running. In all other cases the group identifier is shared
|
||||
# while it is still running. In all other cases, the group identifier is shared
|
||||
# per branch, so that any in-progress runs are cancelled when a new commit is
|
||||
# pushed.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
@@ -98,7 +100,7 @@ jobs:
|
||||
# However, we do not enable ccache for events targeting a release branch,
|
||||
# to protect against the rare case that the output produced by ccache is
|
||||
# not identical to a regular compilation.
|
||||
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }}
|
||||
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release/') }}
|
||||
os: ${{ matrix.os }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -8,7 +8,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release*"
|
||||
- "release/*"
|
||||
- "staging/*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -26,6 +26,10 @@ This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-R
|
||||
|
||||
Version 3.5.0 is not yet released.
|
||||
|
||||
### Breaking changes in 3.5.0
|
||||
|
||||
- The `XChainBridge` amendment and its ledger entries and transactions are removed. `XChainBridge` and `fixXChainRewardRounding` are now obsolete and can no longer be voted for; neither was ever enabled on Mainnet. `ledger_entry` no longer accepts `bridge`, `xchain_owned_claim_id`, or `xchain_owned_create_account_claim_id`; `account_objects` no longer accepts those values in `type`; and `server_definitions` no longer lists the removed ledger entry types, transaction types, fields, and the `XChainBridge` serialized type.
|
||||
|
||||
### Additions in 3.5.0
|
||||
|
||||
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive the same `transaction` message as the `transactions` stream for each validated transaction whose metadata affects a subscribed issuance. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
|
||||
@@ -37,6 +41,9 @@ Version 3.5.0 is not yet released.
|
||||
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
- `account_currencies`, `account_nfts`, `account_offers`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before an invalid or missing `account`. An invalid `limit` or `marker` is reported before `lgrNotFound` or `actNotFound`, and for `account_offers` also before `actMalformed`. A request with a single problem returns the same error as before. [#8561](https://github.com/XRPLF/rippled/pull/8561)
|
||||
- `deposit_authorized`: A `credentials` entry that is all zeros (for example `"0"` or 64 `0` characters) now returns a `badCredentials` error instead of looking up a credential that can never exist.
|
||||
- `account_info`, `account_lines`, `account_channels`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before a missing or invalid `account`, or `ident` for `account_info`. For `account_lines`, a `peer` that is not a string, or an invalid `limit` or `marker`, is reported before `lgrNotFound`, `actNotFound` or a malformed `account`, and an invalid `limit` or `marker` also before a malformed `peer`. For `account_channels`, a malformed `account` is reported before `lgrNotFound`, and an invalid `destination_account`, `limit` or `marker` before `lgrNotFound` or `actNotFound`. A request with a single problem returns the same error as before. [#8573](https://github.com/XRPLF/rippled/pull/8573)
|
||||
- `deposit_authorized`, `nft_buy_offers`, `nft_sell_offers`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before a missing or invalid `source_account`, `destination_account` or `nft_id`, and for `nft_buy_offers` / `nft_sell_offers` also before an invalid `limit`. For `deposit_authorized`, an invalid `credentials` array is reported before `lgrNotFound`, `srcActNotFound`, `dstActNotFound` or `badCredentials`. For `nft_buy_offers` and `nft_sell_offers`, an invalid `marker` is reported before `lgrNotFound` or `objectNotFound`. A request with a single problem returns the same error as before. [#8613](https://github.com/XRPLF/rippled/pull/8613)
|
||||
- `nft_buy_offers`, `nft_sell_offers`: An `nft_id` that is an array or an object now returns an `invalidParams` error instead of an `internal` error. [#8613](https://github.com/XRPLF/rippled/pull/8613)
|
||||
|
||||
### Bugfixes in 3.5.0
|
||||
|
||||
|
||||
@@ -297,11 +297,16 @@ if(xrpld)
|
||||
VALUE_TYPE "::json::Value"
|
||||
VIEW_HEADER "xrpld/rpc/detail/JsonObjectView.hpp"
|
||||
HANDLERS
|
||||
account_channels
|
||||
account_currencies
|
||||
account_info
|
||||
account_lines
|
||||
account_nfts
|
||||
account_offers
|
||||
book_changes
|
||||
deposit_authorized
|
||||
ledger
|
||||
nft_offers_common
|
||||
transaction_entry
|
||||
)
|
||||
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
|
||||
|
||||
+2
-2
@@ -3,11 +3,11 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
|
||||
"xrpl-rpc-spec/0.1.23#56e8946f5b3fb949a3732e1a51135ca2%1790977482.197086",
|
||||
"xrpl-rpc-spec/0.1.29#852afc87cac32aca93cc50a985d73fe0%1792359643.19817",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
|
||||
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
|
||||
"secp256k1/0.7.1#b1f450b7f78a36fff75bb6934a356f3a%1782338841.3729",
|
||||
"secp256k1/0.7.1#f4d16a4d907dd057598954476ca6f88c%1792234329.790275",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.3",
|
||||
"soci/4.0.3",
|
||||
"xrpl-rpc-spec/0.1.23",
|
||||
"xrpl-rpc-spec/0.1.29",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
|
||||
@@ -352,9 +352,9 @@ class Number final
|
||||
using rep = std::int64_t;
|
||||
using InternalRep = MantissaRange::rep;
|
||||
|
||||
bool negative_{false};
|
||||
InternalRep mantissa_{0};
|
||||
int exponent_{std::numeric_limits<int>::lowest()};
|
||||
bool negative_{false};
|
||||
|
||||
public:
|
||||
// The range for the exponent when normalized
|
||||
@@ -650,8 +650,12 @@ private:
|
||||
shiftExponent(int exponentDelta) const;
|
||||
};
|
||||
|
||||
// The number class currently has 3 tail padding bytes. If you are adding new
|
||||
// members, try to fit them there before expanding the size of the class.
|
||||
static_assert(sizeof(Number) == 16);
|
||||
|
||||
constexpr Number::Number(bool negative, InternalRep mantissa, int exponent, Unchecked) noexcept
|
||||
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
|
||||
: mantissa_{mantissa}, exponent_{exponent}, negative_(negative)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename ViewT>
|
||||
class BridgeEntry : public SLEBase<ViewT, ltBRIDGE>
|
||||
{
|
||||
public:
|
||||
using Base = SLEBase<ViewT, ltBRIDGE>;
|
||||
|
||||
// Inherit base constructors: adopt an existing SLE, or resolve one from a
|
||||
// Keylet against the view.
|
||||
using Base::Base;
|
||||
|
||||
explicit BridgeEntry(
|
||||
STXChainBridge const& bridge,
|
||||
STXChainBridge::ChainType chainType,
|
||||
Base::ViewRefType view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: Base(keylet::bridge(bridge, chainType), view, j)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using BridgeEntryR = BridgeEntry<ReadView>;
|
||||
using BridgeEntryW = BridgeEntry<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename ViewT>
|
||||
class XChainOwnedClaimIDEntry : public SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>
|
||||
{
|
||||
public:
|
||||
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CLAIM_ID>;
|
||||
|
||||
// Inherit base constructors: adopt an existing SLE, or resolve one from a
|
||||
// Keylet against the view.
|
||||
using Base::Base;
|
||||
|
||||
explicit XChainOwnedClaimIDEntry(
|
||||
STXChainBridge const& bridge,
|
||||
std::uint64_t seq,
|
||||
Base::ViewRefType view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: Base(keylet::xChainClaimID(bridge, seq), view, j)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using XChainOwnedClaimIDEntryR = XChainOwnedClaimIDEntry<ReadView>;
|
||||
using XChainOwnedClaimIDEntryW = XChainOwnedClaimIDEntry<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename ViewT>
|
||||
class XChainOwnedCreateAccountClaimIDEntry
|
||||
: public SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>
|
||||
{
|
||||
public:
|
||||
using Base = SLEBase<ViewT, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID>;
|
||||
|
||||
// Inherit base constructors: adopt an existing SLE, or resolve one from a
|
||||
// Keylet against the view.
|
||||
using Base::Base;
|
||||
|
||||
explicit XChainOwnedCreateAccountClaimIDEntry(
|
||||
STXChainBridge const& bridge,
|
||||
std::uint64_t seq,
|
||||
Base::ViewRefType view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: Base(keylet::xChainCreateAccountClaimID(bridge, seq), view, j)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using XChainOwnedCreateAccountClaimIDEntryR = XChainOwnedCreateAccountClaimIDEntry<ReadView>;
|
||||
using XChainOwnedCreateAccountClaimIDEntryW = XChainOwnedCreateAccountClaimIDEntry<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
@@ -27,6 +28,8 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
@@ -560,4 +563,54 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
journal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart escrow kill switches, driven by the voted FeeSettings.
|
||||
*
|
||||
* Zeroing `bytecodeSizeLimit` stops new uploads while leaving existing escrows
|
||||
* finishable without forcing holders to wait for `CancelAfter`.
|
||||
* Zeroing `gasLimit` stops both.
|
||||
*/
|
||||
/** @{ */
|
||||
inline bool
|
||||
isBytecodeUploadDisabled(Fees const& fees)
|
||||
{
|
||||
return fees.bytecodeSizeLimit == 0 || fees.gasLimit == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
isBytecodeExecutionDisabled(Fees const& fees)
|
||||
{
|
||||
return fees.gasLimit == 0;
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Owner count an escrow costs.
|
||||
*
|
||||
* An escrow costs one increment per started 500 bytes of bytecode, and one
|
||||
* increment if it carries no bytecode.
|
||||
*/
|
||||
template <class T>
|
||||
int32_t
|
||||
calculateAdditionalReserve(T const& finishFunction)
|
||||
{
|
||||
static auto constexpr kBytecodeReserveIncrement = 500;
|
||||
|
||||
if (!finishFunction)
|
||||
return 1;
|
||||
|
||||
auto const size = finishFunction->size();
|
||||
if (size == 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
// Empty Bytecode is refused at create, so no escrow carries it. Ceiling
|
||||
// division would answer 0, which would subtract less than the create added.
|
||||
UNREACHABLE("xrpl::calculateAdditionalReserve : empty Bytecode");
|
||||
return 1;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return static_cast<int32_t>((size + kBytecodeReserveIncrement - 1) / kBytecodeReserveIncrement);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -142,11 +142,11 @@ deleteProposal(ApplyView& view, SLE::pointer const& sleProposal, beast::Journal
|
||||
* off-ledger signatures it carries, and any change to a non-signature field
|
||||
* results in a false-return value.
|
||||
*
|
||||
* Signature (PaymentChannelClaim and the XChain attestation transactions)
|
||||
* is deliberately not compared: it is outside the account's own TxnSignature
|
||||
* even for an ordinary submission, so a proposal cannot bind it more tightly
|
||||
* than the signatures it collects do, and those transactors still validate a
|
||||
* substituted value exactly as they would for an ordinary submission.
|
||||
* Signature (PaymentChannelClaim) is deliberately not compared: it is
|
||||
* outside the account's own TxnSignature even for an ordinary submission, so
|
||||
* a proposal cannot bind it more tightly than the signatures it collects do,
|
||||
* and that transactor still validates a substituted value exactly as it would
|
||||
* for an ordinary submission.
|
||||
* MasterSignature appears in no transaction format.
|
||||
*
|
||||
* @param proposedTx The proposal's ProposedTransaction field.
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
@@ -321,17 +320,6 @@ amm(UInt256 const& amm) noexcept;
|
||||
Keylet
|
||||
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 const seq);
|
||||
|
||||
// `seq` is stored as `sfXChainAccountCreateCount` in the object
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq);
|
||||
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept;
|
||||
|
||||
|
||||
@@ -103,6 +103,32 @@ enum LedgerEntryType : std::uint16_t {
|
||||
*/
|
||||
ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] =
|
||||
0x0067,
|
||||
|
||||
/**
|
||||
* A deprecated type, removed with the XChainBridge amendment.
|
||||
*
|
||||
* @deprecated **This object type is not supported and should not be used.**
|
||||
* The XChainBridge amendment was never enabled on Mainnet.
|
||||
*/
|
||||
ltBRIDGE [[deprecated("This object type is not supported and should not be used.")]] = 0x0069,
|
||||
|
||||
/**
|
||||
* A deprecated type, removed with the XChainBridge amendment.
|
||||
*
|
||||
* @deprecated **This object type is not supported and should not be used.**
|
||||
* The XChainBridge amendment was never enabled on Mainnet.
|
||||
*/
|
||||
ltXCHAIN_OWNED_CLAIM_ID
|
||||
[[deprecated("This object type is not supported and should not be used.")]] = 0x0071,
|
||||
|
||||
/**
|
||||
* A deprecated type, removed with the XChainBridge amendment.
|
||||
*
|
||||
* @deprecated **This object type is not supported and should not be used.**
|
||||
* The XChainBridge amendment was never enabled on Mainnet.
|
||||
*/
|
||||
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID
|
||||
[[deprecated("This object type is not supported and should not be used.")]] = 0x0074,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -398,6 +398,11 @@ constexpr std::uint32_t kMaxInvestmentPeriod = std::chrono::seconds{std::chrono:
|
||||
*/
|
||||
constexpr std::uint8_t kMaxAssetCheckDepth = 5;
|
||||
|
||||
/**
|
||||
* Maximum length of a Data field in Escrow object that can be updated by WASM code.
|
||||
*/
|
||||
constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB
|
||||
|
||||
/**
|
||||
* A ledger index.
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,6 @@ class STBitString;
|
||||
template <class>
|
||||
class STInteger;
|
||||
class STNumber;
|
||||
class STXChainBridge;
|
||||
class STVector256;
|
||||
class STCurrency;
|
||||
|
||||
@@ -71,7 +70,7 @@ class STCurrency;
|
||||
STYPE(STI_UINT384, 22) \
|
||||
STYPE(STI_UINT512, 23) \
|
||||
STYPE(STI_ISSUE, 24) \
|
||||
STYPE(STI_XCHAIN_BRIDGE, 25) \
|
||||
/* 25 is reserved */ \
|
||||
STYPE(STI_CURRENCY, 26) \
|
||||
\
|
||||
/* high-level types */ \
|
||||
@@ -361,7 +360,6 @@ using SF_CURRENCY = TypedField<STCurrency>;
|
||||
using SF_NUMBER = TypedField<STNumber>;
|
||||
using SF_VL = TypedField<STBlob>;
|
||||
using SF_VECTOR256 = TypedField<STVector256>;
|
||||
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/SOTemplate.h>
|
||||
#include <xrpl/protocol/STAccount.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class Serializer;
|
||||
class STObject;
|
||||
|
||||
class STXChainBridge final : public STBase, public CountedObject<STXChainBridge>
|
||||
{
|
||||
STAccount lockingChainDoor_{sfLockingChainDoor};
|
||||
STIssue lockingChainIssue_{sfLockingChainIssue};
|
||||
STAccount issuingChainDoor_{sfIssuingChainDoor};
|
||||
STIssue issuingChainIssue_{sfIssuingChainIssue};
|
||||
|
||||
public:
|
||||
using value_type = STXChainBridge;
|
||||
|
||||
enum class ChainType { Locking, Issuing };
|
||||
|
||||
static ChainType
|
||||
otherChain(ChainType ct);
|
||||
|
||||
static ChainType
|
||||
srcChain(bool wasLockingChainSend);
|
||||
|
||||
static ChainType
|
||||
dstChain(bool wasLockingChainSend);
|
||||
|
||||
STXChainBridge();
|
||||
|
||||
explicit STXChainBridge(SField const& name);
|
||||
|
||||
STXChainBridge(STXChainBridge const& rhs) = default;
|
||||
|
||||
STXChainBridge(STObject const& o);
|
||||
|
||||
STXChainBridge(
|
||||
AccountID const& srcChainDoor,
|
||||
Issue const& srcChainIssue,
|
||||
AccountID const& dstChainDoor,
|
||||
Issue const& dstChainIssue);
|
||||
|
||||
explicit STXChainBridge(json::Value const& v);
|
||||
|
||||
explicit STXChainBridge(SField const& name, json::Value const& v);
|
||||
|
||||
explicit STXChainBridge(SerialIter& sit, SField const& name);
|
||||
|
||||
STXChainBridge&
|
||||
operator=(STXChainBridge const& rhs) = default;
|
||||
|
||||
[[nodiscard]] std::string
|
||||
getText() const override;
|
||||
|
||||
[[nodiscard]] STObject
|
||||
toSTObject() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
lockingChainDoor() const;
|
||||
|
||||
[[nodiscard]] Issue const&
|
||||
lockingChainIssue() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
issuingChainDoor() const;
|
||||
|
||||
[[nodiscard]] Issue const&
|
||||
issuingChainIssue() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
door(ChainType ct) const;
|
||||
|
||||
[[nodiscard]] Issue const&
|
||||
issue(ChainType ct) const;
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
getSType() const override;
|
||||
|
||||
[[nodiscard]] json::Value getJson(JsonOptions) const override;
|
||||
|
||||
void
|
||||
add(Serializer& s) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isEquivalent(STBase const& t) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDefault() const override;
|
||||
|
||||
[[nodiscard]] value_type const&
|
||||
value() const noexcept;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<STXChainBridge>
|
||||
construct(SerialIter&, SField const& name);
|
||||
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
STBase*
|
||||
move(std::size_t n, void* buf) override;
|
||||
|
||||
friend bool
|
||||
operator==(STXChainBridge const& lhs, STXChainBridge const& rhs);
|
||||
|
||||
friend bool
|
||||
operator<(STXChainBridge const& lhs, STXChainBridge const& rhs);
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator==(STXChainBridge const& lhs, STXChainBridge const& rhs)
|
||||
{
|
||||
return std::tie(
|
||||
lhs.lockingChainDoor_,
|
||||
lhs.lockingChainIssue_,
|
||||
lhs.issuingChainDoor_,
|
||||
lhs.issuingChainIssue_) ==
|
||||
std::tie(
|
||||
rhs.lockingChainDoor_,
|
||||
rhs.lockingChainIssue_,
|
||||
rhs.issuingChainDoor_,
|
||||
rhs.issuingChainIssue_);
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<(STXChainBridge const& lhs, STXChainBridge const& rhs)
|
||||
{
|
||||
return std::tie(
|
||||
lhs.lockingChainDoor_,
|
||||
lhs.lockingChainIssue_,
|
||||
lhs.issuingChainDoor_,
|
||||
lhs.issuingChainIssue_) <
|
||||
std::tie(
|
||||
rhs.lockingChainDoor_,
|
||||
rhs.lockingChainIssue_,
|
||||
rhs.issuingChainDoor_,
|
||||
rhs.issuingChainIssue_);
|
||||
}
|
||||
|
||||
inline AccountID const&
|
||||
STXChainBridge::lockingChainDoor() const
|
||||
{
|
||||
return lockingChainDoor_.value();
|
||||
};
|
||||
|
||||
inline Issue const&
|
||||
STXChainBridge::lockingChainIssue() const
|
||||
{
|
||||
return lockingChainIssue_.value().get<Issue>();
|
||||
};
|
||||
|
||||
inline AccountID const&
|
||||
STXChainBridge::issuingChainDoor() const
|
||||
{
|
||||
return issuingChainDoor_.value();
|
||||
};
|
||||
|
||||
inline Issue const&
|
||||
STXChainBridge::issuingChainIssue() const
|
||||
{
|
||||
return issuingChainIssue_.value().get<Issue>();
|
||||
};
|
||||
|
||||
inline STXChainBridge::value_type const&
|
||||
STXChainBridge::value() const noexcept
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline AccountID const&
|
||||
STXChainBridge::door(ChainType ct) const
|
||||
{
|
||||
if (ct == ChainType::Locking)
|
||||
return lockingChainDoor();
|
||||
return issuingChainDoor();
|
||||
}
|
||||
|
||||
inline Issue const&
|
||||
STXChainBridge::issue(ChainType ct) const
|
||||
{
|
||||
if (ct == ChainType::Locking)
|
||||
return lockingChainIssue();
|
||||
return issuingChainIssue();
|
||||
}
|
||||
|
||||
inline STXChainBridge::ChainType
|
||||
STXChainBridge::otherChain(ChainType ct)
|
||||
{
|
||||
if (ct == ChainType::Locking)
|
||||
return ChainType::Issuing;
|
||||
return ChainType::Locking;
|
||||
}
|
||||
|
||||
inline STXChainBridge::ChainType
|
||||
STXChainBridge::srcChain(bool wasLockingChainSend)
|
||||
{
|
||||
if (wasLockingChainSend)
|
||||
return ChainType::Locking;
|
||||
return ChainType::Issuing;
|
||||
}
|
||||
|
||||
inline STXChainBridge::ChainType
|
||||
STXChainBridge::dstChain(bool wasLockingChainSend)
|
||||
{
|
||||
if (wasLockingChainSend)
|
||||
return ChainType::Issuing;
|
||||
return ChainType::Locking;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -133,7 +133,6 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temBAD_MPT,
|
||||
temBAD_CIPHERTEXT,
|
||||
temINVALID_BYTECODE,
|
||||
temTEMP_DISABLED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -184,6 +183,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefBAD_PATH_COUNT,
|
||||
tefNO_BYTECODE,
|
||||
tefBYTECODE_NOT_INCLUDED,
|
||||
tefTEMP_DISABLED,
|
||||
tefEXCEEDS_LIMIT,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -189,10 +189,6 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfClawTwoAssets, 0x00000001), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(XChainModifyBridge, \
|
||||
TF_FLAG(tfClearAccountCreateAmount, 0x00010000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(VaultCreate, \
|
||||
TF_FLAG(tfVaultPrivate, lsfVaultPrivate) \
|
||||
TF_FLAG(tfVaultShareNonTransferable, 0x00020000), \
|
||||
|
||||
@@ -73,6 +73,54 @@ enum TxType : std::uint16_t
|
||||
* This transaction type installs a hook.
|
||||
*/
|
||||
TtHookSet [[maybe_unused]] = 22,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainCreateClaimID [[deprecated("This transaction type is not supported and should not be used.")]] = 41,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainCommit [[deprecated("This transaction type is not supported and should not be used.")]] = 42,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainClaim [[deprecated("This transaction type is not supported and should not be used.")]] = 43,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainAccountCreateCommit [[deprecated("This transaction type is not supported and should not be used.")]] = 44,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainAddClaimAttestation [[deprecated("This transaction type is not supported and should not be used.")]] = 45,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainAddAccountCreateAttestation [[deprecated("This transaction type is not supported and should not be used.")]] = 46,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainModifyBridge [[deprecated("This transaction type is not supported and should not be used.")]] = 47,
|
||||
|
||||
/**
|
||||
* This transaction type was removed with the XChainBridge amendment, which was never enabled
|
||||
* on Mainnet. The value is reserved for historical purposes.
|
||||
*/
|
||||
TtXChainCreateBridge [[deprecated("This transaction type is not supported and should not be used.")]] = 48,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace attestations {
|
||||
|
||||
struct AttestationBase
|
||||
{
|
||||
// Account associated with the public key
|
||||
AccountID attestationSignerAccount;
|
||||
// Public key from the witness server attesting to the event
|
||||
PublicKey publicKey;
|
||||
// Signature from the witness server attesting to the event
|
||||
Buffer signature;
|
||||
// Account on the sending chain that triggered the event (sent the
|
||||
// transaction)
|
||||
AccountID sendingAccount;
|
||||
// Amount transferred on the sending chain
|
||||
STAmount sendingAmount;
|
||||
// Account on the destination chain that collects a share of the attestation
|
||||
// reward
|
||||
AccountID rewardAccount;
|
||||
// Amount was transferred on the locking chain
|
||||
bool wasLockingChainSend;
|
||||
|
||||
explicit AttestationBase(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend);
|
||||
|
||||
AttestationBase(AttestationBase const&) = default;
|
||||
|
||||
virtual ~AttestationBase() = default;
|
||||
|
||||
AttestationBase&
|
||||
operator=(AttestationBase const&) = default;
|
||||
|
||||
// verify that the signature attests to the data.
|
||||
[[nodiscard]] bool
|
||||
verify(STXChainBridge const& bridge) const;
|
||||
|
||||
protected:
|
||||
explicit AttestationBase(STObject const& o);
|
||||
explicit AttestationBase(json::Value const& v);
|
||||
|
||||
[[nodiscard]] static bool
|
||||
equalHelper(AttestationBase const& lhs, AttestationBase const& rhs);
|
||||
|
||||
[[nodiscard]] static bool
|
||||
sameEventHelper(AttestationBase const& lhs, AttestationBase const& rhs);
|
||||
|
||||
void
|
||||
addHelper(STObject& o) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] virtual std::vector<std::uint8_t>
|
||||
message(STXChainBridge const& bridge) const = 0;
|
||||
};
|
||||
|
||||
// Attest to a regular cross-chain transfer
|
||||
struct AttestationClaim : AttestationBase
|
||||
{
|
||||
std::uint64_t claimID;
|
||||
std::optional<AccountID> dst;
|
||||
|
||||
explicit AttestationClaim(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimId,
|
||||
std::optional<AccountID> const& dst);
|
||||
|
||||
explicit AttestationClaim(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimId,
|
||||
std::optional<AccountID> const& dst);
|
||||
|
||||
explicit AttestationClaim(STObject const& o);
|
||||
explicit AttestationClaim(json::Value const& v);
|
||||
|
||||
[[nodiscard]] STObject
|
||||
toSTObject() const;
|
||||
|
||||
// return true if the two attestations attest to the same thing
|
||||
[[nodiscard]] bool
|
||||
sameEvent(AttestationClaim const& rhs) const;
|
||||
|
||||
[[nodiscard]] static std::vector<std::uint8_t>
|
||||
message(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimID,
|
||||
std::optional<AccountID> const& dst);
|
||||
|
||||
[[nodiscard]] bool
|
||||
validAmounts() const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<std::uint8_t>
|
||||
message(STXChainBridge const& bridge) const override;
|
||||
|
||||
friend bool
|
||||
operator==(AttestationClaim const& lhs, AttestationClaim const& rhs);
|
||||
};
|
||||
|
||||
struct CmpByClaimID
|
||||
{
|
||||
bool
|
||||
operator()(AttestationClaim const& lhs, AttestationClaim const& rhs) const
|
||||
{
|
||||
return lhs.claimID < rhs.claimID;
|
||||
}
|
||||
};
|
||||
|
||||
// Attest to a cross-chain transfer that creates an account
|
||||
struct AttestationCreateAccount : AttestationBase
|
||||
{
|
||||
// createCount on the sending chain. This is the value of the `CreateCount`
|
||||
// field of the bridge on the sending chain when the transaction was
|
||||
// executed.
|
||||
std::uint64_t createCount;
|
||||
// Account to create on the destination chain
|
||||
AccountID toCreate;
|
||||
// Total amount of the reward pool
|
||||
STAmount rewardAmount;
|
||||
|
||||
explicit AttestationCreateAccount(STObject const& o);
|
||||
|
||||
explicit AttestationCreateAccount(json::Value const& v);
|
||||
|
||||
explicit AttestationCreateAccount(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& toCreate);
|
||||
|
||||
explicit AttestationCreateAccount(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& toCreate);
|
||||
|
||||
[[nodiscard]] STObject
|
||||
toSTObject() const;
|
||||
|
||||
// return true if the two attestations attest to the same thing
|
||||
[[nodiscard]] bool
|
||||
sameEvent(AttestationCreateAccount const& rhs) const;
|
||||
|
||||
friend bool
|
||||
operator==(AttestationCreateAccount const& lhs, AttestationCreateAccount const& rhs);
|
||||
|
||||
[[nodiscard]] static std::vector<std::uint8_t>
|
||||
message(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& dst);
|
||||
|
||||
[[nodiscard]] bool
|
||||
validAmounts() const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<std::uint8_t>
|
||||
message(STXChainBridge const& bridge) const override;
|
||||
};
|
||||
|
||||
struct CmpByCreateCount
|
||||
{
|
||||
bool
|
||||
operator()(AttestationCreateAccount const& lhs, AttestationCreateAccount const& rhs) const
|
||||
{
|
||||
return lhs.createCount < rhs.createCount;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace attestations
|
||||
|
||||
// Result when checking when two attestation match.
|
||||
enum class AttestationMatch {
|
||||
// One of the fields doesn't match, and it isn't the dst field
|
||||
NonDstMismatch,
|
||||
// all of the fields match, except the dst field
|
||||
MatchExceptDst,
|
||||
// all of the fields match
|
||||
Match,
|
||||
};
|
||||
|
||||
struct XChainClaimAttestation
|
||||
{
|
||||
using TSignedAttestation = attestations::AttestationClaim;
|
||||
static SField const& arrayFieldName;
|
||||
|
||||
AccountID keyAccount;
|
||||
PublicKey publicKey;
|
||||
STAmount amount;
|
||||
AccountID rewardAccount;
|
||||
bool wasLockingChainSend;
|
||||
std::optional<AccountID> dst;
|
||||
|
||||
struct MatchFields
|
||||
{
|
||||
STAmount amount;
|
||||
bool wasLockingChainSend;
|
||||
std::optional<AccountID> dst;
|
||||
MatchFields(TSignedAttestation const& att);
|
||||
MatchFields(STAmount a, bool b, std::optional<AccountID> const& d)
|
||||
: amount{std::move(a)}, wasLockingChainSend{b}, dst{d}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
explicit XChainClaimAttestation(
|
||||
AccountID const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::optional<AccountID> const& dst);
|
||||
|
||||
explicit XChainClaimAttestation(
|
||||
STAccount const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
STAccount const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::optional<STAccount> const& dst);
|
||||
|
||||
explicit XChainClaimAttestation(TSignedAttestation const& claimAtt);
|
||||
|
||||
explicit XChainClaimAttestation(STObject const& o);
|
||||
|
||||
explicit XChainClaimAttestation(json::Value const& v);
|
||||
|
||||
[[nodiscard]] AttestationMatch
|
||||
match(MatchFields const& rhs) const;
|
||||
|
||||
[[nodiscard]] STObject
|
||||
toSTObject() const;
|
||||
|
||||
friend bool
|
||||
operator==(XChainClaimAttestation const& lhs, XChainClaimAttestation const& rhs);
|
||||
};
|
||||
|
||||
struct XChainCreateAccountAttestation
|
||||
{
|
||||
using TSignedAttestation = attestations::AttestationCreateAccount;
|
||||
static SField const& arrayFieldName;
|
||||
|
||||
AccountID keyAccount;
|
||||
PublicKey publicKey;
|
||||
STAmount amount;
|
||||
STAmount rewardAmount;
|
||||
AccountID rewardAccount;
|
||||
bool wasLockingChainSend;
|
||||
AccountID dst;
|
||||
|
||||
struct MatchFields
|
||||
{
|
||||
STAmount amount;
|
||||
STAmount rewardAmount;
|
||||
bool wasLockingChainSend;
|
||||
AccountID dst;
|
||||
|
||||
MatchFields(TSignedAttestation const& att);
|
||||
};
|
||||
|
||||
explicit XChainCreateAccountAttestation(
|
||||
AccountID const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
AccountID const& dst);
|
||||
|
||||
explicit XChainCreateAccountAttestation(TSignedAttestation const& claimAtt);
|
||||
|
||||
explicit XChainCreateAccountAttestation(STObject const& o);
|
||||
|
||||
explicit XChainCreateAccountAttestation(json::Value const& v);
|
||||
|
||||
[[nodiscard]] STObject
|
||||
toSTObject() const;
|
||||
|
||||
[[nodiscard]] AttestationMatch
|
||||
match(MatchFields const& rhs) const;
|
||||
|
||||
friend bool
|
||||
operator==(
|
||||
XChainCreateAccountAttestation const& lhs,
|
||||
XChainCreateAccountAttestation const& rhs);
|
||||
};
|
||||
|
||||
// Attestations from witness servers for a particular claim ID and bridge.
|
||||
// Only one attestation per signature is allowed.
|
||||
template <class TAttestation>
|
||||
class XChainAttestationsBase
|
||||
{
|
||||
public:
|
||||
using AttCollection = std::vector<TAttestation>;
|
||||
|
||||
private:
|
||||
// Set a max number of allowed attestations to limit the amount of memory
|
||||
// allocated and processing time. This number is much larger than the actual
|
||||
// number of attestation a server would ever expect.
|
||||
static constexpr std::uint32_t kMaxAttestations = 256;
|
||||
AttCollection attestations_;
|
||||
|
||||
protected:
|
||||
// Prevent slicing to the base class
|
||||
~XChainAttestationsBase() = default;
|
||||
|
||||
public:
|
||||
XChainAttestationsBase() = default;
|
||||
XChainAttestationsBase(XChainAttestationsBase const& rhs) = default;
|
||||
XChainAttestationsBase&
|
||||
operator=(XChainAttestationsBase const& rhs) = default;
|
||||
|
||||
explicit XChainAttestationsBase(AttCollection&& sigs);
|
||||
|
||||
explicit XChainAttestationsBase(json::Value const& v);
|
||||
|
||||
explicit XChainAttestationsBase(STArray const& arr);
|
||||
|
||||
[[nodiscard]] STArray
|
||||
toSTArray() const;
|
||||
|
||||
[[nodiscard]] AttCollection::const_iterator
|
||||
begin() const;
|
||||
|
||||
[[nodiscard]] AttCollection::const_iterator
|
||||
end() const;
|
||||
|
||||
AttCollection::iterator
|
||||
begin();
|
||||
|
||||
AttCollection::iterator
|
||||
end();
|
||||
|
||||
template <class F>
|
||||
std::size_t
|
||||
eraseIf(F&& f);
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
size() const;
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const;
|
||||
|
||||
[[nodiscard]] AttCollection const&
|
||||
attestations() const;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
emplaceBack(T&& att);
|
||||
};
|
||||
|
||||
template <class TAttestation>
|
||||
[[nodiscard]] inline bool
|
||||
operator==(
|
||||
XChainAttestationsBase<TAttestation> const& lhs,
|
||||
XChainAttestationsBase<TAttestation> const& rhs)
|
||||
{
|
||||
return lhs.attestations() == rhs.attestations();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
inline XChainAttestationsBase<TAttestation>::AttCollection const&
|
||||
XChainAttestationsBase<TAttestation>::attestations() const
|
||||
{
|
||||
return attestations_;
|
||||
};
|
||||
|
||||
template <class TAttestation>
|
||||
template <class T>
|
||||
inline void
|
||||
XChainAttestationsBase<TAttestation>::emplaceBack(T&& att)
|
||||
{
|
||||
attestations_.emplace_back(std::forward<T>(att));
|
||||
};
|
||||
|
||||
template <class TAttestation>
|
||||
template <class F>
|
||||
inline std::size_t
|
||||
XChainAttestationsBase<TAttestation>::eraseIf(F&& f)
|
||||
{
|
||||
return std::erase_if(attestations_, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
inline std::size_t
|
||||
XChainAttestationsBase<TAttestation>::size() const
|
||||
{
|
||||
return attestations_.size();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
inline bool
|
||||
XChainAttestationsBase<TAttestation>::empty() const
|
||||
{
|
||||
return attestations_.empty();
|
||||
}
|
||||
|
||||
class XChainClaimAttestations final : public XChainAttestationsBase<XChainClaimAttestation>
|
||||
{
|
||||
using TBase = XChainAttestationsBase<XChainClaimAttestation>;
|
||||
using TBase::TBase;
|
||||
};
|
||||
|
||||
class XChainCreateAccountAttestations final
|
||||
: public XChainAttestationsBase<XChainCreateAccountAttestation>
|
||||
{
|
||||
using TBase = XChainAttestationsBase<XChainCreateAccountAttestation>;
|
||||
using TBase::TBase;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -62,12 +62,9 @@ XRPL_FIX (EnforceNFTokenTrustline, Supported::Yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (ReducedOffersV2, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NFTokenMintOffer, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PreviousTxnID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (EmptyDID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PriceOracle, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FillOrKill, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DID, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMM, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(XRPFees, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::Yes, VoteBehavior::DefaultYes)
|
||||
@@ -85,6 +82,9 @@ XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::Yes, VoteBehavior::DefaultYe
|
||||
// to vote for it, the feature can be removed entirely from the code. Until
|
||||
// then the feature needs to be marked explicitly as obsolete, e.g.
|
||||
// XRPL_FEATURE(Example, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::Obsolete)
|
||||
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::Obsolete)
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
// All known amendments and amendments that may appear in a validated ledger
|
||||
@@ -109,6 +109,7 @@ XRPL_RETIRE_FIX(AMMOverflowOffer)
|
||||
XRPL_RETIRE_FIX(AMMv1_1)
|
||||
XRPL_RETIRE_FIX(CheckThreading)
|
||||
XRPL_RETIRE_FIX(DisallowIncomingV1)
|
||||
XRPL_RETIRE_FIX(FillOrKill)
|
||||
XRPL_RETIRE_FIX(InnerObjTemplate)
|
||||
XRPL_RETIRE_FIX(MasterKeyAsRegularKey)
|
||||
XRPL_RETIRE_FIX(NonFungibleTokensV1_2)
|
||||
|
||||
@@ -206,23 +206,6 @@ LEDGER_ENTRY(ltLEDGER_HASHES, 0x0068, LedgerHashes, hashes, ({
|
||||
{sfHashes, SoeRequired},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists details about sidechains.
|
||||
|
||||
\sa keylet::bridge
|
||||
*/
|
||||
LEDGER_ENTRY(ltBRIDGE, 0x0069, Bridge, bridge, ({
|
||||
{sfAccount, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
{sfMinAccountCreateAmount, SoeOptional},
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfXChainAccountCreateCount, SoeRequired},
|
||||
{sfXChainAccountClaimCount, SoeRequired},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
}))
|
||||
|
||||
/** A ledger object which describes an offer on the DEX.
|
||||
|
||||
\sa keylet::offer
|
||||
@@ -255,22 +238,6 @@ LEDGER_ENTRY_DUPLICATE(ltDEPOSIT_PREAUTH, 0x0070, DepositPreauth, deposit_preaut
|
||||
{sfAuthorizeCredentials, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A claim id for a cross chain transaction.
|
||||
|
||||
\sa keylet::xChainClaimID
|
||||
*/
|
||||
LEDGER_ENTRY(ltXCHAIN_OWNED_CLAIM_ID, 0x0071, XChainOwnedClaimID, xchain_owned_claim_id, ({
|
||||
{sfAccount, SoeRequired},
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfOtherChainSource, SoeRequired},
|
||||
{sfXChainClaimAttestations, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
}))
|
||||
|
||||
/** A ledger object which describes a bidirectional trust line.
|
||||
|
||||
@note Per Vinnie Falco this should be renamed to ltTRUST_LINE
|
||||
@@ -318,20 +285,6 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfPreviousTxnLgrSeq, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A claim id for a cross chain create account transaction.
|
||||
|
||||
\sa keylet::xChainCreateAccountClaimID
|
||||
*/
|
||||
LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAccountClaimID, xchain_owned_create_account_claim_id, ({
|
||||
{sfAccount, SoeRequired},
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfXChainAccountCreateCount, SoeRequired},
|
||||
{sfXChainCreateAccountAttestations, SoeRequired},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
}))
|
||||
|
||||
/** A ledger object describing a single escrow.
|
||||
|
||||
\sa keylet::escrow
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#error "undefined macro: TYPED_SFIELD"
|
||||
#endif
|
||||
|
||||
// A code marked "reserved" is not assigned to a field. Do not reuse a reserved
|
||||
// code until a year or two after its field was removed, so that no supported
|
||||
// release or tool still knows the old meaning.
|
||||
|
||||
// untyped
|
||||
UNTYPED_SFIELD(sfLedgerEntry, LEDGERENTRY, 257)
|
||||
@@ -23,8 +26,7 @@ TYPED_SFIELD(sfLEVersion, UINT8, 6)
|
||||
// 8-bit integers (uncommon)
|
||||
TYPED_SFIELD(sfTickSize, UINT8, 16)
|
||||
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
|
||||
// 18 unused
|
||||
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
|
||||
// 18 to 19 reserved
|
||||
TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20)
|
||||
TYPED_SFIELD(sfContractResult, UINT8, 21)
|
||||
TYPED_SFIELD(sfVaultKind, UINT8, 22)
|
||||
@@ -39,7 +41,7 @@ TYPED_SFIELD(sfDiscountedFee, UINT16, 6)
|
||||
|
||||
// 16-bit integers (uncommon)
|
||||
TYPED_SFIELD(sfVersion, UINT16, 16)
|
||||
// 17 to 20 unused
|
||||
// 17 to 20 reserved
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
|
||||
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips)
|
||||
|
||||
@@ -90,7 +92,7 @@ TYPED_SFIELD(sfTicketSequence, UINT32, 41)
|
||||
TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42)
|
||||
TYPED_SFIELD(sfMintedNFTokens, UINT32, 43)
|
||||
TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44)
|
||||
// 45 to 47 unused
|
||||
// 45 to 47 reserved
|
||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
@@ -145,11 +147,9 @@ TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12)
|
||||
TYPED_SFIELD(sfEmitBurden, UINT64, 13)
|
||||
|
||||
// 64-bit integers (uncommon)
|
||||
// 16 to 18 unused
|
||||
// 16 to 18 reserved
|
||||
TYPED_SFIELD(sfReferenceCount, UINT64, 19)
|
||||
TYPED_SFIELD(sfXChainClaimID, UINT64, 20)
|
||||
TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21)
|
||||
TYPED_SFIELD(sfXChainAccountClaimCount, UINT64, 22)
|
||||
// 20 to 22 reserved
|
||||
TYPED_SFIELD(sfAssetPrice, UINT64, 23)
|
||||
TYPED_SFIELD(sfMaximumAmount, UINT64, 24, SField::kSmdBaseTen|SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::kSmdBaseTen|SField::kSmdDefault)
|
||||
@@ -199,7 +199,7 @@ TYPED_SFIELD(sfBookDirectory, UINT256, 16)
|
||||
TYPED_SFIELD(sfInvoiceID, UINT256, 17)
|
||||
TYPED_SFIELD(sfNickname, UINT256, 18)
|
||||
TYPED_SFIELD(sfAmendment, UINT256, 19)
|
||||
// 20 unused
|
||||
// 20 reserved
|
||||
TYPED_SFIELD(sfDigest, UINT256, 21)
|
||||
TYPED_SFIELD(sfChannel, UINT256, 22)
|
||||
TYPED_SFIELD(sfConsensusHash, UINT256, 23)
|
||||
@@ -209,7 +209,7 @@ TYPED_SFIELD(sfPreviousPageMin, UINT256, 26)
|
||||
TYPED_SFIELD(sfNextPageMin, UINT256, 27)
|
||||
TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28)
|
||||
TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29)
|
||||
// 30 to 33 unused
|
||||
// 30 to 33 reserved
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 34)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35,
|
||||
SField::kSmdPseudoAccount | SField::kSmdDefault)
|
||||
@@ -268,7 +268,7 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16)
|
||||
TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17)
|
||||
TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18)
|
||||
TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19)
|
||||
// 20 to 21 unused
|
||||
// 20 to 21 reserved
|
||||
// currency amount (fees)
|
||||
TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
@@ -279,8 +279,7 @@ TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
// 29 to 30 reserved
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
|
||||
@@ -309,7 +308,7 @@ TYPED_SFIELD(sfMasterSignature, VL, 18, SField::kSmdDefault, SFi
|
||||
TYPED_SFIELD(sfUNLModifyValidator, VL, 19)
|
||||
TYPED_SFIELD(sfValidatorToDisable, VL, 20)
|
||||
TYPED_SFIELD(sfValidatorToReEnable, VL, 21)
|
||||
// 22 to 25 unused
|
||||
// 22 to 25 reserved
|
||||
TYPED_SFIELD(sfDIDDocument, VL, 26)
|
||||
TYPED_SFIELD(sfData, VL, 27)
|
||||
TYPED_SFIELD(sfAssetClass, VL, 28)
|
||||
@@ -343,7 +342,7 @@ TYPED_SFIELD(sfDestination, ACCOUNT, 3)
|
||||
TYPED_SFIELD(sfIssuer, ACCOUNT, 4)
|
||||
TYPED_SFIELD(sfAuthorize, ACCOUNT, 5)
|
||||
TYPED_SFIELD(sfUnauthorize, ACCOUNT, 6)
|
||||
// 7 unused
|
||||
// 7 reserved
|
||||
TYPED_SFIELD(sfRegularKey, ACCOUNT, 8)
|
||||
TYPED_SFIELD(sfNFTokenMinter, ACCOUNT, 9)
|
||||
TYPED_SFIELD(sfEmitCallback, ACCOUNT, 10)
|
||||
@@ -351,13 +350,7 @@ TYPED_SFIELD(sfHolder, ACCOUNT, 11)
|
||||
TYPED_SFIELD(sfDelegate, ACCOUNT, 12)
|
||||
|
||||
// account (uncommon)
|
||||
// 16 unused
|
||||
TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18)
|
||||
TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19)
|
||||
TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20)
|
||||
TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21)
|
||||
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
|
||||
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
|
||||
// 16 to 23 reserved
|
||||
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
|
||||
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
|
||||
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
|
||||
@@ -383,14 +376,10 @@ TYPED_SFIELD(sfBaseAsset, CURRENCY, 1)
|
||||
TYPED_SFIELD(sfQuoteAsset, CURRENCY, 2)
|
||||
|
||||
// issue
|
||||
TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1)
|
||||
TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2)
|
||||
// 1 to 2 reserved
|
||||
TYPED_SFIELD(sfAsset, ISSUE, 3)
|
||||
TYPED_SFIELD(sfAsset2, ISSUE, 4)
|
||||
|
||||
// bridge
|
||||
TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1)
|
||||
|
||||
// inner object
|
||||
// OBJECT/1 is reserved for end of object
|
||||
UNTYPED_SFIELD(sfTransactionMetaData, OBJECT, 2)
|
||||
@@ -405,22 +394,19 @@ UNTYPED_SFIELD(sfMemo, OBJECT, 10)
|
||||
UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11)
|
||||
UNTYPED_SFIELD(sfNFToken, OBJECT, 12)
|
||||
UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13)
|
||||
// 14 unused
|
||||
// 14 reserved
|
||||
UNTYPED_SFIELD(sfPermission, OBJECT, 15)
|
||||
|
||||
// inner object (uncommon)
|
||||
UNTYPED_SFIELD(sfSigner, OBJECT, 16)
|
||||
// 17 unused
|
||||
// 17 reserved
|
||||
UNTYPED_SFIELD(sfMajority, OBJECT, 18)
|
||||
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
|
||||
// 20 to 24 unused
|
||||
// 20 to 24 reserved
|
||||
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
|
||||
UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26)
|
||||
UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27)
|
||||
UNTYPED_SFIELD(sfXChainClaimProofSig, OBJECT, 28)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountProofSig, OBJECT, 29)
|
||||
UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
|
||||
// 28 to 31 reserved
|
||||
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
|
||||
@@ -442,17 +428,14 @@ UNTYPED_SFIELD(sfSufficient, ARRAY, 7)
|
||||
UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8)
|
||||
UNTYPED_SFIELD(sfMemos, ARRAY, 9)
|
||||
UNTYPED_SFIELD(sfNFTokens, ARRAY, 10)
|
||||
// 11 unused
|
||||
// 11 reserved
|
||||
UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12)
|
||||
UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13)
|
||||
|
||||
// array of objects (uncommon)
|
||||
UNTYPED_SFIELD(sfMajorities, ARRAY, 16)
|
||||
UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17)
|
||||
// 18 to 20 unused
|
||||
UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22)
|
||||
// 23 unused
|
||||
// 18 to 23 reserved
|
||||
UNTYPED_SFIELD(sfPriceDataSeries, ARRAY, 24)
|
||||
UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
|
||||
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
||||
|
||||
@@ -457,111 +457,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
|
||||
{sfAsset2, SoeRequired, SoeMptSupported},
|
||||
}))
|
||||
|
||||
/** This transactions creates a crosschain sequence number */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/bridge/XChainBridge.h>
|
||||
#endif
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
{sfOtherChainSource, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transactions initiates a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfOtherChainDestination, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction completes a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfDestination, SoeRequired},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfAmount, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction initiates a crosschain account create transaction */
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to a claim */
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
|
||||
({
|
||||
.delegable = Delegation::Delegable,
|
||||
.amendment = featureXChainBridge,
|
||||
.privileges = Privilege::CreateAcct,
|
||||
}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfSignature, SoeRequired},
|
||||
{sfOtherChainSource, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfDestination, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to an account */
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation,
|
||||
({
|
||||
.delegable = Delegation::Delegable,
|
||||
.amendment = featureXChainBridge,
|
||||
.privileges = Privilege::CreateAcct,
|
||||
}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfSignature, SoeRequired},
|
||||
{sfOtherChainSource, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
|
||||
{sfXChainAccountCreateCount, SoeRequired},
|
||||
{sfDestination, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction modifies a sidechain */
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfSignatureReward, SoeOptional},
|
||||
{sfMinAccountCreateAmount, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transactions creates a sidechain */
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureXChainBridge}),
|
||||
({
|
||||
{sfXChainBridge, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
{sfMinAccountCreateAmount, SoeOptional},
|
||||
}))
|
||||
// 41 to 48 deprecated
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
|
||||
@@ -159,7 +159,6 @@ JSS(both); // in: Subscribe, Unsubscribe
|
||||
JSS(both_sides); // in: Subscribe, Unsubscribe
|
||||
JSS(branch); // out: server_info
|
||||
JSS(broadcast); // out: SubmitTransaction
|
||||
JSS(bridge_account); // in: LedgerEntry
|
||||
JSS(build_path); // in: TransactionSign
|
||||
JSS(build_version); // out: NetworkOPs
|
||||
JSS(cancel_after); // out: AccountChannels
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
class BridgeBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: Bridge
|
||||
*
|
||||
* Type: ltBRIDGE (0x0069)
|
||||
* RPC Name: bridge
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use BridgeBuilder to construct new ledger entries.
|
||||
*/
|
||||
class Bridge : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltBRIDGE;
|
||||
|
||||
/**
|
||||
* @brief Construct a Bridge ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit Bridge(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
if (sle_->getType() != entryType)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Bridge");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAccount() const
|
||||
{
|
||||
return this->sle_->at(sfAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->sle_->at(sfSignatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getMinAccountCreateAmount() const
|
||||
{
|
||||
if (hasMinAccountCreateAmount())
|
||||
return this->sle_->at(sfMinAccountCreateAmount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMinAccountCreateAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMinAccountCreateAmount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfMinAccountCreateAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->sle_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainClaimID() const
|
||||
{
|
||||
return this->sle_->at(sfXChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainAccountCreateCount() const
|
||||
{
|
||||
return this->sle_->at(sfXChainAccountCreateCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainAccountClaimCount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainAccountClaimCount() const
|
||||
{
|
||||
return this->sle_->at(sfXChainAccountClaimCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getPreviousTxnID() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getPreviousTxnLgrSeq() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnLgrSeq);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for Bridge ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class BridgeBuilder : public LedgerEntryBuilderBase<BridgeBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new BridgeBuilder with required fields.
|
||||
* @param account The sfAccount field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param xChainClaimID The sfXChainClaimID field value.
|
||||
* @param xChainAccountCreateCount The sfXChainAccountCreateCount field value.
|
||||
* @param xChainAccountClaimCount The sfXChainAccountClaimCount field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
*/
|
||||
BridgeBuilder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward,std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge,std::decay_t<typename SF_UINT64::type::value_type> const& xChainClaimID,std::decay_t<typename SF_UINT64::type::value_type> const& xChainAccountCreateCount,std::decay_t<typename SF_UINT64::type::value_type> const& xChainAccountClaimCount,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq)
|
||||
: LedgerEntryBuilderBase<BridgeBuilder>(ltBRIDGE)
|
||||
{
|
||||
setAccount(account);
|
||||
setSignatureReward(signatureReward);
|
||||
setXChainBridge(xChainBridge);
|
||||
setXChainClaimID(xChainClaimID);
|
||||
setXChainAccountCreateCount(xChainAccountCreateCount);
|
||||
setXChainAccountClaimCount(xChainAccountClaimCount);
|
||||
setOwnerNode(ownerNode);
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a BridgeBuilder from an existing SLE object.
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
BridgeBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltBRIDGE)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Bridge");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setMinAccountCreateAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMinAccountCreateAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setXChainClaimID(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainClaimID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setXChainAccountCreateCount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainAccountCreateCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainAccountClaimCount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setXChainAccountClaimCount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainAccountClaimCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
BridgeBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed Bridge wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
Bridge
|
||||
build(UInt256 const& index)
|
||||
{
|
||||
return Bridge{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
@@ -1,314 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
class XChainOwnedClaimIDBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: XChainOwnedClaimID
|
||||
*
|
||||
* Type: ltXCHAIN_OWNED_CLAIM_ID (0x0071)
|
||||
* RPC Name: xchain_owned_claim_id
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use XChainOwnedClaimIDBuilder to construct new ledger entries.
|
||||
*/
|
||||
class XChainOwnedClaimID : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltXCHAIN_OWNED_CLAIM_ID;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainOwnedClaimID ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit XChainOwnedClaimID(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
if (sle_->getType() != entryType)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for XChainOwnedClaimID");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAccount() const
|
||||
{
|
||||
return this->sle_->at(sfAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->sle_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainClaimID() const
|
||||
{
|
||||
return this->sle_->at(sfXChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOtherChainSource (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOtherChainSource() const
|
||||
{
|
||||
return this->sle_->at(sfOtherChainSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimAttestations (SoeRequired)
|
||||
* @note This is an untyped field (unknown).
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
STArray const&
|
||||
getXChainClaimAttestations() const
|
||||
{
|
||||
return this->sle_->getFieldArray(sfXChainClaimAttestations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->sle_->at(sfSignatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getPreviousTxnID() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getPreviousTxnLgrSeq() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnLgrSeq);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainOwnedClaimID ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class XChainOwnedClaimIDBuilder : public LedgerEntryBuilderBase<XChainOwnedClaimIDBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainOwnedClaimIDBuilder with required fields.
|
||||
* @param account The sfAccount field value.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param xChainClaimID The sfXChainClaimID field value.
|
||||
* @param otherChainSource The sfOtherChainSource field value.
|
||||
* @param xChainClaimAttestations The sfXChainClaimAttestations field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge,std::decay_t<typename SF_UINT64::type::value_type> const& xChainClaimID,std::decay_t<typename SF_ACCOUNT::type::value_type> const& otherChainSource,STArray const& xChainClaimAttestations,std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq)
|
||||
: LedgerEntryBuilderBase<XChainOwnedClaimIDBuilder>(ltXCHAIN_OWNED_CLAIM_ID)
|
||||
{
|
||||
setAccount(account);
|
||||
setXChainBridge(xChainBridge);
|
||||
setXChainClaimID(xChainClaimID);
|
||||
setOtherChainSource(otherChainSource);
|
||||
setXChainClaimAttestations(xChainClaimAttestations);
|
||||
setSignatureReward(signatureReward);
|
||||
setOwnerNode(ownerNode);
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainOwnedClaimIDBuilder from an existing SLE object.
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CLAIM_ID)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for XChainOwnedClaimID");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setXChainClaimID(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainClaimID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOtherChainSource (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setOtherChainSource(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOtherChainSource] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimAttestations (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setXChainClaimAttestations(STArray const& value)
|
||||
{
|
||||
object_.setFieldArray(sfXChainClaimAttestations, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedClaimIDBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed XChainOwnedClaimID wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
XChainOwnedClaimID
|
||||
build(UInt256 const& index)
|
||||
{
|
||||
return XChainOwnedClaimID{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
@@ -1,266 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
class XChainOwnedCreateAccountClaimIDBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: XChainOwnedCreateAccountClaimID
|
||||
*
|
||||
* Type: ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID (0x0074)
|
||||
* RPC Name: xchain_owned_create_account_claim_id
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use XChainOwnedCreateAccountClaimIDBuilder to construct new ledger entries.
|
||||
*/
|
||||
class XChainOwnedCreateAccountClaimID : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainOwnedCreateAccountClaimID ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit XChainOwnedCreateAccountClaimID(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
if (sle_->getType() != entryType)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for XChainOwnedCreateAccountClaimID");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAccount() const
|
||||
{
|
||||
return this->sle_->at(sfAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->sle_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainAccountCreateCount() const
|
||||
{
|
||||
return this->sle_->at(sfXChainAccountCreateCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainCreateAccountAttestations (SoeRequired)
|
||||
* @note This is an untyped field (unknown).
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
STArray const&
|
||||
getXChainCreateAccountAttestations() const
|
||||
{
|
||||
return this->sle_->getFieldArray(sfXChainCreateAccountAttestations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getPreviousTxnID() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getPreviousTxnLgrSeq() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnLgrSeq);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainOwnedCreateAccountClaimID ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class XChainOwnedCreateAccountClaimIDBuilder : public LedgerEntryBuilderBase<XChainOwnedCreateAccountClaimIDBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainOwnedCreateAccountClaimIDBuilder with required fields.
|
||||
* @param account The sfAccount field value.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param xChainAccountCreateCount The sfXChainAccountCreateCount field value.
|
||||
* @param xChainCreateAccountAttestations The sfXChainCreateAccountAttestations field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge,std::decay_t<typename SF_UINT64::type::value_type> const& xChainAccountCreateCount,STArray const& xChainCreateAccountAttestations,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq)
|
||||
: LedgerEntryBuilderBase<XChainOwnedCreateAccountClaimIDBuilder>(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID)
|
||||
{
|
||||
setAccount(account);
|
||||
setXChainBridge(xChainBridge);
|
||||
setXChainAccountCreateCount(xChainAccountCreateCount);
|
||||
setXChainCreateAccountAttestations(xChainCreateAccountAttestations);
|
||||
setOwnerNode(ownerNode);
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainOwnedCreateAccountClaimIDBuilder from an existing SLE object.
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for XChainOwnedCreateAccountClaimID");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setXChainAccountCreateCount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainAccountCreateCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainCreateAccountAttestations (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setXChainCreateAccountAttestations(STArray const& value)
|
||||
{
|
||||
object_.setFieldArray(sfXChainCreateAccountAttestations, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimIDBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed XChainOwnedCreateAccountClaimID wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
XChainOwnedCreateAccountClaimID
|
||||
build(UInt256 const& index)
|
||||
{
|
||||
return XChainOwnedCreateAccountClaimID{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
@@ -1,203 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainAccountCreateCommitBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainAccountCreateCommit
|
||||
*
|
||||
* Type: ttXCHAIN_ACCOUNT_CREATE_COMMIT (44)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainAccountCreateCommitBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainAccountCreateCommit : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_ACCOUNT_CREATE_COMMIT;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAccountCreateCommit transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainAccountCreateCommit(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAccountCreateCommit");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestination (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getDestination() const
|
||||
{
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->tx_->at(sfSignatureReward);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainAccountCreateCommit transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainAccountCreateCommitBuilder : public TransactionBuilderBase<XChainAccountCreateCommitBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainAccountCreateCommitBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param destination The sfDestination field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination, std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainAccountCreateCommitBuilder>(ttXCHAIN_ACCOUNT_CREATE_COMMIT, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setDestination(destination);
|
||||
setAmount(amount);
|
||||
setSignatureReward(signatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAccountCreateCommitBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_ACCOUNT_CREATE_COMMIT)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAccountCreateCommitBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestination (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder&
|
||||
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestination] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAccountCreateCommitBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainAccountCreateCommit wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainAccountCreateCommit
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainAccountCreateCommit{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,371 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainAddAccountCreateAttestationBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainAddAccountCreateAttestation
|
||||
*
|
||||
* Type: ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION (46)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::CreateAcct
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainAddAccountCreateAttestationBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainAddAccountCreateAttestation : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAddAccountCreateAttestation transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainAddAccountCreateAttestation(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAddAccountCreateAttestation");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAttestationSignerAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAttestationSignerAccount() const
|
||||
{
|
||||
return this->tx_->at(sfAttestationSignerAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPublicKey (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getPublicKey() const
|
||||
{
|
||||
return this->tx_->at(sfPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignature (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getSignature() const
|
||||
{
|
||||
return this->tx_->at(sfSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOtherChainSource (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOtherChainSource() const
|
||||
{
|
||||
return this->tx_->at(sfOtherChainSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAttestationRewardAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAttestationRewardAccount() const
|
||||
{
|
||||
return this->tx_->at(sfAttestationRewardAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfWasLockingChainSend (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT8::type::value_type
|
||||
getWasLockingChainSend() const
|
||||
{
|
||||
return this->tx_->at(sfWasLockingChainSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainAccountCreateCount() const
|
||||
{
|
||||
return this->tx_->at(sfXChainAccountCreateCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestination (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getDestination() const
|
||||
{
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->tx_->at(sfSignatureReward);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainAddAccountCreateAttestation transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainAddAccountCreateAttestationBuilder : public TransactionBuilderBase<XChainAddAccountCreateAttestationBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainAddAccountCreateAttestationBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param attestationSignerAccount The sfAttestationSignerAccount field value.
|
||||
* @param publicKey The sfPublicKey field value.
|
||||
* @param signature The sfSignature field value.
|
||||
* @param otherChainSource The sfOtherChainSource field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param attestationRewardAccount The sfAttestationRewardAccount field value.
|
||||
* @param wasLockingChainSend The sfWasLockingChainSend field value.
|
||||
* @param xChainAccountCreateCount The sfXChainAccountCreateCount field value.
|
||||
* @param destination The sfDestination field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_ACCOUNT::type::value_type> const& attestationSignerAccount, std::decay_t<typename SF_VL::type::value_type> const& publicKey, std::decay_t<typename SF_VL::type::value_type> const& signature, std::decay_t<typename SF_ACCOUNT::type::value_type> const& otherChainSource, std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::decay_t<typename SF_ACCOUNT::type::value_type> const& attestationRewardAccount, std::decay_t<typename SF_UINT8::type::value_type> const& wasLockingChainSend, std::decay_t<typename SF_UINT64::type::value_type> const& xChainAccountCreateCount, std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination, std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainAddAccountCreateAttestationBuilder>(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setAttestationSignerAccount(attestationSignerAccount);
|
||||
setPublicKey(publicKey);
|
||||
setSignature(signature);
|
||||
setOtherChainSource(otherChainSource);
|
||||
setAmount(amount);
|
||||
setAttestationRewardAccount(attestationRewardAccount);
|
||||
setWasLockingChainSend(wasLockingChainSend);
|
||||
setXChainAccountCreateCount(xChainAccountCreateCount);
|
||||
setDestination(destination);
|
||||
setSignatureReward(signatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAddAccountCreateAttestationBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAddAccountCreateAttestationBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAttestationSignerAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setAttestationSignerAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAttestationSignerAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPublicKey (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setPublicKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPublicKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignature (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setSignature(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignature] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOtherChainSource (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setOtherChainSource(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOtherChainSource] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAttestationRewardAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setAttestationRewardAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAttestationRewardAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfWasLockingChainSend (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setWasLockingChainSend(std::decay_t<typename SF_UINT8::type::value_type> const& value)
|
||||
{
|
||||
object_[sfWasLockingChainSend] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainAccountCreateCount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setXChainAccountCreateCount(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainAccountCreateCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestination (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestination] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddAccountCreateAttestationBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainAddAccountCreateAttestation wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainAddAccountCreateAttestation
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainAddAccountCreateAttestation{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,360 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainAddClaimAttestationBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainAddClaimAttestation
|
||||
*
|
||||
* Type: ttXCHAIN_ADD_CLAIM_ATTESTATION (45)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::CreateAcct
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainAddClaimAttestationBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainAddClaimAttestation : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_ADD_CLAIM_ATTESTATION;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAddClaimAttestation transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainAddClaimAttestation(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAddClaimAttestation");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAttestationSignerAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAttestationSignerAccount() const
|
||||
{
|
||||
return this->tx_->at(sfAttestationSignerAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPublicKey (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getPublicKey() const
|
||||
{
|
||||
return this->tx_->at(sfPublicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignature (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getSignature() const
|
||||
{
|
||||
return this->tx_->at(sfSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOtherChainSource (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOtherChainSource() const
|
||||
{
|
||||
return this->tx_->at(sfOtherChainSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAttestationRewardAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAttestationRewardAccount() const
|
||||
{
|
||||
return this->tx_->at(sfAttestationRewardAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfWasLockingChainSend (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT8::type::value_type
|
||||
getWasLockingChainSend() const
|
||||
{
|
||||
return this->tx_->at(sfWasLockingChainSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainClaimID() const
|
||||
{
|
||||
return this->tx_->at(sfXChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestination (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getDestination() const
|
||||
{
|
||||
if (hasDestination())
|
||||
{
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestination is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestination() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestination);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainAddClaimAttestation transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainAddClaimAttestationBuilder : public TransactionBuilderBase<XChainAddClaimAttestationBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainAddClaimAttestationBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param attestationSignerAccount The sfAttestationSignerAccount field value.
|
||||
* @param publicKey The sfPublicKey field value.
|
||||
* @param signature The sfSignature field value.
|
||||
* @param otherChainSource The sfOtherChainSource field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param attestationRewardAccount The sfAttestationRewardAccount field value.
|
||||
* @param wasLockingChainSend The sfWasLockingChainSend field value.
|
||||
* @param xChainClaimID The sfXChainClaimID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_ACCOUNT::type::value_type> const& attestationSignerAccount, std::decay_t<typename SF_VL::type::value_type> const& publicKey, std::decay_t<typename SF_VL::type::value_type> const& signature, std::decay_t<typename SF_ACCOUNT::type::value_type> const& otherChainSource, std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::decay_t<typename SF_ACCOUNT::type::value_type> const& attestationRewardAccount, std::decay_t<typename SF_UINT8::type::value_type> const& wasLockingChainSend, std::decay_t<typename SF_UINT64::type::value_type> const& xChainClaimID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainAddClaimAttestationBuilder>(ttXCHAIN_ADD_CLAIM_ATTESTATION, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setAttestationSignerAccount(attestationSignerAccount);
|
||||
setPublicKey(publicKey);
|
||||
setSignature(signature);
|
||||
setOtherChainSource(otherChainSource);
|
||||
setAmount(amount);
|
||||
setAttestationRewardAccount(attestationRewardAccount);
|
||||
setWasLockingChainSend(wasLockingChainSend);
|
||||
setXChainClaimID(xChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainAddClaimAttestationBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_ADD_CLAIM_ATTESTATION)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainAddClaimAttestationBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAttestationSignerAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setAttestationSignerAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAttestationSignerAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPublicKey (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setPublicKey(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPublicKey] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignature (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setSignature(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignature] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOtherChainSource (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setOtherChainSource(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOtherChainSource] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAttestationRewardAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setAttestationRewardAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAttestationRewardAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfWasLockingChainSend (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setWasLockingChainSend(std::decay_t<typename SF_UINT8::type::value_type> const& value)
|
||||
{
|
||||
object_[sfWasLockingChainSend] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setXChainClaimID(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainClaimID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestination (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainAddClaimAttestationBuilder&
|
||||
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestination] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainAddClaimAttestation wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainAddClaimAttestation
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainAddClaimAttestation{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,240 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainClaimBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainClaim
|
||||
*
|
||||
* Type: ttXCHAIN_CLAIM (43)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainClaimBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainClaim : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_CLAIM;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainClaim transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainClaim(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainClaim");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainClaimID() const
|
||||
{
|
||||
return this->tx_->at(sfXChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestination (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getDestination() const
|
||||
{
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainClaim transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainClaimBuilder : public TransactionBuilderBase<XChainClaimBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainClaimBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param xChainClaimID The sfXChainClaimID field value.
|
||||
* @param destination The sfDestination field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainClaimBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_UINT64::type::value_type> const& xChainClaimID, std::decay_t<typename SF_ACCOUNT::type::value_type> const& destination, std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainClaimBuilder>(ttXCHAIN_CLAIM, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setXChainClaimID(xChainClaimID);
|
||||
setDestination(destination);
|
||||
setAmount(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainClaimBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainClaimBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_CLAIM)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainClaimBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainClaimBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainClaimBuilder&
|
||||
setXChainClaimID(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainClaimID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestination (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainClaimBuilder&
|
||||
setDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestination] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainClaimBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainClaimBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainClaim wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainClaim
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainClaim{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,216 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainCommitBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainCommit
|
||||
*
|
||||
* Type: ttXCHAIN_COMMIT (42)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainCommitBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainCommit : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_COMMIT;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCommit transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainCommit(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCommit");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainClaimID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getXChainClaimID() const
|
||||
{
|
||||
return this->tx_->at(sfXChainClaimID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOtherChainDestination (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getOtherChainDestination() const
|
||||
{
|
||||
if (hasOtherChainDestination())
|
||||
{
|
||||
return this->tx_->at(sfOtherChainDestination);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfOtherChainDestination is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasOtherChainDestination() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfOtherChainDestination);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainCommit transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainCommitBuilder : public TransactionBuilderBase<XChainCommitBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainCommitBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param xChainClaimID The sfXChainClaimID field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainCommitBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_UINT64::type::value_type> const& xChainClaimID, std::decay_t<typename SF_AMOUNT::type::value_type> const& amount, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainCommitBuilder>(ttXCHAIN_COMMIT, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setXChainClaimID(xChainClaimID);
|
||||
setAmount(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCommitBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainCommitBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_COMMIT)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCommitBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCommitBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainClaimID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCommitBuilder&
|
||||
setXChainClaimID(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainClaimID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCommitBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOtherChainDestination (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCommitBuilder&
|
||||
setOtherChainDestination(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOtherChainDestination] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainCommit wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainCommit
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainCommit{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,192 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainCreateBridgeBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainCreateBridge
|
||||
*
|
||||
* Type: ttXCHAIN_CREATE_BRIDGE (48)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainCreateBridgeBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainCreateBridge : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_CREATE_BRIDGE;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCreateBridge transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainCreateBridge(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCreateBridge");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->tx_->at(sfSignatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getMinAccountCreateAmount() const
|
||||
{
|
||||
if (hasMinAccountCreateAmount())
|
||||
{
|
||||
return this->tx_->at(sfMinAccountCreateAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMinAccountCreateAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMinAccountCreateAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMinAccountCreateAmount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainCreateBridge transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainCreateBridgeBuilder : public TransactionBuilderBase<XChainCreateBridgeBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainCreateBridgeBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainCreateBridgeBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainCreateBridgeBuilder>(ttXCHAIN_CREATE_BRIDGE, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setSignatureReward(signatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCreateBridgeBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainCreateBridgeBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_CREATE_BRIDGE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCreateBridgeBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateBridgeBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateBridgeBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateBridgeBuilder&
|
||||
setMinAccountCreateAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMinAccountCreateAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainCreateBridge wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainCreateBridge
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainCreateBridge{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,179 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainCreateClaimIDBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainCreateClaimID
|
||||
*
|
||||
* Type: ttXCHAIN_CREATE_CLAIM_ID (41)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainCreateClaimIDBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainCreateClaimID : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_CREATE_CLAIM_ID;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCreateClaimID transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainCreateClaimID(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCreateClaimID");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getSignatureReward() const
|
||||
{
|
||||
return this->tx_->at(sfSignatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOtherChainSource (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOtherChainSource() const
|
||||
{
|
||||
return this->tx_->at(sfOtherChainSource);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainCreateClaimID transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainCreateClaimIDBuilder : public TransactionBuilderBase<XChainCreateClaimIDBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainCreateClaimIDBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param signatureReward The sfSignatureReward field value.
|
||||
* @param otherChainSource The sfOtherChainSource field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainCreateClaimIDBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::decay_t<typename SF_AMOUNT::type::value_type> const& signatureReward, std::decay_t<typename SF_ACCOUNT::type::value_type> const& otherChainSource, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainCreateClaimIDBuilder>(ttXCHAIN_CREATE_CLAIM_ID, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
setSignatureReward(signatureReward);
|
||||
setOtherChainSource(otherChainSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainCreateClaimIDBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainCreateClaimIDBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_CREATE_CLAIM_ID)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainCreateClaimIDBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateClaimIDBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateClaimIDBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOtherChainSource (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainCreateClaimIDBuilder&
|
||||
setOtherChainSource(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOtherChainSource] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainCreateClaimID wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainCreateClaimID
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainCreateClaimID{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -1,205 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class XChainModifyBridgeBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: XChainModifyBridge
|
||||
*
|
||||
* Type: ttXCHAIN_MODIFY_BRIDGE (47)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureXChainBridge
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use XChainModifyBridgeBuilder to construct new transactions.
|
||||
*/
|
||||
class XChainModifyBridge : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttXCHAIN_MODIFY_BRIDGE;
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainModifyBridge transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit XChainModifyBridge(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainModifyBridge");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfXChainBridge (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_XCHAIN_BRIDGE::type::value_type
|
||||
getXChainBridge() const
|
||||
{
|
||||
return this->tx_->at(sfXChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSignatureReward (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getSignatureReward() const
|
||||
{
|
||||
if (hasSignatureReward())
|
||||
{
|
||||
return this->tx_->at(sfSignatureReward);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSignatureReward is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSignatureReward() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfSignatureReward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getMinAccountCreateAmount() const
|
||||
{
|
||||
if (hasMinAccountCreateAmount())
|
||||
{
|
||||
return this->tx_->at(sfMinAccountCreateAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMinAccountCreateAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMinAccountCreateAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMinAccountCreateAmount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for XChainModifyBridge transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class XChainModifyBridgeBuilder : public TransactionBuilderBase<XChainModifyBridgeBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new XChainModifyBridgeBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param xChainBridge The sfXChainBridge field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
XChainModifyBridgeBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& xChainBridge, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<XChainModifyBridgeBuilder>(ttXCHAIN_MODIFY_BRIDGE, account, sequence, fee)
|
||||
{
|
||||
setXChainBridge(xChainBridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a XChainModifyBridgeBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
XChainModifyBridgeBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttXCHAIN_MODIFY_BRIDGE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for XChainModifyBridgeBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfXChainBridge (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainModifyBridgeBuilder&
|
||||
setXChainBridge(std::decay_t<typename SF_XCHAIN_BRIDGE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfXChainBridge] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSignatureReward (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainModifyBridgeBuilder&
|
||||
setSignatureReward(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSignatureReward] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMinAccountCreateAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
XChainModifyBridgeBuilder&
|
||||
setMinAccountCreateAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMinAccountCreateAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the XChainModifyBridge wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
XChainModifyBridge
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return XChainModifyBridge{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ReadView;
|
||||
|
||||
/**
|
||||
* @brief Invariants for TransactionProposal ledger entries.
|
||||
*
|
||||
* - Only transaction types implementing proposal lifecycle operations may
|
||||
* create or modify proposals, and a failed one leaves no proposal effect.
|
||||
* - A proposal is deleted only by TransactionProposalCancel or together with
|
||||
* the Ticket it is keyed to, and a deleted Ticket takes the proposal keyed
|
||||
* to it along.
|
||||
* - Collected signer arrays remain sorted, unique, and bounded.
|
||||
*/
|
||||
class ValidTransactionProposal
|
||||
{
|
||||
// The Ticket each deleted proposal was keyed to; a proposal may go with
|
||||
// its Ticket, or by Cancel, and no other way.
|
||||
std::vector<uint256> ticketsOfDeletedProposals_;
|
||||
// The proposal each deleted Ticket was keyed to, whether or not one
|
||||
// existed; none may survive its Ticket.
|
||||
std::vector<Keylet> proposalsOfDeletedTickets_;
|
||||
std::set<uint256> deletedTickets_;
|
||||
std::uint32_t created_ = 0;
|
||||
std::uint32_t modified_ = 0;
|
||||
std::uint32_t deleted_ = 0;
|
||||
bool invalidSignerArrays_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after);
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalize(STTx const& tx, TER result, XRPAmount, ReadView const& view, beast::Journal const& j)
|
||||
const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
#include <xrpl/tx/invariants/CosignerInvariant.h>
|
||||
#include <xrpl/tx/invariants/DirectoryInvariant.h>
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
|
||||
@@ -483,6 +484,7 @@ using InvariantChecks = std::tuple<
|
||||
ValidAmounts,
|
||||
ValidMPTTransfer,
|
||||
ObjectHasPseudoAccount,
|
||||
ValidTransactionProposal,
|
||||
SponsorshipOwnerCountsMatch,
|
||||
SponsorshipAccountCountMatchesField>;
|
||||
|
||||
|
||||
@@ -818,13 +818,10 @@ flow(
|
||||
* TakerPays. We reverse pays and gets because during crossing
|
||||
* we are taking, therefore the owner must deliver the full TakerPays and
|
||||
* the entire TakerGets doesn't have to be spent.
|
||||
* Pre-fixFillOrKill amendment code fails if the entire TakerGets
|
||||
* is not spent. fixFillOrKill addresses this issue.
|
||||
* 2. If tfSell is also set then the owner must spend the entire TakerGets
|
||||
* even if it means obtaining more than TakerPays. Since the pays and gets
|
||||
* are reversed, the owner must send the entire TakerGets.
|
||||
*/
|
||||
bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
|
||||
|
||||
if (actualOut != outReq)
|
||||
{
|
||||
@@ -840,13 +837,9 @@ flow(
|
||||
}
|
||||
if (!partialPayment)
|
||||
{
|
||||
// If we're offerCrossing a !partialPayment, then we're handling tfFillOrKill.
|
||||
// Pre-fixFillOrKill amendment:
|
||||
// That case is handled below; not here.
|
||||
// fixFillOrKill amendment:
|
||||
// That case is handled here if tfSell is also not set; i.e, case 1.
|
||||
if (offerCrossing == OfferCrossing::No ||
|
||||
(fillOrKillEnabled && offerCrossing != OfferCrossing::Sell))
|
||||
// If partialPayment is false, the caller requires full outReq (payments, or FillOrKill
|
||||
// offers).
|
||||
if (offerCrossing != OfferCrossing::Sell)
|
||||
{
|
||||
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
|
||||
}
|
||||
@@ -856,16 +849,10 @@ flow(
|
||||
return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
|
||||
}
|
||||
}
|
||||
if (offerCrossing != OfferCrossing::No &&
|
||||
(!partialPayment && (!fillOrKillEnabled || offerCrossing == OfferCrossing::Sell)))
|
||||
if (!partialPayment && offerCrossing == OfferCrossing::Sell)
|
||||
{
|
||||
// If we're offer crossing and partialPayment is *not* true, then
|
||||
// we're handling a FillOrKill offer. In this case remainingIn must
|
||||
// be zero (all funds must be consumed) or else we kill the offer.
|
||||
// Pre-fixFillOrKill amendment:
|
||||
// Handles both cases 1. and 2.
|
||||
// fixFillOrKill amendment:
|
||||
// Handles 2. 1. is handled above and falls through for tfSell.
|
||||
// we're handling a FillOrKill offer.
|
||||
XRPL_ASSERT(remainingIn, "xrpl::flow : nonzero remainingIn");
|
||||
if (remainingIn && *remainingIn != beast::kZero)
|
||||
return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
constexpr size_t kXbridgeMaxAccountCreateClaims = 128;
|
||||
|
||||
// Attach a new bridge to a door account. Once this is done, the cross-chain
|
||||
// transfer transactions may be used to transfer funds from this account.
|
||||
class XChainCreateBridge : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit XChainCreateBridge(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
class BridgeModify : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit BridgeModify(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
using XChainModifyBridge = BridgeModify;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Claim funds from a `XChainCommit` transaction. This is normally not needed,
|
||||
// but may be used to handle transaction failures or if the destination account
|
||||
// was not specified in the `XChainCommit` transaction. It may only be used
|
||||
// after a quorum of signatures have been sent from the witness servers.
|
||||
//
|
||||
// If the transaction succeeds in moving funds, the referenced `XChainClaimID`
|
||||
// ledger object will be destroyed. This prevents transaction replay. If the
|
||||
// transaction fails, the `XChainClaimID` will not be destroyed and the
|
||||
// transaction may be re-run with different parameters.
|
||||
class XChainClaim : public Transactor
|
||||
{
|
||||
public:
|
||||
// Blocker since we cannot accurately calculate the consequences
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Blocker;
|
||||
|
||||
explicit XChainClaim(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Put assets into trust on the locking-chain so they may be wrapped on the
|
||||
// issuing-chain, or return wrapped assets on the issuing-chain so they can be
|
||||
// unlocked on the locking-chain. The second step in a cross-chain transfer.
|
||||
class XChainCommit : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Custom;
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
explicit XChainCommit(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Create a new claim id owned by the account. This is the first step in a
|
||||
// cross-chain transfer. The claim id must be created on the destination chain
|
||||
// before the `XChainCommit` transaction (which must reference this number) can
|
||||
// be sent on the source chain. The account that will send the `XChainCommit` on
|
||||
// the source chain must be specified in this transaction (see note on the
|
||||
// `SourceAccount` field in the `XChainClaimID` ledger object for
|
||||
// justification). The actual sequence number must be retrieved from a validated
|
||||
// ledger.
|
||||
class XChainCreateClaimID : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit XChainCreateClaimID(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Provide attestations from a witness server attesting to events on
|
||||
// the other chain. The signatures must be from one of the keys on the door's
|
||||
// signer's list at the time the signature was provided. However, if the
|
||||
// signature list changes between the time the signature was submitted and the
|
||||
// quorum is reached, the new signature set is used and some of the currently
|
||||
// collected signatures may be removed. Also note the reward is only sent to
|
||||
// accounts that have keys on the current list.
|
||||
class XChainAddClaimAttestation : public Transactor
|
||||
{
|
||||
public:
|
||||
// Blocker since we cannot accurately calculate the consequences
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Blocker;
|
||||
|
||||
explicit XChainAddClaimAttestation(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
class XChainAddAccountCreateAttestation : public Transactor
|
||||
{
|
||||
public:
|
||||
// Blocker since we cannot accurately calculate the consequences
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Blocker;
|
||||
|
||||
explicit XChainAddAccountCreateAttestation(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This is a special transaction used for creating accounts through a
|
||||
// cross-chain transfer. A normal cross-chain transfer requires a "chain claim
|
||||
// id" (which requires an existing account on the destination chain). One
|
||||
// purpose of the "chain claim id" is to prevent transaction replay. For this
|
||||
// transaction, we use a different mechanism: the accounts must be claimed on
|
||||
// the destination chain in the same order that the `XChainCreateAccountCommit`
|
||||
// transactions occurred on the source chain.
|
||||
//
|
||||
// This transaction can only be used for XRP to XRP bridges.
|
||||
//
|
||||
// IMPORTANT: This transaction should only be enabled if the witness
|
||||
// attestations will be reliably delivered to the destination chain. If the
|
||||
// signatures are not delivered (for example, the chain relies on user wallets
|
||||
// to collect signatures) then account creation would be blocked for all
|
||||
// transactions that happened after the one waiting on attestations. This could
|
||||
// be used maliciously. To disable this transaction on XRP to XRP bridges, the
|
||||
// bridge's `MinAccountCreateAmount` should not be present.
|
||||
//
|
||||
// Note: If this account already exists, the XRP is transferred to the existing
|
||||
// account. However, note that unlike the `XChainCommit` transaction, there is
|
||||
// no error handling mechanism. If the claim transaction fails, there is no
|
||||
// mechanism for refunds. The funds are permanently lost. This transaction
|
||||
// should still only be used for account creation.
|
||||
class XChainCreateAccountCommit : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit XChainCreateAccountCommit(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
using XChainAccountCreateCommit = XChainCreateAccountCommit;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -20,11 +20,14 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
@@ -43,6 +43,9 @@ constexpr auto kProposalTxTypes = std::to_array<TxType>({
|
||||
bool
|
||||
isValidProposalTxnType(STObject const& proposedTx)
|
||||
{
|
||||
if (!proposedTx.isFieldPresent(sfTransactionType))
|
||||
return false;
|
||||
|
||||
auto const type = proposedTx.getFieldU16(sfTransactionType);
|
||||
|
||||
if (std::ranges::find(kProposalTxTypes, type) != kProposalTxTypes.end())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/ledger/helpers/ProposalHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -319,6 +320,8 @@ getLedgerEntryOwnerCount(SLE const& sle)
|
||||
UNREACHABLE("AccountRoots are not supported by object sponsorship.");
|
||||
return 0;
|
||||
// LCOV_EXCL_STOP
|
||||
case ltESCROW:
|
||||
return calculateAdditionalReserve(sle[~sfBytecode]);
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
@@ -105,9 +103,6 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
NftokenBuyOffers = 'h',
|
||||
NftokenSellOffers = 'i',
|
||||
Amm = 'A',
|
||||
Bridge = 'H',
|
||||
XchainClaimId = 'Q',
|
||||
XchainCreateAccountClaimId = 'K',
|
||||
Did = 'I',
|
||||
Oracle = 'R',
|
||||
MPTokenIssuance = '~',
|
||||
@@ -125,6 +120,9 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
Contract [[deprecated]] = 'c',
|
||||
Generator [[deprecated]] = 'g',
|
||||
Nickname [[deprecated]] = 'n',
|
||||
Bridge [[deprecated]] = 'H',
|
||||
XchainClaimId [[deprecated]] = 'Q',
|
||||
XchainCreateAccountClaimId [[deprecated]] = 'K',
|
||||
};
|
||||
|
||||
template <class... Args>
|
||||
@@ -527,44 +525,6 @@ delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept
|
||||
return {ltDELEGATE, indexHash(LedgerNameSpace::Delegate, account, authorizedAccount)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
|
||||
{
|
||||
// A door account can support multiple bridges. On the locking chain
|
||||
// there can only be one bridge per lockingChainCurrency. On the issuing
|
||||
// chain there can only be one bridge per issuingChainCurrency.
|
||||
auto const& issue = bridge.issue(chainType);
|
||||
return {ltBRIDGE, indexHash(LedgerNameSpace::Bridge, bridge.door(chainType), issue.currency)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
xChainClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
|
||||
{
|
||||
return {
|
||||
ltXCHAIN_OWNED_CLAIM_ID,
|
||||
indexHash(
|
||||
LedgerNameSpace::XchainClaimId,
|
||||
bridge.lockingChainDoor(),
|
||||
bridge.lockingChainIssue(),
|
||||
bridge.issuingChainDoor(),
|
||||
bridge.issuingChainIssue(),
|
||||
seq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t const seq)
|
||||
{
|
||||
return {
|
||||
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
|
||||
indexHash(
|
||||
LedgerNameSpace::XchainCreateAccountClaimId,
|
||||
bridge.lockingChainDoor(),
|
||||
bridge.lockingChainIssue(),
|
||||
bridge.issuingChainDoor(),
|
||||
bridge.issuingChainIssue(),
|
||||
seq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept
|
||||
{
|
||||
|
||||
@@ -65,58 +65,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfAuthAccounts, SoeOptional},
|
||||
});
|
||||
|
||||
add(sfXChainClaimAttestationCollectionElement.jsonName,
|
||||
sfXChainClaimAttestationCollectionElement.getCode(),
|
||||
{
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfSignature, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAccount, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
{sfXChainClaimID, SoeRequired},
|
||||
{sfDestination, SoeOptional},
|
||||
});
|
||||
|
||||
add(sfXChainCreateAccountAttestationCollectionElement.jsonName,
|
||||
sfXChainCreateAccountAttestationCollectionElement.getCode(),
|
||||
{
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfSignature, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAccount, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
{sfXChainAccountCreateCount, SoeRequired},
|
||||
{sfDestination, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
});
|
||||
|
||||
add(sfXChainClaimProofSig.jsonName,
|
||||
sfXChainClaimProofSig.getCode(),
|
||||
{
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
{sfDestination, SoeOptional},
|
||||
});
|
||||
|
||||
add(sfXChainCreateAccountProofSig.jsonName,
|
||||
sfXChainCreateAccountProofSig.getCode(),
|
||||
{
|
||||
{sfAttestationSignerAccount, SoeRequired},
|
||||
{sfPublicKey, SoeRequired},
|
||||
{sfAmount, SoeRequired},
|
||||
{sfSignatureReward, SoeRequired},
|
||||
{sfAttestationRewardAccount, SoeRequired},
|
||||
{sfWasLockingChainSend, SoeRequired},
|
||||
{sfDestination, SoeRequired},
|
||||
});
|
||||
|
||||
add(sfAuthAccount.jsonName,
|
||||
sfAuthAccount.getCode(),
|
||||
{
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -956,18 +956,6 @@ parseLeaf(
|
||||
}
|
||||
break;
|
||||
|
||||
case STI_XCHAIN_BRIDGE:
|
||||
try
|
||||
{
|
||||
ret = detail::makeStvar<STXChainBridge>(STXChainBridge(field, value));
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
error = invalidData(jsonName, fieldName);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
case STI_CURRENCY:
|
||||
try
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <stdexcept>
|
||||
@@ -217,9 +216,6 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
|
||||
case STI_ISSUE:
|
||||
construct<STIssue>(std::forward<Args>(args)...);
|
||||
return;
|
||||
case STI_XCHAIN_BRIDGE:
|
||||
construct<STXChainBridge>(std::forward<Args>(args)...);
|
||||
return;
|
||||
case STI_CURRENCY:
|
||||
construct<STCurrency>(std::forward<Args>(args)...);
|
||||
return;
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/STBase.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
STXChainBridge::STXChainBridge() : STBase{sfXChainBridge}
|
||||
{
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(SField const& name) : STBase{name}
|
||||
{
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(
|
||||
AccountID const& srcChainDoor,
|
||||
Issue const& srcChainIssue,
|
||||
AccountID const& dstChainDoor,
|
||||
Issue const& dstChainIssue)
|
||||
: STBase{sfXChainBridge}
|
||||
, lockingChainDoor_{sfLockingChainDoor, srcChainDoor}
|
||||
, lockingChainIssue_{sfLockingChainIssue, srcChainIssue}
|
||||
, issuingChainDoor_{sfIssuingChainDoor, dstChainDoor}
|
||||
, issuingChainIssue_{sfIssuingChainIssue, dstChainIssue}
|
||||
{
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(STObject const& o)
|
||||
: STBase{sfXChainBridge}
|
||||
, lockingChainDoor_{sfLockingChainDoor, o[sfLockingChainDoor]}
|
||||
, lockingChainIssue_{sfLockingChainIssue, o[sfLockingChainIssue]}
|
||||
, issuingChainDoor_{sfIssuingChainDoor, o[sfIssuingChainDoor]}
|
||||
, issuingChainIssue_{sfIssuingChainIssue, o[sfIssuingChainIssue]}
|
||||
{
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(json::Value const& v) : STXChainBridge{sfXChainBridge, v}
|
||||
{
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(SField const& name, json::Value const& v) : STBase{name}
|
||||
{
|
||||
if (!v.isObject())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"STXChainBridge can only be specified with a 'object' Json value");
|
||||
}
|
||||
|
||||
auto const checkExtra = [](json::Value const& v) {
|
||||
static auto const kBridgeJson =
|
||||
xrpl::STXChainBridge().getJson(xrpl::JsonOptions::Values::None);
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
std::string const name = it.memberName();
|
||||
if (!kBridgeJson.isMember(name))
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
std::format("STXChainBridge extra field detected: {}", name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
checkExtra(v);
|
||||
|
||||
json::Value const& lockingChainDoorStr = v[jss::LockingChainDoor];
|
||||
json::Value const& lockingChainIssue = v[jss::LockingChainIssue];
|
||||
json::Value const& issuingChainDoorStr = v[jss::IssuingChainDoor];
|
||||
json::Value const& issuingChainIssue = v[jss::IssuingChainIssue];
|
||||
|
||||
if (!lockingChainDoorStr.isString())
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge LockingChainDoor must be a string Json value");
|
||||
}
|
||||
if (!issuingChainDoorStr.isString())
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge IssuingChainDoor must be a string Json value");
|
||||
}
|
||||
|
||||
auto const lockingChainDoor = parseBase58<AccountID>(lockingChainDoorStr.asString());
|
||||
auto const issuingChainDoor = parseBase58<AccountID>(issuingChainDoorStr.asString());
|
||||
if (!lockingChainDoor)
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge LockingChainDoor must be a valid account");
|
||||
}
|
||||
if (!issuingChainDoor)
|
||||
{
|
||||
Throw<std::runtime_error>("STXChainBridge IssuingChainDoor must be a valid account");
|
||||
}
|
||||
|
||||
lockingChainDoor_ = STAccount{sfLockingChainDoor, *lockingChainDoor};
|
||||
lockingChainIssue_ = STIssue{sfLockingChainIssue, issueFromJson(lockingChainIssue)};
|
||||
issuingChainDoor_ = STAccount{sfIssuingChainDoor, *issuingChainDoor};
|
||||
issuingChainIssue_ = STIssue{sfIssuingChainIssue, issueFromJson(issuingChainIssue)};
|
||||
}
|
||||
|
||||
STXChainBridge::STXChainBridge(SerialIter& sit, SField const& name)
|
||||
: STBase{name}
|
||||
, lockingChainDoor_{sit, sfLockingChainDoor}
|
||||
, lockingChainIssue_{sit, sfLockingChainIssue}
|
||||
, issuingChainDoor_{sit, sfIssuingChainDoor}
|
||||
, issuingChainIssue_{sit, sfIssuingChainIssue}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
STXChainBridge::add(Serializer& s) const
|
||||
{
|
||||
lockingChainDoor_.add(s);
|
||||
lockingChainIssue_.add(s);
|
||||
issuingChainDoor_.add(s);
|
||||
issuingChainIssue_.add(s);
|
||||
}
|
||||
|
||||
json::Value
|
||||
STXChainBridge::getJson(JsonOptions jo) const
|
||||
{
|
||||
json::Value v;
|
||||
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
|
||||
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
|
||||
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
|
||||
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string
|
||||
STXChainBridge::getText() const
|
||||
{
|
||||
return std::format(
|
||||
"{{ {} = {}, {} = {}, {} = {}, {} = {} }}",
|
||||
sfLockingChainDoor.getName(),
|
||||
lockingChainDoor_.getText(),
|
||||
sfLockingChainIssue.getName(),
|
||||
lockingChainIssue_.getText(),
|
||||
sfIssuingChainDoor.getName(),
|
||||
issuingChainDoor_.getText(),
|
||||
sfIssuingChainIssue.getName(),
|
||||
issuingChainIssue_.getText());
|
||||
}
|
||||
|
||||
STObject
|
||||
STXChainBridge::toSTObject() const
|
||||
{
|
||||
STObject o{sfXChainBridge};
|
||||
o[sfLockingChainDoor] = lockingChainDoor_;
|
||||
o[sfLockingChainIssue] = lockingChainIssue_;
|
||||
o[sfIssuingChainDoor] = issuingChainDoor_;
|
||||
o[sfIssuingChainIssue] = issuingChainIssue_;
|
||||
return o;
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
STXChainBridge::getSType() const
|
||||
{
|
||||
return STI_XCHAIN_BRIDGE;
|
||||
}
|
||||
|
||||
bool
|
||||
STXChainBridge::isEquivalent(STBase const& t) const
|
||||
{
|
||||
auto const* v = dynamic_cast<STXChainBridge const*>(&t);
|
||||
return (v != nullptr) && (*v == *this);
|
||||
}
|
||||
|
||||
bool
|
||||
STXChainBridge::isDefault() const
|
||||
{
|
||||
return lockingChainDoor_.isDefault() && lockingChainIssue_.isDefault() &&
|
||||
issuingChainDoor_.isDefault() && issuingChainIssue_.isDefault();
|
||||
}
|
||||
|
||||
std::unique_ptr<STXChainBridge>
|
||||
STXChainBridge::construct(SerialIter& sit, SField const& name)
|
||||
{
|
||||
return std::make_unique<STXChainBridge>(sit, name);
|
||||
}
|
||||
|
||||
STBase*
|
||||
STXChainBridge::copy(std::size_t n, void* buf) const
|
||||
{
|
||||
return emplace(n, buf, *this);
|
||||
}
|
||||
|
||||
STBase*
|
||||
STXChainBridge::move(std::size_t n, void* buf)
|
||||
{
|
||||
return emplace(n, buf, std::move(*this));
|
||||
}
|
||||
} // namespace xrpl
|
||||
@@ -137,6 +137,8 @@ transResults()
|
||||
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
|
||||
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field that was not included."),
|
||||
MAKE_ERROR(tefTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
|
||||
MAKE_ERROR(tefEXCEEDS_LIMIT, "The transaction exceeds a limit set by fee voting."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -209,7 +211,6 @@ transResults()
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
|
||||
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided byte code is invalid."),
|
||||
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -1,706 +0,0 @@
|
||||
#include <xrpl/protocol/XChainAttestations.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/json_get_or_throw.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
namespace attestations {
|
||||
|
||||
AttestationBase::AttestationBase(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend)
|
||||
: attestationSignerAccount{attestationSignerAccount}
|
||||
, publicKey{publicKey}
|
||||
, signature{std::move(signature)}
|
||||
, sendingAccount{sendingAccount}
|
||||
, sendingAmount{std::move(sendingAmount)}
|
||||
, rewardAccount{rewardAccount}
|
||||
, wasLockingChainSend{wasLockingChainSend}
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationBase::equalHelper(AttestationBase const& lhs, AttestationBase const& rhs)
|
||||
{
|
||||
return std::tie(
|
||||
lhs.attestationSignerAccount,
|
||||
lhs.publicKey,
|
||||
lhs.signature,
|
||||
lhs.sendingAccount,
|
||||
lhs.sendingAmount,
|
||||
lhs.rewardAccount,
|
||||
lhs.wasLockingChainSend) ==
|
||||
std::tie(
|
||||
rhs.attestationSignerAccount,
|
||||
rhs.publicKey,
|
||||
rhs.signature,
|
||||
rhs.sendingAccount,
|
||||
rhs.sendingAmount,
|
||||
rhs.rewardAccount,
|
||||
rhs.wasLockingChainSend);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationBase::sameEventHelper(AttestationBase const& lhs, AttestationBase const& rhs)
|
||||
{
|
||||
return std::tie(lhs.sendingAccount, lhs.sendingAmount, lhs.wasLockingChainSend) ==
|
||||
std::tie(rhs.sendingAccount, rhs.sendingAmount, rhs.wasLockingChainSend);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationBase::verify(STXChainBridge const& bridge) const
|
||||
{
|
||||
std::vector<std::uint8_t> const msg = message(bridge);
|
||||
return xrpl::verify(publicKey, makeSlice(msg), signature);
|
||||
}
|
||||
|
||||
AttestationBase::AttestationBase(STObject const& o)
|
||||
: attestationSignerAccount{o[sfAttestationSignerAccount]}
|
||||
, publicKey{o[sfPublicKey]}
|
||||
, signature{o[sfSignature]}
|
||||
, sendingAccount{o[sfAccount]}
|
||||
, sendingAmount{o[sfAmount]}
|
||||
, rewardAccount{o[sfAttestationRewardAccount]}
|
||||
, wasLockingChainSend{bool(o[sfWasLockingChainSend])}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationBase::AttestationBase(json::Value const& v)
|
||||
: attestationSignerAccount{json::getOrThrow<AccountID>(v, sfAttestationSignerAccount)}
|
||||
, publicKey{json::getOrThrow<PublicKey>(v, sfPublicKey)}
|
||||
, signature{json::getOrThrow<Buffer>(v, sfSignature)}
|
||||
, sendingAccount{json::getOrThrow<AccountID>(v, sfAccount)}
|
||||
, sendingAmount{json::getOrThrow<STAmount>(v, sfAmount)}
|
||||
, rewardAccount{json::getOrThrow<AccountID>(v, sfAttestationRewardAccount)}
|
||||
, wasLockingChainSend{json::getOrThrow<bool>(v, sfWasLockingChainSend)}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AttestationBase::addHelper(STObject& o) const
|
||||
{
|
||||
o[sfAttestationSignerAccount] = attestationSignerAccount;
|
||||
o[sfPublicKey] = publicKey;
|
||||
o[sfSignature] = signature;
|
||||
o[sfAmount] = sendingAmount;
|
||||
o[sfAccount] = sendingAccount;
|
||||
o[sfAttestationRewardAccount] = rewardAccount;
|
||||
o[sfWasLockingChainSend] = wasLockingChainSend;
|
||||
}
|
||||
|
||||
AttestationClaim::AttestationClaim(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimId,
|
||||
std::optional<AccountID> const& dst)
|
||||
: AttestationBase(
|
||||
attestationSignerAccount,
|
||||
publicKey,
|
||||
std::move(signature),
|
||||
sendingAccount,
|
||||
sendingAmount,
|
||||
rewardAccount,
|
||||
wasLockingChainSend)
|
||||
, claimID{claimId}
|
||||
, dst{dst}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationClaim::AttestationClaim(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimId,
|
||||
std::optional<AccountID> const& dst)
|
||||
: AttestationClaim{
|
||||
attestationSignerAccount,
|
||||
publicKey,
|
||||
Buffer{},
|
||||
sendingAccount,
|
||||
sendingAmount,
|
||||
rewardAccount,
|
||||
wasLockingChainSend,
|
||||
claimId,
|
||||
dst}
|
||||
{
|
||||
auto const toSign = message(bridge);
|
||||
signature = sign(publicKey, secretKey, makeSlice(toSign));
|
||||
}
|
||||
|
||||
AttestationClaim::AttestationClaim(STObject const& o)
|
||||
: AttestationBase(o), claimID{o[sfXChainClaimID]}, dst{o[~sfDestination]}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationClaim::AttestationClaim(json::Value const& v)
|
||||
: AttestationBase{v}, claimID{json::getOrThrow<std::uint64_t>(v, sfXChainClaimID)}
|
||||
{
|
||||
if (v.isMember(sfDestination.getJsonName()))
|
||||
dst = json::getOrThrow<AccountID>(v, sfDestination);
|
||||
}
|
||||
|
||||
STObject
|
||||
AttestationClaim::toSTObject() const
|
||||
{
|
||||
STObject o = STObject::makeInnerObject(sfXChainClaimAttestationCollectionElement);
|
||||
addHelper(o);
|
||||
o[sfXChainClaimID] = claimID;
|
||||
if (dst)
|
||||
o[sfDestination] = *dst;
|
||||
return o;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
AttestationClaim::message(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t claimID,
|
||||
std::optional<AccountID> const& dst)
|
||||
{
|
||||
STObject o{sfGeneric};
|
||||
// Serialize in SField order to make python serializers easier to write
|
||||
o[sfXChainClaimID] = claimID;
|
||||
o[sfAmount] = sendingAmount;
|
||||
if (dst)
|
||||
o[sfDestination] = *dst;
|
||||
o[sfOtherChainSource] = sendingAccount;
|
||||
o[sfAttestationRewardAccount] = rewardAccount;
|
||||
o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0;
|
||||
o[sfXChainBridge] = bridge;
|
||||
|
||||
Serializer s;
|
||||
o.add(s);
|
||||
|
||||
return std::move(s.modData());
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
AttestationClaim::message(STXChainBridge const& bridge) const
|
||||
{
|
||||
return AttestationClaim::message(
|
||||
bridge, sendingAccount, sendingAmount, rewardAccount, wasLockingChainSend, claimID, dst);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationClaim::validAmounts() const
|
||||
{
|
||||
return isLegalNet(sendingAmount);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationClaim::sameEvent(AttestationClaim const& rhs) const
|
||||
{
|
||||
return AttestationClaim::sameEventHelper(*this, rhs) &&
|
||||
tie(claimID, dst) == tie(rhs.claimID, rhs.dst);
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(AttestationClaim const& lhs, AttestationClaim const& rhs)
|
||||
{
|
||||
return AttestationClaim::equalHelper(lhs, rhs) &&
|
||||
tie(lhs.claimID, lhs.dst) == tie(rhs.claimID, rhs.dst);
|
||||
}
|
||||
|
||||
AttestationCreateAccount::AttestationCreateAccount(STObject const& o)
|
||||
: AttestationBase(o)
|
||||
, createCount{o[sfXChainAccountCreateCount]}
|
||||
, toCreate{o[sfDestination]}
|
||||
, rewardAmount{o[sfSignatureReward]}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationCreateAccount::AttestationCreateAccount(json::Value const& v)
|
||||
: AttestationBase{v}
|
||||
, createCount{json::getOrThrow<std::uint64_t>(v, sfXChainAccountCreateCount)}
|
||||
, toCreate{json::getOrThrow<AccountID>(v, sfDestination)}
|
||||
, rewardAmount{json::getOrThrow<STAmount>(v, sfSignatureReward)}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationCreateAccount::AttestationCreateAccount(
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
Buffer signature,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& toCreate)
|
||||
: AttestationBase(
|
||||
attestationSignerAccount,
|
||||
publicKey,
|
||||
std::move(signature),
|
||||
sendingAccount,
|
||||
sendingAmount,
|
||||
rewardAccount,
|
||||
wasLockingChainSend)
|
||||
, createCount{createCount}
|
||||
, toCreate{toCreate}
|
||||
, rewardAmount{std::move(rewardAmount)}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationCreateAccount::AttestationCreateAccount(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID attestationSignerAccount,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& toCreate)
|
||||
: AttestationCreateAccount{
|
||||
attestationSignerAccount,
|
||||
publicKey,
|
||||
Buffer{},
|
||||
sendingAccount,
|
||||
sendingAmount,
|
||||
rewardAmount,
|
||||
rewardAccount,
|
||||
wasLockingChainSend,
|
||||
createCount,
|
||||
toCreate}
|
||||
{
|
||||
auto const toSign = message(bridge);
|
||||
signature = sign(publicKey, secretKey, makeSlice(toSign));
|
||||
}
|
||||
|
||||
STObject
|
||||
AttestationCreateAccount::toSTObject() const
|
||||
{
|
||||
STObject o = STObject::makeInnerObject(sfXChainCreateAccountAttestationCollectionElement);
|
||||
addHelper(o);
|
||||
|
||||
o[sfXChainAccountCreateCount] = createCount;
|
||||
o[sfDestination] = toCreate;
|
||||
o[sfSignatureReward] = rewardAmount;
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
AttestationCreateAccount::message(
|
||||
STXChainBridge const& bridge,
|
||||
AccountID const& sendingAccount,
|
||||
STAmount const& sendingAmount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::uint64_t createCount,
|
||||
AccountID const& dst)
|
||||
{
|
||||
STObject o{sfGeneric};
|
||||
// Serialize in SField order to make python serializers easier to write
|
||||
o[sfXChainAccountCreateCount] = createCount;
|
||||
o[sfAmount] = sendingAmount;
|
||||
o[sfSignatureReward] = rewardAmount;
|
||||
o[sfDestination] = dst;
|
||||
o[sfOtherChainSource] = sendingAccount;
|
||||
o[sfAttestationRewardAccount] = rewardAccount;
|
||||
o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0;
|
||||
o[sfXChainBridge] = bridge;
|
||||
|
||||
Serializer s;
|
||||
o.add(s);
|
||||
|
||||
return std::move(s.modData());
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
AttestationCreateAccount::message(STXChainBridge const& bridge) const
|
||||
{
|
||||
return AttestationCreateAccount::message(
|
||||
bridge,
|
||||
sendingAccount,
|
||||
sendingAmount,
|
||||
rewardAmount,
|
||||
rewardAccount,
|
||||
wasLockingChainSend,
|
||||
createCount,
|
||||
toCreate);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationCreateAccount::validAmounts() const
|
||||
{
|
||||
return isLegalNet(rewardAmount) && isLegalNet(sendingAmount);
|
||||
}
|
||||
|
||||
bool
|
||||
AttestationCreateAccount::sameEvent(AttestationCreateAccount const& rhs) const
|
||||
{
|
||||
return AttestationCreateAccount::sameEventHelper(*this, rhs) &&
|
||||
std::tie(createCount, toCreate, rewardAmount) ==
|
||||
std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount);
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(AttestationCreateAccount const& lhs, AttestationCreateAccount const& rhs)
|
||||
{
|
||||
return AttestationCreateAccount::equalHelper(lhs, rhs) &&
|
||||
std::tie(lhs.createCount, lhs.toCreate, lhs.rewardAmount) ==
|
||||
std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount);
|
||||
}
|
||||
|
||||
} // namespace attestations
|
||||
|
||||
SField const& XChainClaimAttestation::arrayFieldName{sfXChainClaimAttestations};
|
||||
SField const& XChainCreateAccountAttestation::arrayFieldName{sfXChainCreateAccountAttestations};
|
||||
|
||||
XChainClaimAttestation::XChainClaimAttestation(
|
||||
AccountID const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::optional<AccountID> const& dst)
|
||||
: keyAccount(keyAccount)
|
||||
, publicKey(publicKey)
|
||||
, amount(sfAmount, amount)
|
||||
, rewardAccount(rewardAccount)
|
||||
, wasLockingChainSend(wasLockingChainSend)
|
||||
, dst(dst)
|
||||
{
|
||||
}
|
||||
|
||||
XChainClaimAttestation::XChainClaimAttestation(
|
||||
STAccount const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
STAccount const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
std::optional<STAccount> const& dst)
|
||||
: XChainClaimAttestation{
|
||||
keyAccount.value(),
|
||||
publicKey,
|
||||
amount,
|
||||
rewardAccount.value(),
|
||||
wasLockingChainSend,
|
||||
dst ? std::optional<AccountID>{dst->value()} : std::nullopt}
|
||||
{
|
||||
}
|
||||
|
||||
XChainClaimAttestation::XChainClaimAttestation(STObject const& o)
|
||||
: XChainClaimAttestation{
|
||||
o[sfAttestationSignerAccount],
|
||||
PublicKey{o[sfPublicKey]},
|
||||
o[sfAmount],
|
||||
o[sfAttestationRewardAccount],
|
||||
o[sfWasLockingChainSend] != 0,
|
||||
o[~sfDestination]} {};
|
||||
|
||||
XChainClaimAttestation::XChainClaimAttestation(json::Value const& v)
|
||||
: XChainClaimAttestation{
|
||||
json::getOrThrow<AccountID>(v, sfAttestationSignerAccount),
|
||||
json::getOrThrow<PublicKey>(v, sfPublicKey),
|
||||
json::getOrThrow<STAmount>(v, sfAmount),
|
||||
json::getOrThrow<AccountID>(v, sfAttestationRewardAccount),
|
||||
json::getOrThrow<bool>(v, sfWasLockingChainSend),
|
||||
std::nullopt}
|
||||
{
|
||||
if (v.isMember(sfDestination.getJsonName()))
|
||||
dst = json::getOrThrow<AccountID>(v, sfDestination);
|
||||
};
|
||||
|
||||
XChainClaimAttestation::XChainClaimAttestation(
|
||||
XChainClaimAttestation::TSignedAttestation const& claimAtt)
|
||||
: XChainClaimAttestation{
|
||||
claimAtt.attestationSignerAccount,
|
||||
claimAtt.publicKey,
|
||||
claimAtt.sendingAmount,
|
||||
claimAtt.rewardAccount,
|
||||
claimAtt.wasLockingChainSend,
|
||||
claimAtt.dst}
|
||||
{
|
||||
}
|
||||
|
||||
STObject
|
||||
XChainClaimAttestation::toSTObject() const
|
||||
{
|
||||
STObject o = STObject::makeInnerObject(sfXChainClaimProofSig);
|
||||
o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount};
|
||||
o[sfPublicKey] = publicKey;
|
||||
o[sfAmount] = STAmount{sfAmount, amount};
|
||||
o[sfAttestationRewardAccount] = STAccount{sfAttestationRewardAccount, rewardAccount};
|
||||
o[sfWasLockingChainSend] = wasLockingChainSend;
|
||||
if (dst)
|
||||
o[sfDestination] = STAccount{sfDestination, *dst};
|
||||
return o;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(XChainClaimAttestation const& lhs, XChainClaimAttestation const& rhs)
|
||||
{
|
||||
return std::tie(
|
||||
lhs.keyAccount,
|
||||
lhs.publicKey,
|
||||
lhs.amount,
|
||||
lhs.rewardAccount,
|
||||
lhs.wasLockingChainSend,
|
||||
lhs.dst) ==
|
||||
std::tie(
|
||||
rhs.keyAccount,
|
||||
rhs.publicKey,
|
||||
rhs.amount,
|
||||
rhs.rewardAccount,
|
||||
rhs.wasLockingChainSend,
|
||||
rhs.dst);
|
||||
}
|
||||
|
||||
XChainClaimAttestation::MatchFields::MatchFields(
|
||||
XChainClaimAttestation::TSignedAttestation const& att)
|
||||
: amount{att.sendingAmount}, wasLockingChainSend{att.wasLockingChainSend}, dst{att.dst}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationMatch
|
||||
XChainClaimAttestation::match(XChainClaimAttestation::MatchFields const& rhs) const
|
||||
{
|
||||
if (std::tie(amount, wasLockingChainSend) != std::tie(rhs.amount, rhs.wasLockingChainSend))
|
||||
return AttestationMatch::NonDstMismatch;
|
||||
if (dst != rhs.dst)
|
||||
return AttestationMatch::MatchExceptDst;
|
||||
return AttestationMatch::Match;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
XChainCreateAccountAttestation::XChainCreateAccountAttestation(
|
||||
AccountID const& keyAccount,
|
||||
PublicKey const& publicKey,
|
||||
STAmount const& amount,
|
||||
STAmount const& rewardAmount,
|
||||
AccountID const& rewardAccount,
|
||||
bool wasLockingChainSend,
|
||||
AccountID const& dst)
|
||||
: keyAccount(keyAccount)
|
||||
, publicKey(publicKey)
|
||||
, amount(sfAmount, amount)
|
||||
, rewardAmount(sfSignatureReward, rewardAmount)
|
||||
, rewardAccount(rewardAccount)
|
||||
, wasLockingChainSend(wasLockingChainSend)
|
||||
, dst(dst)
|
||||
{
|
||||
}
|
||||
|
||||
XChainCreateAccountAttestation::XChainCreateAccountAttestation(STObject const& o)
|
||||
: XChainCreateAccountAttestation{
|
||||
o[sfAttestationSignerAccount],
|
||||
PublicKey{o[sfPublicKey]},
|
||||
o[sfAmount],
|
||||
o[sfSignatureReward],
|
||||
o[sfAttestationRewardAccount],
|
||||
o[sfWasLockingChainSend] != 0,
|
||||
o[sfDestination]} {};
|
||||
|
||||
XChainCreateAccountAttestation::XChainCreateAccountAttestation(json::Value const& v)
|
||||
: XChainCreateAccountAttestation{
|
||||
json::getOrThrow<AccountID>(v, sfAttestationSignerAccount),
|
||||
json::getOrThrow<PublicKey>(v, sfPublicKey),
|
||||
json::getOrThrow<STAmount>(v, sfAmount),
|
||||
json::getOrThrow<STAmount>(v, sfSignatureReward),
|
||||
json::getOrThrow<AccountID>(v, sfAttestationRewardAccount),
|
||||
json::getOrThrow<bool>(v, sfWasLockingChainSend),
|
||||
json::getOrThrow<AccountID>(v, sfDestination)}
|
||||
{
|
||||
}
|
||||
|
||||
XChainCreateAccountAttestation::XChainCreateAccountAttestation(
|
||||
XChainCreateAccountAttestation::TSignedAttestation const& createAtt)
|
||||
: XChainCreateAccountAttestation{
|
||||
createAtt.attestationSignerAccount,
|
||||
createAtt.publicKey,
|
||||
createAtt.sendingAmount,
|
||||
createAtt.rewardAmount,
|
||||
createAtt.rewardAccount,
|
||||
createAtt.wasLockingChainSend,
|
||||
createAtt.toCreate}
|
||||
{
|
||||
}
|
||||
|
||||
STObject
|
||||
XChainCreateAccountAttestation::toSTObject() const
|
||||
{
|
||||
STObject o = STObject::makeInnerObject(sfXChainCreateAccountProofSig);
|
||||
|
||||
o[sfAttestationSignerAccount] = STAccount{sfAttestationSignerAccount, keyAccount};
|
||||
o[sfPublicKey] = publicKey;
|
||||
o[sfAmount] = STAmount{sfAmount, amount};
|
||||
o[sfSignatureReward] = STAmount{sfSignatureReward, rewardAmount};
|
||||
o[sfAttestationRewardAccount] = STAccount{sfAttestationRewardAccount, rewardAccount};
|
||||
o[sfWasLockingChainSend] = wasLockingChainSend;
|
||||
o[sfDestination] = STAccount{sfDestination, dst};
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
XChainCreateAccountAttestation::MatchFields::MatchFields(
|
||||
XChainCreateAccountAttestation::TSignedAttestation const& att)
|
||||
: amount{att.sendingAmount}
|
||||
, rewardAmount(att.rewardAmount)
|
||||
, wasLockingChainSend{att.wasLockingChainSend}
|
||||
, dst{att.toCreate}
|
||||
{
|
||||
}
|
||||
|
||||
AttestationMatch
|
||||
XChainCreateAccountAttestation::match(XChainCreateAccountAttestation::MatchFields const& rhs) const
|
||||
{
|
||||
if (std::tie(amount, rewardAmount, wasLockingChainSend) !=
|
||||
std::tie(rhs.amount, rhs.rewardAmount, rhs.wasLockingChainSend))
|
||||
return AttestationMatch::NonDstMismatch;
|
||||
if (dst != rhs.dst)
|
||||
return AttestationMatch::MatchExceptDst;
|
||||
return AttestationMatch::Match;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(XChainCreateAccountAttestation const& lhs, XChainCreateAccountAttestation const& rhs)
|
||||
{
|
||||
return std::tie(
|
||||
lhs.keyAccount,
|
||||
lhs.publicKey,
|
||||
lhs.amount,
|
||||
lhs.rewardAmount,
|
||||
lhs.rewardAccount,
|
||||
lhs.wasLockingChainSend,
|
||||
lhs.dst) ==
|
||||
std::tie(
|
||||
rhs.keyAccount,
|
||||
rhs.publicKey,
|
||||
rhs.amount,
|
||||
rhs.rewardAmount,
|
||||
rhs.rewardAccount,
|
||||
rhs.wasLockingChainSend,
|
||||
rhs.dst);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(
|
||||
XChainAttestationsBase<TAttestation>::AttCollection&& atts)
|
||||
: attestations_{std::move(atts)}
|
||||
{
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::AttCollection::const_iterator
|
||||
XChainAttestationsBase<TAttestation>::begin() const
|
||||
{
|
||||
return attestations_.begin();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::AttCollection::const_iterator
|
||||
XChainAttestationsBase<TAttestation>::end() const
|
||||
{
|
||||
return attestations_.end();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::AttCollection::iterator
|
||||
XChainAttestationsBase<TAttestation>::begin()
|
||||
{
|
||||
return attestations_.begin();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::AttCollection::iterator
|
||||
XChainAttestationsBase<TAttestation>::end()
|
||||
{
|
||||
return attestations_.end();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(json::Value const& v)
|
||||
{
|
||||
if (!v.isObject())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"XChainAttestationsBase can only be specified with an 'object' "
|
||||
"Json value");
|
||||
}
|
||||
|
||||
attestations_ = [&] {
|
||||
auto const jAtts = v[jss::attestations];
|
||||
|
||||
if (jAtts.size() > kMaxAttestations)
|
||||
Throw<std::runtime_error>("XChainAttestationsBase exceeded max number of attestations");
|
||||
|
||||
std::vector<TAttestation> r;
|
||||
r.reserve(jAtts.size());
|
||||
for (auto const& a : jAtts)
|
||||
r.emplace_back(a);
|
||||
return r;
|
||||
}();
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(STArray const& arr)
|
||||
{
|
||||
if (arr.size() > kMaxAttestations)
|
||||
Throw<std::runtime_error>("XChainAttestationsBase exceeded max number of attestations");
|
||||
|
||||
attestations_.reserve(arr.size());
|
||||
for (auto const& o : arr)
|
||||
attestations_.emplace_back(o);
|
||||
}
|
||||
|
||||
template <class TAttestation>
|
||||
STArray
|
||||
XChainAttestationsBase<TAttestation>::toSTArray() const
|
||||
{
|
||||
STArray r{TAttestation::arrayFieldName, attestations_.size()};
|
||||
for (auto const& e : attestations_)
|
||||
r.emplaceBack(e.toSTObject());
|
||||
return r;
|
||||
}
|
||||
|
||||
template class XChainAttestationsBase<XChainClaimAttestation>;
|
||||
template class XChainAttestationsBase<XChainCreateAccountAttestation>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -31,6 +31,8 @@
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#undef TRANSACTION_INCLUDE
|
||||
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
|
||||
// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#include <xrpl/tx/invariants/CosignerInvariant.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
validSignerArray(STArray const& signers, std::size_t limit, SField const& elementName)
|
||||
{
|
||||
if (signers.size() > limit)
|
||||
return false;
|
||||
|
||||
std::optional<AccountID> previous;
|
||||
for (auto const& signer : signers)
|
||||
{
|
||||
if (signer.getFName() != elementName || !signer.isFieldPresent(sfAccount))
|
||||
return false;
|
||||
|
||||
auto const account = signer.getAccountID(sfAccount);
|
||||
if (previous && account <= *previous)
|
||||
return false;
|
||||
previous = account;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
validNestedSigners(STObject const& signature)
|
||||
{
|
||||
return !signature.isFieldPresent(sfSigners) ||
|
||||
validSignerArray(signature.getFieldArray(sfSigners), STTx::kMaxMultiSigners, sfSigner);
|
||||
}
|
||||
|
||||
bool
|
||||
validSignerArrays(STObject const& proposedTx)
|
||||
{
|
||||
if (!validNestedSigners(proposedTx))
|
||||
return false;
|
||||
|
||||
for (auto const* field :
|
||||
std::array<SField const*, 2>{&sfCounterpartySignature, &sfSponsorSignature})
|
||||
{
|
||||
if (proposedTx.isFieldPresent(*field) &&
|
||||
!validNestedSigners(proposedTx.getFieldObject(*field)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!proposedTx.isFieldPresent(sfBatchSigners))
|
||||
return true;
|
||||
|
||||
auto const& batchSigners = proposedTx.getFieldArray(sfBatchSigners);
|
||||
if (!validSignerArray(batchSigners, kMaxBatchSigners, sfBatchSigner))
|
||||
return false;
|
||||
|
||||
return std::ranges::all_of(
|
||||
batchSigners, [](auto const& batchSigner) { return validNestedSigners(batchSigner); });
|
||||
}
|
||||
|
||||
// The Ticket a proposal is keyed to: the one its proposed transaction spends.
|
||||
uint256
|
||||
proposalTicketKey(SLE const& proposal)
|
||||
{
|
||||
auto const& proposedTx = proposal.getFieldObject(sfProposedTransaction);
|
||||
return keylet::ticket(
|
||||
proposedTx.getAccountID(sfAccount),
|
||||
SeqProxy::rawTicket(proposedTx.getFieldU32(sfTicketSequence)))
|
||||
.key;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
ValidTransactionProposal::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after)
|
||||
{
|
||||
// `after` is always present (on deletion it holds the final state), so it
|
||||
// alone determines the entry type. LedgerEntryTypesMatch owns malformed
|
||||
// type transitions; skip them rather than read one side with the other's
|
||||
// schema.
|
||||
if (!after || (before && before->getType() != after->getType()))
|
||||
return;
|
||||
|
||||
auto const type = after->getType();
|
||||
|
||||
// Consuming a Ticket deletes the proposal keyed to it (XLS-0103 §4.5).
|
||||
// Remember each Ticket that went, so finalize can tell a proposal deleted
|
||||
// with its Ticket from one that simply vanished, and the proposal each
|
||||
// Ticket was keyed to, so finalize can check that none stayed behind.
|
||||
if (type == ltTICKET)
|
||||
{
|
||||
if (isDelete)
|
||||
{
|
||||
deletedTickets_.insert(after->key());
|
||||
proposalsOfDeletedTickets_.push_back(
|
||||
keylet::txProposal(
|
||||
after->getAccountID(sfAccount), after->getFieldU32(sfTicketSequence)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != ltTRANSACTION_PROPOSAL)
|
||||
return;
|
||||
|
||||
if (!before)
|
||||
{
|
||||
++created_;
|
||||
}
|
||||
else if (isDelete)
|
||||
{
|
||||
++deleted_;
|
||||
ticketsOfDeletedProposals_.push_back(proposalTicketKey(*before));
|
||||
}
|
||||
else
|
||||
{
|
||||
++modified_;
|
||||
}
|
||||
|
||||
// A live proposal's signer lists must stay submittable: within size
|
||||
// limits, sorted by account, and free of duplicates.
|
||||
if (!isDelete && !validSignerArrays(after->getFieldObject(sfProposedTransaction)))
|
||||
invalidSignerArrays_ = true;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidTransactionProposal::finalize(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!view.rules().enabled(featureCosign))
|
||||
return true;
|
||||
|
||||
if (invalidSignerArrays_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: TransactionProposal signer arrays are not canonical.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A proposal leaves the ledger in two ways (XLS-0103 §4.5): a
|
||||
// TransactionProposalCancel deletes the proposal it names, and consuming
|
||||
// the Ticket a proposal is keyed to deletes the proposal with it, whether
|
||||
// the proposed transaction executed (or failed with a claimed-fee tec) or
|
||||
// AccountDelete swept the target's Tickets.
|
||||
std::uint32_t deletedWithTicket = 0;
|
||||
for (auto const& ticketKey : ticketsOfDeletedProposals_)
|
||||
{
|
||||
if (deletedTickets_.contains(ticketKey))
|
||||
++deletedWithTicket;
|
||||
}
|
||||
|
||||
bool const succeeded = isTesSuccess(result);
|
||||
bool effectsMatch = false;
|
||||
if (tx.getTxnType() == ttTRANSACTION_PROPOSAL_CREATE)
|
||||
{
|
||||
effectsMatch = created_ == (succeeded ? 1u : 0u) && modified_ == 0 && deleted_ == 0;
|
||||
}
|
||||
else if (tx.getTxnType() == ttTRANSACTION_PROPOSAL_CANCEL)
|
||||
{
|
||||
// A Cancel deletes one proposal and nothing else. It cannot
|
||||
// spend a reserved Ticket, so no proposal goes with its Ticket here.
|
||||
effectsMatch = created_ == 0 && modified_ == 0 && deletedWithTicket == 0 &&
|
||||
deleted_ == (succeeded ? 1u : 0u);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any other transaction deletes a proposal only together with its
|
||||
// Ticket, and modifies at most one, only as a successful
|
||||
// SponsorshipTransfer.
|
||||
// TODO: extend this when TransactionProposalSign is implemented.
|
||||
std::uint32_t const modifiable =
|
||||
tx.getTxnType() == ttSPONSORSHIP_TRANSFER && succeeded ? 1 : 0;
|
||||
effectsMatch = created_ == 0 && modified_ <= modifiable && deleted_ == deletedWithTicket;
|
||||
}
|
||||
|
||||
if (!effectsMatch)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: TransactionProposal changes do not match transaction result.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// The other direction of the deletion rule: ticketDelete deletes the
|
||||
// proposal keyed to a Ticket along with it (XLS-0103 §4.5), so no
|
||||
// proposal may outlive its Ticket, orphaned on a key that can no longer
|
||||
// be spent.
|
||||
bool const orphaned = std::ranges::any_of(
|
||||
proposalsOfDeletedTickets_, [&view](Keylet const& keylet) { return view.exists(keylet); });
|
||||
if (orphaned)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: TransactionProposal outlived the Ticket it is keyed to.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -935,8 +935,12 @@ ValidClawback::finalize(
|
||||
return !mptV2Enabled;
|
||||
}
|
||||
|
||||
// IOU balances keep 16 significant digits, so a clawback
|
||||
// much smaller than the balance changes it by a rounded
|
||||
// amount, possibly zero. Compare with the same rounded
|
||||
// subtraction the balance update performs.
|
||||
if (*afterBalance > *beforeBalance ||
|
||||
(*beforeBalance - *afterBalance) != std::min(*beforeBalance, clawAmount))
|
||||
*afterBalance != *beforeBalance - std::min(*beforeBalance, clawAmount))
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: trustline clawback balance change is invalid";
|
||||
|
||||
@@ -254,6 +254,15 @@ private:
|
||||
TAmounts<TIn, TOut> const& stepAmt,
|
||||
TOut const& ownerGives) const;
|
||||
|
||||
template <template <typename, typename> typename Offer>
|
||||
void
|
||||
tryConsumeOffer(
|
||||
PaymentSandbox& sb,
|
||||
Offer<TIn, TOut>& offer,
|
||||
TAmounts<TIn, TOut> const& ofrAmt,
|
||||
TAmounts<TIn, TOut> const& stepAmt,
|
||||
TOut const& ownerGives) const;
|
||||
|
||||
// If clobQuality is available and has a better quality then return nullopt,
|
||||
// otherwise if amm liquidity is available return AMM offer adjusted based
|
||||
// on clobQuality.
|
||||
@@ -810,10 +819,11 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
auto const amtIn = [&](TIn const& in) { return capInMPT && *maxIn_ < in ? *maxIn_ : in; };
|
||||
try
|
||||
{
|
||||
// All arithmetic in this block runs before the offer is consumed.
|
||||
// A crafted MPTokensV2 offer can overflow while transfer rates or
|
||||
// crossing limits are applied; remove that unusable offer instead
|
||||
// of letting it persist as a tecINTERNAL source.
|
||||
// crossing limits are applied, here or in the callback's
|
||||
// limitStepIn()/limitStepOut(); remove that unusable offer instead
|
||||
// of letting it persist as a tecINTERNAL source. Overflows while
|
||||
// consuming the offer are handled by tryConsumeOffer().
|
||||
stpAmt.in = mulRatio(amtIn(ofrAmt.in), ofrInRate, QUALITY_ONE, /*roundUp*/ true);
|
||||
|
||||
// owner pays the transfer fee. An MPT gross-up that doesn't fit in
|
||||
@@ -941,16 +951,9 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
removeOffer("Removing offer with overflowing amount calculation");
|
||||
return true;
|
||||
}
|
||||
// An overflow can only be produced by a crafted MPT offer, and MPT
|
||||
// offers require featureMPTokensV2 (enforced at OfferCreate
|
||||
// preflight). So the amendment is always enabled when we get here
|
||||
// and this legacy re-throw is unreachable in practice.
|
||||
// LCOV_EXCL_START
|
||||
XRPL_ASSERT(
|
||||
sb.rules().enabled(featureMPTokensV2),
|
||||
"xrpl::BookStep::forEachOffer : overflow implies MPTokensV2");
|
||||
// Legacy behavior: the exception escapes flow() and the
|
||||
// transaction fails with tecINTERNAL.
|
||||
throw;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
};
|
||||
|
||||
@@ -991,6 +994,32 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
return {offers.permToRemove(), counter.count()};
|
||||
}
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
template <template <typename, typename> typename Offer>
|
||||
void
|
||||
BookStep<TIn, TOut, TDerived>::tryConsumeOffer(
|
||||
PaymentSandbox& sb,
|
||||
Offer<TIn, TOut>& offer,
|
||||
TAmounts<TIn, TOut> const& ofrAmt,
|
||||
TAmounts<TIn, TOut> const& stepAmt,
|
||||
TOut const& ownerGives) const
|
||||
{
|
||||
try
|
||||
{
|
||||
// Under MPTokensV2, an overflow while consuming the offer fails the
|
||||
// strand instead of being handled by forEachOffer().
|
||||
consumeOffer(sb, offer, ofrAmt, stepAmt, ownerGives);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
if (sb.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
Throw<FlowException>(tecINTERNAL, "Overflow while consuming offer");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
template <template <typename, typename> typename Offer>
|
||||
void
|
||||
@@ -1192,7 +1221,7 @@ BookStep<TIn, TOut, TDerived>::revImp(
|
||||
savedOuts.insert(stpAmt.out);
|
||||
result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
|
||||
remainingOut = out - result.out;
|
||||
this->consumeOffer(sb, offer, ofrAmt, stpAmt, ownerGives);
|
||||
this->tryConsumeOffer(sb, offer, ofrAmt, stpAmt, ownerGives);
|
||||
// return true b/c even if the payment is satisfied,
|
||||
// we need to consume the offer. An offer limited by what the
|
||||
// issuer can issue is not consumed, though: its remainder is still
|
||||
@@ -1224,7 +1253,7 @@ BookStep<TIn, TOut, TDerived>::revImp(
|
||||
savedOuts.insert(remainingOut);
|
||||
result.in = sum(savedIns);
|
||||
result.out = out;
|
||||
this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
this->tryConsumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
|
||||
// Explicitly check whether the offer is funded. Given that we have
|
||||
// (stpAmt.out > remainingOut), it's natural to assume the offer
|
||||
@@ -1408,7 +1437,7 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
savedOuts = std::move(savedOutsAdj);
|
||||
result = resultAdj;
|
||||
remainingIn = in - result.in;
|
||||
this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
this->tryConsumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
|
||||
// When the mantissas of two iou amounts differ by less than ten, then
|
||||
// subtracting them leaves a result of zero. This can cause the check
|
||||
@@ -1512,6 +1541,18 @@ BookStep<TIn, TOut, TDerived>::check(StrandContext const& ctx) const
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
// An issuer paying with its own MPT issues it into the first book. A
|
||||
// holder-sourced strand records the MPT in the first MPTEndpointStep and
|
||||
// so rejects a later book that outputs it again; do the same here. The
|
||||
// step after such a book issues the MPT a second time and the payment
|
||||
// engine cannot keep the two within MaximumAmount once StrandFlow
|
||||
// rebuilds the sandbox on a limiting step.
|
||||
if (ctx.isFirst && book_.in.holds<MPTIssue>() && book_.in.getIssuer() == ctx.strandSrc &&
|
||||
ctx.view.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
ctx.seenDirectAssets[0].insert(book_.in);
|
||||
}
|
||||
|
||||
// Do not allow two books to output the same issue. This may cause offers on
|
||||
// one step to unfund offers in another step.
|
||||
if (!ctx.seenBookOuts.insert(book_.out).second ||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -874,15 +875,18 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
|
||||
// pure issue/redeem can't be frozen (issuer/holder)
|
||||
// For the first step: check global freeze of the step's own asset.
|
||||
// For the last step: check only the per-holder MPToken lock.
|
||||
// For the last step: check only the per-holder lock.
|
||||
// Global freeze of the deliver asset is not checked here
|
||||
// because MPT semantics allow issuer<->holder transfers even when globally
|
||||
// locked — only holder-to-holder DEX paths are restricted.
|
||||
// For both steps: a vault share inherits the freeze of the vault's
|
||||
// underlying asset.
|
||||
if (!(ctx.isLast && ctx.isFirst))
|
||||
{
|
||||
auto const& account = ctx.isFirst ? src_ : dst_;
|
||||
bool const frozen = (ctx.isFirst && isGlobalFrozen(ctx.view, mptIssue_)) ||
|
||||
isIndividualFrozen(ctx.view, account, mptIssue_);
|
||||
isIndividualFrozen(ctx.view, account, mptIssue_) ||
|
||||
isVaultPseudoAccountFrozen(ctx.view, account, mptIssue_, 0);
|
||||
if (frozen)
|
||||
return terLOCKED;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -110,11 +110,16 @@ EscrowCancel::doApply()
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
STAmount const amount = escrow->getFieldAmount(sfAmount);
|
||||
|
||||
auto const reserveToSubtract = calculateAdditionalReserve((*escrow)[~sfBytecode]);
|
||||
|
||||
// The return can re-create a holding the owner deleted while the escrow
|
||||
// was pending; the removed escrow must not be counted against its reserve.
|
||||
bool const recycleReserve = ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
{
|
||||
decreaseOwnerCountForObject(
|
||||
ctx_.view(), sle, escrow.mutableRawSle(), reserveToSubtract, ctx_.journal);
|
||||
}
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
@@ -160,7 +165,10 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
{
|
||||
decreaseOwnerCountForObject(
|
||||
ctx_.view(), sle, escrow.mutableRawSle(), reserveToSubtract, ctx_.journal);
|
||||
}
|
||||
|
||||
// Remove escrow from ledger
|
||||
escrow.erase();
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/conditions/Condition.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
@@ -130,6 +131,19 @@ 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(sfBytecode))
|
||||
{
|
||||
// Ten base fees in all: `Transactor::calculateBaseFee` charges one,
|
||||
// this adds nine. Plus 5 drops per byte.
|
||||
txnFees += 9 * view.fees().base + 5 * tx[sfBytecode].size();
|
||||
}
|
||||
return txnFees;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -161,12 +175,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfCancelAfter))
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
// 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[~sfBytecode])
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
|
||||
"Condition, or Bytecode.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
{
|
||||
@@ -182,6 +203,33 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfData))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate with Data requires Bytecode";
|
||||
return temMALFORMED;
|
||||
}
|
||||
auto const data = ctx.tx.getFieldVL(sfData);
|
||||
if (data.size() > kMaxWasmDataLength)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
// amendment was checked by checkExtraFeatures
|
||||
if (ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
auto const code = ctx.tx.getFieldVL(sfBytecode);
|
||||
// Protocol ceiling only; `preflight` has no view.
|
||||
if (code.empty() || code.size() > kMaxBytecodeSizeLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode bad size " << code.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -216,6 +264,30 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The limits are voted, so they can change between ledgers. A `tef`, unlike
|
||||
// a `tem`, does not mark the transaction bad for the hold time.
|
||||
if (ctx.tx.isFieldPresent(sfBytecode))
|
||||
{
|
||||
auto const& fees = ctx.view.fees();
|
||||
if (isBytecodeUploadDisabled(fees))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
|
||||
return tefTEMP_DISABLED;
|
||||
}
|
||||
|
||||
auto const code = ctx.tx.getFieldVL(sfBytecode);
|
||||
if (code.size() > fees.bytecodeSizeLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.Bytecode over voted limit " << code.size();
|
||||
return tefEXCEEDS_LIMIT;
|
||||
}
|
||||
|
||||
// TODO(SmartEscrow): screen the module itself here - that it compiles,
|
||||
// and that it imports and exports what the engine can serve - once the
|
||||
// WASM engine lands. Until then any blob of the right size is taken.
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -236,6 +308,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
auto const reserveToAdd = calculateAdditionalReserve(ctx_.tx[~sfBytecode]);
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance).xrp();
|
||||
// First check: whoever is on the hook for the new owner increment
|
||||
@@ -243,8 +316,8 @@ EscrowCreate::doApply()
|
||||
// validates the sponsor's reserve + remaining credit. When
|
||||
// unsponsored this hits the source branch and validates the
|
||||
// source's pre-lock balance against base + (currentOC+1)*increment.
|
||||
if (auto const ret =
|
||||
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = reserveToAdd}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -258,11 +331,15 @@ EscrowCreate::doApply()
|
||||
// checkReserve) because that helper diverts to the sponsor's balance
|
||||
// when a sponsor is present and would ignore the source's post-lock
|
||||
// balance entirely. ownerCountDelta differs by case:
|
||||
// - sponsored: 0 — sponsor covers the new owner increment, so the
|
||||
// source only owes reserve for its current owners.
|
||||
// - unsponsored: 1 — source owes reserve including the new increment.
|
||||
// - sponsored: 0 — sponsor covers the new owner increment, so
|
||||
// the source only owes reserve for its current owners.
|
||||
// - unsponsored: reserveToAdd — source owes reserve including the new
|
||||
// increment, which Bytecode can make larger than one unit.
|
||||
auto const sourceReserve = accountReserve(
|
||||
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
|
||||
ctx_.view(),
|
||||
sle,
|
||||
j_,
|
||||
{.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : reserveToAdd});
|
||||
if (balance - STAmount(amount).xrp() < sourceReserve)
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
@@ -289,6 +366,8 @@ EscrowCreate::doApply()
|
||||
(*escrow)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*escrow)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*escrow)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
(*escrow)[~sfBytecode] = ctx_.tx[~sfBytecode];
|
||||
(*escrow)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
@@ -356,7 +435,7 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, reserveToAdd, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), escrow.mutableRawSle());
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -27,6 +28,8 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include <variant>
|
||||
|
||||
@@ -79,7 +82,24 @@ 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;
|
||||
}
|
||||
|
||||
if (auto const allowance = ctx.tx[~sfGas]; allowance)
|
||||
{
|
||||
// Protocol ceiling only; `preflight` has no view.
|
||||
if (*allowance == 0)
|
||||
{
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
if (*allowance > kMaxGasLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Gas too large: " << *allowance;
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -148,7 +168,15 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
if (auto const allowance = tx[~sfGas]; allowance)
|
||||
{
|
||||
// The extra fee is the allowance in drops, rounded up to the nearest
|
||||
// whole drop. Widen before multiplying: both factors are 32-bit.
|
||||
std::uint64_t const allowanceFee =
|
||||
((std::uint64_t{*allowance} * view.fees().gasPrice) + microDropsPerDrop - 1) /
|
||||
microDropsPerDrop;
|
||||
extraFee += allowanceFee;
|
||||
}
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
@@ -162,25 +190,66 @@ 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 seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
|
||||
if (!escrow)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*escrow)[sfDestination];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (escrow->isFieldPresent(sfBytecode))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfGas))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Bytecode requires Gas";
|
||||
return tefBYTECODE_NOT_INCLUDED;
|
||||
}
|
||||
|
||||
// The limits are voted, so they can change between ledgers.
|
||||
// A `tef`, unlike a `tem`, does not mark the transaction bad
|
||||
// for the hold time.
|
||||
auto const& fees = ctx.view.fees();
|
||||
if (isBytecodeExecutionDisabled(fees))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting";
|
||||
return tefTEMP_DISABLED;
|
||||
}
|
||||
|
||||
if (ctx.tx[sfGas] > fees.gasLimit)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Gas over voted limit: " << ctx.tx[sfGas];
|
||||
return tefEXCEEDS_LIMIT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfGas))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Bytecode not present, "
|
||||
"Gas present";
|
||||
return tefNO_BYTECODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
AccountID const dest = (*escrow)[sfDestination];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -193,7 +262,8 @@ EscrowFinish::doApply()
|
||||
EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
|
||||
if (!escrow)
|
||||
{
|
||||
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;
|
||||
@@ -211,6 +281,20 @@ EscrowFinish::doApply()
|
||||
if ((*escrow)[~sfCancelAfter] && after(now, (*escrow)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
AccountID const destID = (*escrow)[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 (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
{
|
||||
auto const id = ctx_.tx.getTransactionID();
|
||||
@@ -264,16 +348,33 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*escrow)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
if (!ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Execute custom release function
|
||||
if ((*escrow)[~sfBytecode])
|
||||
{
|
||||
// TODO(SmartEscrow): run the escrow's contract here once the WASM
|
||||
// engine lands. That change also brings with it the reporting the run
|
||||
// produces - the gas it consumed and the code it returned, in the
|
||||
// transaction metadata - and the tecBYTECODE_REJECTED result that lets
|
||||
// a rejected escrow survive with the data the contract wrote.
|
||||
//
|
||||
// Until then an escrow carrying bytecode can be created, funded and
|
||||
// cancelled, but not finished. Reachable only under featureSmartEscrow,
|
||||
// which is Supported::No.
|
||||
JLOG(j_.debug()) << "EscrowFinish: no WASM engine to run the escrow's bytecode";
|
||||
return tecFAILED_PROCESSING;
|
||||
}
|
||||
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
|
||||
@@ -301,13 +402,18 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
auto const reserveToSubtract = calculateAdditionalReserve((*escrow)[~sfBytecode]);
|
||||
|
||||
// Delivery can auto-create the destination's holding; the removed escrow
|
||||
// must not be counted against its reserve. The two share a reserve payer
|
||||
// for a self-escrow, or when one sponsor covers both.
|
||||
bool const recycleReserve =
|
||||
ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
{
|
||||
decreaseOwnerCountForObject(
|
||||
ctx_.view(), account, escrow.mutableRawSle(), reserveToSubtract, ctx_.journal);
|
||||
}
|
||||
|
||||
STAmount const amount = escrow->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
@@ -359,7 +465,10 @@ EscrowFinish::doApply()
|
||||
ctx_.view().update(sled);
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
{
|
||||
decreaseOwnerCountForObject(
|
||||
ctx_.view(), account, escrow.mutableRawSle(), reserveToSubtract, ctx_.journal);
|
||||
}
|
||||
|
||||
// Remove escrow from ledger
|
||||
escrow.erase();
|
||||
|
||||
@@ -37,7 +37,7 @@ TransactionProposalCancel::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// Never read the zero keylet. Preflight already rejected a zero
|
||||
// ProposalID, and no real proposal can have one (every ProposalID is a
|
||||
// hash), so reaching here with zero is an internal error. (and a mathematical rarity. )
|
||||
// hash), so reaching here with zero is an internal error (and a mathematical rarity).
|
||||
if (proposalID == beast::kZero)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -894,8 +895,9 @@ public:
|
||||
auto const jNoCred = credentials::ledgerEntry(env, becky, carol, credType);
|
||||
BEAST_EXPECT(
|
||||
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
|
||||
jNoCred[jss::result].isMember(jss::error) &&
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound");
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jNoCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
testcase("Credentials that aren't required");
|
||||
@@ -918,8 +920,9 @@ public:
|
||||
|
||||
BEAST_EXPECT(
|
||||
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
|
||||
jNoCred[jss::result].isMember(jss::error) &&
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound");
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jNoCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -957,8 +960,9 @@ public:
|
||||
|
||||
BEAST_EXPECT(
|
||||
jNoCred.isObject() && jNoCred.isMember(jss::result) &&
|
||||
jNoCred[jss::result].isMember(jss::error) &&
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound");
|
||||
jNoCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jNoCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jNoCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
testcase("Expired credentials");
|
||||
@@ -994,8 +998,9 @@ public:
|
||||
auto jv = credentials::ledgerEntry(env, john, carol, credType);
|
||||
BEAST_EXPECT(
|
||||
jv.isObject() && jv.isMember(jss::result) &&
|
||||
jv[jss::result].isMember(jss::error) &&
|
||||
jv[jss::result][jss::error] == "entryNotFound");
|
||||
jv[jss::result][jss::error] == "entryNotFound" &&
|
||||
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jv[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1079,8 +1084,9 @@ public:
|
||||
auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
|
||||
BEAST_EXPECT(
|
||||
jv.isObject() && jv.isMember(jss::result) &&
|
||||
jv[jss::result].isMember(jss::error) &&
|
||||
jv[jss::result][jss::error] == "entryNotFound");
|
||||
jv[jss::result][jss::error] == "entryNotFound" &&
|
||||
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jv[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1121,8 +1127,9 @@ public:
|
||||
auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
|
||||
BEAST_EXPECT(
|
||||
jv.isObject() && jv.isMember(jss::result) &&
|
||||
jv[jss::result].isMember(jss::error) &&
|
||||
jv[jss::result][jss::error] == "entryNotFound");
|
||||
jv[jss::result][jss::error] == "entryNotFound" &&
|
||||
jv[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jv[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Batch.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -2800,6 +2801,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
|
||||
expect(
|
||||
jrr[jss::status] == "error" && jrr[jss::error] == "invalidTransaction" &&
|
||||
!jrr.isMember(jss::error_code) && jrr[jss::error_message].isNull() &&
|
||||
jrr[jss::error_exception] ==
|
||||
"fails local checks: Batch inner transactions are never "
|
||||
"considered validly signed.",
|
||||
@@ -5407,7 +5409,10 @@ class Batch_test : public beast::unit_test::Suite
|
||||
|
||||
// The delegated payment never applied.
|
||||
BEAST_EXPECT(env.balance(mallory) == preMallory);
|
||||
BEAST_EXPECT(env.rpc("tx", txIDs[1])[jss::result][jss::error] == "txnNotFound");
|
||||
auto const txResult = env.rpc("tx", txIDs[1])[jss::result];
|
||||
BEAST_EXPECT(txResult[jss::error] == "txnNotFound");
|
||||
BEAST_EXPECT(txResult[jss::error_code] == RpcTxnNotFound);
|
||||
BEAST_EXPECT(txResult[jss::error_message] == "Transaction not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5818,7 +5823,10 @@ class Batch_test : public beast::unit_test::Suite
|
||||
validateClosedLedger(env, testCases);
|
||||
|
||||
// verify jv2 is not present in the closed ledger.
|
||||
BEAST_EXPECT(env.rpc("tx", txIDs[1])[jss::result][jss::error] == "txnNotFound");
|
||||
auto const txResult = env.rpc("tx", txIDs[1])[jss::result];
|
||||
BEAST_EXPECT(txResult[jss::error] == "txnNotFound");
|
||||
BEAST_EXPECT(txResult[jss::error_code] == RpcTxnNotFound);
|
||||
BEAST_EXPECT(txResult[jss::error_message] == "Transaction not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -14,13 +15,17 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -803,6 +808,108 @@ class Clawback_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(ownerCount(env, bob) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testSubResolutionAmount(FeatureBitset features)
|
||||
{
|
||||
testcase("Sub-resolution amount");
|
||||
using namespace test::jtx;
|
||||
|
||||
// IOU balances keep 16 significant digits. When alice claws back an
|
||||
// amount much smaller than bob's balance, the balance changes by the
|
||||
// rounded amount, which can be zero, less, or more than requested. The
|
||||
// clawback invariant must accept that instead of failing the
|
||||
// transaction under MPTokensV2 or logging a failure without it.
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
struct Case
|
||||
{
|
||||
std::uint64_t balanceMantissa;
|
||||
int balanceExponent;
|
||||
std::uint64_t clawMantissa;
|
||||
int clawExponent;
|
||||
std::uint64_t afterMantissa;
|
||||
int afterExponent;
|
||||
};
|
||||
std::array const cases{
|
||||
// 1e7 - 1e-10 rounds back to 1e7
|
||||
Case{
|
||||
.balanceMantissa = 10'000'000,
|
||||
.balanceExponent = 0,
|
||||
.clawMantissa = 1,
|
||||
.clawExponent = -10,
|
||||
.afterMantissa = 10'000'000,
|
||||
.afterExponent = 0,
|
||||
},
|
||||
// 1234567890.123456 - 0.0000005 rounds back to the balance
|
||||
Case{
|
||||
.balanceMantissa = 1'234'567'890'123'456,
|
||||
.balanceExponent = -6,
|
||||
.clawMantissa = 5,
|
||||
.clawExponent = -7,
|
||||
.afterMantissa = 1'234'567'890'123'456,
|
||||
.afterExponent = -6,
|
||||
},
|
||||
// 0.0000014 removes 0.000001
|
||||
Case{
|
||||
.balanceMantissa = 1'234'567'890'123'456,
|
||||
.balanceExponent = -6,
|
||||
.clawMantissa = 14,
|
||||
.clawExponent = -7,
|
||||
.afterMantissa = 1'234'567'890'123'455,
|
||||
.afterExponent = -6,
|
||||
},
|
||||
// 0.0000016 removes 0.000002
|
||||
Case{
|
||||
.balanceMantissa = 1'234'567'890'123'456,
|
||||
.balanceExponent = -6,
|
||||
.clawMantissa = 16,
|
||||
.clawExponent = -7,
|
||||
.afterMantissa = 1'234'567'890'123'454,
|
||||
.afterExponent = -6,
|
||||
},
|
||||
};
|
||||
|
||||
for (auto const feat : {features, features - featureMPTokensV2})
|
||||
{
|
||||
for (auto const& c : cases)
|
||||
{
|
||||
std::string logs;
|
||||
{
|
||||
Env env(*this, feat, std::make_unique<test::CaptureLogs>(&logs));
|
||||
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const usd = alice["USD"];
|
||||
|
||||
env(fset(alice, asfAllowTrustLineClawback));
|
||||
env.close();
|
||||
|
||||
env.trust(usd(10'000'000'000), bob);
|
||||
env(pay(
|
||||
alice, bob, STAmount{usd.issue(), c.balanceMantissa, c.balanceExponent}));
|
||||
env.close();
|
||||
|
||||
env(claw(
|
||||
alice,
|
||||
STAmount{
|
||||
Issue{usd.currency, bob.id()}, c.clawMantissa, c.clawExponent}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, usd).value() ==
|
||||
STAmount(usd.issue(), c.afterMantissa, c.afterExponent));
|
||||
}
|
||||
// Without MPTokensV2 a failed invariant only logs and the
|
||||
// transaction still succeeds, so check the logs instead.
|
||||
if (!feat[featureMPTokensV2])
|
||||
BEAST_EXPECT(!logs.contains("Invariant failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTickets(FeatureBitset features)
|
||||
{
|
||||
@@ -872,6 +979,7 @@ class Clawback_test : public beast::unit_test::Suite
|
||||
testDeleteDefaultLine(features);
|
||||
testFrozenLine(features);
|
||||
testAmountExceedsAvailable(features);
|
||||
testSubResolutionAmount(features);
|
||||
testTickets(features);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -259,8 +260,9 @@ class ConfidentialTransferExtended_test : public ConfidentialTransferTestBase
|
||||
|
||||
auto const credentialDeleted = [&](Env& env, Account const& subject) -> bool {
|
||||
auto const credentials = credentials::ledgerEntry(env, subject, dpIssuer, credType);
|
||||
return credentials[jss::result].isMember(jss::error) &&
|
||||
credentials[jss::result][jss::error] == "entryNotFound";
|
||||
return credentials[jss::result][jss::error] == "entryNotFound" &&
|
||||
credentials[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
credentials[jss::result][jss::error_message] == "Entry not found.";
|
||||
};
|
||||
|
||||
// TEST 4: Expired credential with matching depositPreauth entry.
|
||||
|
||||
@@ -7944,6 +7944,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// Signature verification fails — rejected before ZKP check
|
||||
auto const jr = env.rpc("submit", strHex(tampered.slice()));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
|
||||
}
|
||||
|
||||
// Variant B: Re-signed with inflated ciphertext.
|
||||
@@ -8051,6 +8053,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
auto const jr = env.rpc("submit", strHex(tampered.slice()));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
|
||||
}
|
||||
|
||||
// Variant B: Re-signed with all negated ciphertexts.
|
||||
@@ -8154,6 +8158,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
auto const jr = env.rpc("submit", strHex(tampered.slice()));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
|
||||
}
|
||||
|
||||
// Variant B: Re-signed with combined ciphertext.
|
||||
@@ -8278,6 +8284,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// Signature verification fails
|
||||
auto const jr = env.rpc("submit", strHex(tampered.slice()));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
|
||||
}
|
||||
|
||||
// Variant B: Re-signed C1 substitution.
|
||||
@@ -8368,6 +8376,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// signed — transaction rejected before ZKP verification.
|
||||
auto const jr = env.rpc("submit", strHex(tampered.slice()));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jr[jss::result][jss::error_message].isNull());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpl/ledger/ApplyViewImpl.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -124,8 +125,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +175,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, issuer, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,8 +225,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
// resurrection
|
||||
@@ -259,8 +263,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
// resurrection
|
||||
@@ -294,8 +299,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
// resurrection
|
||||
@@ -331,8 +337,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
// resurrection
|
||||
@@ -368,8 +375,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,8 +398,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,8 +419,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jle.isObject() && jle.isMember(jss::result) &&
|
||||
jle[jss::result].isMember(jss::error) &&
|
||||
jle[jss::result][jss::error] == "entryNotFound");
|
||||
jle[jss::result][jss::error] == "entryNotFound" &&
|
||||
jle[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,8 +754,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType2);
|
||||
BEAST_EXPECT(
|
||||
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
|
||||
jDelCred[jss::result].isMember(jss::error) &&
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound");
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDelCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, subject) == 1);
|
||||
@@ -781,8 +792,9 @@ struct Credentials_test : public beast::unit_test::Suite
|
||||
auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
|
||||
jDelCred[jss::result].isMember(jss::error) &&
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound");
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDelCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -159,6 +160,8 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
auto const jle = delegate::entry(env, gw, alice);
|
||||
BEAST_EXPECT(jle[jss::result][jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jle[jss::result][jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jle[jss::result][jss::error_message] == "Entry not found.");
|
||||
|
||||
// alice can delegate permissions to gw as well
|
||||
env(delegate::set(alice, gw, permissions));
|
||||
@@ -167,6 +170,8 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
auto const response = delegate::entry(env, gw, alice);
|
||||
// alice has not been granted any permissions by gw
|
||||
BEAST_EXPECT(response[jss::result][jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(response[jss::result][jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(response[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -2780,9 +2785,11 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
jv[jss::secret] = bob.name();
|
||||
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::error_message].asString().contains(
|
||||
"A Signer may not be the transaction's Account"));
|
||||
jrr[jss::error_message] ==
|
||||
"A Signer may not be the transaction's Account (" + bob.human() + ").");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2843,14 +2850,6 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
{"AMMVote", featureAMM},
|
||||
{"AMMBid", featureAMM},
|
||||
{"AMMDelete", featureAMM},
|
||||
{"XChainCreateClaimID", featureXChainBridge},
|
||||
{"XChainCommit", featureXChainBridge},
|
||||
{"XChainClaim", featureXChainBridge},
|
||||
{"XChainAccountCreateCommit", featureXChainBridge},
|
||||
{"XChainAddClaimAttestation", featureXChainBridge},
|
||||
{"XChainAddAccountCreateAttestation", featureXChainBridge},
|
||||
{"XChainModifyBridge", featureXChainBridge},
|
||||
{"XChainCreateBridge", featureXChainBridge},
|
||||
{"DIDSet", featureDID},
|
||||
{"DIDDelete", featureDID},
|
||||
{"OracleSet", featurePriceOracle},
|
||||
@@ -3007,7 +3006,7 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
// DO NOT modify expectedDelegableCount unless all scenarios, including
|
||||
// edge cases, have been fully tested and verified.
|
||||
// ====================================================================
|
||||
std::size_t const expectedDelegableCount = 57;
|
||||
std::size_t const expectedDelegableCount = 49;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
delegableCount == expectedDelegableCount,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -1143,8 +1144,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
|
||||
ledgerEntryDepositPreauth(env, bob, {{.issuer = issuer, .credType = credType}});
|
||||
BEAST_EXPECT(
|
||||
jDP.isObject() && jDP.isMember(jss::result) &&
|
||||
jDP[jss::result].isMember(jss::error) &&
|
||||
jDP[jss::result][jss::error] == "entryNotFound");
|
||||
jDP[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDP[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDP[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1232,8 +1234,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
|
||||
auto const jDelCred = credentials::ledgerEntry(env, alice, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
|
||||
jDelCred[jss::result].isMember(jss::error) &&
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound");
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDelCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1283,8 +1286,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
|
||||
auto const jDelCred = credentials::ledgerEntry(env, gw, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
|
||||
jDelCred[jss::result].isMember(jss::error) &&
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound");
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDelCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, issuer) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, gw) == 0);
|
||||
@@ -1358,8 +1362,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite
|
||||
auto const jDelCred = credentials::ledgerEntry(env, zelda, issuer, credType);
|
||||
BEAST_EXPECT(
|
||||
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
|
||||
jDelCred[jss::result].isMember(jss::error) &&
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound");
|
||||
jDelCred[jss::result][jss::error] == "entryNotFound" &&
|
||||
jDelCred[jss::result][jss::error_code] == RpcEntryNotFound &&
|
||||
jDelCred[jss::result][jss::error_message] == "Entry not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,787 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/ter.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/core/StartUpType.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* Smart Escrow's `Bytecode`, `Data` and `Gas` fields.
|
||||
*
|
||||
* The WASM engine that runs an escrow's bytecode is not in this build, so the
|
||||
* `Bytecode` field is opaque here: `EscrowCreate` weighs its size and nothing
|
||||
* reads it, and `EscrowFinish` has nothing to run it with. Everything around
|
||||
* the run is testable, and is tested below - the preflight and preclaim rules,
|
||||
* the fee the bytecode costs, the owner reserve it occupies, and the refund of
|
||||
* that reserve.
|
||||
*
|
||||
* What waits on the engine: a successful finish, tecBYTECODE_REJECTED, the
|
||||
* gas and return code the run reports in the transaction metadata, and the
|
||||
* `Data` a rejected contract leaves behind.
|
||||
*/
|
||||
struct EscrowSmart_test : public beast::unit_test::Suite
|
||||
{
|
||||
// A blob of `bytes` bytes, as hex. Opens with the WASM preamble - "\0asm"
|
||||
// and version 1 - so a reader can see what the field is meant to carry,
|
||||
// but nothing in this build parses it.
|
||||
static std::string
|
||||
bytecodeOfSize(std::size_t bytes)
|
||||
{
|
||||
static std::string const kPreamble = "0061736D01000000";
|
||||
std::string hex = kPreamble;
|
||||
hex.append((bytes * 2) - kPreamble.size(), 'A');
|
||||
return hex;
|
||||
}
|
||||
|
||||
// What EscrowCreate charges for `bytecodeHex`: ten base fees, plus five
|
||||
// drops per byte.
|
||||
static XRPAmount
|
||||
createFeeFor(jtx::Env const& env, std::string const& bytecodeHex)
|
||||
{
|
||||
return env.current()->fees().base * 10 + bytecodeHex.size() / 2 * 5;
|
||||
}
|
||||
|
||||
// A validator's config that votes for `value` on the fee setting `key`.
|
||||
static std::unique_ptr<Config>
|
||||
votingFor(std::string const& key, std::uint32_t value)
|
||||
{
|
||||
auto cfg = jtx::envconfig(jtx::validator, "");
|
||||
cfg->section(Sections::kVoting).set(key, std::to_string(value));
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Close past the flag ledger, so the vote lands in FeeSettings.
|
||||
static void
|
||||
closePastFlagLedger(jtx::Env& env)
|
||||
{
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
}
|
||||
|
||||
// What EscrowFinish charges for `gas`: the gas cost rounded up to the drop.
|
||||
static XRPAmount
|
||||
finishFeeFor(jtx::Env const& env, std::uint64_t gas)
|
||||
{
|
||||
return env.current()->fees().base +
|
||||
((gas * env.current()->fees().gasPrice) + microDropsPerDrop - 1) / microDropsPerDrop;
|
||||
}
|
||||
|
||||
void
|
||||
testCreatePreflight(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowCreate preflight");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
auto const bytecode = bytecodeOfSize(64);
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(temDISABLED));
|
||||
env.close();
|
||||
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
escrow::Data("00112233"),
|
||||
Fee(txnFees),
|
||||
Ter(temDISABLED));
|
||||
env.close();
|
||||
|
||||
env(escrowCreate,
|
||||
escrow::Data("00112233"),
|
||||
escrow::kFinishTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// Bytecode > max length
|
||||
Env env(*this, votingFor(Keys::kBytecodeSizeLimit, 10), features); // 10 bytes
|
||||
closePastFlagLedger(env);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// 11-byte string
|
||||
std::string const longBytecode = "00112233445566778899AA";
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(longBytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(tefEXCEEDS_LIMIT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// gas limit set to 0
|
||||
Env env(*this, votingFor(Keys::kGasLimit, 0), features); // WASM runtime disabled
|
||||
closePastFlagLedger(env);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(tefTEMP_DISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// size limit set to 0
|
||||
Env env(
|
||||
*this, votingFor(Keys::kBytecodeSizeLimit, 0), features); // WASM upload disabled
|
||||
closePastFlagLedger(env);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// 1-byte string
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode("AA"),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(tefTEMP_DISABLED));
|
||||
env.close();
|
||||
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(tefTEMP_DISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// Data without Bytecode
|
||||
Env env(*this, features);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 100000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
std::string const data(4, 'A');
|
||||
env(escrowCreate,
|
||||
escrow::Data(data),
|
||||
escrow::kFinishTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// Data > max length
|
||||
Env env(*this, features);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 100000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// string of length (kMaxWasmDataLength + 1) * 2
|
||||
std::string const longData((kMaxWasmDataLength + 1) * 2, 'B');
|
||||
env(escrowCreate,
|
||||
escrow::Data(longData),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(txnFees),
|
||||
Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Bytecode joins FinishAfter and Condition as a way to say how the
|
||||
// escrow completes, but it always needs a CancelAfter: nothing else
|
||||
// can free the funds if the contract never accepts.
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->startUp = StartUpType::Fresh;
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
XRPAmount const txnFees = createFeeFor(env, bytecode);
|
||||
|
||||
// Success situations
|
||||
{
|
||||
// Bytecode + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 20s),
|
||||
Fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 30s),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
Fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode + FinishAfter + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 40s),
|
||||
escrow::kFinishTime(env.now() + 2s),
|
||||
Fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode + Data + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::Data("00112233"),
|
||||
escrow::kCancelTime(env.now() + 50s),
|
||||
Fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Failure situations (i.e. all other combinations)
|
||||
{
|
||||
// only Bytecode
|
||||
env(escrowCreate, escrow::Bytecode(bytecode), Fee(txnFees), Ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode + FinishAfter
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kFinishTime(env.now() + 2s),
|
||||
Fee(txnFees),
|
||||
Ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode + Condition
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
Fee(txnFees),
|
||||
Ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Bytecode 0 length
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(""),
|
||||
escrow::kCancelTime(env.now() + 60s),
|
||||
Fee(txnFees),
|
||||
Ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Not enough fees
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 70s),
|
||||
Fee(txnFees - 1),
|
||||
Ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateFeeAndReserve(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowCreate fee and reserve");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
// The fee scales with the bytecode: ten base fees plus five drops
|
||||
// per byte, and a drop short of it is refused.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const bytecode = bytecodeOfSize(1'000);
|
||||
auto const escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
auto const fee = createFeeFor(env, bytecode);
|
||||
BEAST_EXPECT(fee == env.current()->fees().base * 10 + 5'000);
|
||||
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(fee - 1),
|
||||
Ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrowCreate,
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::Data("00112233"),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(fee));
|
||||
env.close();
|
||||
|
||||
// The ledger entry carries both fields.
|
||||
auto const sle = env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)));
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfBytecode));
|
||||
BEAST_EXPECT(strHex(sle->getFieldVL(sfBytecode)) == bytecode);
|
||||
BEAST_EXPECT(strHex(sle->getFieldVL(sfData)) == "00112233");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// The reserve is one owner increment per started 500 bytes of
|
||||
// bytecode.
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
std::uint32_t expectedOwnerCount = 0;
|
||||
for (auto const size : {
|
||||
std::size_t{64},
|
||||
std::size_t{500},
|
||||
std::size_t{501},
|
||||
std::size_t{1'000},
|
||||
std::size_t{1'001},
|
||||
})
|
||||
{
|
||||
auto const bytecode = bytecodeOfSize(size);
|
||||
env(escrow::create(alice, carol, XRP(100)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 1000s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
env.close();
|
||||
|
||||
expectedOwnerCount += (size + 499) / 500;
|
||||
BEAST_EXPECTS(
|
||||
env.ownerCount(alice) == expectedOwnerCount,
|
||||
std::to_string(size) + " bytes: " + std::to_string(env.ownerCount(alice)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// A bytecode escrow the owner cannot reserve is refused, where the
|
||||
// same escrow without bytecode would have been accepted.
|
||||
Env env(*this, features);
|
||||
// Base 200 XRP + 50 XRP per owner object, from the unit-test
|
||||
// config. Enough for one owner object, not for two.
|
||||
env.fund(XRP(300), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const bytecode = bytecodeOfSize(1'000);
|
||||
env(escrow::create(alice, carol, XRP(20)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(createFeeFor(env, bytecode)),
|
||||
Ter(tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
|
||||
// The same escrow without bytecode costs one increment and fits.
|
||||
env(escrow::create(alice, carol, XRP(20)),
|
||||
escrow::kFinishTime(env.now() + 10s),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
}
|
||||
|
||||
{
|
||||
// After the lock, an XRP escrow's owner must still hold the
|
||||
// reserve the bytecode costs, not the single increment a plain
|
||||
// escrow costs.
|
||||
Env env(*this, features);
|
||||
Account const dave{"dave"};
|
||||
Account const erin{"erin"};
|
||||
env.fund(XRP(5000), carol);
|
||||
env.close();
|
||||
|
||||
auto const bytecode = bytecodeOfSize(600); // two increments
|
||||
auto const fee = createFeeFor(env, bytecode);
|
||||
auto const amount = XRP(1'000);
|
||||
auto const fees = env.current()->fees();
|
||||
|
||||
// One increment short after the lock.
|
||||
env.fund(
|
||||
STAmount{fees.accountReserve(1, 1) + amount.value().xrp() + fee}, noripple(dave));
|
||||
// Exactly enough after the lock.
|
||||
env.fund(
|
||||
STAmount{fees.accountReserve(2, 1) + amount.value().xrp() + fee}, noripple(erin));
|
||||
env.close();
|
||||
|
||||
env(escrow::create(dave, carol, amount),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(fee),
|
||||
Ter(tecUNFUNDED));
|
||||
env(escrow::create(erin, carol, amount),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(fee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(dave) == 0);
|
||||
BEAST_EXPECT(env.ownerCount(erin) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishPreflight(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish preflight");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
Fee(env.current()->fees().base + 1000),
|
||||
escrow::Gas(4),
|
||||
Ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// Gas > gas limit
|
||||
Env env(*this, votingFor(Keys::kGasLimit, 1'000), features); // in gas
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
// Gas above the voted limit is checked against the escrow it
|
||||
// would run, so there has to be one.
|
||||
auto const bytecode = bytecodeOfSize(64);
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100'000s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
closePastFlagLedger(env);
|
||||
|
||||
auto const gas = 1'001;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
Fee(finishFeeFor(env, gas)),
|
||||
escrow::Gas(gas),
|
||||
Ter(tefEXCEEDS_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
// Gas of 0
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
Fee(env.current()->fees().base + 1000),
|
||||
escrow::Gas(0),
|
||||
Ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
// Gas above the protocol ceiling, refused before any view is read
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
Fee(env.current()->fees().base + 1000),
|
||||
escrow::Gas(kMaxGasLimit + 1),
|
||||
Ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
// WASM compute disabled after the escrow was created.
|
||||
Env env(*this, votingFor(Keys::kGasLimit, 0), features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const bytecode = bytecodeOfSize(64);
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100'000s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
closePastFlagLedger(env);
|
||||
|
||||
auto const gas = 1'000;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::Gas(gas),
|
||||
Fee(finishFeeFor(env, gas)),
|
||||
Ter(tefTEMP_DISABLED));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishBytecodePairing(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish Bytecode and Gas must agree");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
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();
|
||||
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const bytecode = bytecodeOfSize(64);
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// Bytecode on the escrow, no Gas on the finish
|
||||
env(escrow::finish(carol, alice, seq), Ter(tefBYTECODE_NOT_INCLUDED));
|
||||
}
|
||||
|
||||
{
|
||||
// Gas on the finish, no Bytecode on the escrow
|
||||
auto const plainSeq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::kFinishTime(env.now() + 10s),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const gas = 100;
|
||||
env(escrow::finish(carol, alice, plainSeq),
|
||||
Fee(finishFeeFor(env, gas)),
|
||||
escrow::Gas(gas),
|
||||
Ter(tefNO_BYTECODE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishFee(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish fee");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->fees.gasPrice = 1'000'000; // 1 drop per gas
|
||||
return cfg;
|
||||
}),
|
||||
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();
|
||||
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto const bytecode = bytecodeOfSize(64);
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(1000)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
env.close();
|
||||
|
||||
// A large gas allowance costs more than the allowance itself, and does
|
||||
// not wrap.
|
||||
auto const gas = 996'433;
|
||||
auto const fee = finishFeeFor(env, gas);
|
||||
BEAST_EXPECT(fee.drops() > gas);
|
||||
// At one drop per gas the cost divides evenly, so no drop is added.
|
||||
BEAST_EXPECT(fee == env.current()->fees().base + gas);
|
||||
|
||||
// Intentional low value to test overflow handling
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
Fee(drops(30)),
|
||||
escrow::Gas(gas),
|
||||
Ter(telINSUF_FEE_P));
|
||||
|
||||
env(escrow::finish(carol, alice, seq), Fee(fee - 1), escrow::Gas(gas), Ter(telINSUF_FEE_P));
|
||||
|
||||
// TODO(SmartEscrow): tesSUCCESS once the WASM engine lands. Until then
|
||||
// the fee is charged and the run reports that it could not happen.
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
Fee(fee),
|
||||
escrow::Gas(gas),
|
||||
Ter(tecFAILED_PROCESSING));
|
||||
|
||||
{
|
||||
// A gas cost of a fraction of a drop rounds up to a whole drop:
|
||||
// two gas at one micro-drop each costs one drop, not zero, and not
|
||||
// one drop per gas.
|
||||
Env env(*this, votingFor(Keys::kGasPrice, 1), features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(1000)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 100'000s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
closePastFlagLedger(env);
|
||||
BEAST_EXPECT(env.current()->fees().gasPrice == 1);
|
||||
|
||||
auto const base = env.current()->fees().base;
|
||||
env(escrow::finish(carol, alice, seq), Fee(base), escrow::Gas(2), Ter(telINSUF_FEE_P));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
Fee(base + 1),
|
||||
escrow::Gas(2),
|
||||
Ter(tecFAILED_PROCESSING));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishWithoutEngine(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish has no WASM engine");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const bytecode = bytecodeOfSize(1'000);
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::Bytecode(bytecode),
|
||||
escrow::kCancelTime(env.now() + 20s),
|
||||
Fee(createFeeFor(env, bytecode)));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 2);
|
||||
|
||||
// TODO(SmartEscrow): the contract decides this once the WASM engine
|
||||
// lands - tesSUCCESS on accept, tecBYTECODE_REJECTED on reject.
|
||||
auto const gas = 1'000;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
Fee(finishFeeFor(env, gas)),
|
||||
escrow::Gas(gas),
|
||||
Ter(tecFAILED_PROCESSING));
|
||||
env.close();
|
||||
|
||||
// The escrow and its reserve survive.
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 2);
|
||||
|
||||
// Cancelling returns the whole reserve, not just one increment.
|
||||
env.close(30s);
|
||||
env(escrow::cancel(carol, alice, seq));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testFinishReleasesReserve(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish releases the reserve");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Without featureSponsor and fixCleanup3_4_0, the reserve is released
|
||||
// after delivery rather than before.
|
||||
Env env(*this, features - featureSponsor - fixCleanup3_4_0);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)), escrow::kFinishTime(env.now() + 1s));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
|
||||
env(escrow::finish(carol, alice, seq));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testCreatePreflight(features);
|
||||
testCreateFeeAndReserve(features);
|
||||
testFinishPreflight(features);
|
||||
testFinishBytecodePairing(features);
|
||||
testFinishFee(features);
|
||||
testFinishWithoutEngine(features);
|
||||
testFinishReleasesReserve(features);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(EscrowSmart, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1524,7 +1524,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";
|
||||
@@ -1682,6 +1682,8 @@ public:
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
testWithFeats(all - fixCleanup3_5_0);
|
||||
testWithFeats(all - featureSmartEscrow);
|
||||
testWithFeats(all - featureTokenEscrow - featureSmartEscrow);
|
||||
testTags(all - fixIncludeKeyletFields);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1215,6 +1215,61 @@ struct FlowMPT_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(env.balance(bob, y) == y(0));
|
||||
}
|
||||
|
||||
void
|
||||
testConsumeOfferOverflow(FeatureBitset features)
|
||||
{
|
||||
// An overflow while consuming an offer fails the strand under
|
||||
// MPTokensV2, and the transaction with tecINTERNAL otherwise. Either
|
||||
// way no balances change and the offer stays on the book.
|
||||
testcase("BookStep consumeOffer overflow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Account const gw("gateway");
|
||||
Account const eurGW("eur_gateway");
|
||||
Account const maker("maker");
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
|
||||
for (auto const& testFeatures : {features, features - featureMPTokensV2})
|
||||
{
|
||||
Env env(*this, testFeatures);
|
||||
|
||||
auto const withV2 = testFeatures[featureMPTokensV2];
|
||||
|
||||
env.fund(XRP(10'000), gw, eurGW, maker, alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const usd = gw["USD"];
|
||||
auto const eur = eurGW["EUR"];
|
||||
STAmount const usdMax{usd.issue(), STAmount::kMaxValue, STAmount::kMaxOffset};
|
||||
STAmount const usdIn{usd.issue(), 1, 90};
|
||||
|
||||
env.trust(usdMax, maker, alice);
|
||||
env.trust(eur(1'000), maker, bob);
|
||||
env.close();
|
||||
|
||||
env(pay(gw, maker, usdMax));
|
||||
env(pay(gw, alice, usdIn));
|
||||
env(pay(eurGW, maker, eur(100)));
|
||||
env(offer(maker, usdIn, eur(100)));
|
||||
env.close();
|
||||
|
||||
env(pay(alice, bob, eur(100)),
|
||||
Path(~eur),
|
||||
Sendmax(usdIn),
|
||||
Ter(withV2 ? TER{tecPATH_PARTIAL} : TER{tecINTERNAL}));
|
||||
env.close();
|
||||
|
||||
env.require(
|
||||
Balance(bob, eur(0)),
|
||||
Balance(maker, eur(100)),
|
||||
Balance(maker, usdMax),
|
||||
Balance(alice, usdIn));
|
||||
BEAST_EXPECT(isOffer(env, maker, usdIn, eur(100)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFalseDry(FeatureBitset features)
|
||||
{
|
||||
@@ -3008,6 +3063,7 @@ struct FlowMPT_test : public beast::unit_test::Suite
|
||||
testBookStepInputAboveMaximumAmount(features);
|
||||
testBookStepInputAboveMaximumAmountWithFee(features);
|
||||
testBookStepMissingIssuance(features);
|
||||
testConsumeOfferOverflow(features);
|
||||
testSelfPayment1(features);
|
||||
testSelfPayment2(features);
|
||||
testSelfFundedXRPEndpoint(false, features);
|
||||
@@ -3016,6 +3072,85 @@ struct FlowMPT_test : public beast::unit_test::Suite
|
||||
testReExecuteDirectStep(features);
|
||||
testSelfPayLowQualityOffer(features);
|
||||
testLockedMidPathHolder(features);
|
||||
testIssuerSourceMPTLoop(features);
|
||||
}
|
||||
|
||||
void
|
||||
testIssuerSourceMPTLoop(FeatureBitset features)
|
||||
{
|
||||
// An issuer paying with its own MPT may not route it back through a
|
||||
// book that outputs the same MPT. It would issue at the first book
|
||||
// and again after the second, and with the issuer's own offer in
|
||||
// the second book the engine could not keep the sum within
|
||||
// MaximumAmount. Holders already get temBAD_PATH_LOOP for this.
|
||||
testcase("Issuer-sourced payment looping through its own MPT");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const c1{"c1"};
|
||||
Account const e1{"e1"};
|
||||
Account const dan{"dan"};
|
||||
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(1'000), gw, alice, c1, e1, dan);
|
||||
env.close();
|
||||
|
||||
MPT const x =
|
||||
MPTTester({.env = env, .issuer = gw, .holders = {alice, c1, e1, dan}, .maxAmt = 100});
|
||||
auto const eur = gw["EUR"];
|
||||
auto const usd = gw["USD"];
|
||||
env(trust(e1, eur(1'000)));
|
||||
env(trust(e1, usd(1'000)));
|
||||
env(trust(dan, eur(1'000)));
|
||||
env(pay(gw, e1, eur(1'000)));
|
||||
env(pay(gw, e1, usd(1'000)));
|
||||
env(pay(gw, alice, x(10)));
|
||||
env.close();
|
||||
|
||||
// Liquidity for every path below, arranged so nothing crosses at
|
||||
// creation.
|
||||
env(offer(gw, XRP(60), x(30))); // issuer sells x for XRP
|
||||
env(offer(gw, usd(100), x(100))); // issuer sells x for usd
|
||||
env.close();
|
||||
env(offer(c1, x(100), XRP(100))); // buys x for XRP
|
||||
env(offer(e1, XRP(70), usd(70))); // sells usd for XRP
|
||||
env(offer(e1, x(100), eur(100))); // sells eur for x
|
||||
env.close();
|
||||
BEAST_EXPECT(expectOffers(env, gw, 2));
|
||||
BEAST_EXPECT(expectOffers(env, c1, 1));
|
||||
BEAST_EXPECT(expectOffers(env, e1, 2));
|
||||
|
||||
auto const flags = Txflags(tfNoRippleDirect | tfPartialPayment);
|
||||
|
||||
// x -> XRP -> x, the issuer's own offer feeds the endpoint
|
||||
env(pay(gw, dan, x(100)), Sendmax(x(100)), Path(~XRP, ~x), flags, Ter(temBAD_PATH_LOOP));
|
||||
// x -> XRP -> x -> eur, the issuer's own offer feeds a third book
|
||||
env(pay(gw, dan, eur(100)),
|
||||
Sendmax(x(100)),
|
||||
Path(~XRP, ~x, ~eur),
|
||||
flags,
|
||||
Ter(temBAD_PATH_LOOP));
|
||||
// x -> XRP -> usd -> x, a limiting book before the issuer's own offer
|
||||
env(pay(gw, dan, x(100)),
|
||||
Sendmax(x(100)),
|
||||
Path(~XRP, ~usd, ~x),
|
||||
flags,
|
||||
Ter(temBAD_PATH_LOOP));
|
||||
// Same loop from a holder is already rejected
|
||||
env(pay(alice, dan, x(10)), Sendmax(x(10)), Path(~XRP, ~x), flags, Ter(temBAD_PATH_LOOP));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(gw, x) == x(-10));
|
||||
BEAST_EXPECT(env.balance(c1, x) == x(0));
|
||||
BEAST_EXPECT(env.balance(dan, x) == x(0));
|
||||
|
||||
// The issuer paying with another asset may still take its own offer
|
||||
env(pay(gw, dan, x(50)), Sendmax(usd(50)), Path(~x), flags);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(dan, x) == x(50));
|
||||
BEAST_EXPECT(env.balance(gw, x) == x(-60));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
@@ -26,7 +25,6 @@
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
#include <test/jtx/xchain_bridge.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
@@ -39,6 +37,7 @@
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -994,8 +993,9 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
test::jtx::MPT const mpT1("MPT", makeMptID(env.seq(alice) + 10, alice));
|
||||
err = !mpTokensV2 ? Ter(temMALFORMED) : Ter(tecOBJECT_NOT_FOUND);
|
||||
env(pay(alice, carol, mpT1(100)), Sendmax(mpt(100)), err);
|
||||
// "paths" is invalid in V1
|
||||
err = !mpTokensV2 ? Ter(temMALFORMED) : Ter(tesSUCCESS);
|
||||
// "paths" is invalid in V1. In V2 any path from the issuer back
|
||||
// to its own MPT is a loop, like it is for a holder.
|
||||
err = !mpTokensV2 ? Ter(temMALFORMED) : Ter(temBAD_PATH_LOOP);
|
||||
env(pay(alice, carol, mpt(100)), Path(~usd), err);
|
||||
}
|
||||
|
||||
@@ -1019,6 +1019,7 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
payment[jss::build_path] = true;
|
||||
auto jrr = env.rpc("json", "submit", to_string(payment));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::result][jss::error_message] ==
|
||||
"Field 'build_path' not allowed in this context.");
|
||||
@@ -1580,6 +1581,8 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
jv[jss::tx_json][jss::Amount][jss::value] = std::to_string(kMaxMpTokenAmount + 1);
|
||||
auto const jrr = env.rpc("json", "submit", to_string(jv));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Invalid field 'tx_json.Amount'.");
|
||||
}
|
||||
|
||||
// Pay maximum amount with the transfer fee, SendMax, and
|
||||
@@ -1929,7 +1932,6 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
Account const carol("carol");
|
||||
MPTIssue const issue(makeMptID(1, alice));
|
||||
STAmount mpt{issue, UINT64_C(100)};
|
||||
auto const jvb = bridge(alice, usd, alice, usd);
|
||||
for (auto const& feature : {features, features - featureMPTokensV1})
|
||||
{
|
||||
Env env{*this, feature};
|
||||
@@ -1944,6 +1946,8 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
jtx.stx->add(s);
|
||||
auto jrr = env.rpc("submit", strHex(s.slice()));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jrr[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_message].isNull());
|
||||
|
||||
// tx is unsigned
|
||||
json::Value jv1;
|
||||
@@ -1951,9 +1955,13 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
jv1[jss::tx_json] = jv;
|
||||
jrr = env.rpc("json", "submit", to_string(jv1));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Amount can not be MPT.");
|
||||
|
||||
jrr = env.rpc("json", "sign", to_string(jv1));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_message] == "Amount can not be MPT.");
|
||||
};
|
||||
auto setMPTFields = [&](SField const& field, json::Value& jv, bool withAmount = true) {
|
||||
jv[jss::Asset] = toJson(xrpIssue());
|
||||
@@ -2075,70 +2083,6 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
};
|
||||
trustSet(sfLimitAmount);
|
||||
trustSet(sfFee);
|
||||
// XChainCommit
|
||||
{
|
||||
json::Value const jv = xchainCommit(alice, jvb, 1, mpt);
|
||||
test(jv, jss::Amount.cStr());
|
||||
}
|
||||
// XChainClaim
|
||||
{
|
||||
json::Value const jv = xchainClaim(alice, jvb, 1, mpt, alice);
|
||||
test(jv, jss::Amount.cStr());
|
||||
}
|
||||
// XChainCreateClaimID
|
||||
{
|
||||
json::Value const jv = xchainCreateClaimId(alice, jvb, mpt, alice);
|
||||
test(jv, sfSignatureReward.fieldName);
|
||||
}
|
||||
// XChainAddClaimAttestation
|
||||
{
|
||||
json::Value const jv =
|
||||
claimAttestation(alice, jvb, alice, mpt, alice, true, 1, alice, Signer(alice));
|
||||
test(jv, jss::Amount.cStr());
|
||||
}
|
||||
// XChainAddAccountCreateAttestation
|
||||
{
|
||||
json::Value jv = createAccountAttestation(
|
||||
alice, jvb, alice, mpt, XRP(10), alice, false, 1, alice, Signer(alice));
|
||||
for (auto const& field : {sfAmount.fieldName, sfSignatureReward.fieldName})
|
||||
{
|
||||
jv[field] = mpt.getJson(JsonOptions::Values::None);
|
||||
test(jv, field);
|
||||
}
|
||||
}
|
||||
// XChainAccountCreateCommit
|
||||
{
|
||||
json::Value jv = sidechainXchainAccountCreate(alice, jvb, alice, mpt, XRP(10));
|
||||
for (auto const& field : {sfAmount.fieldName, sfSignatureReward.fieldName})
|
||||
{
|
||||
jv[field] = mpt.getJson(JsonOptions::Values::None);
|
||||
test(jv, field);
|
||||
}
|
||||
}
|
||||
// XChain[Create|Modify]Bridge
|
||||
auto const bridgeTx = [&](json::StaticString const& tt,
|
||||
STAmount const& rewardAmount,
|
||||
STAmount const& minAccountAmount,
|
||||
std::string const& field) {
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = tt;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[sfXChainBridge.fieldName] = jvb;
|
||||
jv[sfSignatureReward.fieldName] = rewardAmount.getJson(JsonOptions::Values::None);
|
||||
jv[sfMinAccountCreateAmount.fieldName] =
|
||||
minAccountAmount.getJson(JsonOptions::Values::None);
|
||||
test(jv, field);
|
||||
};
|
||||
auto reward = STAmount{sfSignatureReward, mpt};
|
||||
auto minAmount = STAmount{sfMinAccountCreateAmount, usd(10)};
|
||||
for (SField const& field :
|
||||
{std::ref(sfSignatureReward), std::ref(sfMinAccountCreateAmount)})
|
||||
{
|
||||
bridgeTx(jss::XChainCreateBridge, reward, minAmount, field.fieldName);
|
||||
bridgeTx(jss::XChainModifyBridge, reward, minAmount, field.fieldName);
|
||||
reward = STAmount{sfSignatureReward, usd(10)};
|
||||
minAmount = STAmount{sfMinAccountCreateAmount, mpt};
|
||||
}
|
||||
// SponsorshipSet
|
||||
{
|
||||
json::Value jv;
|
||||
@@ -3197,6 +3141,9 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
jv1[jss::tx_json] = jv;
|
||||
auto const jrr = env.rpc("json", "submit", to_string(jv1));
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::result][jss::error_message] == "Field 'tx_json.Amount' has invalid data.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
@@ -586,6 +587,7 @@ public:
|
||||
auto jrr = env.rpc("json", "sign_for", to_string(jvOne))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::error_message] ==
|
||||
"When multi-signing 'tx_json.SigningPubKey' must be empty.");
|
||||
@@ -613,6 +615,7 @@ public:
|
||||
jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::error_message] == "Invalid Fee field. Fees must be greater than zero.");
|
||||
}
|
||||
@@ -639,6 +642,7 @@ public:
|
||||
jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "internal");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInternal);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Internal error.");
|
||||
}
|
||||
|
||||
@@ -651,6 +655,8 @@ public:
|
||||
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "masterDisabled");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcMasterDisabled);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Master key is disabled.");
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
@@ -690,12 +696,16 @@ public:
|
||||
auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "srcActMalformed");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcActMalformed);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'tx_json.Account'.");
|
||||
|
||||
Account const jimmy{"jimmy"};
|
||||
jv[jss::tx_json][jss::Account] = jimmy.human();
|
||||
jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "srcActNotFound");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcSrcActNotFound);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Source account not found.");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -706,6 +716,7 @@ public:
|
||||
auto jrr = env.rpc("json", "submit_multisigned", to_string(jv))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "error");
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "tx_json.Signers array may not be empty.");
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
@@ -1068,6 +1079,9 @@ public:
|
||||
STTx local = *tx.stx;
|
||||
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Empty SigningPubKey.");
|
||||
@@ -1082,6 +1096,9 @@ public:
|
||||
local.setFieldVL(sfTxnSignature, badSig);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid signature.");
|
||||
@@ -1095,6 +1112,9 @@ public:
|
||||
local.setFieldU32(sfSequence, seq + 1);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid signature.");
|
||||
@@ -1105,6 +1125,9 @@ public:
|
||||
STTx local = *tx.stx;
|
||||
local[sfSigningPubKey] = alice.pk(); // Insert sfSigningPubKey
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Cannot both single- and multi-sign.");
|
||||
@@ -1116,6 +1139,9 @@ public:
|
||||
local.sign(alice.pk(), alice.sk());
|
||||
local.setFieldVL(sfSigningPubKey, Blob()); // Empty SigningPubKey
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Cannot both single- and multi-sign.");
|
||||
@@ -1131,6 +1157,9 @@ public:
|
||||
signer.setFieldVL(sfTxnSignature, badSig);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception].asString().contains(
|
||||
"Invalid signature on account r"));
|
||||
@@ -1141,6 +1170,9 @@ public:
|
||||
STTx local = *tx.stx;
|
||||
local.peekFieldArray(sfSigners).clear(); // Empty Signers array.
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid Signers array size.");
|
||||
@@ -1186,6 +1218,9 @@ public:
|
||||
bogie_));
|
||||
STTx const local = *tx.stx;
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid Signers array size.");
|
||||
@@ -1195,6 +1230,9 @@ public:
|
||||
JTx const tx = env.jt(noop(alice), Fee(2 * baseFee), Msig(alice));
|
||||
STTx const local = *tx.stx;
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Invalid multisigner.");
|
||||
@@ -1204,6 +1242,9 @@ public:
|
||||
JTx const tx = env.jt(noop(alice), Fee(2 * baseFee), Msig(bogie_, bogie_));
|
||||
STTx const local = *tx.stx;
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Duplicate Signers not allowed.");
|
||||
@@ -1217,6 +1258,9 @@ public:
|
||||
std::ranges::reverse(signers);
|
||||
// Signature should fail.
|
||||
auto const info = submitSTTx(local);
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!info[jss::result].isMember(jss::error_code));
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
info[jss::result][jss::error_exception] ==
|
||||
"fails local checks: Unsorted Signers array.");
|
||||
|
||||
+203
-12
@@ -19,17 +19,24 @@
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/RPCHandler.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -40,8 +47,12 @@
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/nft.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -4653,18 +4664,22 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
|
||||
if (expect(
|
||||
nftOffers.isMember(jss::result), "expected \"result\"", __FILE__, line))
|
||||
{
|
||||
if (expect(
|
||||
nftOffers[jss::result].isMember(jss::error),
|
||||
"expected \"error\"",
|
||||
__FILE__,
|
||||
line))
|
||||
{
|
||||
expect(
|
||||
nftOffers[jss::result][jss::error].asString() == "objectNotFound",
|
||||
"expected \"objectNotFound\"",
|
||||
__FILE__,
|
||||
line);
|
||||
}
|
||||
json::Value const& result = nftOffers[jss::result];
|
||||
expect(
|
||||
result[jss::error].asString() == "objectNotFound",
|
||||
"expected \"objectNotFound\"",
|
||||
__FILE__,
|
||||
line);
|
||||
expect(
|
||||
result[jss::error_code] == RpcObjectNotFound,
|
||||
"expected RpcObjectNotFound",
|
||||
__FILE__,
|
||||
line);
|
||||
expect(
|
||||
result[jss::error_message] == "The requested object was not found.",
|
||||
"expected \"The requested object was not found.\"",
|
||||
__FILE__,
|
||||
line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -4860,11 +4875,15 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
|
||||
// Sell-side marker passed to nft_buy_offers.
|
||||
json::Value const result = queryWithMarker("nft_buy_offers", sellOfferIndex);
|
||||
BEAST_EXPECT(result[jss::error].asString() == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
{
|
||||
// Buy-side marker passed to nft_sell_offers.
|
||||
json::Value const result = queryWithMarker("nft_sell_offers", buyOfferIndex);
|
||||
BEAST_EXPECT(result[jss::error].asString() == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
|
||||
// A same-side marker is still accepted. With a single offer on each
|
||||
@@ -4879,6 +4898,176 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNftXxxOffersInvalidParams(FeatureBitset features)
|
||||
{
|
||||
testcase("nft_buy_offers and nft_sell_offers invalid params");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const buyer{"buyer"};
|
||||
|
||||
env.fund(XRP(10000), issuer, buyer);
|
||||
env.close();
|
||||
|
||||
UInt256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
|
||||
env(token::mint(issuer, 0), Txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
env(token::createOffer(issuer, nftID, XRP(100)), Txflags(tfSellNFToken));
|
||||
env(token::createOffer(issuer, nftID, XRP(200)), Txflags(tfSellNFToken));
|
||||
env(token::createOffer(buyer, nftID, XRP(50)), token::Owner(issuer));
|
||||
env(token::createOffer(buyer, nftID, XRP(60)), token::Owner(issuer));
|
||||
env.close();
|
||||
|
||||
for (auto const* request : {"nft_buy_offers", "nft_sell_offers"})
|
||||
{
|
||||
auto const query = [&env, request](json::Value const& params) {
|
||||
return env.rpc("json", request, to_string(params))[jss::result];
|
||||
};
|
||||
auto const withField = [&nftID](json::StaticString field, json::Value value) {
|
||||
json::Value params;
|
||||
params[jss::nft_id] = to_string(nftID);
|
||||
params[field] = std::move(value);
|
||||
return params;
|
||||
};
|
||||
auto const expectError = [this](json::Value const& result, std::string const& message) {
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == message);
|
||||
};
|
||||
|
||||
expectError(query(json::ValueType::Object), "Missing field 'nft_id'.");
|
||||
for (json::Value const& nftId : {
|
||||
json::Value{"NOTHEX"},
|
||||
json::Value{json::ValueType::Array},
|
||||
json::Value{json::ValueType::Object},
|
||||
})
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::nft_id] = nftId;
|
||||
expectError(query(params), "Invalid field 'nft_id'.");
|
||||
}
|
||||
|
||||
expectError(
|
||||
query(withField(jss::limit, "1")), "Invalid field 'limit', not unsigned integer.");
|
||||
expectError(
|
||||
query(withField(jss::limit, -1)), "Invalid field 'limit', not unsigned integer.");
|
||||
expectError(query(withField(jss::limit, 0)), "Invalid field 'limit'.");
|
||||
{
|
||||
json::Value const result = query(withField(jss::limit, json::ValueType::Null));
|
||||
BEAST_EXPECT(!result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::offers].size() == 2);
|
||||
}
|
||||
{
|
||||
json::Value const result = query(withField(jss::limit, 1));
|
||||
BEAST_EXPECT(!result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::offers].size() == 1);
|
||||
BEAST_EXPECT(result[jss::limit] == 1);
|
||||
BEAST_EXPECT(result.isMember(jss::marker));
|
||||
}
|
||||
|
||||
expectError(query(withField(jss::marker, 5)), "Invalid field 'marker', not string.");
|
||||
expectError(query(withField(jss::marker, "NOTHEX")), "Invalid parameters.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNftXxxOffersNonAdminLimit(FeatureBitset features)
|
||||
{
|
||||
testcase("nft_buy_offers and nft_sell_offers non-admin limit");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const buyer{"buyer"};
|
||||
|
||||
env.fund(XRP(1000000), issuer, buyer);
|
||||
env.close();
|
||||
|
||||
UInt256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
|
||||
env(token::mint(issuer, 0), Txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
auto const& range = rpc::tuning::kNftOffers;
|
||||
for (auto i = 1u; i <= range.rmax + 1; ++i)
|
||||
{
|
||||
env(token::createOffer(issuer, nftID, XRP(i)), Txflags(tfSellNFToken));
|
||||
env(token::createOffer(buyer, nftID, XRP(i)), token::Owner(issuer));
|
||||
if (i % 5 == 0)
|
||||
env.close();
|
||||
}
|
||||
env.close();
|
||||
|
||||
auto const userQuery = [this, &env, &nftID](char const* request, unsigned int limit) {
|
||||
auto& app = env.app();
|
||||
resource::Charge loadType = resource::kFeeReferenceRpc;
|
||||
resource::Consumer c;
|
||||
rpc::JsonContext context{
|
||||
{
|
||||
.j = env.journal,
|
||||
.app = app,
|
||||
.loadType = loadType,
|
||||
.netOps = app.getOPs(),
|
||||
.ledgerMaster = app.getLedgerMaster(),
|
||||
.consumer = c,
|
||||
.role = Role::USER,
|
||||
.coro = {},
|
||||
.infoSub = {},
|
||||
.apiVersion = rpc::kApiVersionIfUnspecified,
|
||||
},
|
||||
{},
|
||||
// NOLINTNEXTLINE(readability-trailing-comma) fix-it drops comma after `{}`
|
||||
{},
|
||||
};
|
||||
|
||||
json::Value params;
|
||||
params[jss::command] = request;
|
||||
params[jss::nft_id] = to_string(nftID);
|
||||
params[jss::limit] = limit;
|
||||
|
||||
json::Value result;
|
||||
Gate g;
|
||||
auto const job =
|
||||
app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
|
||||
context.params = std::move(params);
|
||||
context.coro = coro;
|
||||
rpc::doCommand(context, result);
|
||||
g.signal();
|
||||
});
|
||||
if (!BEAST_EXPECT(job))
|
||||
return result;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
BEAST_EXPECT(g.waitFor(5s));
|
||||
job->join();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
for (auto const* request : {"nft_buy_offers", "nft_sell_offers"})
|
||||
{
|
||||
{
|
||||
json::Value const result = userQuery(request, 1);
|
||||
BEAST_EXPECT(!result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::offers].size() == range.rmin);
|
||||
BEAST_EXPECT(result[jss::limit] == range.rmin);
|
||||
BEAST_EXPECT(result.isMember(jss::marker));
|
||||
}
|
||||
{
|
||||
json::Value const result = userQuery(request, range.rmax * 2);
|
||||
BEAST_EXPECT(!result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::offers].size() == range.rmax);
|
||||
BEAST_EXPECT(result[jss::limit] == range.rmax);
|
||||
BEAST_EXPECT(result.isMember(jss::marker));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNFTokenNegOffer(FeatureBitset features)
|
||||
{
|
||||
@@ -7601,6 +7790,8 @@ protected:
|
||||
testNFTokenDeleteAccount(features);
|
||||
testNftXxxOffers(features);
|
||||
testNftXxxOffersMarkerWrongSide(features);
|
||||
testNftXxxOffersInvalidParams(features);
|
||||
testNftXxxOffersNonAdminLimit(features);
|
||||
testNFTokenNegOffer(features);
|
||||
testIOUWithTransferFee(features);
|
||||
testBrokeredSaleToSelf(features);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -2755,6 +2756,8 @@ public:
|
||||
|
||||
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
|
||||
env.require(Owners(alice, 1), Owners(bob, 1));
|
||||
}
|
||||
@@ -2788,6 +2791,8 @@ public:
|
||||
|
||||
auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
};
|
||||
testHelper3TokensMix(test);
|
||||
}
|
||||
@@ -2847,6 +2852,8 @@ public:
|
||||
// Verify the offer was consumed
|
||||
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
|
||||
// verify balances look right after the partial payment
|
||||
// only 300 XRP should have been payed since that's all
|
||||
@@ -6557,57 +6564,46 @@ public:
|
||||
|
||||
// tfFillOrKill, TakerPays must be filled
|
||||
{
|
||||
TER const err = features[fixFillOrKill] ? TER(tesSUCCESS) : tecKILLED;
|
||||
|
||||
env(offer(maker, XRP(100), usd(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, usd(100), XRP(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, usd(100), XRP(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (err == tesSUCCESS)
|
||||
{
|
||||
makerUSDBalance -= usd(100);
|
||||
takerUSDBalance += usd(100);
|
||||
makerXRPBalance += XRP(100).value();
|
||||
takerXRPBalance -= XRP(100).value();
|
||||
}
|
||||
makerUSDBalance -= usd(100);
|
||||
takerUSDBalance += usd(100);
|
||||
makerXRPBalance += XRP(100).value();
|
||||
takerXRPBalance -= XRP(100).value();
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
|
||||
env(offer(maker, usd(100), XRP(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, XRP(100), usd(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, XRP(100), usd(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (err == tesSUCCESS)
|
||||
{
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerXRPBalance -= XRP(100).value();
|
||||
takerXRPBalance += XRP(100).value();
|
||||
}
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerXRPBalance -= XRP(100).value();
|
||||
takerXRPBalance += XRP(100).value();
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
|
||||
env(offer(maker, usd(100), eur(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, eur(100), usd(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, eur(100), usd(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (err == tesSUCCESS)
|
||||
{
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerEURBalance -= eur(100);
|
||||
takerEURBalance += eur(100);
|
||||
}
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerEURBalance -= eur(100);
|
||||
takerEURBalance += eur(100);
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
}
|
||||
|
||||
|
||||
+24
-64
@@ -30,6 +30,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -1707,6 +1708,8 @@ public:
|
||||
|
||||
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
|
||||
env.require(Owners(alice, 1), Owners(bob, 1));
|
||||
}
|
||||
@@ -1738,6 +1741,8 @@ public:
|
||||
|
||||
auto jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1798,6 +1803,8 @@ public:
|
||||
// Verify the offer was consumed
|
||||
jro = ledgerEntryOffer(env, bob, bobOfferSeq);
|
||||
BEAST_EXPECT(jro[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jro[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jro[jss::error_message] == "Entry not found.");
|
||||
|
||||
// verify balances look right after the partial payment
|
||||
// only 300 XRP should be have been payed since that's all
|
||||
@@ -5228,59 +5235,48 @@ public:
|
||||
auto makerXRPBalance = env.balance(maker, XRP).value();
|
||||
auto takerXRPBalance = env.balance(taker, XRP).value();
|
||||
|
||||
// tfFillOrKill, TakerPays must be filled
|
||||
// TakerPays must be filled
|
||||
{
|
||||
TER const err = features[fixFillOrKill] ? TER(tesSUCCESS) : tecKILLED;
|
||||
|
||||
env(offer(maker, XRP(100), usd(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, usd(100), XRP(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, usd(100), XRP(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (isTesSuccess(err))
|
||||
{
|
||||
makerUSDBalance -= usd(100);
|
||||
takerUSDBalance += usd(100);
|
||||
makerXRPBalance += XRP(100).value();
|
||||
takerXRPBalance -= XRP(100).value();
|
||||
}
|
||||
makerUSDBalance -= usd(100);
|
||||
takerUSDBalance += usd(100);
|
||||
makerXRPBalance += XRP(100).value();
|
||||
takerXRPBalance -= XRP(100).value();
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
|
||||
env(offer(maker, usd(100), XRP(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, XRP(100), usd(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, XRP(100), usd(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (isTesSuccess(err))
|
||||
{
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerXRPBalance -= XRP(100).value();
|
||||
takerXRPBalance += XRP(100).value();
|
||||
}
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerXRPBalance -= XRP(100).value();
|
||||
takerXRPBalance += XRP(100).value();
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
|
||||
env(offer(maker, usd(100), eur(100)));
|
||||
env.close();
|
||||
|
||||
env(offer(taker, eur(100), usd(101)), Txflags(tfFillOrKill), Ter(err));
|
||||
env(offer(taker, eur(100), usd(101)), Txflags(tfFillOrKill));
|
||||
env.close();
|
||||
|
||||
makerXRPBalance -= txFee(env, 1);
|
||||
takerXRPBalance -= txFee(env, 1);
|
||||
if (isTesSuccess(err))
|
||||
{
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerEURBalance -= eur(100);
|
||||
takerEURBalance += eur(100);
|
||||
}
|
||||
makerUSDBalance += usd(100);
|
||||
takerUSDBalance -= usd(100);
|
||||
makerEURBalance -= eur(100);
|
||||
takerEURBalance += eur(100);
|
||||
BEAST_EXPECT(expectOffers(env, taker, 0));
|
||||
}
|
||||
|
||||
@@ -5440,47 +5436,11 @@ public:
|
||||
run() override
|
||||
{
|
||||
testAll(allFeatures - featurePermissionedDEX);
|
||||
testAll(allFeatures);
|
||||
testFalseAssert();
|
||||
}
|
||||
};
|
||||
|
||||
class OfferWOSmallQOffers_test : public OfferBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testAll(allFeatures - fixFillOrKill - featurePermissionedDEX);
|
||||
}
|
||||
};
|
||||
|
||||
class OfferAllFeatures_test : public OfferBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testAll(allFeatures);
|
||||
}
|
||||
};
|
||||
|
||||
class Offer_manual_test : public OfferBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
FeatureBitset const fillOrKill{fixFillOrKill};
|
||||
FeatureBitset const permDEX{featurePermissionedDEX};
|
||||
|
||||
testAll(all - fillOrKill - permDEX);
|
||||
testAll(all - permDEX);
|
||||
testAll(all);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, app, xrpl, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, app, xrpl, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, app, xrpl, 2);
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, app, xrpl, 20);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PathAsset.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -162,7 +163,9 @@ public:
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.waitFor(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::error] == "srcCurMalformed");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcSrcCurMalformed);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Source currency is malformed.");
|
||||
|
||||
// Test rpc::tuning::max_auto_src_cur source currencies.
|
||||
numSrc.clear();
|
||||
@@ -189,7 +192,9 @@ public:
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.waitFor(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::error] == "internal");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcInternal);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Internal error.");
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -318,7 +319,9 @@ public:
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.waitFor(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::error] == "srcCurMalformed");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcSrcCurMalformed);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Source currency is malformed.");
|
||||
|
||||
// Test rpc::tuning::max_auto_src_cur source currencies.
|
||||
for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i)
|
||||
@@ -341,7 +344,9 @@ public:
|
||||
g.signal();
|
||||
});
|
||||
BEAST_EXPECT(g.waitFor(5s));
|
||||
BEAST_EXPECT(result.isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::error] == "internal");
|
||||
BEAST_EXPECT(result[jss::error_code] == RpcInternal);
|
||||
BEAST_EXPECT(result[jss::error_message] == "Internal error.");
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
@@ -1009,6 +1010,7 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const jrr =
|
||||
env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
|
||||
};
|
||||
|
||||
@@ -1028,6 +1030,7 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const jrr =
|
||||
env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'destination_account'.");
|
||||
};
|
||||
|
||||
@@ -1038,6 +1041,55 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
testInvalidDestAccountParam(json::Value(json::ValueType::Object));
|
||||
testInvalidDestAccountParam(json::Value(json::ValueType::Array));
|
||||
}
|
||||
{
|
||||
// An empty destination_account is no destination at all.
|
||||
json::Value params;
|
||||
params[jss::account] = alice.human();
|
||||
params[jss::destination_account] = "";
|
||||
auto const jrr = env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::channels].size() == 1);
|
||||
BEAST_EXPECT(jrr[jss::channels][0u][jss::channel_id] == chan1Str);
|
||||
BEAST_EXPECT(jrr[jss::channels][0u][jss::destination_account] == bob.human());
|
||||
}
|
||||
{
|
||||
// A malformed account is reported without the ledger fields.
|
||||
json::Value params;
|
||||
params[jss::account] = "llIIOO";
|
||||
auto const jrr = env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::error] == "actMalformed");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcActMalformed);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
|
||||
BEAST_EXPECT(!jrr.isMember(jss::ledger_index));
|
||||
BEAST_EXPECT(!jrr.isMember(jss::ledger_current_index));
|
||||
}
|
||||
{
|
||||
// A bad limit is reported before an account that is not found.
|
||||
json::Value params;
|
||||
params[jss::account] = Account("bogie").human();
|
||||
params[jss::limit] = 0;
|
||||
auto const jrr = env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit'.");
|
||||
}
|
||||
{
|
||||
// A non-string marker names the field; one that does not parse does not.
|
||||
json::Value params;
|
||||
params[jss::account] = alice.human();
|
||||
params[jss::marker] = 1;
|
||||
auto const notString =
|
||||
env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(notString[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(notString[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(notString[jss::error_message] == "Invalid field 'marker', not string.");
|
||||
|
||||
params[jss::marker] = "NOT_A_MARKER";
|
||||
auto const malformed =
|
||||
env.rpc("json", "account_channels", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(malformed[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(malformed[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(malformed[jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
{
|
||||
auto const r = env.rpc("account_channels", alice.human(), bob.human());
|
||||
BEAST_EXPECT(r[jss::result][jss::channels].size() == 1);
|
||||
@@ -1181,7 +1233,34 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
{
|
||||
// degenerate case
|
||||
auto const r = testLimit(env, alice, 0);
|
||||
BEAST_EXPECT(r.isMember(jss::error_message));
|
||||
BEAST_EXPECT(r[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(r[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(r[jss::error_message] == "Invalid field 'limit'.");
|
||||
}
|
||||
|
||||
{
|
||||
// The marker's start hint may have a leading '+', and anything after a second
|
||||
// comma is ignored.
|
||||
auto const first = testLimit(env, alice, 1);
|
||||
auto const marker = first[jss::marker].asString();
|
||||
auto const comma = marker.find(',');
|
||||
BEAST_EXPECT(comma != std::string::npos);
|
||||
|
||||
auto const rest = testLimit(
|
||||
env,
|
||||
alice,
|
||||
std::nullopt,
|
||||
marker.substr(0, comma) + ",+" + marker.substr(comma + 1) + ",ignored");
|
||||
BEAST_EXPECT(rest[jss::channels].size() == bobs.size() - 1);
|
||||
|
||||
std::set<std::string> found{
|
||||
first[jss::channels][0u][jss::destination_account].asString(),
|
||||
};
|
||||
|
||||
for (auto const& c : rest[jss::channels])
|
||||
BEAST_EXPECT(found.insert(c[jss::destination_account].asString()).second);
|
||||
|
||||
BEAST_EXPECT(found == bobsB58);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1250,7 +1329,12 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const rs =
|
||||
env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
auto const error = apiVersion < 2u ? "invalidParams" : "badKeyType";
|
||||
auto const errorCode = apiVersion < 2u ? RpcInvalidParams : RpcBadKeyType;
|
||||
auto const errorMessage =
|
||||
apiVersion < 2u ? "Invalid field 'key_type'." : "Bad key type.";
|
||||
BEAST_EXPECT(rs[jss::error] == error);
|
||||
BEAST_EXPECT(rs[jss::error_code] == errorCode);
|
||||
BEAST_EXPECT(rs[jss::error_message] == errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1339,22 +1423,40 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const pkAsHex = sliceToHex(pk.slice());
|
||||
auto rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000x", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 ", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, " ", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1,000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, " 1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1Str, "", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel amount is malformed.");
|
||||
}
|
||||
{
|
||||
// malformed channel
|
||||
@@ -1363,22 +1465,34 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
chan1StrBad.pop_back();
|
||||
auto rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
|
||||
chan1StrBad = chan1Str;
|
||||
chan1StrBad.push_back('0');
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
|
||||
chan1StrBad = chan1Str;
|
||||
chan1StrBad.back() = 'x';
|
||||
rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig);
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Payment channel is malformed.");
|
||||
}
|
||||
{
|
||||
// channel_id is not a string.
|
||||
@@ -1391,6 +1505,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const ra =
|
||||
env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(ra[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(ra[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(ra[jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
{
|
||||
// channel_id is not a string.
|
||||
@@ -1403,6 +1519,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
auto const rv =
|
||||
env.rpc("json", "channel_verify", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rv[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(rv[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(rv[jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
{
|
||||
// give an ill formed base 58 public key
|
||||
@@ -1461,6 +1579,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
// Try to authorize using an unknown key type, except an error:
|
||||
auto const rs1 = env.rpc("channel_authorize", "charlie", "nyx", chan, "1000");
|
||||
BEAST_EXPECT(rs1[jss::error] == "badKeyType");
|
||||
BEAST_EXPECT(rs1[jss::error_code] == RpcBadKeyType);
|
||||
BEAST_EXPECT(rs1[jss::error_message] == "Bad key type.");
|
||||
|
||||
// Try to authorize using secp256k1; the authorization _should_
|
||||
// succeed but the verification should fail:
|
||||
@@ -1486,10 +1606,16 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
// send malformed amounts rpc requests
|
||||
auto rs = env.rpc("channel_authorize", "alice", chan1Str, "1000x");
|
||||
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rs = env.rpc("channel_authorize", "alice", chan1Str, "x1000");
|
||||
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
|
||||
rs = env.rpc("channel_authorize", "alice", chan1Str, "x");
|
||||
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
|
||||
{
|
||||
// Missing channel_id
|
||||
json::Value args{json::ValueType::Object};
|
||||
@@ -1498,6 +1624,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'channel_id'.");
|
||||
}
|
||||
{
|
||||
// Missing amount
|
||||
@@ -1507,6 +1635,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'amount'.");
|
||||
}
|
||||
{
|
||||
// Missing key_type and no secret.
|
||||
@@ -1516,6 +1646,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Missing field 'secret'.");
|
||||
}
|
||||
{
|
||||
// Both passphrase and seed specified.
|
||||
@@ -1527,6 +1659,11 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::seed] = "seed can be anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
rs[jss::error_message] ==
|
||||
"Exactly one of the following must be specified: passphrase, secret, seed or "
|
||||
"seed_hex");
|
||||
}
|
||||
{
|
||||
// channel_id is not exact hex.
|
||||
@@ -1537,6 +1674,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "channelMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel is malformed.");
|
||||
}
|
||||
{
|
||||
// amount is not a string
|
||||
@@ -1547,6 +1686,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
|
||||
}
|
||||
{
|
||||
// Amount is not a decimal string.
|
||||
@@ -1557,6 +1698,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
rs = env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed");
|
||||
BEAST_EXPECT(rs[jss::error_code] == RpcChannelAmtMalformed);
|
||||
BEAST_EXPECT(rs[jss::error_message] == "Payment channel amount is malformed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1807,6 +1950,8 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
)*";
|
||||
auto const r = env.rpc("json", "channel_verify", txn);
|
||||
BEAST_EXPECT(r["result"]["error"] == "publicMalformed");
|
||||
BEAST_EXPECT(r["result"]["error_code"] == RpcPublicMalformed);
|
||||
BEAST_EXPECT(r["result"]["error_message"] == "Public key is malformed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -42,8 +43,9 @@ class ReducedOffer_test : public beast::unit_test::Suite
|
||||
{
|
||||
json::Value ledgerOffer = ledgerEntryOffer(env, acct, offerSeq);
|
||||
return !(
|
||||
ledgerOffer.isMember(jss::error) &&
|
||||
ledgerOffer[jss::error].asString() == "entryNotFound");
|
||||
ledgerOffer[jss::error].asString() == "entryNotFound" &&
|
||||
ledgerOffer[jss::error_code] == RpcEntryNotFound &&
|
||||
ledgerOffer[jss::error_message] == "Entry not found.");
|
||||
}
|
||||
|
||||
// Common code to clean up unneeded offers.
|
||||
|
||||
@@ -302,10 +302,14 @@ class SHAMapStore_test : public beast::unit_test::Suite
|
||||
}
|
||||
|
||||
static bool
|
||||
bad(json::Value const& json, ErrorCodeI error = RpcLgrNotFound)
|
||||
bad(json::Value const& json,
|
||||
std::string const& token,
|
||||
ErrorCodeI error,
|
||||
std::string const& message)
|
||||
{
|
||||
return json.isMember(jss::result) && rpc::containsError(json[jss::result]) &&
|
||||
json[jss::result][jss::error_code] == error;
|
||||
return json.isMember(jss::result) && json[jss::result][jss::error] == token &&
|
||||
json[jss::result][jss::error_code] == error &&
|
||||
json[jss::result][jss::error_message] == message;
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -883,7 +887,7 @@ public:
|
||||
std::map<std::uint32_t, json::Value const> ledgers;
|
||||
|
||||
auto ledgerTmp = env.rpc("ledger", "0");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
|
||||
|
||||
ledgers.emplace(1, env.rpc("ledger", "1"));
|
||||
BEAST_EXPECT(goodLedger(env, ledgers[1], "1"));
|
||||
@@ -895,10 +899,10 @@ public:
|
||||
BEAST_EXPECT(goodLedger(env, ledgerTmp, "3"));
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "4");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
|
||||
|
||||
ledgerTmp = env.rpc("ledger", "100");
|
||||
BEAST_EXPECT(bad(ledgerTmp));
|
||||
BEAST_EXPECT(bad(ledgerTmp, "lgrNotFound", RpcLgrNotFound, "ledgerNotFound"));
|
||||
|
||||
auto const firstSeq = waitForReady(env);
|
||||
auto lastRotated = firstSeq - 1;
|
||||
@@ -987,7 +991,7 @@ public:
|
||||
// Because advisory_delete is unset,
|
||||
// "can_delete" is disabled.
|
||||
auto const canDelete = env.rpc("can_delete");
|
||||
BEAST_EXPECT(bad(canDelete, RpcNotEnabled));
|
||||
BEAST_EXPECT(bad(canDelete, "notEnabled", RpcNotEnabled, "Not enabled in configuration."));
|
||||
|
||||
// Close ledgers without triggering a rotate
|
||||
for (; ledgerSeq < lastRotated + kDeleteInterval; ++ledgerSeq)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include <xrpl/tx/apply.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@@ -3550,6 +3551,101 @@ public:
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Bytecode Escrow: its reserve scales with the Bytecode size, so
|
||||
// every leg of a sponsorship has to move the whole weight.
|
||||
auto env = Env{*this, testableAmendments()};
|
||||
|
||||
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, bob, XRP(100)),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const escrowKeylet = keylet::escrow(alice, SeqProxy::rawSequence(seq));
|
||||
std::size_t const bytecodeSize = 1200;
|
||||
std::uint32_t const bytecodeWeight = 3; // ceil(1200 / 500)
|
||||
|
||||
if (!cosigning)
|
||||
{
|
||||
env(sponsor::set_reserve(sponsor, 0, bytecodeWeight), sponsor::SponseeAcc(alice));
|
||||
env(sponsor::set_reserve(sponsor2, 0, bytecodeWeight), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// EscrowCreate's preclaim runs the WASM preflight, so a real
|
||||
// 1200-byte contract cannot be assembled from here; the Bytecode
|
||||
// field is injected directly and the owner's OwnerCount restored
|
||||
// to the weight such a create would have charged.
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool {
|
||||
auto const existing = view.read(escrowKeylet);
|
||||
if (!existing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const escrowSle = std::make_shared<SLE>(*existing);
|
||||
auto const account = std::make_shared<SLE>(*view.read(keylet::account(alice.id())));
|
||||
escrowSle->setFieldVL(sfBytecode, Blob(bytecodeSize, 0x00));
|
||||
account->setFieldU32(sfOwnerCount, bytecodeWeight);
|
||||
view.rawReplace(escrowSle);
|
||||
view.rawReplace(account);
|
||||
return true;
|
||||
});
|
||||
BEAST_EXPECT(ownerCount(env, alice) == bytecodeWeight);
|
||||
|
||||
std::optional<Sig> const sponsorSig =
|
||||
cosigning ? std::optional<Sig>(Sig(sfSponsorSignature, sponsor)) : std::nullopt;
|
||||
std::optional<Sig> const sponsor2Sig =
|
||||
cosigning ? std::optional<Sig>(Sig(sfSponsorSignature, sponsor2)) : std::nullopt;
|
||||
|
||||
// create sponsor: charges the whole bytecode weight, not 1
|
||||
if (sponsorSig)
|
||||
{
|
||||
env(sponsor::transfer(alice, tfSponsorshipCreate, escrowKeylet.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
*sponsorSig);
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::transfer(alice, tfSponsorshipCreate, escrowKeylet.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve));
|
||||
}
|
||||
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == bytecodeWeight);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == bytecodeWeight);
|
||||
BEAST_EXPECT(env.le(escrowKeylet)->getAccountID(sfSponsor) == sponsor.id());
|
||||
|
||||
// transfer sponsor: moves the whole weight between the two sponsors
|
||||
if (sponsor2Sig)
|
||||
{
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, escrowKeylet.key),
|
||||
sponsor::As(sponsor2, spfSponsorReserve),
|
||||
*sponsor2Sig);
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, escrowKeylet.key),
|
||||
sponsor::As(sponsor2, spfSponsorReserve));
|
||||
}
|
||||
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == bytecodeWeight);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == bytecodeWeight);
|
||||
BEAST_EXPECT(env.le(escrowKeylet)->getAccountID(sfSponsor) == sponsor2.id());
|
||||
|
||||
// end sponsor: hands the whole weight back to the owner
|
||||
env(sponsor::transfer(alice, tfSponsorshipEnd, escrowKeylet.key));
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == bytecodeWeight);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
|
||||
BEAST_EXPECT(!env.le(escrowKeylet)->isFieldPresent(sfSponsor));
|
||||
}
|
||||
|
||||
Account const gw("gw");
|
||||
auto const usd = gw["usd"];
|
||||
{
|
||||
@@ -4405,6 +4501,89 @@ public:
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
|
||||
}
|
||||
|
||||
// An escrow carrying Bytecode costs one owner count per 500 bytes, so
|
||||
// sponsoring one must move that full weight rather than a single unit.
|
||||
void
|
||||
testBytecodeEscrowReserve()
|
||||
{
|
||||
testcase("Bytecode escrow sponsorship reserve");
|
||||
using namespace test::jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
auto const sponsor = Account{"sponsor"};
|
||||
|
||||
auto env = Env{*this, testableAmendments()};
|
||||
env.fund(XRP(1000000), alice, bob, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, bob, XRP(100)),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const escrowKeylet = keylet::escrow(alice, SeqProxy::rawSequence(seq));
|
||||
auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id());
|
||||
std::size_t const bytecodeSize = 1200;
|
||||
std::uint32_t const bytecodeWeight = 3; // ceil(1200 / 500)
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
|
||||
// Pre-fund exactly the bytecode weight
|
||||
env(sponsor::set_reserve(sponsor, 0, bytecodeWeight), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == bytecodeWeight);
|
||||
}
|
||||
|
||||
// Synthesize a bytecode escrow. EscrowCreate's preclaim runs the WASM
|
||||
// preflight, so a real 1200-byte contract cannot be assembled from
|
||||
// here; the Bytecode field is injected directly and the owner's
|
||||
// OwnerCount restored to the weight such a create would have charged.
|
||||
env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool {
|
||||
auto const existing = view.read(escrowKeylet);
|
||||
if (!existing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const escrowSle = std::make_shared<SLE>(*existing);
|
||||
auto const account = std::make_shared<SLE>(*view.read(keylet::account(alice.id())));
|
||||
escrowSle->setFieldVL(sfBytecode, Blob(bytecodeSize, 0x00));
|
||||
account->setFieldU32(sfOwnerCount, bytecodeWeight);
|
||||
view.rawReplace(escrowSle);
|
||||
view.rawReplace(account);
|
||||
return true;
|
||||
});
|
||||
BEAST_EXPECT(ownerCount(env, alice) == bytecodeWeight);
|
||||
|
||||
// Create must charge the full bytecode weight (3), not 1: counting the
|
||||
// escrow as a single unit would leave 2 pre-funded units unspent.
|
||||
env(sponsor::transfer(alice, tfSponsorshipCreate, escrowKeylet.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve));
|
||||
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == bytecodeWeight);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == bytecodeWeight);
|
||||
// All pre-funded units consumed (drained to absent).
|
||||
if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
|
||||
}
|
||||
|
||||
// End hands the same weight back; create bumped by the same amount, so
|
||||
// the counters return to 0 (counting 1 here would underflow).
|
||||
env(sponsor::transfer(alice, tfSponsorshipEnd, escrowKeylet.key));
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == bytecodeWeight);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
if (auto const sle = env.le(escrowKeylet); BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfSponsor));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSponsoredTrustLineNoFreeReserve()
|
||||
{
|
||||
@@ -5790,6 +5969,7 @@ protected:
|
||||
testTransferSponsor(jtx::testableAmendments());
|
||||
testTransferSponsor(jtx::testableAmendments() - fixCleanup3_4_0);
|
||||
testLegacySignerListReserve();
|
||||
testBytecodeEscrowReserve();
|
||||
testSponsorFee();
|
||||
testSponsorAccount();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -215,8 +216,9 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
|
||||
}
|
||||
|
||||
// The target account may refuse any proposal made for it, at any point in
|
||||
// its lifecycle, without owning the object: anyone can create a proposal
|
||||
// against any account and tie up one of its tickets (On-Chain Cosigner spec §7.2).
|
||||
// its lifecycle, without owning the object: any of its signers, an
|
||||
// authorized delegate, or that delegate's signers can create one and tie
|
||||
// up one of its tickets (XLS-0103 §7.2).
|
||||
void
|
||||
testTargetCancel(FeatureBitset features)
|
||||
{
|
||||
@@ -683,6 +685,25 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(env.le(keylet::txProposal(proposalID)));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == proposal::kProposalOwnerCount);
|
||||
}
|
||||
|
||||
// Paying with a ticket changes none of that. The failed cancel still
|
||||
// consumes carol's ticket, and nothing else moves: the proposal and
|
||||
// its reserve stay as they were.
|
||||
{
|
||||
uint256 const proposalID = makeProposal(env, alice, target, bob);
|
||||
std::uint32_t const cancelTicket = proposal::createTicket(env, carol);
|
||||
auto const aliceOwnerCount = ownerCount(env, alice);
|
||||
|
||||
env(proposal::cancel(carol, proposalID),
|
||||
ticket::Use(cancelTicket),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.le(keylet::txProposal(proposalID)));
|
||||
BEAST_EXPECT(!env.le(keylet::ticket(carol.id(), SeqProxy::rawTicket(cancelTicket))));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount);
|
||||
BEAST_EXPECT(ownerCount(env, carol) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// A Cancel works as an inner Batch transaction, where each inner runs its
|
||||
@@ -1012,15 +1033,25 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
|
||||
{
|
||||
json::Value noTicket;
|
||||
noTicket[jss::account] = target.human();
|
||||
BEAST_EXPECT(ledgerEntry(noTicket)[jss::error] == "malformedRequest");
|
||||
auto const noTicketResult = ledgerEntry(noTicket);
|
||||
BEAST_EXPECT(noTicketResult[jss::error] == "malformedRequest");
|
||||
BEAST_EXPECT(noTicketResult[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(noTicketResult[jss::error_message] == "Missing field 'ticket_seq'.");
|
||||
|
||||
json::Value noAccount;
|
||||
noAccount[jss::ticket_seq] = ticketSeq;
|
||||
BEAST_EXPECT(ledgerEntry(noAccount)[jss::error] == "malformedRequest");
|
||||
auto const noAccountResult = ledgerEntry(noAccount);
|
||||
BEAST_EXPECT(noAccountResult[jss::error] == "malformedRequest");
|
||||
BEAST_EXPECT(noAccountResult[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(noAccountResult[jss::error_message] == "Missing field 'account'.");
|
||||
|
||||
json::Value badAccount = byPair;
|
||||
badAccount[jss::account] = "not an account";
|
||||
BEAST_EXPECT(ledgerEntry(badAccount)[jss::error] == "malformedAddress");
|
||||
auto const badAccountResult = ledgerEntry(badAccount);
|
||||
BEAST_EXPECT(badAccountResult[jss::error] == "malformedAddress");
|
||||
BEAST_EXPECT(badAccountResult[jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(
|
||||
badAccountResult[jss::error_message] == "Invalid field 'account', not AccountID.");
|
||||
}
|
||||
|
||||
// Listed for the proposer, who owns it, and not for the target.
|
||||
@@ -1037,6 +1068,8 @@ struct TransactionProposalCancel_test : public beast::unit_test::Suite
|
||||
{
|
||||
auto const jrr = ledgerEntry(byPair);
|
||||
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
|
||||
BEAST_EXPECT(!jrr.isMember(jss::node));
|
||||
BEAST_EXPECT(jrr[jss::index] == to_string(proposalID));
|
||||
}
|
||||
|
||||
@@ -1357,6 +1357,51 @@ struct TransactionProposalCreate_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
// A proposer may pay for the Create with one of its own Tickets. The
|
||||
// Ticket's reserve goes as the proposal's arrives, so the proposer's owner
|
||||
// count moves by the difference.
|
||||
void
|
||||
testTicketedCreate(FeatureBitset features)
|
||||
{
|
||||
testcase("create paid for with the proposer's own ticket");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
Account const alice{"alice"}; // the proposer
|
||||
Account const target{"target"}; // the account the proposal is for
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, target, bob);
|
||||
env.close();
|
||||
proposal::authorizeProposer(env, target, alice);
|
||||
|
||||
std::uint32_t const targetTicketSeq = proposal::createTicket(env, target);
|
||||
std::uint32_t const aliceTicketSeq = proposal::createTicket(env, alice);
|
||||
std::uint32_t const aliceSeq = env.seq(alice);
|
||||
std::uint32_t const targetOwnerCount = ownerCount(env, target);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
|
||||
// The proposal's reserve lands on alice less the Ticket she paid with,
|
||||
// and her Sequence is untouched.
|
||||
env(proposal::create(
|
||||
alice,
|
||||
proposal::unsignedPayload(env, pay(target, bob, XRP(1)), targetTicketSeq),
|
||||
proposal::expiration(env, 100s)),
|
||||
ticket::Use(aliceTicketSeq),
|
||||
proposal::verify::create());
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(proposal::entry(env, target, targetTicketSeq));
|
||||
BEAST_EXPECT(!env.le(keylet::ticket(alice.id(), SeqProxy::rawTicket(aliceTicketSeq))));
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == proposal::kProposalOwnerCount);
|
||||
|
||||
// The target's Ticket, the one the proposal is keyed to, stays.
|
||||
BEAST_EXPECT(ownerCount(env, target) == targetOwnerCount);
|
||||
}
|
||||
|
||||
// The proposal's reserve can instead be sponsored: the reserve is charged
|
||||
// to the sponsor's account, and the ledger object records the sponsor, the
|
||||
// same as any other reserve-sponsorable object (TransactionProposalCreate
|
||||
@@ -1728,6 +1773,7 @@ struct TransactionProposalCreate_test : public beast::unit_test::Suite
|
||||
testOtherTransactionTypes(all);
|
||||
testAuxiliaryCoSignatureTypes(all);
|
||||
testReserve(all);
|
||||
testTicketedCreate(all);
|
||||
testSponsoredReserve(all);
|
||||
testSponsorshipTransfer(all);
|
||||
testFeeSponsored(all);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -66,6 +67,8 @@ class TrustAndBalance_test : public beast::unit_test::Suite
|
||||
// reflects this
|
||||
auto jrr = ledgerEntryState(env, gw, alice, "USD");
|
||||
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
|
||||
|
||||
// now create a credit limit
|
||||
env(trust(alice, gw["USD"](800)));
|
||||
@@ -100,6 +103,8 @@ class TrustAndBalance_test : public beast::unit_test::Suite
|
||||
// ensure line is deleted
|
||||
jrr = ledgerEntryState(env, gw, alice, "USD");
|
||||
BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
|
||||
BEAST_EXPECT(jrr[jss::error_code] == RpcEntryNotFound);
|
||||
BEAST_EXPECT(jrr[jss::error_message] == "Entry not found.");
|
||||
|
||||
// TODO Check in both owner books.
|
||||
|
||||
|
||||
@@ -3231,7 +3231,9 @@ public:
|
||||
|
||||
{
|
||||
auto const info = env.rpc("json", "account_info", to_string(prevLedgerWithQueue));
|
||||
BEAST_EXPECT(info.isMember(jss::result) && rpc::containsError(info[jss::result]));
|
||||
BEAST_EXPECT(info[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(info[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(info[jss::result][jss::error_message] == "Invalid parameters.");
|
||||
}
|
||||
|
||||
env.close();
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,6 +4,7 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/token.h>
|
||||
#include <test/jtx/trust.h>
|
||||
@@ -1330,6 +1331,51 @@ class InvariantsMisc_test : public InvariantsBase
|
||||
env(check::create(a1, a2, XRP(1)));
|
||||
return true;
|
||||
});
|
||||
|
||||
// An escrow's owner count scales with its Bytecode size, so
|
||||
// sponsoring one has to move that whole weight. Counting it as a
|
||||
// single unit leaves the sponsored counters short of the object's
|
||||
// real weight, which is what this check exists to catch.
|
||||
UInt256 escrowID;
|
||||
|
||||
doInvariantCheck(
|
||||
{{expectMessage}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto const escrow = ac.view().peek(keylet::unchecked(escrowID));
|
||||
if (!escrow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// ceil(1200 / 500) == 3 owner counts
|
||||
escrow->setFieldVL(sfBytecode, Blob(1200, 0x00));
|
||||
escrow->setAccountID(sfSponsor, a2.id());
|
||||
ac.view().update(escrow);
|
||||
|
||||
// Move the counters by a single unit, as a sponsorship
|
||||
// blind to the Bytecode weight would.
|
||||
auto const sle1 = ac.view().peek(keylet::account(a1.id()));
|
||||
auto const sle2 = ac.view().peek(keylet::account(a2.id()));
|
||||
if (!sle1 || !sle2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
sle1->setFieldU32(sfSponsoredOwnerCount, 1);
|
||||
sle2->setFieldU32(sfSponsoringOwnerCount, 1);
|
||||
ac.view().update(sle1);
|
||||
ac.view().update(sle2);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&escrowID](Account const& a1, Account const& a2, Env& env) {
|
||||
using namespace std::chrono_literals;
|
||||
escrowID = keylet::escrow(a1.id(), SeqProxy::rawSequence(env.seq(a1))).key;
|
||||
env(escrow::create(a1, a2, XRP(1)),
|
||||
escrow::kCondition(escrow::kCb1),
|
||||
escrow::kCancelTime(env.now() + 100s));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
#include <test/app/invariants/InvariantsBase.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/proposal.h>
|
||||
#include <test/jtx/utility.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/ProposalHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class InvariantsTransactionProposal_test : public InvariantsBase
|
||||
{
|
||||
void
|
||||
testTransactionProposalInvariants()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Submits a real proposal, so the entry is in the closed ledger before
|
||||
// the precheck runs and the precheck can modify it.
|
||||
std::optional<Keylet> liveProposalKeylet;
|
||||
std::optional<std::uint32_t> liveTicketSeq;
|
||||
Preclose const precloseLiveProposal =
|
||||
[&](Account const& owner, Account const& destination, Env& env) {
|
||||
auto const ticketSeq = proposal::createTicket(env, owner);
|
||||
auto const proposedTx =
|
||||
proposal::unsignedPayload(env, pay(owner, destination, XRP(1)), ticketSeq);
|
||||
liveProposalKeylet = keylet::txProposal(owner.id(), ticketSeq);
|
||||
liveTicketSeq = ticketSeq;
|
||||
env(proposal::create(owner, proposedTx, proposal::expiration(env, 100s)));
|
||||
return BEAST_EXPECT(env.le(*liveProposalKeylet));
|
||||
};
|
||||
|
||||
auto peekLiveProposal = [&](ApplyContext& ac) -> SLE::pointer {
|
||||
if (!liveProposalKeylet)
|
||||
return nullptr;
|
||||
return ac.view().peek(*liveProposalKeylet);
|
||||
};
|
||||
|
||||
// Deletes the live proposal as deleteProposal would: out of the
|
||||
// owner's directory, with the owner's reserve released.
|
||||
auto deleteLiveProposal = [&](Account const& owner, ApplyContext& ac) {
|
||||
auto const sle = peekLiveProposal(ac);
|
||||
if (!sle)
|
||||
return false;
|
||||
auto const account = ac.view().peek(keylet::account(owner.id()));
|
||||
if (!account)
|
||||
return false;
|
||||
account->at(sfOwnerCount) -=
|
||||
xrpl::proposal::proposalOwnerCount(sle->getFieldObject(sfProposedTransaction));
|
||||
ac.view().update(account);
|
||||
if (!ac.view().dirRemove(
|
||||
keylet::ownerDir(owner.id()), sle->getFieldU64(sfOwnerNode), sle->key(), false))
|
||||
return false;
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Consumes one of the owner's Tickets as ticketDelete would.
|
||||
auto deleteTicket = [&](Account const& owner, std::uint32_t ticketSeq, ApplyContext& ac) {
|
||||
auto const ticket =
|
||||
ac.view().peek(keylet::ticket(owner.id(), SeqProxy::rawTicket(ticketSeq)));
|
||||
auto const account = ac.view().peek(keylet::account(owner.id()));
|
||||
if (!ticket || !account)
|
||||
return false;
|
||||
if (!ac.view().dirRemove(
|
||||
keylet::ownerDir(owner.id()),
|
||||
ticket->getFieldU64(sfOwnerNode),
|
||||
ticket->key(),
|
||||
false))
|
||||
return false;
|
||||
if (auto const ticketCount = account->getFieldU32(sfTicketCount); ticketCount > 1)
|
||||
{
|
||||
account->setFieldU32(sfTicketCount, ticketCount - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
account->makeFieldAbsent(sfTicketCount);
|
||||
}
|
||||
account->at(sfOwnerCount) -= 1;
|
||||
ac.view().update(account);
|
||||
ac.view().erase(ticket);
|
||||
return true;
|
||||
};
|
||||
|
||||
// A transaction outside the whitelist may not modify a proposal at
|
||||
// all; the invariant rejects it on the count, whatever field changed.
|
||||
testcase("TransactionProposal modified outside the whitelist");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto const sle = peekLiveProposal(ac);
|
||||
if (!sle)
|
||||
return false;
|
||||
auto proposedTx = sle->getFieldObject(sfProposedTransaction);
|
||||
proposedTx.setFieldU32(
|
||||
sfTicketSequence, proposedTx.getFieldU32(sfTicketSequence) + 1);
|
||||
sle->setFieldObject(sfProposedTransaction, proposedTx);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal);
|
||||
|
||||
// Only stages the payload and the key it would live under; nothing
|
||||
// reaches the ledger, so a precheck that inserts the entry by hand
|
||||
// presents the invariant with a creation.
|
||||
std::optional<STObject> stagedPayload;
|
||||
std::optional<Keylet> stagedProposalKeylet;
|
||||
Preclose const precloseStagedPayload =
|
||||
[&](Account const& owner, Account const& destination, Env& env) {
|
||||
auto const ticketSeq = proposal::createTicket(env, owner);
|
||||
auto const proposedJson =
|
||||
proposal::unsignedPayload(env, pay(owner, destination, XRP(1)), ticketSeq);
|
||||
stagedPayload.emplace(parse(proposedJson));
|
||||
stagedProposalKeylet = keylet::txProposal(owner.id(), ticketSeq);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Inserts the staged proposal as doApply would, with the owner's
|
||||
// reserve charged.
|
||||
auto insertStagedProposal = [&](Account const& owner, ApplyContext& ac) {
|
||||
if (!stagedPayload || !stagedProposalKeylet)
|
||||
return false;
|
||||
|
||||
auto const sle = std::make_shared<SLE>(*stagedProposalKeylet);
|
||||
sle->setAccountID(sfOwner, owner.id());
|
||||
sle->setFieldObject(sfProposedTransaction, *stagedPayload);
|
||||
sle->setFieldU32(sfExpiration, 1);
|
||||
sle->setFieldU64(sfOwnerNode, 0);
|
||||
ac.view().insert(sle);
|
||||
|
||||
auto const account = ac.view().peek(keylet::account(owner.id()));
|
||||
if (!account)
|
||||
return false;
|
||||
account->at(sfOwnerCount) += xrpl::proposal::proposalOwnerCount(*stagedPayload);
|
||||
ac.view().update(account);
|
||||
return true;
|
||||
};
|
||||
|
||||
testcase("TransactionProposal effect whitelist");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return insertStagedProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseStagedPayload);
|
||||
|
||||
// The harness runs no transactor, so a failed result has to be fed in
|
||||
// directly. A failed create must leave no trace.
|
||||
testcase("TransactionProposal failed create still inserts");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return insertStagedProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttTRANSACTION_PROPOSAL_CREATE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseStagedPayload,
|
||||
TxAccount::None,
|
||||
std::source_location::current(),
|
||||
tecNO_PERMISSION);
|
||||
|
||||
// Reassigns the live proposal's reserve to a sponsor as a
|
||||
// SponsorshipTransfer would. The sponsored and sponsoring counts move
|
||||
// with it so that SponsorshipOwnerCountsMatch stays quiet.
|
||||
auto const reassignLiveProposal =
|
||||
[&](Account const& owner, Account const& sponsor, ApplyContext& ac) {
|
||||
auto const sle = peekLiveProposal(ac);
|
||||
if (!sle)
|
||||
return false;
|
||||
auto const reserve =
|
||||
xrpl::proposal::proposalOwnerCount(sle->getFieldObject(sfProposedTransaction));
|
||||
sle->setAccountID(sfSponsor, sponsor.id());
|
||||
ac.view().update(sle);
|
||||
|
||||
auto const account = ac.view().peek(keylet::account(owner.id()));
|
||||
auto const sponsorRoot = ac.view().peek(keylet::account(sponsor.id()));
|
||||
if (!account || !sponsorRoot)
|
||||
return false;
|
||||
account->at(sfSponsoredOwnerCount) += reserve;
|
||||
ac.view().update(account);
|
||||
sponsorRoot->at(sfSponsoringOwnerCount) += reserve;
|
||||
ac.view().update(sponsorRoot);
|
||||
return true;
|
||||
};
|
||||
|
||||
// A failed transfer must not have reassigned anything. The counters are
|
||||
// kept consistent so only the result mismatch is left to catch.
|
||||
testcase("TransactionProposal failed sponsorship transfer still reassigns");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
reassignLiveProposal,
|
||||
XRPAmount{},
|
||||
STTx{ttSPONSORSHIP_TRANSFER, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal,
|
||||
TxAccount::None,
|
||||
std::source_location::current(),
|
||||
tecNO_PERMISSION);
|
||||
|
||||
// A proposal leaves the ledger only through TransactionProposalCancel
|
||||
// or together with the Ticket it is keyed to. The harness runs no
|
||||
// transactor, so each deletion below is staged by hand, releasing the
|
||||
// reserve as a real one would, to leave the deletion itself as the
|
||||
// only possible violation.
|
||||
testcase("TransactionProposal deletion outside the whitelist");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return deleteLiveProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal);
|
||||
|
||||
// The owner holds a second Ticket, one no proposal is keyed to.
|
||||
std::optional<std::uint32_t> spareTicketSeq;
|
||||
Preclose const precloseLiveProposalAndSpareTicket =
|
||||
[&](Account const& owner, Account const& destination, Env& env) {
|
||||
spareTicketSeq = proposal::createTicket(env, owner);
|
||||
return precloseLiveProposal(owner, destination, env);
|
||||
};
|
||||
|
||||
// Consuming some other Ticket of the owner's is no reason for the
|
||||
// proposal to go.
|
||||
testcase("TransactionProposal deleted with an unrelated ticket");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return spareTicketSeq && deleteTicket(owner, *spareTicketSeq, ac) &&
|
||||
deleteLiveProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposalAndSpareTicket);
|
||||
|
||||
// And a deleted Ticket takes the proposal keyed to it along: here the
|
||||
// Ticket went and the proposal stayed behind.
|
||||
testcase("TransactionProposal outlives its ticket");
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal outlived the Ticket it is keyed to"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return liveTicketSeq && deleteTicket(owner, *liveTicketSeq, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal);
|
||||
|
||||
// A Cancel names the live proposal and may pay with the owner's spare
|
||||
// Ticket. Both exist only once the preclose has run, so the Env is
|
||||
// built here rather than by the harness.
|
||||
struct CancelOptions
|
||||
{
|
||||
// The Cancel pays with A1's spare Ticket.
|
||||
bool paySpareTicket = false;
|
||||
TER initialResult = tesSUCCESS;
|
||||
};
|
||||
auto const checkCancel = [&](std::vector<std::string> const& expectLogs,
|
||||
Precheck const& precheck,
|
||||
std::initializer_list<TER> ters,
|
||||
Preclose const& preclose,
|
||||
CancelOptions const& options = CancelOptions(),
|
||||
std::source_location const& loc =
|
||||
std::source_location::current()) {
|
||||
Account const a1{"A1"};
|
||||
Account const a2{"A2"};
|
||||
Env env = makeEnv(testableAmendments());
|
||||
env.fund(XRP(1000), a1, a2);
|
||||
if (!BEAST_EXPECT(preclose(a1, a2, env)) || !BEAST_EXPECT(liveProposalKeylet))
|
||||
return;
|
||||
env.close();
|
||||
|
||||
STTx const tx{ttTRANSACTION_PROPOSAL_CANCEL, [&](STObject& obj) {
|
||||
obj.setAccountID(sfAccount, a1.id());
|
||||
obj.setFieldH256(sfProposalID, liveProposalKeylet->key);
|
||||
if (options.paySpareTicket && spareTicketSeq)
|
||||
{
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfTicketSequence, *spareTicketSeq);
|
||||
}
|
||||
}};
|
||||
doInvariantCheck(
|
||||
std::move(env),
|
||||
a1,
|
||||
a2,
|
||||
expectLogs,
|
||||
precheck,
|
||||
XRPAmount{},
|
||||
tx,
|
||||
ters,
|
||||
loc,
|
||||
options.initialResult);
|
||||
};
|
||||
|
||||
auto const cancelDeletesLiveProposal =
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return deleteLiveProposal(owner, ac);
|
||||
};
|
||||
|
||||
// A Cancel that did not succeed deleted nothing.
|
||||
testcase("TransactionProposal failed cancel still deletes");
|
||||
{
|
||||
CancelOptions options;
|
||||
options.initialResult = tecNO_PERMISSION;
|
||||
checkCancel(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
cancelDeletesLiveProposal,
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal,
|
||||
options);
|
||||
}
|
||||
|
||||
// A Cancel cannot spend a reserved Ticket, so the proposal it deletes
|
||||
// never goes with its Ticket.
|
||||
testcase("TransactionProposal cancel consumes the reserved ticket");
|
||||
checkCancel(
|
||||
{{"TransactionProposal changes do not match transaction result"}},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return liveTicketSeq && deleteTicket(owner, *liveTicketSeq, ac) &&
|
||||
deleteLiveProposal(owner, ac);
|
||||
},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal);
|
||||
|
||||
// Distinct AccountIDs in ascending order, so an array built from them
|
||||
// violates only the bound on its length.
|
||||
auto ascendingAccounts = [](std::size_t count) {
|
||||
std::vector<AccountID> accounts;
|
||||
accounts.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
AccountID account{beast::kZero};
|
||||
account.data()[0] = static_cast<std::uint8_t>(i + 1);
|
||||
accounts.push_back(account);
|
||||
}
|
||||
return accounts;
|
||||
};
|
||||
|
||||
auto signerArray = [](std::vector<AccountID> const& accounts) {
|
||||
STArray signers{sfSigners};
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
STObject signer{sfSigner};
|
||||
signer.setAccountID(sfAccount, account);
|
||||
signers.pushBack(signer);
|
||||
}
|
||||
return signers;
|
||||
};
|
||||
|
||||
// No transaction type on this amendment branch collects signatures yet,
|
||||
// so every rejection below has to be provoked by editing the stored
|
||||
// payload directly.
|
||||
auto const testBrokenSignerArray = [&](std::string const& name, auto const& breakPayload) {
|
||||
testcase("TransactionProposal canonical signer arrays: " + name);
|
||||
doInvariantCheck(
|
||||
{{"TransactionProposal signer arrays are not canonical"}},
|
||||
[&](Account const&, Account const& signerAccount, ApplyContext& ac) {
|
||||
auto const sle = peekLiveProposal(ac);
|
||||
if (!sle)
|
||||
return false;
|
||||
auto proposedTx = sle->getFieldObject(sfProposedTransaction);
|
||||
breakPayload(proposedTx, signerAccount);
|
||||
sle->setFieldObject(sfProposedTransaction, proposedTx);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
precloseLiveProposal);
|
||||
};
|
||||
|
||||
testBrokenSignerArray("duplicate entries", [&](STObject& tx, Account const& signer) {
|
||||
tx.setFieldArray(sfSigners, signerArray({signer.id(), signer.id()}));
|
||||
});
|
||||
|
||||
testBrokenSignerArray("descending order", [&](STObject& tx, Account const&) {
|
||||
auto accounts = ascendingAccounts(2);
|
||||
std::ranges::reverse(accounts);
|
||||
tx.setFieldArray(sfSigners, signerArray(accounts));
|
||||
});
|
||||
|
||||
testBrokenSignerArray("past the multi-sign limit", [&](STObject& tx, Account const&) {
|
||||
tx.setFieldArray(sfSigners, signerArray(ascendingAccounts(STTx::kMaxMultiSigners + 1)));
|
||||
});
|
||||
|
||||
testBrokenSignerArray("entry without an Account", [](STObject& tx, Account const&) {
|
||||
STArray signers{sfSigners};
|
||||
signers.pushBack(STObject{sfSigner});
|
||||
tx.setFieldArray(sfSigners, signers);
|
||||
});
|
||||
|
||||
for (auto const* field :
|
||||
std::array<SField const*, 2>{&sfCounterpartySignature, &sfSponsorSignature})
|
||||
{
|
||||
testBrokenSignerArray(
|
||||
"duplicates under " + field->getName(), [&](STObject& tx, Account const& signer) {
|
||||
STObject signature{*field};
|
||||
signature.setFieldArray(sfSigners, signerArray({signer.id(), signer.id()}));
|
||||
tx.setFieldObject(*field, signature);
|
||||
});
|
||||
}
|
||||
|
||||
testBrokenSignerArray("past the batch-signer limit", [&](STObject& tx, Account const&) {
|
||||
STArray batchSigners{sfBatchSigners};
|
||||
for (auto const& account : ascendingAccounts(kMaxBatchSigners + 1))
|
||||
{
|
||||
STObject batchSigner{sfBatchSigner};
|
||||
batchSigner.setAccountID(sfAccount, account);
|
||||
batchSigners.pushBack(batchSigner);
|
||||
}
|
||||
tx.setFieldArray(sfBatchSigners, batchSigners);
|
||||
});
|
||||
|
||||
testBrokenSignerArray(
|
||||
"duplicates under a BatchSigner", [&](STObject& tx, Account const& signer) {
|
||||
STObject batchSigner{sfBatchSigner};
|
||||
batchSigner.setAccountID(sfAccount, signer.id());
|
||||
batchSigner.setFieldArray(sfSigners, signerArray({signer.id(), signer.id()}));
|
||||
|
||||
STArray batchSigners{sfBatchSigners};
|
||||
batchSigners.pushBack(batchSigner);
|
||||
tx.setFieldArray(sfBatchSigners, batchSigners);
|
||||
});
|
||||
|
||||
// Negative controls. These share the setup of the failure cases above,
|
||||
// so they confirm each of those failures came from the defect it names
|
||||
// rather than from the hand-built ledger state.
|
||||
std::initializer_list<TER> const passTers = {tesSUCCESS, tesSUCCESS};
|
||||
|
||||
testcase("TransactionProposal well-formed create");
|
||||
doInvariantCheck(
|
||||
{},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return insertStagedProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttTRANSACTION_PROPOSAL_CREATE, [](STObject&) {}},
|
||||
passTers,
|
||||
precloseStagedPayload);
|
||||
|
||||
testcase("TransactionProposal left untouched");
|
||||
doInvariantCheck(
|
||||
{},
|
||||
[&](Account const&, Account const&, ApplyContext& ac) {
|
||||
return liveProposalKeylet && ac.view().read(*liveProposalKeylet) != nullptr;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
passTers,
|
||||
precloseLiveProposal);
|
||||
|
||||
// A Cancel deletes the proposal it names.
|
||||
testcase("TransactionProposal cancelled");
|
||||
checkCancel({}, cancelDeletesLiveProposal, passTers, precloseLiveProposal);
|
||||
|
||||
// The Ticket a Cancel pays with is not the one its proposal is keyed
|
||||
// to, so the proposal did not go with its Ticket.
|
||||
testcase("TransactionProposal cancel paid with the canceller's own ticket");
|
||||
{
|
||||
CancelOptions options;
|
||||
options.paySpareTicket = true;
|
||||
checkCancel(
|
||||
{},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return spareTicketSeq && deleteTicket(owner, *spareTicketSeq, ac) &&
|
||||
deleteLiveProposal(owner, ac);
|
||||
},
|
||||
passTers,
|
||||
precloseLiveProposalAndSpareTicket,
|
||||
options);
|
||||
}
|
||||
|
||||
// A SponsorshipTransfer reassigns the Sponsor.
|
||||
testcase("TransactionProposal sponsor reassigned");
|
||||
doInvariantCheck(
|
||||
{},
|
||||
reassignLiveProposal,
|
||||
XRPAmount{},
|
||||
STTx{ttSPONSORSHIP_TRANSFER, [](STObject&) {}},
|
||||
passTers,
|
||||
precloseLiveProposal);
|
||||
|
||||
// The proposed transaction executing spends the proposal's Ticket and
|
||||
// takes the proposal with it.
|
||||
testcase("TransactionProposal deleted with its ticket");
|
||||
doInvariantCheck(
|
||||
{},
|
||||
[&](Account const& owner, Account const&, ApplyContext& ac) {
|
||||
return liveTicketSeq && deleteTicket(owner, *liveTicketSeq, ac) &&
|
||||
deleteLiveProposal(owner, ac);
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
passTers,
|
||||
precloseLiveProposal);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testTransactionProposalInvariants();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(InvariantsTransactionProposal, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1154,6 +1154,8 @@ class LoanBroker_test : public beast::unit_test::Suite
|
||||
auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
|
||||
// fails in doSubmit() on STTx construction
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jrr.isMember(jss::error_code));
|
||||
BEAST_EXPECT(jrr[jss::error_message].isNull());
|
||||
BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,8 @@ private:
|
||||
BEAST_EXPECT(jr.isMember(jss::result));
|
||||
auto const jResult = jr[jss::result];
|
||||
BEAST_EXPECT(jResult[jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(!jResult.isMember(jss::error_code));
|
||||
BEAST_EXPECT(jResult[jss::error_message].isNull());
|
||||
BEAST_EXPECT(
|
||||
jResult[jss::error_exception] ==
|
||||
"fails local checks: Transaction has bad signature.");
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
@@ -126,12 +127,9 @@ private:
|
||||
return params;
|
||||
}();
|
||||
auto const jSignBorrower = env.rpc("json", "sign", to_string(borrowerSignParams));
|
||||
BEAST_EXPECT(
|
||||
jSignBorrower.isMember(jss::result) &&
|
||||
jSignBorrower[jss::result].isMember(jss::error) &&
|
||||
jSignBorrower[jss::result][jss::error] == "invalidParams" &&
|
||||
jSignBorrower[jss::result].isMember(jss::error_message) &&
|
||||
jSignBorrower[jss::result][jss::error_message] == target);
|
||||
BEAST_EXPECT(jSignBorrower[jss::result][jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(jSignBorrower[jss::result][jss::error_code] == RpcInvalidParams);
|
||||
BEAST_EXPECT(jSignBorrower[jss::result][jss::error_message] == target);
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
@@ -196,7 +196,8 @@ private:
|
||||
jvResult[jss::tx_blob] = strHex(local.getSerializer().slice());
|
||||
auto res = env.rpc("json", "submit", to_string(jvResult))["result"];
|
||||
BEAST_EXPECT(
|
||||
res[jss::error] == "invalidTransaction" &&
|
||||
res[jss::error] == "invalidTransaction" && !res.isMember(jss::error_code) &&
|
||||
res[jss::error_message].isNull() &&
|
||||
res[jss::error_exception] ==
|
||||
"fails local checks: Counterparty: Invalid signature.");
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
@@ -1609,26 +1610,263 @@ private:
|
||||
env.close();
|
||||
|
||||
// Direct vault share payment inherits the underlying lock via
|
||||
// sfReferenceHolding.
|
||||
// sfReferenceHolding. Caught in the strand check, like a global
|
||||
// or individual share lock.
|
||||
BEAST_EXPECT(shareBalance(alice) == 499);
|
||||
BEAST_EXPECT(shareBalance(bob) == 501);
|
||||
env(pay(alice, bob, shares(1)), Ter{tecLOCKED});
|
||||
env(pay(alice, bob, shares(1)), Ter{tecPATH_DRY});
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == 499);
|
||||
BEAST_EXPECT(shareBalance(bob) == 501);
|
||||
|
||||
// The same inherited lock must also block DEX payment paths that
|
||||
// would consume an offer selling vault shares.
|
||||
// would consume an offer selling vault shares. bob, the
|
||||
// destination, inherits the lock and is caught in the strand
|
||||
// check.
|
||||
env(pay(carol, bob, shares(1)),
|
||||
Sendmax(XRP(1)),
|
||||
Path(BookSpec{shares.raw()}),
|
||||
Ter{tecPATH_PARTIAL});
|
||||
Ter{tecPATH_DRY});
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == 499);
|
||||
BEAST_EXPECT(shareBalance(bob) == 501);
|
||||
BEAST_EXPECT(expectOffers(env, alice, 1));
|
||||
}
|
||||
|
||||
// A holder individually frozen on the underlying can neither sell
|
||||
// nor buy vault shares through an unfrozen holder's offer. The
|
||||
// inherited freeze is caught when the strand is built, before the
|
||||
// offer is consumed.
|
||||
auto const testShareDEXEndpointFrozen =
|
||||
[&](Env& env,
|
||||
PrettyAsset const& asset,
|
||||
Account const& owner,
|
||||
Account const& alice,
|
||||
Account const& bob,
|
||||
Account const& carol,
|
||||
std::function<void(bool)> const& setAliceFrozen) {
|
||||
Vault const vault{env};
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)}));
|
||||
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)}));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const shares = MPTIssue(env.le(keylet)->at(sfShareMPTID));
|
||||
auto const shareMptID = shares.raw().get<MPTIssue>().getMptID();
|
||||
auto const shareBalance = [&](Account const& account) {
|
||||
auto const sle = env.le(keylet::mptoken(shareMptID, account));
|
||||
return sle ? sle->at(sfMPTAmount) : 0;
|
||||
};
|
||||
|
||||
// bob bids XRP for shares and asks more XRP for shares, so that
|
||||
// his offers don't cross
|
||||
env(offer(bob, shares(1), XRP(1)));
|
||||
env(offer(bob, XRP(2), shares(1)));
|
||||
env.close();
|
||||
|
||||
setAliceFrozen(true);
|
||||
|
||||
auto const aliceShares = shareBalance(alice);
|
||||
auto const bobShares = shareBalance(bob);
|
||||
auto const expectUnchanged = [&] {
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares);
|
||||
BEAST_EXPECT(expectOffers(env, bob, 2));
|
||||
};
|
||||
|
||||
// alice sells shares: the first step's source is frozen
|
||||
env(pay(alice, carol, XRP(1)),
|
||||
Sendmax(shares(1)),
|
||||
Path(~XRP),
|
||||
Txflags(tfNoRippleDirect),
|
||||
Ter{tecPATH_DRY});
|
||||
env.close();
|
||||
expectUnchanged();
|
||||
|
||||
// carol buys shares for alice: the last step's destination is
|
||||
// frozen
|
||||
env(pay(carol, alice, shares(1)),
|
||||
Sendmax(XRP(2)),
|
||||
Path(BookSpec{shares.raw()}),
|
||||
Txflags(tfNoRippleDirect),
|
||||
Ter{tecPATH_DRY});
|
||||
env.close();
|
||||
expectUnchanged();
|
||||
|
||||
// Both succeed once alice is unfrozen
|
||||
setAliceFrozen(false);
|
||||
|
||||
env(pay(alice, carol, XRP(1)),
|
||||
Sendmax(shares(1)),
|
||||
Path(~XRP),
|
||||
Txflags(tfNoRippleDirect));
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares + 1);
|
||||
|
||||
env(pay(carol, alice, shares(1)),
|
||||
Sendmax(XRP(2)),
|
||||
Path(BookSpec{shares.raw()}),
|
||||
Txflags(tfNoRippleDirect));
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares);
|
||||
BEAST_EXPECT(expectOffers(env, bob, 0));
|
||||
};
|
||||
|
||||
// Vault shares as the intermediate asset between two books. Only the
|
||||
// offer owners hold the shares in transit. An owner individually
|
||||
// frozen on the underlying can neither give nor receive the shares.
|
||||
auto const testShareDEXMiddleFrozen = [&](Env& env,
|
||||
PrettyAsset const& asset,
|
||||
Account const& owner,
|
||||
Account const& alice,
|
||||
Account const& bob,
|
||||
Account const& carol,
|
||||
std::function<void(bool)> const& setAliceFrozen) {
|
||||
Vault const vault{env};
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)}));
|
||||
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)}));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const shares = MPTIssue(env.le(keylet)->at(sfShareMPTID));
|
||||
auto const shareMptID = shares.raw().get<MPTIssue>().getMptID();
|
||||
auto const shareBalance = [&](Account const& account) {
|
||||
auto const sle = env.le(keylet::mptoken(shareMptID, account));
|
||||
return sle ? sle->at(sfMPTAmount) : 0;
|
||||
};
|
||||
|
||||
// alice gives the shares: XRP -> shares (alice) -> asset (bob)
|
||||
{
|
||||
env(offer(alice, XRP(1), shares(1)));
|
||||
env(offer(bob, shares(1), asset(1)));
|
||||
env.close();
|
||||
|
||||
setAliceFrozen(true);
|
||||
|
||||
auto const aliceShares = shareBalance(alice);
|
||||
auto const bobShares = shareBalance(bob);
|
||||
env(pay(carol, owner, asset(1)),
|
||||
Sendmax(XRP(1)),
|
||||
Path(BookSpec{shares.raw()}, BookSpec{asset.raw()}),
|
||||
Txflags(tfNoRippleDirect),
|
||||
Ter{tecPATH_PARTIAL});
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares);
|
||||
|
||||
setAliceFrozen(false);
|
||||
|
||||
env(pay(carol, owner, asset(1)),
|
||||
Sendmax(XRP(1)),
|
||||
Path(BookSpec{shares.raw()}, BookSpec{asset.raw()}),
|
||||
Txflags(tfNoRippleDirect));
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares - 1);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares + 1);
|
||||
BEAST_EXPECT(expectOffers(env, alice, 0));
|
||||
BEAST_EXPECT(expectOffers(env, bob, 0));
|
||||
}
|
||||
|
||||
// alice receives the shares: asset -> shares (bob) -> XRP (alice)
|
||||
{
|
||||
env(offer(bob, asset(1), shares(1)));
|
||||
env(offer(alice, shares(1), XRP(1)));
|
||||
env.close();
|
||||
|
||||
setAliceFrozen(true);
|
||||
|
||||
auto const aliceShares = shareBalance(alice);
|
||||
auto const bobShares = shareBalance(bob);
|
||||
env(pay(owner, carol, XRP(1)),
|
||||
Sendmax(asset(1)),
|
||||
Path(BookSpec{shares.raw()}, ~XRP),
|
||||
Txflags(tfNoRippleDirect),
|
||||
Ter{tecPATH_PARTIAL});
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares);
|
||||
|
||||
setAliceFrozen(false);
|
||||
|
||||
env(pay(owner, carol, XRP(1)),
|
||||
Sendmax(asset(1)),
|
||||
Path(BookSpec{shares.raw()}, ~XRP),
|
||||
Txflags(tfNoRippleDirect));
|
||||
env.close();
|
||||
BEAST_EXPECT(shareBalance(alice) == aliceShares + 1);
|
||||
BEAST_EXPECT(shareBalance(bob) == bobShares - 1);
|
||||
BEAST_EXPECT(expectOffers(env, alice, 0));
|
||||
BEAST_EXPECT(expectOffers(env, bob, 0));
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
testcase("MPT individually locked: vault share DEX inherits underlying lock");
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
env.fund(XRP(10'000), issuer, owner, alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
MPTTester asset{{
|
||||
.env = env,
|
||||
.issuer = issuer,
|
||||
.holders = {owner, alice, bob},
|
||||
.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock,
|
||||
}};
|
||||
env(pay(issuer, owner, asset(1'000)));
|
||||
env(pay(issuer, alice, asset(2'000)));
|
||||
env(pay(issuer, bob, asset(2'000)));
|
||||
env.close();
|
||||
|
||||
auto const setAliceFrozen = [&](bool frozen) {
|
||||
asset.set({.holder = alice, .flags = frozen ? tfMPTLock : tfMPTUnlock});
|
||||
env.close();
|
||||
};
|
||||
testShareDEXEndpointFrozen(env, asset, owner, alice, bob, carol, setAliceFrozen);
|
||||
testShareDEXMiddleFrozen(env, asset, owner, alice, bob, carol, setAliceFrozen);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("IOU individually frozen: vault share DEX inherits underlying freeze");
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
env.fund(XRP(10'000), issuer, owner, alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
// Deliver the IOU from a book to a holder
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
PrettyAsset const asset = issuer["IOU"];
|
||||
env.trust(asset(10'000), owner, alice, bob);
|
||||
env(pay(issuer, owner, asset(1'000)));
|
||||
env(pay(issuer, alice, asset(2'000)));
|
||||
env(pay(issuer, bob, asset(2'000)));
|
||||
env.close();
|
||||
|
||||
auto const setAliceFrozen = [&](bool frozen) {
|
||||
env(trust(issuer, asset(0), alice, frozen ? tfSetFreeze : tfClearFreeze));
|
||||
env.close();
|
||||
};
|
||||
testShareDEXEndpointFrozen(env, asset, owner, alice, bob, carol, setAliceFrozen);
|
||||
testShareDEXMiddleFrozen(env, asset, owner, alice, bob, carol, setAliceFrozen);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("MPT CanTrade governance: share inherits underlying on DEX and AMM");
|
||||
|
||||
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user