mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-28 07:48:01 +00:00
Merge remote-tracking branch 'Transia-RnD-rippled/dangell7/coupon-payments' into develop
# Conflicts: # include/xrpl/protocol/detail/ledger_entries.macro # include/xrpl/protocol/jss.h # include/xrpl/tx/invariants/InvariantCheck.h # src/libxrpl/protocol/Indexes.cpp # src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp
This commit is contained in:
49
include/xrpl/ledger/helpers/CouponHelpers.h
Normal file
49
include/xrpl/ledger/helpers/CouponHelpers.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Settle a holder's accrued coupons on their MPToken.
|
||||
*
|
||||
* Adds units * (AccruedPerUnit - CouponIndex) to CouponAccrued, rounded
|
||||
* once downward to the coupon asset, then advances CouponIndex to
|
||||
* AccruedPerUnit. Units are MPTAmount + LockedAmount. Increments the
|
||||
* schedule's ClaimantCount when CouponAccrued becomes non-zero on a
|
||||
* holder that had none. The caller must view.update() both entries on
|
||||
* tesSUCCESS.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
couponSettleMPToken(SLE::ref schedule, SLE::ref mptoken, beast::Journal j);
|
||||
|
||||
/**
|
||||
* Settle a holder against the schedule of the issuance their MPToken
|
||||
* belongs to, if that issuance carries lsfMPTCouponSchedule. Does
|
||||
* nothing when the flag is clear, which is the case for every issuance
|
||||
* without a coupon schedule.
|
||||
*
|
||||
* This is the entry point called from the MPT unit-change helpers.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
couponSettleIfScheduled(
|
||||
ApplyView& view,
|
||||
SLE::const_ref issuance,
|
||||
SLE::ref mptoken,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* The units a holder is credited for: MPTAmount + LockedAmount.
|
||||
*/
|
||||
[[nodiscard]] std::uint64_t
|
||||
couponHolderUnits(SLE::const_ref mptoken);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -396,6 +396,15 @@ loan(uint256 const& key)
|
||||
return {ltLOAN, key};
|
||||
}
|
||||
|
||||
Keylet
|
||||
couponSchedule(uint192 const& mptIssuanceID) noexcept;
|
||||
|
||||
inline Keylet
|
||||
couponSchedule(uint256 const& scheduleKey)
|
||||
{
|
||||
return {ltCOUPON_SCHEDULE, scheduleKey};
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept;
|
||||
|
||||
|
||||
@@ -179,7 +179,8 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsfMPTCanTrade, 0x00000010) \
|
||||
LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \
|
||||
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsfMPTCanHoldConfidentialBalance, 0x00000080)) \
|
||||
LSF_FLAG(lsfMPTCanHoldConfidentialBalance, 0x00000080) \
|
||||
LSF_FLAG(lsfMPTCouponSchedule, 0x00000100)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPToken, \
|
||||
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
|
||||
|
||||
@@ -155,3 +155,4 @@ XRPL_FEATURE(Passkey, Supported::Yes, VoteBehavior::DefaultN
|
||||
XRPL_FEATURE(MPTStructuredData, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(TokenIssuance, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(VaultContinuousAccrual, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(CouponPayments, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -443,6 +443,8 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfAuditorEncryptedBalance, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
{sfRedemptionAfter, SoeDefault},
|
||||
{sfCouponAccrued, SoeOptional},
|
||||
{sfCouponIndex, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Oracle
|
||||
@@ -732,5 +734,32 @@ LEDGER_ENTRY(ltTOKEN_ISSUANCE, 0x0092, TokenIssuance, token_issuance, ({
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
}))
|
||||
|
||||
/** A ledger object representing a bond coupon schedule: the issuer's
|
||||
standing offer of fixed periodic coupon payments per unit of a bond
|
||||
asset, in a settlement asset. One schedule per (Account, BondAsset).
|
||||
|
||||
\sa keylet::couponSchedule
|
||||
*/
|
||||
LEDGER_ENTRY(ltCOUPON_SCHEDULE, 0x0093, CouponSchedule, coupon_schedule, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfOwner, SoeRequired},
|
||||
{sfAccount, SoeRequired},
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfCouponAsset, SoeRequired},
|
||||
{sfAccruedPerUnit, SoeDefault},
|
||||
{sfPoolAmount, SoeDefault},
|
||||
{sfClaimantCount, SoeDefault},
|
||||
{sfCouponCount, SoeDefault},
|
||||
{sfLastCouponTime, SoeOptional},
|
||||
{sfCouponAmount, SoeOptional},
|
||||
{sfCouponInterval, SoeOptional},
|
||||
{sfFirstCouponTime, SoeOptional},
|
||||
{sfExpiration, SoeOptional},
|
||||
{sfCallNoticePeriod, SoeOptional},
|
||||
{sfEarliestCallTime, SoeOptional},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -495,3 +495,16 @@ TYPED_SFIELD(sfRedemptionAfter, UINT32, 95)
|
||||
TYPED_SFIELD(sfUnearnedInterest, NUMBER, 19, SField::kSmdNeedsAsset | SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfAccrualRate, NUMBER, 20, SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfStruckPrice, NUMBER, 21, SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfCouponInterval, UINT32, 96)
|
||||
TYPED_SFIELD(sfFirstCouponTime, UINT32, 97)
|
||||
TYPED_SFIELD(sfCallNoticePeriod, UINT32, 98)
|
||||
TYPED_SFIELD(sfEarliestCallTime, UINT32, 99)
|
||||
TYPED_SFIELD(sfLastCouponTime, UINT32, 100)
|
||||
TYPED_SFIELD(sfClaimantCount, UINT32, 101)
|
||||
TYPED_SFIELD(sfCouponCount, UINT32, 102)
|
||||
TYPED_SFIELD(sfCouponAmount, AMOUNT, 35)
|
||||
TYPED_SFIELD(sfAccruedPerUnit, AMOUNT, 36)
|
||||
TYPED_SFIELD(sfPoolAmount, AMOUNT, 37)
|
||||
TYPED_SFIELD(sfCouponAccrued, AMOUNT, 38)
|
||||
TYPED_SFIELD(sfCouponIndex, AMOUNT, 39)
|
||||
TYPED_SFIELD(sfCouponAsset, ISSUE, 5)
|
||||
|
||||
@@ -1363,3 +1363,53 @@ TRANSACTION(ttTOKEN_CONVERT, 107, TokenConvert,
|
||||
({
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
}))
|
||||
TRANSACTION(ttCOUPON_SCHEDULE_CREATE, 108, CouponScheduleCreate,
|
||||
({
|
||||
.delegable = Delegation::NotDelegable,
|
||||
.amendment = featureCouponPayments,
|
||||
}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfCouponAsset, SoeRequired, SoeMptSupported},
|
||||
{sfCouponAmount, SoeOptional, SoeMptSupported},
|
||||
{sfCouponInterval, SoeOptional},
|
||||
{sfFirstCouponTime, SoeOptional},
|
||||
{sfExpiration, SoeOptional},
|
||||
{sfCallNoticePeriod, SoeOptional},
|
||||
{sfEarliestCallTime, SoeOptional},
|
||||
}))
|
||||
TRANSACTION(ttCOUPON_SCHEDULE_SET, 109, CouponScheduleSet,
|
||||
({
|
||||
.delegable = Delegation::NotDelegable,
|
||||
.amendment = featureCouponPayments,
|
||||
}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfExpiration, SoeRequired},
|
||||
}))
|
||||
TRANSACTION(ttCOUPON_SCHEDULE_DELETE, 110, CouponScheduleDelete,
|
||||
({
|
||||
.delegable = Delegation::NotDelegable,
|
||||
.amendment = featureCouponPayments,
|
||||
}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
}))
|
||||
TRANSACTION(ttCOUPON_CLAIM, 111, CouponClaim,
|
||||
({
|
||||
.delegable = Delegation::Delegable,
|
||||
.amendment = featureCouponPayments,
|
||||
}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfAmount, SoeOptional, SoeMptSupported},
|
||||
}))
|
||||
TRANSACTION(ttCOUPON_PAY, 112, CouponPay,
|
||||
({
|
||||
.delegable = Delegation::NotDelegable,
|
||||
.amendment = featureCouponPayments,
|
||||
}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
}))
|
||||
|
||||
@@ -374,6 +374,7 @@ JSS(load_factor_local); // out: NetworkOPs
|
||||
JSS(load_factor_net); // out: NetworkOPs
|
||||
JSS(load_factor_server); // out: NetworkOPs
|
||||
JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs
|
||||
JSS(coupon_schedule_id); // in: LedgerEntry
|
||||
JSS(loan_broker_id); // in: LedgerEntry
|
||||
JSS(loan_seq); // in: LedgerEntry
|
||||
JSS(local); // out: resource/Logic.h
|
||||
|
||||
650
include/xrpl/protocol_autogen/ledger_entries/CouponSchedule.h
Normal file
650
include/xrpl/protocol_autogen/ledger_entries/CouponSchedule.h
Normal file
@@ -0,0 +1,650 @@
|
||||
// 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 CouponScheduleBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: CouponSchedule
|
||||
*
|
||||
* Type: ltCOUPON_SCHEDULE (0x0093)
|
||||
* RPC Name: coupon_schedule
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use CouponScheduleBuilder to construct new ledger entries.
|
||||
*/
|
||||
class CouponSchedule : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltCOUPON_SCHEDULE;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponSchedule ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit CouponSchedule(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 CouponSchedule");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @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 Get sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwner (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOwner() const
|
||||
{
|
||||
return this->sle_->at(sfOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAccount (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getAccount() const
|
||||
{
|
||||
return this->sle_->at(sfAccount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->sle_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponAsset (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ISSUE::type::value_type
|
||||
getCouponAsset() const
|
||||
{
|
||||
return this->sle_->at(sfCouponAsset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAccruedPerUnit (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getAccruedPerUnit() const
|
||||
{
|
||||
if (hasAccruedPerUnit())
|
||||
return this->sle_->at(sfAccruedPerUnit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAccruedPerUnit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAccruedPerUnit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfAccruedPerUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPoolAmount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getPoolAmount() const
|
||||
{
|
||||
if (hasPoolAmount())
|
||||
return this->sle_->at(sfPoolAmount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfPoolAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasPoolAmount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfPoolAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfClaimantCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getClaimantCount() const
|
||||
{
|
||||
if (hasClaimantCount())
|
||||
return this->sle_->at(sfClaimantCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfClaimantCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasClaimantCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfClaimantCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getCouponCount() const
|
||||
{
|
||||
if (hasCouponCount())
|
||||
return this->sle_->at(sfCouponCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCouponCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfLastCouponTime (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getLastCouponTime() const
|
||||
{
|
||||
if (hasLastCouponTime())
|
||||
return this->sle_->at(sfLastCouponTime);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfLastCouponTime is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasLastCouponTime() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfLastCouponTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getCouponAmount() const
|
||||
{
|
||||
if (hasCouponAmount())
|
||||
return this->sle_->at(sfCouponAmount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponAmount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCouponAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponInterval (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getCouponInterval() const
|
||||
{
|
||||
if (hasCouponInterval())
|
||||
return this->sle_->at(sfCouponInterval);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponInterval is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponInterval() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCouponInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFirstCouponTime (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getFirstCouponTime() const
|
||||
{
|
||||
if (hasFirstCouponTime())
|
||||
return this->sle_->at(sfFirstCouponTime);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFirstCouponTime is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFirstCouponTime() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfFirstCouponTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExpiration (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExpiration() const
|
||||
{
|
||||
if (hasExpiration())
|
||||
return this->sle_->at(sfExpiration);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExpiration is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExpiration() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCallNoticePeriod (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getCallNoticePeriod() const
|
||||
{
|
||||
if (hasCallNoticePeriod())
|
||||
return this->sle_->at(sfCallNoticePeriod);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCallNoticePeriod is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCallNoticePeriod() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCallNoticePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfEarliestCallTime (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getEarliestCallTime() const
|
||||
{
|
||||
if (hasEarliestCallTime())
|
||||
return this->sle_->at(sfEarliestCallTime);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfEarliestCallTime is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasEarliestCallTime() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfEarliestCallTime);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponSchedule 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 CouponScheduleBuilder : public LedgerEntryBuilderBase<CouponScheduleBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponScheduleBuilder with required fields.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param owner The sfOwner field value.
|
||||
* @param account The sfAccount field value.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param couponAsset The sfCouponAsset field value.
|
||||
*/
|
||||
CouponScheduleBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& account,std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID,std::decay_t<typename SF_ISSUE::type::value_type> const& couponAsset)
|
||||
: LedgerEntryBuilderBase<CouponScheduleBuilder>(ltCOUPON_SCHEDULE)
|
||||
{
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
setOwnerNode(ownerNode);
|
||||
setOwner(owner);
|
||||
setAccount(account);
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setCouponAsset(couponAsset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleBuilder 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.
|
||||
*/
|
||||
CouponScheduleBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltCOUPON_SCHEDULE)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for CouponSchedule");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ledger entry-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
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.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwner (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwner] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAccount (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setAccount(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponAsset (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setCouponAsset(std::decay_t<typename SF_ISSUE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponAsset] = STIssue(sfCouponAsset, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAccruedPerUnit (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setAccruedPerUnit(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAccruedPerUnit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPoolAmount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setPoolAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPoolAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfClaimantCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setClaimantCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfClaimantCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setCouponCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfLastCouponTime (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setLastCouponTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfLastCouponTime] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setCouponAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponInterval (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setCouponInterval(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponInterval] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFirstCouponTime (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setFirstCouponTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFirstCouponTime] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExpiration (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExpiration] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCallNoticePeriod (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setCallNoticePeriod(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCallNoticePeriod] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfEarliestCallTime (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleBuilder&
|
||||
setEarliestCallTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfEarliestCallTime] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed CouponSchedule wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
CouponSchedule
|
||||
build(uint256 const& index)
|
||||
{
|
||||
return CouponSchedule{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
@@ -291,6 +291,54 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfHolderEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponAccrued (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getCouponAccrued() const
|
||||
{
|
||||
if (hasCouponAccrued())
|
||||
return this->sle_->at(sfCouponAccrued);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponAccrued is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponAccrued() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCouponAccrued);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponIndex (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getCouponIndex() const
|
||||
{
|
||||
if (hasCouponIndex())
|
||||
return this->sle_->at(sfCouponIndex);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponIndex is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponIndex() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfCouponIndex);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -482,6 +530,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponAccrued (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setCouponAccrued(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponAccrued] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponIndex (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setCouponIndex(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponIndex] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed MPToken wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
170
include/xrpl/protocol_autogen/transactions/CouponClaim.h
Normal file
170
include/xrpl/protocol_autogen/transactions/CouponClaim.h
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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 CouponClaimBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: CouponClaim
|
||||
*
|
||||
* Type: ttCOUPON_CLAIM (95)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureCouponPayments
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CouponClaimBuilder to construct new transactions.
|
||||
*/
|
||||
class CouponClaim : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCOUPON_CLAIM;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponClaim transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit CouponClaim(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 CouponClaim");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getAmount() const
|
||||
{
|
||||
if (hasAmount())
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfAmount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponClaim 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 CouponClaimBuilder : public TransactionBuilderBase<CouponClaimBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponClaimBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
CouponClaimBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<CouponClaimBuilder>(ttCOUPON_CLAIM, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponClaimBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
CouponClaimBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCOUPON_CLAIM)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for CouponClaimBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponClaimBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponClaimBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the CouponClaim wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
CouponClaim
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return CouponClaim{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
157
include/xrpl/protocol_autogen/transactions/CouponPay.h
Normal file
157
include/xrpl/protocol_autogen/transactions/CouponPay.h
Normal file
@@ -0,0 +1,157 @@
|
||||
// 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 CouponPayBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: CouponPay
|
||||
*
|
||||
* Type: ttCOUPON_PAY (96)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCouponPayments
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CouponPayBuilder to construct new transactions.
|
||||
*/
|
||||
class CouponPay : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCOUPON_PAY;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponPay transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit CouponPay(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 CouponPay");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_AMOUNT::type::value_type
|
||||
getAmount() const
|
||||
{
|
||||
return this->tx_->at(sfAmount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponPay 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 CouponPayBuilder : public TransactionBuilderBase<CouponPayBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponPayBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param amount The sfAmount field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
CouponPayBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, 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<CouponPayBuilder>(ttCOUPON_PAY, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setAmount(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponPayBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
CouponPayBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCOUPON_PAY)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for CouponPayBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponPayBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponPayBuilder&
|
||||
setAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the CouponPay wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
CouponPay
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return CouponPay{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -0,0 +1,381 @@
|
||||
// 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 CouponScheduleCreateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: CouponScheduleCreate
|
||||
*
|
||||
* Type: ttCOUPON_SCHEDULE_CREATE (92)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCouponPayments
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CouponScheduleCreateBuilder to construct new transactions.
|
||||
*/
|
||||
class CouponScheduleCreate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCOUPON_SCHEDULE_CREATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleCreate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit CouponScheduleCreate(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 CouponScheduleCreate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponAsset (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ISSUE::type::value_type
|
||||
getCouponAsset() const
|
||||
{
|
||||
return this->tx_->at(sfCouponAsset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getCouponAmount() const
|
||||
{
|
||||
if (hasCouponAmount())
|
||||
{
|
||||
return this->tx_->at(sfCouponAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCouponAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCouponInterval (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getCouponInterval() const
|
||||
{
|
||||
if (hasCouponInterval())
|
||||
{
|
||||
return this->tx_->at(sfCouponInterval);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCouponInterval is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCouponInterval() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCouponInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFirstCouponTime (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getFirstCouponTime() const
|
||||
{
|
||||
if (hasFirstCouponTime())
|
||||
{
|
||||
return this->tx_->at(sfFirstCouponTime);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFirstCouponTime is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFirstCouponTime() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfFirstCouponTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExpiration (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getExpiration() const
|
||||
{
|
||||
if (hasExpiration())
|
||||
{
|
||||
return this->tx_->at(sfExpiration);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfExpiration is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasExpiration() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfCallNoticePeriod (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getCallNoticePeriod() const
|
||||
{
|
||||
if (hasCallNoticePeriod())
|
||||
{
|
||||
return this->tx_->at(sfCallNoticePeriod);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCallNoticePeriod is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCallNoticePeriod() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCallNoticePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfEarliestCallTime (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getEarliestCallTime() const
|
||||
{
|
||||
if (hasEarliestCallTime())
|
||||
{
|
||||
return this->tx_->at(sfEarliestCallTime);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfEarliestCallTime is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasEarliestCallTime() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfEarliestCallTime);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponScheduleCreate 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 CouponScheduleCreateBuilder : public TransactionBuilderBase<CouponScheduleCreateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponScheduleCreateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param couponAsset The sfCouponAsset field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
CouponScheduleCreateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_ISSUE::type::value_type> const& couponAsset, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<CouponScheduleCreateBuilder>(ttCOUPON_SCHEDULE_CREATE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setCouponAsset(couponAsset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleCreateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
CouponScheduleCreateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCOUPON_SCHEDULE_CREATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for CouponScheduleCreateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponAsset (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setCouponAsset(std::decay_t<typename SF_ISSUE::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponAsset] = STIssue(sfCouponAsset, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setCouponAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCouponInterval (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setCouponInterval(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCouponInterval] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFirstCouponTime (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setFirstCouponTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFirstCouponTime] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExpiration (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExpiration] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfCallNoticePeriod (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setCallNoticePeriod(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCallNoticePeriod] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfEarliestCallTime (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleCreateBuilder&
|
||||
setEarliestCallTime(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfEarliestCallTime] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the CouponScheduleCreate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
CouponScheduleCreate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return CouponScheduleCreate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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 CouponScheduleDeleteBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: CouponScheduleDelete
|
||||
*
|
||||
* Type: ttCOUPON_SCHEDULE_DELETE (94)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCouponPayments
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CouponScheduleDeleteBuilder to construct new transactions.
|
||||
*/
|
||||
class CouponScheduleDelete : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCOUPON_SCHEDULE_DELETE;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleDelete transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit CouponScheduleDelete(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 CouponScheduleDelete");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponScheduleDelete 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 CouponScheduleDeleteBuilder : public TransactionBuilderBase<CouponScheduleDeleteBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponScheduleDeleteBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
CouponScheduleDeleteBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<CouponScheduleDeleteBuilder>(ttCOUPON_SCHEDULE_DELETE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleDeleteBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
CouponScheduleDeleteBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCOUPON_SCHEDULE_DELETE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for CouponScheduleDeleteBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleDeleteBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the CouponScheduleDelete wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
CouponScheduleDelete
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return CouponScheduleDelete{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
155
include/xrpl/protocol_autogen/transactions/CouponScheduleSet.h
Normal file
155
include/xrpl/protocol_autogen/transactions/CouponScheduleSet.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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 CouponScheduleSetBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: CouponScheduleSet
|
||||
*
|
||||
* Type: ttCOUPON_SCHEDULE_SET (93)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureCouponPayments
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use CouponScheduleSetBuilder to construct new transactions.
|
||||
*/
|
||||
class CouponScheduleSet : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCOUPON_SCHEDULE_SET;
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleSet transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit CouponScheduleSet(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 CouponScheduleSet");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfExpiration (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getExpiration() const
|
||||
{
|
||||
return this->tx_->at(sfExpiration);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for CouponScheduleSet 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 CouponScheduleSetBuilder : public TransactionBuilderBase<CouponScheduleSetBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new CouponScheduleSetBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param expiration The sfExpiration field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
CouponScheduleSetBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_UINT32::type::value_type> const& expiration, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<CouponScheduleSetBuilder>(ttCOUPON_SCHEDULE_SET, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setExpiration(expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a CouponScheduleSetBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
CouponScheduleSetBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCOUPON_SCHEDULE_SET)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for CouponScheduleSetBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleSetBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfExpiration (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
CouponScheduleSetBuilder&
|
||||
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfExpiration] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the CouponScheduleSet wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
CouponScheduleSet
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return CouponScheduleSet{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
39
include/xrpl/tx/invariants/CouponInvariant.h
Normal file
39
include/xrpl/tx/invariants/CouponInvariant.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariants: a coupon schedule's accumulator and pool only move the
|
||||
* ways the amendment allows.
|
||||
*
|
||||
* 1. `AccruedPerUnit` never decreases, and changes only under `CouponPay`.
|
||||
* 2. `PoolAmount` is never negative. It increases only under `CouponPay` and
|
||||
* decreases only under `CouponClaim` or `CouponScheduleDelete`.
|
||||
* 3. A holder's `CouponIndex` never decreases: settlement only advances it
|
||||
* toward the schedule's `AccruedPerUnit`.
|
||||
*/
|
||||
class ValidCouponSchedule
|
||||
{
|
||||
// Pair is <before, after>; both are needed to check direction of change.
|
||||
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> schedules_;
|
||||
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> mptokens_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
#include <xrpl/tx/invariants/CouponInvariant.h>
|
||||
#include <xrpl/tx/invariants/DirectoryInvariant.h>
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
|
||||
@@ -490,7 +491,8 @@ using InvariantChecks = std::tuple<
|
||||
ObjectHasPseudoAccount,
|
||||
SponsorshipOwnerCountsMatch,
|
||||
SponsorshipAccountCountMatchesField,
|
||||
ValidTokenIssuance>;
|
||||
ValidTokenIssuance,
|
||||
ValidCouponSchedule>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
43
include/xrpl/tx/transactors/coupon/CouponClaim.h
Normal file
43
include/xrpl/tx/transactors/coupon/CouponClaim.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.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>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CouponClaim : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit CouponClaim(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
43
include/xrpl/tx/transactors/coupon/CouponPay.h
Normal file
43
include/xrpl/tx/transactors/coupon/CouponPay.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.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>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CouponPay : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit CouponPay(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
43
include/xrpl/tx/transactors/coupon/CouponScheduleCreate.h
Normal file
43
include/xrpl/tx/transactors/coupon/CouponScheduleCreate.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.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>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CouponScheduleCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit CouponScheduleCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
43
include/xrpl/tx/transactors/coupon/CouponScheduleDelete.h
Normal file
43
include/xrpl/tx/transactors/coupon/CouponScheduleDelete.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.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>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CouponScheduleDelete : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit CouponScheduleDelete(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
43
include/xrpl/tx/transactors/coupon/CouponScheduleSet.h
Normal file
43
include/xrpl/tx/transactors/coupon/CouponScheduleSet.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.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>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CouponScheduleSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit CouponScheduleSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
87
src/libxrpl/ledger/helpers/CouponHelpers.cpp
Normal file
87
src/libxrpl/ledger/helpers/CouponHelpers.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::uint64_t
|
||||
couponHolderUnits(SLE::const_ref mptoken)
|
||||
{
|
||||
std::uint64_t units = mptoken->at(sfMPTAmount);
|
||||
if (auto const locked = mptoken->at(~sfLockedAmount))
|
||||
units += *locked;
|
||||
return units;
|
||||
}
|
||||
|
||||
TER
|
||||
couponSettleMPToken(SLE::ref schedule, SLE::ref mptoken, beast::Journal j)
|
||||
{
|
||||
STAmount const perUnit = schedule->at(sfAccruedPerUnit);
|
||||
STAmount const zero{perUnit.asset(), 0};
|
||||
auto const indexField = mptoken->at(~sfCouponIndex);
|
||||
STAmount const index = indexField ? *indexField : zero;
|
||||
|
||||
// Nothing declared since this holder last settled.
|
||||
if (perUnit <= index)
|
||||
return tesSUCCESS;
|
||||
|
||||
std::uint64_t const units = couponHolderUnits(mptoken);
|
||||
if (units != 0)
|
||||
{
|
||||
auto const accruedField = mptoken->at(~sfCouponAccrued);
|
||||
STAmount const accrued = accruedField ? *accruedField : zero;
|
||||
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::Downward);
|
||||
Number const addition = Number(units) * (Number(perUnit) - Number(index));
|
||||
STAmount const additionAmt{perUnit.asset(), addition};
|
||||
|
||||
if (!canAdd(accrued, additionAmt))
|
||||
{
|
||||
JLOG(j.debug()) << "couponSettleMPToken: accrual addition loses precision.";
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
bool const wasEmpty = accrued == beast::kZero;
|
||||
STAmount const settled = accrued + additionAmt;
|
||||
mptoken->at(sfCouponAccrued) = settled;
|
||||
|
||||
// ClaimantCount tracks MPTokens holding an unclaimed balance.
|
||||
if (wasEmpty && settled != beast::kZero)
|
||||
schedule->at(sfClaimantCount) = schedule->at(sfClaimantCount) + 1;
|
||||
}
|
||||
|
||||
mptoken->at(sfCouponIndex) = perUnit;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
couponSettleIfScheduled(
|
||||
ApplyView& view,
|
||||
SLE::const_ref issuance,
|
||||
SLE::ref mptoken,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!issuance || !issuance->isFlag(lsfMPTCouponSchedule))
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const sleSchedule = view.peek(keylet::couponSchedule(mptoken->at(sfMPTokenIssuanceID)));
|
||||
if (!sleSchedule)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (auto const ter = couponSettleMPToken(sleSchedule, mptoken, j); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
view.update(sleSchedule);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
@@ -873,6 +874,14 @@ unlockEscrowMPT(
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (sender != receiver)
|
||||
{
|
||||
if (auto const ter = couponSettleIfScheduled(view, sleIssuance, sle, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
auto current = sle->getFieldU64(sfMPTAmount);
|
||||
auto delta = netAmount.mpt().value();
|
||||
|
||||
@@ -920,6 +929,13 @@ unlockEscrowMPT(
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (sender != receiver)
|
||||
{
|
||||
if (auto const ter = couponSettleIfScheduled(view, sleIssuance, sle, j); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (!sle->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "unlockEscrowMPT: no locked amount in MPToken for " << to_string(sender);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
@@ -1278,6 +1279,11 @@ directSendNoFeeMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
{
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (auto const ter = couponSettleIfScheduled(view, sleIssuance, sle, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
auto const senderBalance = sle->getFieldU64(sfMPTAmount);
|
||||
if (senderBalance < amt)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
@@ -1308,6 +1314,11 @@ directSendNoFeeMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
|
||||
if (auto sle = view.peek(mptokenID))
|
||||
{
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (auto const ter = couponSettleIfScheduled(view, sleIssuance, sle, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (view.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
if ((*sle)[sfMPTAmount] > (std::numeric_limits<std::uint64_t>::max() - amt))
|
||||
|
||||
@@ -106,6 +106,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
PasskeyList = 'k',
|
||||
Sponsorship = '>',
|
||||
TokenIssuance = 'F',
|
||||
CouponSchedule = 'J',
|
||||
|
||||
ContractSource = 'Z',
|
||||
Contract = 'c',
|
||||
@@ -607,6 +608,12 @@ loan(uint256 const& loanBrokerID, SeqProxy const& loanSeq) noexcept
|
||||
return loan(indexHash(LedgerNameSpace::Loan, loanBrokerID, loanSeq.value()));
|
||||
}
|
||||
|
||||
Keylet
|
||||
couponSchedule(uint192 const& mptIssuanceID) noexcept
|
||||
{
|
||||
return couponSchedule(indexHash(LedgerNameSpace::CouponSchedule, mptIssuanceID));
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, SeqProxy const& seq) noexcept
|
||||
{
|
||||
|
||||
111
src/libxrpl/tx/invariants/CouponInvariant.cpp
Normal file
111
src/libxrpl/tx/invariants/CouponInvariant.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <xrpl/tx/invariants/CouponInvariant.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ValidCouponSchedule::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
if (isDelete)
|
||||
return;
|
||||
|
||||
if (before && before->getType() == ltCOUPON_SCHEDULE)
|
||||
schedules_.emplace_back(before, after);
|
||||
else if (!before && after && after->getType() == ltCOUPON_SCHEDULE)
|
||||
schedules_.emplace_back(before, after);
|
||||
|
||||
if (before && before->getType() == ltMPTOKEN)
|
||||
mptokens_.emplace_back(before, after);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidCouponSchedule::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (!isTesSuccess(result))
|
||||
return true;
|
||||
|
||||
auto const txType = tx.getTxnType();
|
||||
bool const isPay = txType == ttCOUPON_PAY;
|
||||
bool const mayDrainPool = txType == ttCOUPON_CLAIM || txType == ttCOUPON_SCHEDULE_DELETE;
|
||||
|
||||
for (auto const& [before, after] : schedules_)
|
||||
{
|
||||
if (!after)
|
||||
continue;
|
||||
|
||||
if (after->at(sfPoolAmount) < beast::kZero)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: coupon pool is negative";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!before)
|
||||
continue;
|
||||
|
||||
STAmount const perUnitBefore = before->at(sfAccruedPerUnit);
|
||||
STAmount const perUnitAfter = after->at(sfAccruedPerUnit);
|
||||
|
||||
if (perUnitAfter < perUnitBefore)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: coupon accumulator decreased";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (perUnitAfter != perUnitBefore && !isPay)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: coupon accumulator moved outside CouponPay";
|
||||
return false;
|
||||
}
|
||||
|
||||
STAmount const poolBefore = before->at(sfPoolAmount);
|
||||
STAmount const poolAfter = after->at(sfPoolAmount);
|
||||
|
||||
if (poolAfter > poolBefore && !isPay)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: coupon pool grew outside CouponPay";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (poolAfter < poolBefore && !mayDrainPool)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: coupon pool shrank outside a claim or delete";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [before, after] : mptokens_)
|
||||
{
|
||||
if (!before || !after)
|
||||
continue;
|
||||
|
||||
auto const indexBefore = before->at(~sfCouponIndex);
|
||||
auto const indexAfter = after->at(~sfCouponIndex);
|
||||
|
||||
if (indexBefore && indexAfter && *indexAfter < *indexBefore)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: holder coupon index moved backwards";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A holder's index may not be dropped while it carries an accrual.
|
||||
if (indexBefore && !indexAfter && after->isFieldPresent(sfCouponAccrued))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: holder coupon index removed while owed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
172
src/libxrpl/tx/transactors/coupon/CouponClaim.cpp
Normal file
172
src/libxrpl/tx/transactors/coupon/CouponClaim.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include <xrpl/tx/transactors/coupon/CouponClaim.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
CouponClaim::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfMPTokenIssuanceID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
if (auto const amount = ctx.tx[~sfAmount]; amount && *amount <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponClaim::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
AccountID const holder = ctx.tx[sfAccount];
|
||||
|
||||
auto const schedule = ctx.view.read(keylet::couponSchedule(issuanceID));
|
||||
if (!schedule)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceID, holder)))
|
||||
return tecNO_ENTRY;
|
||||
|
||||
auto const& couponAsset = schedule->at(sfCouponAsset);
|
||||
|
||||
if (auto const amount = ctx.tx[~sfAmount]; amount && amount->asset() != couponAsset)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Delivery eligibility on the holder's side only. The payer's side was
|
||||
// settled when CouponPay funded the pool.
|
||||
if (!couponAsset.native() && holder != couponAsset.getIssuer())
|
||||
{
|
||||
if (auto const ter = requireAuth(ctx.view, couponAsset, holder); !isTesSuccess(ter))
|
||||
return ter;
|
||||
if (isFrozen(ctx.view, holder, couponAsset))
|
||||
return couponAsset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponClaim::doApply()
|
||||
{
|
||||
auto const issuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto schedule = view().peek(keylet::couponSchedule(issuanceID));
|
||||
if (!schedule)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto mptoken = view().peek(keylet::mptoken(issuanceID, accountID_));
|
||||
if (!mptoken)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Bring the holder current before reading what they are owed.
|
||||
if (auto const ter = couponSettleMPToken(schedule, mptoken, j_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const zero{schedule->at(sfCouponAsset), 0};
|
||||
auto const accruedField = mptoken->at(~sfCouponAccrued);
|
||||
STAmount const accrued = accruedField ? *accruedField : zero;
|
||||
if (accrued <= beast::kZero)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const requested = ctx_.tx[~sfAmount];
|
||||
STAmount const pay = std::min(requested ? *requested : accrued, accrued);
|
||||
STAmount const pool = schedule->at(sfPoolAmount);
|
||||
if (pool < pay)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE — the pool always covers accruals.
|
||||
|
||||
AccountID const payer = schedule->at(sfAccount);
|
||||
auto const& couponAsset = pay.asset();
|
||||
|
||||
// Release from the pool. XRP is credited directly; an IOU comes back off
|
||||
// its issuer's trust line with the transfer fee deducted and an MPT is
|
||||
// unlocked, the same release the token escrow performs.
|
||||
if (couponAsset.native())
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
sle->at(sfBalance) = sle->at(sfBalance) + pay;
|
||||
view().update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const issuerID = couponAsset.getIssuer();
|
||||
auto sleDest = view().peek(keylet::account(accountID_));
|
||||
if (!sleDest)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
Rate const lockedRate = transferRate(view(), issuerID);
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.getApplyViewContext(),
|
||||
lockedRate,
|
||||
sleDest,
|
||||
preFeeBalance_,
|
||||
pay,
|
||||
issuerID,
|
||||
payer,
|
||||
accountID_,
|
||||
true,
|
||||
j_);
|
||||
},
|
||||
couponAsset.value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
STAmount const remaining = accrued - pay;
|
||||
if (remaining <= beast::kZero)
|
||||
{
|
||||
mptoken->makeFieldAbsent(sfCouponAccrued);
|
||||
schedule->at(sfClaimantCount) = schedule->at(sfClaimantCount) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mptoken->at(sfCouponAccrued) = remaining;
|
||||
}
|
||||
view().update(mptoken);
|
||||
|
||||
schedule->at(sfPoolAmount) = pool - pay;
|
||||
view().update(schedule);
|
||||
|
||||
ctx_.deliver(pay);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CouponClaim::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
}
|
||||
|
||||
bool
|
||||
CouponClaim::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
187
src/libxrpl/tx/transactors/coupon/CouponPay.cpp
Normal file
187
src/libxrpl/tx/transactors/coupon/CouponPay.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include <xrpl/tx/transactors/coupon/CouponPay.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
// The coupon total for one payment: per-unit amount times units outstanding,
|
||||
// rounded up so the pool is never short of what holders can settle.
|
||||
STAmount
|
||||
couponTotal(STAmount const& perUnit, std::uint64_t outstanding)
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
|
||||
return STAmount{perUnit.asset(), Number(outstanding) * Number(perUnit)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NotTEC
|
||||
CouponPay::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfMPTokenIssuanceID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (amount <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponPay::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto const schedule = ctx.view.read(keylet::couponSchedule(issuanceID));
|
||||
if (!schedule)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
// Only the payer named on the schedule may declare a coupon. For a vault
|
||||
// share issuance that is the vault pseudo-account, not the administrator.
|
||||
if (ctx.tx[sfAccount] != schedule->at(sfAccount))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (amount.asset() != schedule->at(sfCouponAsset))
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
auto const closeTime = ctx.view.header().parentCloseTime.time_since_epoch().count();
|
||||
if (auto const expiration = schedule->at(~sfExpiration); expiration && closeTime >= *expiration)
|
||||
return tecEXPIRED;
|
||||
|
||||
auto const issuance = ctx.view.read(keylet::mptokenIssuance(issuanceID));
|
||||
if (!issuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
std::uint64_t const outstanding = issuance->at(sfOutstandingAmount);
|
||||
if (outstanding == 0)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// The pool must be funded in full at declaration, so a claim can never
|
||||
// fail for want of funds.
|
||||
STAmount const total = couponTotal(amount, outstanding);
|
||||
AccountID const payer = ctx.tx[sfAccount];
|
||||
if (total.native())
|
||||
{
|
||||
if (xrpLiquid(ctx.view, payer, 0, ctx.j) < total.xrp())
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
else if (payer != total.getIssuer())
|
||||
{
|
||||
STAmount const spendable = std::visit(
|
||||
[&]<typename T>(T const& issue) {
|
||||
if constexpr (std::is_same_v<T, Issue>)
|
||||
return accountHolds(
|
||||
ctx.view,
|
||||
payer,
|
||||
issue.currency,
|
||||
issue.account,
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
ctx.j);
|
||||
else
|
||||
return accountHolds(
|
||||
ctx.view,
|
||||
payer,
|
||||
issue,
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ctx.j);
|
||||
},
|
||||
total.asset().value());
|
||||
if (spendable < total)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponPay::doApply()
|
||||
{
|
||||
auto const issuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto schedule = view().peek(keylet::couponSchedule(issuanceID));
|
||||
if (!schedule)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuance = view().read(keylet::mptokenIssuance(issuanceID));
|
||||
if (!issuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const perUnit = ctx_.tx[sfAmount];
|
||||
STAmount const total = couponTotal(perUnit, issuance->at(sfOutstandingAmount));
|
||||
|
||||
// Move the coupon out of the payer's spendable balance. XRP is debited
|
||||
// directly; an IOU parks on its issuer's trust line and an MPT moves into
|
||||
// LockedAmount, the same custody the token escrow uses.
|
||||
if (total.native())
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
auto const balance = sle->at(sfBalance);
|
||||
if (balance < total)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
sle->at(sfBalance) = balance - total;
|
||||
view().update(sle);
|
||||
}
|
||||
else if (total.holds<MPTIssue>())
|
||||
{
|
||||
if (auto const ter = lockEscrowMPT(view(), accountID_, total, j_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const issuerID = total.getIssuer();
|
||||
if (issuerID == accountID_)
|
||||
return tecNO_PERMISSION;
|
||||
if (auto const ter = directSendNoFee(view(), accountID_, issuerID, total, true, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
STAmount const accruedPerUnit = schedule->at(sfAccruedPerUnit);
|
||||
STAmount const pool = schedule->at(sfPoolAmount);
|
||||
if (!canAdd(accruedPerUnit, perUnit) || !canAdd(pool, total))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
schedule->at(sfAccruedPerUnit) = accruedPerUnit + perUnit;
|
||||
schedule->at(sfPoolAmount) = pool + total;
|
||||
schedule->at(sfCouponCount) = schedule->at(sfCouponCount) + 1;
|
||||
schedule->at(sfLastCouponTime) =
|
||||
static_cast<std::uint32_t>(view().header().parentCloseTime.time_since_epoch().count());
|
||||
view().update(schedule);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CouponPay::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
}
|
||||
|
||||
bool
|
||||
CouponPay::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
202
src/libxrpl/tx/transactors/coupon/CouponScheduleCreate.cpp
Normal file
202
src/libxrpl/tx/transactors/coupon/CouponScheduleCreate.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include <xrpl/tx/transactors/coupon/CouponScheduleCreate.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
CouponScheduleCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfMPTokenIssuanceID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
// A bond paying coupons in itself is rebasing, not interest.
|
||||
if (auto const& couponAsset = ctx.tx[sfCouponAsset]; couponAsset.holds<MPTIssue>() &&
|
||||
couponAsset.get<MPTIssue>().getMptID() == ctx.tx[sfMPTokenIssuanceID])
|
||||
return temMALFORMED;
|
||||
|
||||
// The declared terms are all-or-nothing: a schedule either publishes a
|
||||
// coupon date grid or distributes ad hoc.
|
||||
bool const hasAmount = ctx.tx.isFieldPresent(sfCouponAmount);
|
||||
bool const hasInterval = ctx.tx.isFieldPresent(sfCouponInterval);
|
||||
bool const hasFirst = ctx.tx.isFieldPresent(sfFirstCouponTime);
|
||||
if (hasAmount != hasInterval || hasAmount != hasFirst)
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasAmount)
|
||||
{
|
||||
if (ctx.tx[sfCouponAmount] <= beast::kZero)
|
||||
return temBAD_AMOUNT;
|
||||
if (ctx.tx[sfCouponInterval] == 0 || ctx.tx[sfFirstCouponTime] == 0)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const expiration = ctx.tx[~sfExpiration];
|
||||
if (expiration && hasFirst && *expiration <= ctx.tx[sfFirstCouponTime])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (auto const notice = ctx.tx[~sfCallNoticePeriod]; notice && *notice == 0)
|
||||
return temMALFORMED;
|
||||
|
||||
if (auto const earliestCall = ctx.tx[~sfEarliestCallTime])
|
||||
{
|
||||
// Call protection is meaningless on a non-callable schedule.
|
||||
if (!ctx.tx.isFieldPresent(sfCallNoticePeriod))
|
||||
return temMALFORMED;
|
||||
if (expiration && *earliestCall >= *expiration)
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
AccountID const submitter = ctx.tx[sfAccount];
|
||||
|
||||
// One schedule per issuance: the keylet is the issuance, so a second
|
||||
// create collides here.
|
||||
if (ctx.view.exists(keylet::couponSchedule(issuanceID)))
|
||||
return tecDUPLICATE;
|
||||
|
||||
auto const issuance = ctx.view.read(keylet::mptokenIssuance(issuanceID));
|
||||
if (!issuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Confidential balances hide the units a coupon would accrue against.
|
||||
if (issuance->isFlag(lsfMPTCanHoldConfidentialBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const& couponAsset = ctx.tx[sfCouponAsset];
|
||||
|
||||
if (!couponAsset.native())
|
||||
{
|
||||
if (auto const ter = std::visit(
|
||||
[&]<typename T>(T const& issue) -> TER {
|
||||
if constexpr (std::is_same_v<T, Issue>)
|
||||
{
|
||||
if (!ctx.view.exists(keylet::account(issue.account)))
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx.view.exists(keylet::mptokenIssuance(issue.getMptID())))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
},
|
||||
couponAsset.value());
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
// The payer is the issuance's issuer. For a vault share issuance that is
|
||||
// the vault pseudo-account, and the vault owner administers the schedule.
|
||||
AccountID const payer = issuance->at(sfIssuer);
|
||||
if (payer == submitter)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(payer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const vaultID = sleIssuer->at(~sfVaultID))
|
||||
{
|
||||
auto const vault = ctx.view.read(keylet::vault(*vaultID));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
|
||||
if (vault->at(sfOwner) != submitter)
|
||||
return tecNO_PERMISSION;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleCreate::doApply()
|
||||
{
|
||||
auto const& tx = ctx_.tx;
|
||||
auto const issuanceID = tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto const owner = view().peek(keylet::account(accountID_));
|
||||
if (!owner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto issuance = view().peek(keylet::mptokenIssuance(issuanceID));
|
||||
if (!issuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& couponAsset = tx[sfCouponAsset];
|
||||
auto schedule = std::make_shared<SLE>(keylet::couponSchedule(issuanceID));
|
||||
|
||||
schedule->at(sfOwner) = accountID_;
|
||||
schedule->at(sfAccount) = issuance->at(sfIssuer);
|
||||
schedule->at(sfMPTokenIssuanceID) = issuanceID;
|
||||
schedule->setFieldIssue(sfCouponAsset, STIssue{sfCouponAsset, couponAsset});
|
||||
schedule->at(sfAccruedPerUnit) = STAmount{couponAsset, 0};
|
||||
schedule->at(sfPoolAmount) = STAmount{couponAsset, 0};
|
||||
if (auto const couponAmount = tx[~sfCouponAmount])
|
||||
{
|
||||
schedule->at(sfCouponAmount) = *couponAmount;
|
||||
schedule->at(sfCouponInterval) = tx[sfCouponInterval];
|
||||
schedule->at(sfFirstCouponTime) = tx[sfFirstCouponTime];
|
||||
}
|
||||
if (auto const expiration = tx[~sfExpiration])
|
||||
schedule->at(sfExpiration) = *expiration;
|
||||
if (auto const notice = tx[~sfCallNoticePeriod])
|
||||
schedule->at(sfCallNoticePeriod) = *notice;
|
||||
if (auto const earliestCall = tx[~sfEarliestCallTime])
|
||||
schedule->at(sfEarliestCallTime) = *earliestCall;
|
||||
|
||||
if (auto const ter = dirLink(view(), accountID_, schedule))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
increaseOwnerCount(view(), owner, {}, 1, j_);
|
||||
if (preFeeBalance_ < accountReserve(view(), owner, j_))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
view().insert(schedule);
|
||||
|
||||
// Settlement reads this flag to skip issuances that pay no coupons.
|
||||
issuance->setFlag(lsfMPTCouponSchedule);
|
||||
view().update(issuance);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CouponScheduleCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
}
|
||||
|
||||
bool
|
||||
CouponScheduleCreate::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
93
src/libxrpl/tx/transactors/coupon/CouponScheduleDelete.cpp
Normal file
93
src/libxrpl/tx/transactors/coupon/CouponScheduleDelete.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <xrpl/tx/transactors/coupon/CouponScheduleDelete.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
CouponScheduleDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfMPTokenIssuanceID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleDelete::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const schedule = ctx.view.read(keylet::couponSchedule(issuanceID));
|
||||
if (!schedule)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (ctx.tx[sfAccount] != schedule->at(sfOwner))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const issuance = ctx.view.read(keylet::mptokenIssuance(issuanceID));
|
||||
if (!issuance)
|
||||
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
|
||||
|
||||
if (issuance->at(sfOutstandingAmount) > 0 || schedule->at(sfClaimantCount) > 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleDelete::doApply()
|
||||
{
|
||||
auto const issuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto const k = keylet::couponSchedule(issuanceID);
|
||||
auto schedule = view().peek(k);
|
||||
if (!schedule)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view().dirRemove(keylet::ownerDir(accountID_), schedule->at(sfOwnerNode), k.key, false))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "CouponScheduleDelete: failed to remove dir link.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
decreaseOwnerCount(view(), accountID_, std::nullopt, 1, j_);
|
||||
view().erase(schedule);
|
||||
|
||||
// Settlement can stop looking at this issuance.
|
||||
if (auto issuance = view().peek(keylet::mptokenIssuance(issuanceID)))
|
||||
{
|
||||
issuance->clearFlag(lsfMPTCouponSchedule);
|
||||
view().update(issuance);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CouponScheduleDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
}
|
||||
|
||||
bool
|
||||
CouponScheduleDelete::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
100
src/libxrpl/tx/transactors/coupon/CouponScheduleSet.cpp
Normal file
100
src/libxrpl/tx/transactors/coupon/CouponScheduleSet.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <xrpl/tx/transactors/coupon/CouponScheduleSet.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
CouponScheduleSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfMPTokenIssuanceID] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx[sfExpiration] == 0)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const schedule = ctx.view.read(keylet::couponSchedule(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!schedule)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
if (ctx.tx[sfAccount] != schedule->at(sfOwner))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Non-callable schedules are fully immutable.
|
||||
auto const notice = schedule->at(~sfCallNoticePeriod);
|
||||
if (!notice)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "CouponScheduleSet: schedule is not callable.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
std::int64_t const newExpiration = ctx.tx[sfExpiration];
|
||||
std::int64_t const now = ctx.view.header().parentCloseTime.time_since_epoch().count();
|
||||
|
||||
// The notice floor: a call never takes effect with less warning than
|
||||
// the schedule declared at creation.
|
||||
if (newExpiration < now + static_cast<std::int64_t>(*notice))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Call protection.
|
||||
if (auto const earliestCall = schedule->at(~sfEarliestCallTime);
|
||||
earliestCall && newExpiration < static_cast<std::int64_t>(*earliestCall))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Shorten-only: a call can never extend the coupon liability. A
|
||||
// perpetual callable schedule may have Expiration set for the first
|
||||
// time.
|
||||
if (auto const expiration = schedule->at(~sfExpiration);
|
||||
expiration && newExpiration >= static_cast<std::int64_t>(*expiration))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CouponScheduleSet::doApply()
|
||||
{
|
||||
auto schedule = view().peek(keylet::couponSchedule(ctx_.tx[sfMPTokenIssuanceID]));
|
||||
if (!schedule)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
schedule->at(sfExpiration) = ctx_.tx[sfExpiration];
|
||||
view().update(schedule);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
CouponScheduleSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
}
|
||||
|
||||
bool
|
||||
CouponScheduleSet::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
// No transaction-specific invariants yet (future work).
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -247,6 +248,11 @@ ConfidentialMPTConvert::doApply()
|
||||
if (amtToConvert > kMaxMpTokenAmount - currentCOA)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (auto const ter = couponSettleIfScheduled(view(), sleIssuance, sleMptoken, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
(*sleMptoken)[sfMPTAmount] = amt - amtToConvert;
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] = currentCOA + amtToConvert;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/CouponHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -264,6 +265,11 @@ ConfidentialMPTConvertBack::doApply()
|
||||
// outstanding. This is the inverse of Convert.
|
||||
if (amt > kMaxMpTokenAmount - amtToConvertBack)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// Coupons accrue against units held, so settle before they change.
|
||||
if (auto const ter = couponSettleIfScheduled(view(), sleIssuance, sleMptoken, j_);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
(*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
|
||||
|
||||
auto const coa = (*sleIssuance)[~sfConfidentialOutstandingAmount].valueOr(0);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -61,6 +62,10 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
if (!sleMpt)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Unclaimed coupons are an obligation to this holder.
|
||||
if ((*sleMpt)[~sfCouponAccrued] && (*sleMpt)[sfCouponAccrued] != beast::kZero)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if ((*sleMpt)[sfMPTAmount] != 0)
|
||||
{
|
||||
if (!sleMptIssuance)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -41,6 +42,10 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
|
||||
if (sleMPT->isFieldPresent(sfTokenIssuanceID))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// A coupon schedule still references this issuance.
|
||||
if (sleMPT->isFlag(lsfMPTCouponSchedule))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -68,6 +69,10 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
|
||||
// Verify we can destroy MPTokenIssuance
|
||||
auto const sleMPT = ctx.view.read(keylet::mptokenIssuance(vault->at(sfShareMPTID)));
|
||||
|
||||
// A coupon schedule on the share issuance is an unmet obligation.
|
||||
if (sleMPT && sleMPT->isFlag(lsfMPTCouponSchedule))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if (!sleMPT)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
|
||||
526
src/test/app/Coupon_test.cpp
Normal file
526
src/test/app/Coupon_test.cpp
Normal file
@@ -0,0 +1,526 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/balance.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/utility.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class Coupon_test : public beast::unit_test::Suite
|
||||
{
|
||||
using MPTTester = test::jtx::MPTTester;
|
||||
|
||||
static json::Value
|
||||
scheduleCreate(
|
||||
test::jtx::Account const& admin,
|
||||
MPTID const& issuanceID,
|
||||
Asset const& couponAsset)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::CouponScheduleCreate;
|
||||
jv[jss::Account] = admin.human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(issuanceID);
|
||||
jv[sfCouponAsset] = toJson(couponAsset);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static json::Value
|
||||
couponPay(test::jtx::Account const& payer, MPTID const& issuanceID, STAmount const& perUnit)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::CouponPay;
|
||||
jv[jss::Account] = payer.human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(issuanceID);
|
||||
jv[sfAmount] = toJson(perUnit);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static json::Value
|
||||
couponClaim(
|
||||
test::jtx::Account const& holder,
|
||||
MPTID const& issuanceID,
|
||||
std::optional<STAmount> const& amount = std::nullopt)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::CouponClaim;
|
||||
jv[jss::Account] = holder.human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(issuanceID);
|
||||
if (amount)
|
||||
jv[sfAmount] = toJson(*amount);
|
||||
return jv;
|
||||
}
|
||||
|
||||
static json::Value
|
||||
scheduleDelete(test::jtx::Account const& admin, MPTID const& issuanceID)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::CouponScheduleDelete;
|
||||
jv[jss::Account] = admin.human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(issuanceID);
|
||||
return jv;
|
||||
}
|
||||
|
||||
// The schedule entry, or nullptr when no schedule exists.
|
||||
static std::shared_ptr<SLE const>
|
||||
scheduleOf(test::jtx::Env& env, MPTID const& issuanceID)
|
||||
{
|
||||
return env.le(keylet::couponSchedule(issuanceID));
|
||||
}
|
||||
|
||||
static STAmount
|
||||
accruedOf(test::jtx::Env& env, MPTID const& issuanceID, test::jtx::Account const& holder)
|
||||
{
|
||||
auto const sle = env.le(keylet::mptoken(issuanceID, holder.id()));
|
||||
if (!sle || !sle->isFieldPresent(sfCouponAccrued))
|
||||
return STAmount{};
|
||||
return sle->getFieldAmount(sfCouponAccrued);
|
||||
}
|
||||
|
||||
void
|
||||
testDisabled()
|
||||
{
|
||||
testcase("disabled");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments() - featureCouponPayments};
|
||||
Account const alice{"alice"};
|
||||
Account const gw{"gw"};
|
||||
env.fund(XRP(10'000), alice, gw);
|
||||
env.close();
|
||||
|
||||
MPTID const fake{};
|
||||
env(scheduleCreate(alice, fake, gw["USD"]), Ter(temDISABLED));
|
||||
env(couponPay(alice, fake, gw["USD"](1)), Ter(temDISABLED));
|
||||
env(couponClaim(alice, fake), Ter(temDISABLED));
|
||||
env(scheduleDelete(alice, fake), Ter(temDISABLED));
|
||||
}
|
||||
|
||||
void
|
||||
testScheduleCreate()
|
||||
{
|
||||
testcase("schedule create");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
|
||||
// A stranger cannot create a schedule on someone else's issuance.
|
||||
env(scheduleCreate(bob, id, USD), Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// A bond paying coupons in itself is rebasing, not interest.
|
||||
env(scheduleCreate(alice, id, mpt), Ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const sle = scheduleOf(env, id);
|
||||
BEAST_EXPECT(sle);
|
||||
if (sle)
|
||||
{
|
||||
BEAST_EXPECT(sle->getAccountID(sfOwner) == alice.id());
|
||||
BEAST_EXPECT(sle->getAccountID(sfAccount) == alice.id());
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfPoolAmount) == USD(0));
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfAccruedPerUnit) == USD(0));
|
||||
BEAST_EXPECT(sle->getFieldU32(sfClaimantCount) == 0);
|
||||
BEAST_EXPECT(sle->getFieldU32(sfCouponCount) == 0);
|
||||
}
|
||||
|
||||
// The issuance is flagged so settlement can skip issuances without one.
|
||||
auto const issuance = env.le(keylet::mptokenIssuance(id));
|
||||
BEAST_EXPECT(issuance && issuance->isFlag(lsfMPTCouponSchedule));
|
||||
|
||||
// One schedule per issuance.
|
||||
env(scheduleCreate(alice, id, USD), Ter(tecDUPLICATE));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testCouponPay()
|
||||
{
|
||||
testcase("coupon pay funds the pool");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
auto const EUR = gw["EUR"];
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env.trust(EUR(10'000), alice);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Only the payer named on the schedule may declare a coupon.
|
||||
env(couponPay(bob, id, USD(1)), Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// The coupon must be denominated in the schedule's asset.
|
||||
env(couponPay(alice, id, EUR(1)), Ter(tecWRONG_ASSET));
|
||||
env.close();
|
||||
|
||||
auto const before = env.balance(alice, USD);
|
||||
env(couponPay(alice, id, USD(2)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// 100 units outstanding at 2 USD per unit funds 200 USD.
|
||||
auto const sle = scheduleOf(env, id);
|
||||
BEAST_EXPECT(sle);
|
||||
if (sle)
|
||||
{
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfPoolAmount) == USD(200));
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfAccruedPerUnit) == USD(2));
|
||||
BEAST_EXPECT(sle->getFieldU32(sfCouponCount) == 1);
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfLastCouponTime));
|
||||
}
|
||||
BEAST_EXPECT(env.balance(alice, USD) == before - USD(200));
|
||||
}
|
||||
|
||||
void
|
||||
testAccrualAndClaim()
|
||||
{
|
||||
testcase("accrual and claim from the pool");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Nothing declared yet: nothing to claim.
|
||||
env(couponClaim(bob, id), Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
env(couponPay(alice, id, USD(2)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const bobBefore = env.balance(bob, USD);
|
||||
env(couponClaim(bob, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// 100 units at 2 USD per unit.
|
||||
BEAST_EXPECT(env.balance(bob, USD) == bobBefore + USD(200));
|
||||
BEAST_EXPECT(accruedOf(env, id, bob) == STAmount{});
|
||||
|
||||
auto const sle = scheduleOf(env, id);
|
||||
BEAST_EXPECT(sle);
|
||||
if (sle)
|
||||
{
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfPoolAmount) == USD(0));
|
||||
BEAST_EXPECT(sle->getFieldU32(sfClaimantCount) == 0);
|
||||
}
|
||||
|
||||
// A second claim has nothing left.
|
||||
env(couponClaim(bob, id), Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testArrears()
|
||||
{
|
||||
testcase("arrears collect in one claim");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.pay(alice, bob, 10);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
env(couponPay(alice, id, USD(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const bobBefore = env.balance(bob, USD);
|
||||
env(couponClaim(bob, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Three coupons of 1 USD on 10 units.
|
||||
BEAST_EXPECT(env.balance(bob, USD) == bobBefore + USD(30));
|
||||
}
|
||||
|
||||
void
|
||||
testSettlementOnTransfer()
|
||||
{
|
||||
testcase("units settle before they move");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
env.fund(XRP(10'000), gw, alice, bob, carol);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob, carol);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob, carol}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.authorize({.account = carol});
|
||||
mpt.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(couponPay(alice, id, USD(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// bob sells the whole position to carol after the coupon is declared.
|
||||
mpt.pay(bob, carol, 100);
|
||||
env.close();
|
||||
|
||||
// The coupon belongs to bob, who held the units when it was declared.
|
||||
BEAST_EXPECT(accruedOf(env, id, bob) == USD(100));
|
||||
BEAST_EXPECT(accruedOf(env, id, carol) == STAmount{});
|
||||
|
||||
// A second coupon now belongs entirely to carol.
|
||||
env(couponPay(alice, id, USD(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
env(couponClaim(carol, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// bob keeps his arrears on a zero-unit MPToken and collects later.
|
||||
env(couponClaim(bob, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(accruedOf(env, id, bob) == STAmount{});
|
||||
}
|
||||
|
||||
void
|
||||
testPoolAlwaysCovers()
|
||||
{
|
||||
testcase("a declared coupon cannot be spent away");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const dave{"dave"};
|
||||
env.fund(XRP(10'000), gw, alice, bob, dave);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob, dave);
|
||||
env(pay(gw, alice, USD(500)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(couponPay(alice, id, USD(2)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// The payer spends everything else she has.
|
||||
env(pay(alice, dave, env.balance(alice, USD)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice, USD) == USD(0));
|
||||
|
||||
// The claim is still honored: the pool holds it, not the payer.
|
||||
auto const bobBefore = env.balance(bob, USD);
|
||||
env(couponClaim(bob, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob, USD) == bobBefore + USD(200));
|
||||
}
|
||||
|
||||
void
|
||||
testScheduleDelete()
|
||||
{
|
||||
testcase("schedule delete requires no obligations");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
auto const USD = gw["USD"];
|
||||
env.trust(USD(10'000), alice, bob);
|
||||
env(pay(gw, alice, USD(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mpt{env, alice, {.holders = {bob}, .fund = false}};
|
||||
mpt.create({.flags = tfMPTCanTransfer});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const id = mpt.issuanceID();
|
||||
env(scheduleCreate(alice, id, USD), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Units are outstanding.
|
||||
env(scheduleDelete(alice, id), Ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
env(couponPay(alice, id, USD(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
mpt.pay(bob, alice, 100);
|
||||
env.close();
|
||||
|
||||
// Nothing outstanding, but bob is still owed his coupon.
|
||||
env(scheduleDelete(alice, id), Ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
env(couponClaim(bob, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(scheduleDelete(alice, id), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(!scheduleOf(env, id));
|
||||
|
||||
auto const issuance = env.le(keylet::mptokenIssuance(id));
|
||||
BEAST_EXPECT(issuance && !issuance->isFlag(lsfMPTCouponSchedule));
|
||||
}
|
||||
|
||||
void
|
||||
testMPTCouponAsset()
|
||||
{
|
||||
testcase("coupons paid in an MPT");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const gw{"gw"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), gw, alice, bob);
|
||||
env.close();
|
||||
|
||||
// gw issues the coupon asset; alice holds it and pays coupons with it.
|
||||
MPTTester coupon{env, gw, {.holders = {alice, bob}, .fund = false}};
|
||||
coupon.create({.flags = tfMPTCanTransfer});
|
||||
coupon.authorize({.account = alice});
|
||||
coupon.authorize({.account = bob});
|
||||
coupon.pay(gw, alice, 10'000);
|
||||
env.close();
|
||||
|
||||
// alice issues the bond.
|
||||
MPTTester bond{env, alice, {.holders = {bob}, .fund = false}};
|
||||
bond.create({.flags = tfMPTCanTransfer});
|
||||
bond.authorize({.account = bob});
|
||||
bond.pay(alice, bob, 100);
|
||||
env.close();
|
||||
|
||||
auto const bondID = bond.issuanceID();
|
||||
env(scheduleCreate(alice, bondID, coupon), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(couponPay(alice, bondID, STAmount{coupon, 2}), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const sle = scheduleOf(env, bondID);
|
||||
BEAST_EXPECT(sle);
|
||||
if (sle)
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfPoolAmount) == STAmount(coupon, 200));
|
||||
|
||||
env(couponClaim(bob, bondID), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// bob received 100 units at 2 of the coupon MPT each.
|
||||
coupon.checkMPTokenAmount(bob, 200);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDisabled();
|
||||
testScheduleCreate();
|
||||
testCouponPay();
|
||||
testAccrualAndClaim();
|
||||
testArrears();
|
||||
testSettlementOnTransfer();
|
||||
testPoolAlwaysCovers();
|
||||
testScheduleDelete();
|
||||
testMPTCouponAsset();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Coupon, app, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
131
src/test/app/invariants/InvariantsCoupon_test.cpp
Normal file
131
src/test/app/invariants/InvariantsCoupon_test.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <test/app/invariants/InvariantsBase.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/pay.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class InvariantsCoupon_test : public InvariantsBase
|
||||
{
|
||||
FeatureBitset const all_{test::jtx::testableAmendments()};
|
||||
|
||||
void
|
||||
testValidCouponSchedule()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
testcase << "coupon schedule invariants";
|
||||
|
||||
// Build an issuance with a coupon schedule, then corrupt it.
|
||||
auto setup = [&](Env& env, Account const& issuer, Account const& holder) {
|
||||
MPTTester mpt(
|
||||
{.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
|
||||
auto const id = mpt.issuanceID();
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::CouponScheduleCreate;
|
||||
jv[jss::Account] = issuer.human();
|
||||
jv[sfMPTokenIssuanceID] = to_string(id);
|
||||
jv[sfCouponAsset] = toJson(xrpIssue());
|
||||
env(jv);
|
||||
env.close();
|
||||
return id;
|
||||
};
|
||||
|
||||
{
|
||||
Env env(*this, all_);
|
||||
Account const issuer{"issuer"};
|
||||
Account const holder{"holder"};
|
||||
env.fund(XRP(10'000), issuer, holder);
|
||||
auto const id = setup(env, issuer, holder);
|
||||
|
||||
doInvariantCheck(
|
||||
std::move(env),
|
||||
issuer,
|
||||
holder,
|
||||
{{"Invariant failed: coupon accumulator decreased"}},
|
||||
[id](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::couponSchedule(id));
|
||||
if (!sle)
|
||||
return false;
|
||||
sle->at(sfAccruedPerUnit) = XRP(-1).value();
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttCOUPON_PAY, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this, all_);
|
||||
Account const issuer{"issuer"};
|
||||
Account const holder{"holder"};
|
||||
env.fund(XRP(10'000), issuer, holder);
|
||||
auto const id = setup(env, issuer, holder);
|
||||
|
||||
doInvariantCheck(
|
||||
std::move(env),
|
||||
issuer,
|
||||
holder,
|
||||
{{"Invariant failed: coupon accumulator moved outside CouponPay"}},
|
||||
[id](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::couponSchedule(id));
|
||||
if (!sle)
|
||||
return false;
|
||||
sle->at(sfAccruedPerUnit) = XRP(5).value();
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this, all_);
|
||||
Account const issuer{"issuer"};
|
||||
Account const holder{"holder"};
|
||||
env.fund(XRP(10'000), issuer, holder);
|
||||
auto const id = setup(env, issuer, holder);
|
||||
|
||||
doInvariantCheck(
|
||||
std::move(env),
|
||||
issuer,
|
||||
holder,
|
||||
{{"Invariant failed: coupon pool grew outside CouponPay"}},
|
||||
[id](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::couponSchedule(id));
|
||||
if (!sle)
|
||||
return false;
|
||||
sle->at(sfPoolAmount) = XRP(10).value();
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttACCOUNT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testValidCouponSchedule();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(InvariantsCoupon, app, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,567 @@
|
||||
// Auto-generated unit tests for ledger entry CouponSchedule
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/CouponSchedule.h>
|
||||
#include <xrpl/protocol_autogen/ledger_entries/Ticket.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed for both the
|
||||
// builder's STObject and the wrapper's SLE.
|
||||
TEST(CouponScheduleTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
uint256 const index{1u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
auto const accruedPerUnitValue = canonical_AMOUNT();
|
||||
auto const poolAmountValue = canonical_AMOUNT();
|
||||
auto const claimantCountValue = canonical_UINT32();
|
||||
auto const couponCountValue = canonical_UINT32();
|
||||
auto const lastCouponTimeValue = canonical_UINT32();
|
||||
auto const couponAmountValue = canonical_AMOUNT();
|
||||
auto const couponIntervalValue = canonical_UINT32();
|
||||
auto const firstCouponTimeValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const callNoticePeriodValue = canonical_UINT32();
|
||||
auto const earliestCallTimeValue = canonical_UINT32();
|
||||
|
||||
CouponScheduleBuilder builder{
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue,
|
||||
ownerNodeValue,
|
||||
ownerValue,
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
couponAssetValue
|
||||
};
|
||||
|
||||
builder.setAccruedPerUnit(accruedPerUnitValue);
|
||||
builder.setPoolAmount(poolAmountValue);
|
||||
builder.setClaimantCount(claimantCountValue);
|
||||
builder.setCouponCount(couponCountValue);
|
||||
builder.setLastCouponTime(lastCouponTimeValue);
|
||||
builder.setCouponAmount(couponAmountValue);
|
||||
builder.setCouponInterval(couponIntervalValue);
|
||||
builder.setFirstCouponTime(firstCouponTimeValue);
|
||||
builder.setExpiration(expirationValue);
|
||||
builder.setCallNoticePeriod(callNoticePeriodValue);
|
||||
builder.setEarliestCallTime(earliestCallTimeValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
builder.setFlags(0x1u);
|
||||
|
||||
EXPECT_TRUE(builder.validate());
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
EXPECT_TRUE(entry.validate());
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
auto const actual = entry.getPreviousTxnID();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
auto const actual = entry.getPreviousTxnLgrSeq();
|
||||
expectEqualField(expected, actual, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
auto const actual = entry.getOwnerNode();
|
||||
expectEqualField(expected, actual, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerValue;
|
||||
auto const actual = entry.getOwner();
|
||||
expectEqualField(expected, actual, "sfOwner");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = accountValue;
|
||||
auto const actual = entry.getAccount();
|
||||
expectEqualField(expected, actual, "sfAccount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = entry.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAssetValue;
|
||||
auto const actual = entry.getCouponAsset();
|
||||
expectEqualField(expected, actual, "sfCouponAsset");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = accruedPerUnitValue;
|
||||
auto const actualOpt = entry.getAccruedPerUnit();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfAccruedPerUnit");
|
||||
EXPECT_TRUE(entry.hasAccruedPerUnit());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = poolAmountValue;
|
||||
auto const actualOpt = entry.getPoolAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfPoolAmount");
|
||||
EXPECT_TRUE(entry.hasPoolAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = claimantCountValue;
|
||||
auto const actualOpt = entry.getClaimantCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfClaimantCount");
|
||||
EXPECT_TRUE(entry.hasClaimantCount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponCountValue;
|
||||
auto const actualOpt = entry.getCouponCount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCouponCount");
|
||||
EXPECT_TRUE(entry.hasCouponCount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = lastCouponTimeValue;
|
||||
auto const actualOpt = entry.getLastCouponTime();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfLastCouponTime");
|
||||
EXPECT_TRUE(entry.hasLastCouponTime());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAmountValue;
|
||||
auto const actualOpt = entry.getCouponAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCouponAmount");
|
||||
EXPECT_TRUE(entry.hasCouponAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIntervalValue;
|
||||
auto const actualOpt = entry.getCouponInterval();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCouponInterval");
|
||||
EXPECT_TRUE(entry.hasCouponInterval());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = firstCouponTimeValue;
|
||||
auto const actualOpt = entry.getFirstCouponTime();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfFirstCouponTime");
|
||||
EXPECT_TRUE(entry.hasFirstCouponTime());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actualOpt = entry.getExpiration();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfExpiration");
|
||||
EXPECT_TRUE(entry.hasExpiration());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = callNoticePeriodValue;
|
||||
auto const actualOpt = entry.getCallNoticePeriod();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCallNoticePeriod");
|
||||
EXPECT_TRUE(entry.hasCallNoticePeriod());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = earliestCallTimeValue;
|
||||
auto const actualOpt = entry.getEarliestCallTime();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfEarliestCallTime");
|
||||
EXPECT_TRUE(entry.hasEarliestCallTime());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(entry.hasLedgerIndex());
|
||||
auto const ledgerIndex = entry.getLedgerIndex();
|
||||
ASSERT_TRUE(ledgerIndex.has_value());
|
||||
EXPECT_EQ(*ledgerIndex, index);
|
||||
EXPECT_EQ(entry.getKey(), index);
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an SLE, set fields directly on it, construct a builder
|
||||
// from that SLE, build a new wrapper, and verify all fields (and validate()).
|
||||
TEST(CouponScheduleTests, BuilderFromSleRoundTrip)
|
||||
{
|
||||
uint256 const index{2u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
auto const accruedPerUnitValue = canonical_AMOUNT();
|
||||
auto const poolAmountValue = canonical_AMOUNT();
|
||||
auto const claimantCountValue = canonical_UINT32();
|
||||
auto const couponCountValue = canonical_UINT32();
|
||||
auto const lastCouponTimeValue = canonical_UINT32();
|
||||
auto const couponAmountValue = canonical_AMOUNT();
|
||||
auto const couponIntervalValue = canonical_UINT32();
|
||||
auto const firstCouponTimeValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const callNoticePeriodValue = canonical_UINT32();
|
||||
auto const earliestCallTimeValue = canonical_UINT32();
|
||||
|
||||
auto sle = std::make_shared<SLE>(CouponSchedule::entryType, index);
|
||||
|
||||
sle->at(sfPreviousTxnID) = previousTxnIDValue;
|
||||
sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue;
|
||||
sle->at(sfOwnerNode) = ownerNodeValue;
|
||||
sle->at(sfOwner) = ownerValue;
|
||||
sle->at(sfAccount) = accountValue;
|
||||
sle->at(sfMPTokenIssuanceID) = mPTokenIssuanceIDValue;
|
||||
sle->at(sfCouponAsset) = STIssue(sfCouponAsset, couponAssetValue);
|
||||
sle->at(sfAccruedPerUnit) = accruedPerUnitValue;
|
||||
sle->at(sfPoolAmount) = poolAmountValue;
|
||||
sle->at(sfClaimantCount) = claimantCountValue;
|
||||
sle->at(sfCouponCount) = couponCountValue;
|
||||
sle->at(sfLastCouponTime) = lastCouponTimeValue;
|
||||
sle->at(sfCouponAmount) = couponAmountValue;
|
||||
sle->at(sfCouponInterval) = couponIntervalValue;
|
||||
sle->at(sfFirstCouponTime) = firstCouponTimeValue;
|
||||
sle->at(sfExpiration) = expirationValue;
|
||||
sle->at(sfCallNoticePeriod) = callNoticePeriodValue;
|
||||
sle->at(sfEarliestCallTime) = earliestCallTimeValue;
|
||||
|
||||
CouponScheduleBuilder builderFromSle{sle};
|
||||
EXPECT_TRUE(builderFromSle.validate());
|
||||
|
||||
auto const entryFromBuilder = builderFromSle.build(index);
|
||||
|
||||
CouponSchedule entryFromSle{sle};
|
||||
EXPECT_TRUE(entryFromBuilder.validate());
|
||||
EXPECT_TRUE(entryFromSle.validate());
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnID();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnID();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnID");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnLgrSeqValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getPreviousTxnLgrSeq();
|
||||
auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq");
|
||||
expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerNodeValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getOwnerNode();
|
||||
auto const fromBuilder = entryFromBuilder.getOwnerNode();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfOwnerNode");
|
||||
expectEqualField(expected, fromBuilder, "sfOwnerNode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = ownerValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getOwner();
|
||||
auto const fromBuilder = entryFromBuilder.getOwner();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfOwner");
|
||||
expectEqualField(expected, fromBuilder, "sfOwner");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = accountValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getAccount();
|
||||
auto const fromBuilder = entryFromBuilder.getAccount();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfAccount");
|
||||
expectEqualField(expected, fromBuilder, "sfAccount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getMPTokenIssuanceID();
|
||||
auto const fromBuilder = entryFromBuilder.getMPTokenIssuanceID();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfMPTokenIssuanceID");
|
||||
expectEqualField(expected, fromBuilder, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAssetValue;
|
||||
|
||||
auto const fromSle = entryFromSle.getCouponAsset();
|
||||
auto const fromBuilder = entryFromBuilder.getCouponAsset();
|
||||
|
||||
expectEqualField(expected, fromSle, "sfCouponAsset");
|
||||
expectEqualField(expected, fromBuilder, "sfCouponAsset");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = accruedPerUnitValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getAccruedPerUnit();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getAccruedPerUnit();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfAccruedPerUnit");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAccruedPerUnit");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = poolAmountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getPoolAmount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getPoolAmount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfPoolAmount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfPoolAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = claimantCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getClaimantCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getClaimantCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfClaimantCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfClaimantCount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponCountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCouponCount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCouponCount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCouponCount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCouponCount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = lastCouponTimeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getLastCouponTime();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getLastCouponTime();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfLastCouponTime");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfLastCouponTime");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAmountValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCouponAmount();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCouponAmount();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCouponAmount");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCouponAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIntervalValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCouponInterval();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCouponInterval();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCouponInterval");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCouponInterval");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = firstCouponTimeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getFirstCouponTime();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getFirstCouponTime();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfFirstCouponTime");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfFirstCouponTime");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getExpiration();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getExpiration();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfExpiration");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = callNoticePeriodValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCallNoticePeriod();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCallNoticePeriod();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCallNoticePeriod");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCallNoticePeriod");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = earliestCallTimeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getEarliestCallTime();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getEarliestCallTime();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfEarliestCallTime");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfEarliestCallTime");
|
||||
}
|
||||
|
||||
EXPECT_EQ(entryFromSle.getKey(), index);
|
||||
EXPECT_EQ(entryFromBuilder.getKey(), index);
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
|
||||
TEST(CouponScheduleTests, WrapperThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
|
||||
// Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(CouponSchedule{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong ledger entry type.
|
||||
TEST(CouponScheduleTests, BuilderThrowsOnWrongEntryType)
|
||||
{
|
||||
uint256 const index{4u};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
TicketBuilder wrongBuilder{
|
||||
canonical_ACCOUNT(),
|
||||
canonical_UINT64(),
|
||||
canonical_UINT32(),
|
||||
canonical_UINT256(),
|
||||
canonical_UINT32()};
|
||||
auto wrongEntry = wrongBuilder.build(index);
|
||||
|
||||
EXPECT_THROW(CouponScheduleBuilder{wrongEntry.getSle()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(CouponScheduleTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
uint256 const index{3u};
|
||||
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
auto const ownerValue = canonical_ACCOUNT();
|
||||
auto const accountValue = canonical_ACCOUNT();
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
|
||||
CouponScheduleBuilder builder{
|
||||
previousTxnIDValue,
|
||||
previousTxnLgrSeqValue,
|
||||
ownerNodeValue,
|
||||
ownerValue,
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
couponAssetValue
|
||||
};
|
||||
|
||||
auto const entry = builder.build(index);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(entry.hasAccruedPerUnit());
|
||||
EXPECT_FALSE(entry.getAccruedPerUnit().has_value());
|
||||
EXPECT_FALSE(entry.hasPoolAmount());
|
||||
EXPECT_FALSE(entry.getPoolAmount().has_value());
|
||||
EXPECT_FALSE(entry.hasClaimantCount());
|
||||
EXPECT_FALSE(entry.getClaimantCount().has_value());
|
||||
EXPECT_FALSE(entry.hasCouponCount());
|
||||
EXPECT_FALSE(entry.getCouponCount().has_value());
|
||||
EXPECT_FALSE(entry.hasLastCouponTime());
|
||||
EXPECT_FALSE(entry.getLastCouponTime().has_value());
|
||||
EXPECT_FALSE(entry.hasCouponAmount());
|
||||
EXPECT_FALSE(entry.getCouponAmount().has_value());
|
||||
EXPECT_FALSE(entry.hasCouponInterval());
|
||||
EXPECT_FALSE(entry.getCouponInterval().has_value());
|
||||
EXPECT_FALSE(entry.hasFirstCouponTime());
|
||||
EXPECT_FALSE(entry.getFirstCouponTime().has_value());
|
||||
EXPECT_FALSE(entry.hasExpiration());
|
||||
EXPECT_FALSE(entry.getExpiration().has_value());
|
||||
EXPECT_FALSE(entry.hasCallNoticePeriod());
|
||||
EXPECT_FALSE(entry.getCallNoticePeriod().has_value());
|
||||
EXPECT_FALSE(entry.hasEarliestCallTime());
|
||||
EXPECT_FALSE(entry.getEarliestCallTime().has_value());
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
auto const issuerEncryptedBalanceValue = canonical_VL();
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
auto const couponAccruedValue = canonical_AMOUNT();
|
||||
auto const couponIndexValue = canonical_AMOUNT();
|
||||
|
||||
MPTokenBuilder builder{
|
||||
accountValue,
|
||||
@@ -50,6 +52,8 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
builder.setIssuerEncryptedBalance(issuerEncryptedBalanceValue);
|
||||
builder.setAuditorEncryptedBalance(auditorEncryptedBalanceValue);
|
||||
builder.setHolderEncryptionKey(holderEncryptionKeyValue);
|
||||
builder.setCouponAccrued(couponAccruedValue);
|
||||
builder.setCouponIndex(couponIndexValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
builder.setFlags(0x1u);
|
||||
@@ -154,6 +158,22 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasHolderEncryptionKey());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAccruedValue;
|
||||
auto const actualOpt = entry.getCouponAccrued();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCouponAccrued");
|
||||
EXPECT_TRUE(entry.hasCouponAccrued());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIndexValue;
|
||||
auto const actualOpt = entry.getCouponIndex();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfCouponIndex");
|
||||
EXPECT_TRUE(entry.hasCouponIndex());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(entry.hasLedgerIndex());
|
||||
auto const ledgerIndex = entry.getLedgerIndex();
|
||||
ASSERT_TRUE(ledgerIndex.has_value());
|
||||
@@ -180,6 +200,8 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
auto const issuerEncryptedBalanceValue = canonical_VL();
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
auto const couponAccruedValue = canonical_AMOUNT();
|
||||
auto const couponIndexValue = canonical_AMOUNT();
|
||||
|
||||
auto sle = std::make_shared<SLE>(MPToken::entryType, index);
|
||||
|
||||
@@ -196,6 +218,8 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfIssuerEncryptedBalance) = issuerEncryptedBalanceValue;
|
||||
sle->at(sfAuditorEncryptedBalance) = auditorEncryptedBalanceValue;
|
||||
sle->at(sfHolderEncryptionKey) = holderEncryptionKeyValue;
|
||||
sle->at(sfCouponAccrued) = couponAccruedValue;
|
||||
sle->at(sfCouponIndex) = couponIndexValue;
|
||||
|
||||
MPTokenBuilder builderFromSle{sle};
|
||||
EXPECT_TRUE(builderFromSle.validate());
|
||||
@@ -360,6 +384,32 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfHolderEncryptionKey");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAccruedValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCouponAccrued();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCouponAccrued();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCouponAccrued");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCouponAccrued");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIndexValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getCouponIndex();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getCouponIndex();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfCouponIndex");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfCouponIndex");
|
||||
}
|
||||
|
||||
EXPECT_EQ(entryFromSle.getKey(), index);
|
||||
EXPECT_EQ(entryFromBuilder.getKey(), index);
|
||||
}
|
||||
@@ -438,5 +488,9 @@ TEST(MPTokenTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getAuditorEncryptedBalance().has_value());
|
||||
EXPECT_FALSE(entry.hasHolderEncryptionKey());
|
||||
EXPECT_FALSE(entry.getHolderEncryptionKey().has_value());
|
||||
EXPECT_FALSE(entry.hasCouponAccrued());
|
||||
EXPECT_FALSE(entry.getCouponAccrued().has_value());
|
||||
EXPECT_FALSE(entry.hasCouponIndex());
|
||||
EXPECT_FALSE(entry.getCouponIndex().has_value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
// Auto-generated unit tests for transaction CouponClaim
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/CouponClaim.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsCouponClaimTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponClaim"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
|
||||
CouponClaimBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setAmount(amountValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = amountValue;
|
||||
auto const actualOpt = tx.getAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAmount");
|
||||
EXPECT_TRUE(tx.hasAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsCouponClaimTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponClaimFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
|
||||
// Build an initial transaction
|
||||
CouponClaimBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setAmount(amountValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
CouponClaimBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = amountValue;
|
||||
auto const actualOpt = rebuiltTx.getAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAmount");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponClaimTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponClaim{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponClaimTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponClaimBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsCouponClaimTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponClaimNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
|
||||
CouponClaimBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasAmount());
|
||||
EXPECT_FALSE(tx.getAmount().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Auto-generated unit tests for transaction CouponPay
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/CouponPay.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsCouponPayTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponPay"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
|
||||
CouponPayBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
amountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = amountValue;
|
||||
auto const actual = tx.getAmount();
|
||||
expectEqualField(expected, actual, "sfAmount");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsCouponPayTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponPayFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
|
||||
// Build an initial transaction
|
||||
CouponPayBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
amountValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
CouponPayBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = amountValue;
|
||||
auto const actual = rebuiltTx.getAmount();
|
||||
expectEqualField(expected, actual, "sfAmount");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponPayTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponPay{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponPayTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponPayBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
// Auto-generated unit tests for transaction CouponScheduleCreate
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/CouponScheduleCreate.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsCouponScheduleCreateTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleCreate"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
auto const couponAmountValue = canonical_AMOUNT();
|
||||
auto const couponIntervalValue = canonical_UINT32();
|
||||
auto const firstCouponTimeValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const callNoticePeriodValue = canonical_UINT32();
|
||||
auto const earliestCallTimeValue = canonical_UINT32();
|
||||
|
||||
CouponScheduleCreateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
couponAssetValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setCouponAmount(couponAmountValue);
|
||||
builder.setCouponInterval(couponIntervalValue);
|
||||
builder.setFirstCouponTime(firstCouponTimeValue);
|
||||
builder.setExpiration(expirationValue);
|
||||
builder.setCallNoticePeriod(callNoticePeriodValue);
|
||||
builder.setEarliestCallTime(earliestCallTimeValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAssetValue;
|
||||
auto const actual = tx.getCouponAsset();
|
||||
expectEqualField(expected, actual, "sfCouponAsset");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = couponAmountValue;
|
||||
auto const actualOpt = tx.getCouponAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCouponAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCouponAmount");
|
||||
EXPECT_TRUE(tx.hasCouponAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIntervalValue;
|
||||
auto const actualOpt = tx.getCouponInterval();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCouponInterval should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCouponInterval");
|
||||
EXPECT_TRUE(tx.hasCouponInterval());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = firstCouponTimeValue;
|
||||
auto const actualOpt = tx.getFirstCouponTime();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFirstCouponTime should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfFirstCouponTime");
|
||||
EXPECT_TRUE(tx.hasFirstCouponTime());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actualOpt = tx.getExpiration();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExpiration should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfExpiration");
|
||||
EXPECT_TRUE(tx.hasExpiration());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = callNoticePeriodValue;
|
||||
auto const actualOpt = tx.getCallNoticePeriod();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCallNoticePeriod should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCallNoticePeriod");
|
||||
EXPECT_TRUE(tx.hasCallNoticePeriod());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = earliestCallTimeValue;
|
||||
auto const actualOpt = tx.getEarliestCallTime();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfEarliestCallTime should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfEarliestCallTime");
|
||||
EXPECT_TRUE(tx.hasEarliestCallTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsCouponScheduleCreateTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleCreateFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
auto const couponAmountValue = canonical_AMOUNT();
|
||||
auto const couponIntervalValue = canonical_UINT32();
|
||||
auto const firstCouponTimeValue = canonical_UINT32();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
auto const callNoticePeriodValue = canonical_UINT32();
|
||||
auto const earliestCallTimeValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
CouponScheduleCreateBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
couponAssetValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setCouponAmount(couponAmountValue);
|
||||
initialBuilder.setCouponInterval(couponIntervalValue);
|
||||
initialBuilder.setFirstCouponTime(firstCouponTimeValue);
|
||||
initialBuilder.setExpiration(expirationValue);
|
||||
initialBuilder.setCallNoticePeriod(callNoticePeriodValue);
|
||||
initialBuilder.setEarliestCallTime(earliestCallTimeValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
CouponScheduleCreateBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponAssetValue;
|
||||
auto const actual = rebuiltTx.getCouponAsset();
|
||||
expectEqualField(expected, actual, "sfCouponAsset");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = couponAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getCouponAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCouponAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCouponAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = couponIntervalValue;
|
||||
auto const actualOpt = rebuiltTx.getCouponInterval();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCouponInterval should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCouponInterval");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = firstCouponTimeValue;
|
||||
auto const actualOpt = rebuiltTx.getFirstCouponTime();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFirstCouponTime should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfFirstCouponTime");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actualOpt = rebuiltTx.getExpiration();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExpiration should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfExpiration");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = callNoticePeriodValue;
|
||||
auto const actualOpt = rebuiltTx.getCallNoticePeriod();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfCallNoticePeriod should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfCallNoticePeriod");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = earliestCallTimeValue;
|
||||
auto const actualOpt = rebuiltTx.getEarliestCallTime();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfEarliestCallTime should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfEarliestCallTime");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleCreateTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleCreate{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleCreateTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleCreateBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsCouponScheduleCreateTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleCreateNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const couponAssetValue = canonical_ISSUE();
|
||||
|
||||
CouponScheduleCreateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
couponAssetValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasCouponAmount());
|
||||
EXPECT_FALSE(tx.getCouponAmount().has_value());
|
||||
EXPECT_FALSE(tx.hasCouponInterval());
|
||||
EXPECT_FALSE(tx.getCouponInterval().has_value());
|
||||
EXPECT_FALSE(tx.hasFirstCouponTime());
|
||||
EXPECT_FALSE(tx.getFirstCouponTime().has_value());
|
||||
EXPECT_FALSE(tx.hasExpiration());
|
||||
EXPECT_FALSE(tx.getExpiration().has_value());
|
||||
EXPECT_FALSE(tx.hasCallNoticePeriod());
|
||||
EXPECT_FALSE(tx.getCallNoticePeriod().has_value());
|
||||
EXPECT_FALSE(tx.hasEarliestCallTime());
|
||||
EXPECT_FALSE(tx.getEarliestCallTime().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Auto-generated unit tests for transaction CouponScheduleDelete
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/CouponScheduleDelete.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsCouponScheduleDeleteTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleDelete"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
|
||||
CouponScheduleDeleteBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsCouponScheduleDeleteTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleDeleteFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
|
||||
// Build an initial transaction
|
||||
CouponScheduleDeleteBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
CouponScheduleDeleteBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleDeleteTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleDelete{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleDeleteTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Auto-generated unit tests for transaction CouponScheduleSet
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/CouponScheduleSet.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsCouponScheduleSetTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleSet"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
|
||||
CouponScheduleSetBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
expirationValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actual = tx.getExpiration();
|
||||
expectEqualField(expected, actual, "sfExpiration");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsCouponScheduleSetTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testCouponScheduleSetFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const expirationValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
CouponScheduleSetBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
expirationValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
CouponScheduleSetBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = expirationValue;
|
||||
auto const actual = rebuiltTx.getExpiration();
|
||||
expectEqualField(expected, actual, "sfExpiration");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleSetTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleSet{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsCouponScheduleSetTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(CouponScheduleSetBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -479,6 +479,25 @@ parseLedgerHashes(
|
||||
return parseFixed(keylet::skip(), params, fieldName, apiVersion);
|
||||
}
|
||||
|
||||
static std::expected<uint256, json::Value>
|
||||
parseCouponSchedule(
|
||||
json::Value const& params,
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const issuanceID =
|
||||
ledger_entry_helpers::requiredUInt192(params, jss::mpt_issuance_id, "malformedRequest");
|
||||
if (!issuanceID)
|
||||
return std::unexpected(issuanceID.error());
|
||||
|
||||
return keylet::couponSchedule(*issuanceID).key;
|
||||
}
|
||||
|
||||
static std::expected<uint256, json::Value>
|
||||
parseLoanBroker(
|
||||
json::Value const& params,
|
||||
|
||||
Reference in New Issue
Block a user