Add MPTIssue to STIssue (#5200)

Replace Issue in STIssue with Asset. STIssue with MPTIssue is only used in MPT tests.
Will be used in Vault and in transactions with STIssue fields once MPT is integrated into DEX.
This commit is contained in:
Gregory Tsipenyuk
2024-12-16 17:52:48 -05:00
committed by tequ
parent 0fa542f672
commit 7de6a70221
22 changed files with 463 additions and 143 deletions

View File

@@ -26,10 +26,17 @@
namespace ripple {
class Asset;
template <typename TIss>
concept ValidIssueType =
std::is_same_v<TIss, Issue> || std::is_same_v<TIss, MPTIssue>;
template <typename A>
concept AssetType =
std::is_convertible_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
/* Asset is an abstraction of three different issue types: XRP, IOU, MPT.
* For historical reasons, two issue types XRP and IOU are wrapped in Issue
* type. Many functions and classes there were first written for Issue
@@ -37,8 +44,10 @@ concept ValidIssueType =
*/
class Asset
{
private:
public:
using value_type = std::variant<Issue, MPTIssue>;
private:
value_type issue_;
public:
@@ -92,8 +101,8 @@ public:
friend constexpr bool
operator==(Asset const& lhs, Asset const& rhs);
friend constexpr bool
operator!=(Asset const& lhs, Asset const& rhs);
friend constexpr std::weak_ordering
operator<=>(Asset const& lhs, Asset const& rhs);
friend constexpr bool
operator==(Currency const& lhs, Asset const& rhs);
@@ -151,10 +160,22 @@ operator==(Asset const& lhs, Asset const& rhs)
rhs.issue_);
}
constexpr bool
operator!=(Asset const& lhs, Asset const& rhs)
constexpr std::weak_ordering
operator<=>(Asset const& lhs, Asset const& rhs)
{
return !(lhs == rhs);
return std::visit(
[]<ValidIssueType TLhs, ValidIssueType TRhs>(
TLhs const& lhs_, TRhs const& rhs_) {
if constexpr (std::is_same_v<TLhs, TRhs>)
return std::weak_ordering(lhs_ <=> rhs_);
else if constexpr (
std::is_same_v<TLhs, Issue> && std::is_same_v<TRhs, MPTIssue>)
return std::weak_ordering::greater;
else
return std::weak_ordering::less;
},
lhs.issue_,
rhs.issue_);
}
constexpr bool

View File

@@ -308,7 +308,7 @@ uritoken(AccountID const& issuer, Blob const& uri);
/** AMM entry */
Keylet
amm(Issue const& issue1, Issue const& issue2) noexcept;
amm(Asset const& issue1, Asset const& issue2) noexcept;
Keylet
amm(uint256 const& amm) noexcept;

View File

@@ -58,6 +58,9 @@ public:
bool
native() const;
friend constexpr std::weak_ordering
operator<=>(Issue const& lhs, Issue const& rhs);
};
bool
@@ -95,7 +98,7 @@ operator==(Issue const& lhs, Issue const& rhs)
/** Strict weak ordering. */
/** @{ */
[[nodiscard]] inline constexpr std::weak_ordering
[[nodiscard]] constexpr std::weak_ordering
operator<=>(Issue const& lhs, Issue const& rhs)
{
if (auto const c{lhs.currency <=> rhs.currency}; c != 0)

View File

@@ -54,8 +54,8 @@ public:
friend constexpr bool
operator==(MPTIssue const& lhs, MPTIssue const& rhs);
friend constexpr bool
operator!=(MPTIssue const& lhs, MPTIssue const& rhs);
friend constexpr std::weak_ordering
operator<=>(MPTIssue const& lhs, MPTIssue const& rhs);
bool
native() const
@@ -70,10 +70,10 @@ operator==(MPTIssue const& lhs, MPTIssue const& rhs)
return lhs.mptID_ == rhs.mptID_;
}
constexpr bool
operator!=(MPTIssue const& lhs, MPTIssue const& rhs)
constexpr std::weak_ordering
operator<=>(MPTIssue const& lhs, MPTIssue const& rhs)
{
return !(lhs == rhs);
return lhs.mptID_ <=> rhs.mptID_;
}
/** MPT is a non-native token.

View File

@@ -40,7 +40,7 @@ enum SOEStyle {
};
/** Amount fields that can support MPT */
enum SOETxMPTAmount { soeMPTNone, soeMPTSupported, soeMPTNotSupported };
enum SOETxMPTIssue { soeMPTNone, soeMPTSupported, soeMPTNotSupported };
//------------------------------------------------------------------------------
@@ -50,7 +50,7 @@ class SOElement
// Use std::reference_wrapper so SOElement can be stored in a std::vector.
std::reference_wrapper<SField const> sField_;
SOEStyle style_;
SOETxMPTAmount supportMpt_ = soeMPTNone;
SOETxMPTIssue supportMpt_ = soeMPTNone;
private:
void
@@ -75,10 +75,13 @@ public:
supportMpt_ = soeMPTNotSupported;
init(fieldName);
}
template <typename T>
requires(std::is_same_v<T, STAmount> || std::is_same_v<T, STIssue>)
SOElement(
TypedField<STAmount> const& fieldName,
TypedField<T> const& fieldName,
SOEStyle style,
SOETxMPTAmount supportMpt = soeMPTNotSupported)
SOETxMPTIssue supportMpt = soeMPTNotSupported)
: sField_(fieldName), style_(style), supportMpt_(supportMpt)
{
init(fieldName);
@@ -96,7 +99,7 @@ public:
return style_;
}
SOETxMPTAmount
SOETxMPTIssue
supportMPT() const
{
return supportMpt_;

View File

@@ -36,11 +36,6 @@
namespace ripple {
template <typename A>
concept AssetType =
std::is_same_v<A, Asset> || std::is_convertible_v<A, Issue> ||
std::is_convertible_v<A, MPTIssue> || std::is_convertible_v<A, MPTID>;
// Internal form:
// 1: If amount is zero, then value is zero and offset is -100
// 2: Otherwise:

View File

@@ -21,7 +21,7 @@
#define RIPPLE_PROTOCOL_STISSUE_H_INCLUDED
#include <xrpl/basics/CountedObject.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/Serializer.h>
@@ -31,31 +31,40 @@ namespace ripple {
class STIssue final : public STBase, CountedObject<STIssue>
{
private:
Issue issue_{xrpIssue()};
Asset asset_{xrpIssue()};
public:
using value_type = Issue;
using value_type = Asset;
STIssue() = default;
explicit STIssue(SerialIter& sit, SField const& name);
explicit STIssue(SField const& name, Issue const& issue);
template <AssetType A>
explicit STIssue(SField const& name, A const& issue);
explicit STIssue(SField const& name);
Issue const&
issue() const;
template <ValidIssueType TIss>
TIss const&
get() const;
Issue const&
template <ValidIssueType TIss>
bool
holds() const;
value_type const&
value() const noexcept;
void
setIssue(Issue const& issue);
setIssue(Asset const& issue);
SerializedTypeID
getSType() const override;
std::string
getText() const override;
Json::Value getJson(JsonOptions) const override;
void
@@ -67,6 +76,18 @@ public:
bool
isDefault() const override;
friend constexpr bool
operator==(STIssue const& lhs, STIssue const& rhs);
friend constexpr std::weak_ordering
operator<=>(STIssue const& lhs, STIssue const& rhs);
friend constexpr bool
operator==(STIssue const& lhs, Asset const& rhs);
friend constexpr std::weak_ordering
operator<=>(STIssue const& lhs, Asset const& rhs);
private:
STBase*
copy(std::size_t n, void* buf) const override;
@@ -76,59 +97,72 @@ private:
friend class detail::STVar;
};
template <AssetType A>
STIssue::STIssue(SField const& name, A const& asset)
: STBase{name}, asset_{asset}
{
if (holds<Issue>() && !isConsistent(asset_.get<Issue>()))
Throw<std::runtime_error>(
"Invalid asset: currency and account native mismatch");
}
STIssue
issueFromJson(SField const& name, Json::Value const& v);
inline Issue const&
STIssue::issue() const
template <ValidIssueType TIss>
bool
STIssue::holds() const
{
return issue_;
return asset_.holds<TIss>();
}
inline Issue const&
template <ValidIssueType TIss>
TIss const&
STIssue::get() const
{
if (!holds<TIss>(asset_))
Throw<std::runtime_error>("Asset doesn't hold the requested issue");
return std::get<TIss>(asset_);
}
inline STIssue::value_type const&
STIssue::value() const noexcept
{
return issue_;
return asset_;
}
inline void
STIssue::setIssue(Issue const& issue)
STIssue::setIssue(Asset const& asset)
{
if (isXRP(issue_.currency) != isXRP(issue_.account))
if (holds<Issue>() && !isConsistent(asset_.get<Issue>()))
Throw<std::runtime_error>(
"invalid issue: currency and account native mismatch");
"Invalid asset: currency and account native mismatch");
issue_ = issue;
asset_ = asset;
}
inline bool
constexpr bool
operator==(STIssue const& lhs, STIssue const& rhs)
{
return lhs.issue() == rhs.issue();
return lhs.asset_ == rhs.asset_;
}
inline bool
operator!=(STIssue const& lhs, STIssue const& rhs)
constexpr std::weak_ordering
operator<=>(STIssue const& lhs, STIssue const& rhs)
{
return !operator==(lhs, rhs);
return lhs.asset_ <=> rhs.asset_;
}
inline bool
operator<(STIssue const& lhs, STIssue const& rhs)
constexpr bool
operator==(STIssue const& lhs, Asset const& rhs)
{
return lhs.issue() < rhs.issue();
return lhs.asset_ == rhs;
}
inline bool
operator==(STIssue const& lhs, Issue const& rhs)
constexpr std::weak_ordering
operator<=>(STIssue const& lhs, Asset const& rhs)
{
return lhs.issue() == rhs;
}
inline bool
operator<(STIssue const& lhs, Issue const& rhs)
{
return lhs.issue() < rhs;
return lhs.asset_ <=> rhs;
}
} // namespace ripple

View File

@@ -170,7 +170,7 @@ STXChainBridge::lockingChainDoor() const
inline Issue const&
STXChainBridge::lockingChainIssue() const
{
return lockingChainIssue_.value();
return lockingChainIssue_.value().get<Issue>();
};
inline AccountID const&
@@ -182,7 +182,7 @@ STXChainBridge::issuingChainDoor() const
inline Issue const&
STXChainBridge::issuingChainIssue() const
{
return issuingChainIssue_.value();
return issuingChainIssue_.value().get<Issue>();
};
inline STXChainBridge::value_type const&

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
@@ -490,9 +491,10 @@ uritoken(AccountID const& issuer, Blob const& uri)
}
Keylet
amm(Issue const& issue1, Issue const& issue2) noexcept
amm(Asset const& issue1, Asset const& issue2) noexcept
{
auto const& [minI, maxI] = std::minmax(issue1, issue2);
auto const& [minI, maxI] =
std::minmax(issue1.get<Issue>(), issue2.get<Issue>());
return amm(indexHash(
LedgerNameSpace::AMM,
minI.account,

View File

@@ -40,23 +40,46 @@ STIssue::STIssue(SField const& name) : STBase{name}
STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
{
issue_.currency = sit.get160();
if (!isXRP(issue_.currency))
issue_.account = sit.get160();
auto const currencyOrAccount = sit.get160();
if (isXRP(static_cast<Currency>(currencyOrAccount)))
{
asset_ = xrpIssue();
}
// Check if MPT
else
issue_.account = xrpAccount();
if (isXRP(issue_.currency) != isXRP(issue_.account))
Throw<std::runtime_error>(
"invalid issue: currency and account native mismatch");
}
STIssue::STIssue(SField const& name, Issue const& issue)
: STBase{name}, issue_{issue}
{
if (isXRP(issue_.currency) != isXRP(issue_.account))
Throw<std::runtime_error>(
"invalid issue: currency and account native mismatch");
{
// MPT is serialized as:
// - 160 bits MPT issuer account
// - 160 bits black hole account
// - 32 bits sequence
AccountID account = static_cast<AccountID>(sit.get160());
// MPT
if (noAccount() == account)
{
MPTID mptID;
std::uint32_t sequence = sit.get32();
static_assert(
MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
memcpy(mptID.data(), &sequence, sizeof(sequence));
memcpy(
mptID.data() + sizeof(sequence),
currencyOrAccount.data(),
sizeof(currencyOrAccount));
MPTIssue issue{mptID};
asset_ = issue;
}
else
{
Issue issue;
issue.currency = currencyOrAccount;
issue.account = account;
if (!isConsistent(issue))
Throw<std::runtime_error>(
"invalid issue: currency and account native mismatch");
asset_ = issue;
}
}
}
SerializedTypeID
@@ -65,18 +88,39 @@ STIssue::getSType() const
return STI_ISSUE;
}
std::string
STIssue::getText() const
{
return asset_.getText();
}
Json::Value
STIssue::getJson(JsonOptions) const
{
return to_json(issue_);
Json::Value jv;
asset_.setJson(jv);
return jv;
}
void
STIssue::add(Serializer& s) const
{
s.addBitString(issue_.currency);
if (!isXRP(issue_.currency))
s.addBitString(issue_.account);
if (holds<Issue>())
{
auto const& issue = asset_.get<Issue>();
s.addBitString(issue.currency);
if (!isXRP(issue.currency))
s.addBitString(issue.account);
}
else
{
auto const& issue = asset_.get<MPTIssue>();
s.addBitString(issue.getIssuer());
s.addBitString(noAccount());
std::uint32_t sequence;
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
s.add32(sequence);
}
}
bool
@@ -89,7 +133,7 @@ STIssue::isEquivalent(const STBase& t) const
bool
STIssue::isDefault() const
{
return issue_ == xrpIssue();
return holds<Issue>() && asset_.get<Issue>() == xrpIssue();
}
STBase*
@@ -107,7 +151,7 @@ STIssue::move(std::size_t n, void* buf)
STIssue
issueFromJson(SField const& name, Json::Value const& v)
{
return STIssue{name, issueFromJson(v)};
return STIssue{name, assetFromJson(v)};
}
} // namespace ripple

View File

@@ -609,8 +609,10 @@ invalidMPTAmountInTx(STObject const& tx)
if (tx.isFieldPresent(e.sField()) && e.supportMPT() != soeMPTNone)
{
auto const& field = tx.peekAtField(e.sField());
if (field.getSType() == STI_AMOUNT &&
static_cast<STAmount const&>(field).holds<MPTIssue>())
if ((field.getSType() == STI_AMOUNT &&
static_cast<STAmount const&>(field).holds<MPTIssue>()) ||
(field.getSType() == STI_ISSUE &&
static_cast<STIssue const&>(field).holds<MPTIssue>()))
{
if (e.supportMPT() != soeMPTSupported)
return true;

View File

@@ -1470,19 +1470,19 @@ class MPToken_test : public beast::unit_test::suite
void
testMPTInvalidInTx(FeatureBitset features)
{
testcase("MPT Amount Invalid in Transaction");
testcase("MPT Issue Invalid in Transaction");
using namespace test::jtx;
// Validate that every transaction with an amount field,
// Validate that every transaction with an amount/issue field,
// which doesn't support MPT, fails.
// keyed by transaction + amount field
// keyed by transaction + amount/issue field
std::set<std::string> txWithAmounts;
for (auto const& format : TxFormats::getInstance())
{
for (auto const& e : format.getSOTemplate())
{
// Transaction has amount fields.
// Transaction has amount/issue fields.
// Exclude pseudo-transaction SetFee. Don't consider
// the Fee field since it's included in every transaction.
if (e.supportMPT() == soeMPTNotSupported &&
@@ -1508,9 +1508,9 @@ class MPToken_test : public beast::unit_test::suite
env.fund(XRP(1'000), alice);
env.fund(XRP(1'000), carol);
auto test = [&](Json::Value const& jv,
std::string const& amtField) {
std::string const& mptField) {
txWithAmounts.erase(
jv[jss::TransactionType].asString() + amtField);
jv[jss::TransactionType].asString() + mptField);
// tx is signed
auto jtx = env.jt(jv);
@@ -1530,8 +1530,27 @@ class MPToken_test : public beast::unit_test::suite
jrr = env.rpc("json", "sign", to_string(jv1));
BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
};
// All transactions with sfAmount, which don't support MPT
// and transactions with amount fields, which can't be MPT
auto toSFieldRef = [](SField const& field) {
return std::ref(field);
};
auto setMPTFields = [&](SField const& field,
Json::Value& jv,
bool withAmount = true) {
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
if (withAmount)
jv[field.fieldName] =
USD(10).value().getJson(JsonOptions::none);
if (field == sfAsset)
jv[jss::Asset] = to_json(mpt.get<MPTIssue>());
else if (field == sfAsset2)
jv[jss::Asset2] = to_json(mpt.get<MPTIssue>());
else
jv[field.fieldName] = mpt.getJson(JsonOptions::none);
};
// All transactions with sfAmount, which don't support MPT.
// Transactions with amount fields, which can't be MPT.
// Transactions with issue fields, which can't be MPT.
// AMMCreate
auto ammCreate = [&](SField const& field) {
@@ -1554,58 +1573,84 @@ class MPToken_test : public beast::unit_test::suite
Json::Value jv;
jv[jss::TransactionType] = jss::AMMDeposit;
jv[jss::Account] = alice.human();
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
jv[field.fieldName] = mpt.getJson(JsonOptions::none);
jv[jss::Flags] = tfSingleAsset;
setMPTFields(field, jv);
test(jv, field.fieldName);
};
for (SField const& field :
{std::ref(sfAmount),
std::ref(sfAmount2),
std::ref(sfEPrice),
std::ref(sfLPTokenOut)})
{toSFieldRef(sfAmount),
toSFieldRef(sfAmount2),
toSFieldRef(sfEPrice),
toSFieldRef(sfLPTokenOut),
toSFieldRef(sfAsset),
toSFieldRef(sfAsset2)})
ammDeposit(field);
// AMMWithdraw
auto ammWithdraw = [&](SField const& field) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMWithdraw;
jv[jss::Account] = alice.human();
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
jv[jss::Flags] = tfSingleAsset;
jv[field.fieldName] = mpt.getJson(JsonOptions::none);
setMPTFields(field, jv);
test(jv, field.fieldName);
};
ammWithdraw(sfAmount);
for (SField const& field :
{std::ref(sfAmount2),
std::ref(sfEPrice),
std::ref(sfLPTokenIn)})
{toSFieldRef(sfAmount2),
toSFieldRef(sfEPrice),
toSFieldRef(sfLPTokenIn),
toSFieldRef(sfAsset),
toSFieldRef(sfAsset2)})
ammWithdraw(field);
// AMMBid
auto ammBid = [&](SField const& field) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMBid;
jv[jss::Account] = alice.human();
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
jv[field.fieldName] = mpt.getJson(JsonOptions::none);
setMPTFields(field, jv);
test(jv, field.fieldName);
};
ammBid(sfBidMin);
ammBid(sfBidMax);
for (SField const& field :
{toSFieldRef(sfBidMin),
toSFieldRef(sfBidMax),
toSFieldRef(sfAsset),
toSFieldRef(sfAsset2)})
ammBid(field);
// AMMClawback
{
auto ammClawback = [&](SField const& field) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMClawback;
jv[jss::Account] = alice.human();
jv[jss::Holder] = carol.human();
jv[jss::Asset] = to_json(xrpIssue());
jv[jss::Asset2] = to_json(USD.issue());
jv[jss::Amount] = mpt.getJson(JsonOptions::none);
test(jv, jss::Amount.c_str());
}
setMPTFields(field, jv);
test(jv, field.fieldName);
};
for (SField const& field :
{toSFieldRef(sfAmount),
toSFieldRef(sfAsset),
toSFieldRef(sfAsset2)})
ammClawback(field);
// AMMDelete
auto ammDelete = [&](SField const& field) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMDelete;
jv[jss::Account] = alice.human();
setMPTFields(field, jv, false);
test(jv, field.fieldName);
};
ammDelete(sfAsset);
ammDelete(sfAsset2);
// AMMVote
auto ammVote = [&](SField const& field) {
Json::Value jv;
jv[jss::TransactionType] = jss::AMMVote;
jv[jss::Account] = alice.human();
jv[jss::TradingFee] = 100;
setMPTFields(field, jv, false);
test(jv, field.fieldName);
};
ammVote(sfAsset);
ammVote(sfAsset2);
// CheckCash
auto checkCash = [&](SField const& field) {
Json::Value jv;

View File

@@ -0,0 +1,164 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2024 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/STIssue.h>
namespace ripple {
namespace test {
class STIssue_test : public beast::unit_test::suite
{
public:
void
testConstructor()
{
testcase("Constructor");
using namespace jtx;
Account const alice{"alice"};
auto const USD = alice["USD"];
Issue issue;
try
{
issue = xrpIssue();
issue.account = alice;
STIssue stissue(sfAsset, Asset{issue});
fail("Inconsistent XRP Issue doesn't fail");
}
catch (...)
{
pass();
}
try
{
issue = USD;
issue.account = xrpAccount();
STIssue stissue(sfAsset, Asset{issue});
fail("Inconsistent IOU Issue doesn't fail");
}
catch (...)
{
pass();
}
try
{
// Currency is USD but account is XRP
auto const data =
"00000000000000000000000055534400000000000000000000000000000000"
"000000000000000000";
base_uint<320> uint;
(void)uint.parseHex(data);
SerialIter iter(Slice(uint.data(), uint.size()));
STIssue stissue(iter, sfAsset);
fail("Inconsistent IOU Issue doesn't fail on serializer");
}
catch (...)
{
pass();
}
try
{
STIssue stissue(sfAsset, Asset{xrpIssue()});
}
catch (...)
{
fail("XRP issue failed");
}
try
{
STIssue stissue(sfAsset, Asset{USD});
}
catch (...)
{
fail("USD issue failed");
}
try
{
auto const data =
"0000000000000000000000005553440000000000ae123a8556f3cf91154711"
"376afb0f894f832b3d";
base_uint<320> uint;
(void)uint.parseHex(data);
SerialIter iter(Slice(uint.data(), uint.size()));
STIssue stissue(iter, sfAsset);
BEAST_EXPECT(stissue.value() == USD);
}
catch (...)
{
fail("USD Issue fails on serializer");
}
try
{
auto const data = "0000000000000000000000000000000000000000";
base_uint<160> uint;
(void)uint.parseHex(data);
SerialIter iter(Slice(uint.data(), uint.size()));
STIssue stissue(iter, sfAsset);
BEAST_EXPECT(stissue.value() == xrpCurrency());
}
catch (...)
{
fail("XRP Issue fails on serializer");
}
}
void
testCompare()
{
testcase("Compare");
using namespace jtx;
Account const alice{"alice"};
auto const USD = alice["USD"];
Asset const asset1{xrpIssue()};
Asset const asset2{USD};
Asset const asset3{MPTID{2}};
BEAST_EXPECT(STIssue(sfAsset, asset1) != asset2);
BEAST_EXPECT(STIssue(sfAsset, asset1) != asset3);
BEAST_EXPECT(STIssue(sfAsset, asset1) == asset1);
BEAST_EXPECT(STIssue(sfAsset, asset1).getText() == "XAH");
BEAST_EXPECT(
STIssue(sfAsset, asset2).getText() ==
"USD/rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn");
BEAST_EXPECT(
STIssue(sfAsset, asset3).getText() ==
"000000000000000000000000000000000000000000000002");
}
void
run() override
{
// compliments other unit tests to ensure complete coverage
testConstructor();
testCompare();
}
};
BEAST_DEFINE_TESTSUITE(STIssue, ripple_data, ripple);
} // namespace test
} // namespace ripple

View File

@@ -129,8 +129,8 @@ OrderBookDB::update(std::shared_ptr<ReadView const> const& ledger)
}
else if (sle->getType() == ltAMM)
{
auto const issue1 = (*sle)[sfAsset];
auto const issue2 = (*sle)[sfAsset2];
auto const issue1 = (*sle)[sfAsset].get<Issue>();
auto const issue2 = (*sle)[sfAsset2].get<Issue>();
auto addBook = [&](Issue const& in, Issue const& out) {
allBooks[in].insert(out);

View File

@@ -51,8 +51,8 @@ ammHolds(
beast::Journal const j)
{
auto const issues = [&]() -> std::optional<std::pair<Issue, Issue>> {
auto const issue1 = ammSle[sfAsset];
auto const issue2 = ammSle[sfAsset2];
auto const issue1 = ammSle[sfAsset].get<Issue>();
auto const issue2 = ammSle[sfAsset2].get<Issue>();
if (optIssue1 && optIssue2)
{
if (invalidAMMAssetPair(
@@ -134,8 +134,8 @@ ammLPHolds(
{
return ammLPHolds(
view,
ammSle[sfAsset].currency,
ammSle[sfAsset2].currency,
ammSle[sfAsset].get<Issue>().currency,
ammSle[sfAsset2].get<Issue>().currency,
ammSle[sfAccount],
lpAccount,
j);

View File

@@ -46,7 +46,8 @@ AMMBid::preflight(PreflightContext const& ctx)
return temINVALID_FLAG;
}
if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2]))
if (auto const res = invalidAMMAssetPair(
ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
{
JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
return res;

View File

@@ -57,8 +57,8 @@ AMMClawback::preflight(PreflightContext const& ctx)
}
std::optional<STAmount> const clawAmount = ctx.tx[~sfAmount];
auto const asset = ctx.tx[sfAsset];
auto const asset2 = ctx.tx[sfAsset2];
auto const asset = ctx.tx[sfAsset].get<Issue>();
auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
if (isXRP(asset))
return temMALFORMED;
@@ -78,7 +78,7 @@ AMMClawback::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (clawAmount && clawAmount->issue() != asset)
if (clawAmount && clawAmount->get<Issue>() != asset)
{
JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield "
"does not match Asset field";
@@ -94,8 +94,8 @@ AMMClawback::preflight(PreflightContext const& ctx)
TER
AMMClawback::preclaim(PreclaimContext const& ctx)
{
auto const asset = ctx.tx[sfAsset];
auto const asset2 = ctx.tx[sfAsset2];
auto const asset = ctx.tx[sfAsset].get<Issue>();
auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount]));
if (!sleIssuer)
return terNO_ACCOUNT; // LCOV_EXCL_LINE
@@ -139,8 +139,8 @@ AMMClawback::applyGuts(Sandbox& sb)
std::optional<STAmount> const clawAmount = ctx_.tx[~sfAmount];
AccountID const issuer = ctx_.tx[sfAccount];
AccountID const holder = ctx_.tx[sfHolder];
Issue const asset = ctx_.tx[sfAsset];
Issue const asset2 = ctx_.tx[sfAsset2];
Issue const asset = ctx_.tx[sfAsset].get<Issue>();
Issue const asset2 = ctx_.tx[sfAsset2].get<Issue>();
auto ammSle = sb.peek(keylet::amm(asset, asset2));
if (!ammSle)

View File

@@ -72,8 +72,8 @@ AMMDelete::doApply()
// as we go on processing transactions.
Sandbox sb(&ctx_.view());
auto const ter =
deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
auto const ter = deleteAMMAccount(
sb, ctx_.tx[sfAsset].get<Issue>(), ctx_.tx[sfAsset2].get<Issue>(), j_);
if (ter == tesSUCCESS || ter == tecINCOMPLETE)
sb.apply(ctx_.rawView());

View File

@@ -100,8 +100,8 @@ AMMDeposit::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
auto const asset = ctx.tx[sfAsset];
auto const asset2 = ctx.tx[sfAsset2];
auto const asset = ctx.tx[sfAsset].get<Issue>();
auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
if (auto const res = invalidAMMAssetPair(asset, asset2))
{
JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair.";
@@ -268,10 +268,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
};
if (auto const ter = checkAsset(ctx.tx[sfAsset]))
if (auto const ter = checkAsset(ctx.tx[sfAsset].get<Issue>()))
return ter;
if (auto const ter = checkAsset(ctx.tx[sfAsset2]))
if (auto const ter = checkAsset(ctx.tx[sfAsset2].get<Issue>()))
return ter;
}

View File

@@ -38,7 +38,8 @@ AMMVote::preflight(PreflightContext const& ctx)
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2]))
if (auto const res = invalidAMMAssetPair(
ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
{
JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
return res;

View File

@@ -100,8 +100,8 @@ AMMWithdraw::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
auto const asset = ctx.tx[sfAsset];
auto const asset2 = ctx.tx[sfAsset2];
auto const asset = ctx.tx[sfAsset].get<Issue>();
auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
if (auto const res = invalidAMMAssetPair(asset, asset2))
{
JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
@@ -418,7 +418,12 @@ AMMWithdraw::applyGuts(Sandbox& sb)
return {result, false};
auto const res = deleteAMMAccountIfEmpty(
sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_);
sb,
ammSle,
newLPTokenBalance,
ctx_.tx[sfAsset].get<Issue>(),
ctx_.tx[sfAsset2].get<Issue>(),
j_);
// LCOV_EXCL_START
if (!res.second)
return {res.first, false};

View File

@@ -163,8 +163,8 @@ doAMMInfo(RPC::JsonContext& context)
return Unexpected(rpcACT_NOT_FOUND);
if (!issue1 && !issue2)
{
issue1 = (*amm)[sfAsset];
issue2 = (*amm)[sfAsset2];
issue1 = (*amm)[sfAsset].get<Issue>();
issue2 = (*amm)[sfAsset2].get<Issue>();
}
return ValuesFromContextParams{