[WIP] Continue implementing LoanSet Transactor

- Needs tests
- Started trying to add strong typing to the fields used as bips or 1/10
  bips.
This commit is contained in:
Ed Hennis
2025-04-21 17:39:48 -04:00
parent cbb14627ea
commit 89f0b3b9e4
31 changed files with 895 additions and 556 deletions

View File

@@ -349,7 +349,10 @@ loanbroker(uint256 const& vaultKey)
}
Keylet
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept;
loan(
AccountID const& borrower,
uint256 loanBrokerID,
std::uint32_t seq) noexcept;
inline Keylet
loan(uint256 const& vaultKey)

View File

@@ -23,6 +23,8 @@
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/partitioned_unordered_map.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/Units.h>
#include <cstdint>
@@ -98,70 +100,73 @@ std::uint16_t constexpr maxTransferFee = 50000;
*
* Example: 50% is 0.50 * bipsPerUnity = 5,000 bps.
*/
std::uint32_t constexpr bipsPerUnity = 100 * 100;
std::uint32_t constexpr tenthBipsPerUnity = bipsPerUnity * 10;
Bips32 constexpr bipsPerUnity(100 * 100);
TenthBips32 constexpr tenthBipsPerUnity(bipsPerUnity.value() * 10);
constexpr std::uint32_t
constexpr Bips32
percentageToBips(std::uint32_t percentage)
{
return percentage * bipsPerUnity / 100;
return Bips32(percentage * bipsPerUnity.value() / 100);
}
constexpr std::uint32_t
constexpr TenthBips32
percentageToTenthBips(std::uint32_t percentage)
{
return percentage * tenthBipsPerUnity / 100;
return TenthBips32(percentage * tenthBipsPerUnity.value() / 100);
}
template <typename T>
template <typename T, class TBips>
constexpr T
bipsOfValue(T value, std::uint32_t bips)
bipsOfValue(T value, Bips<TBips> bips)
{
return value * bips / bipsPerUnity;
return value * bips.value() / bipsPerUnity.value();
}
template <typename T>
template <typename T, class TBips>
constexpr T
tenthBipsOfValue(T value, std::uint32_t bips)
tenthBipsOfValue(T value, TenthBips<TBips> bips)
{
return value * bips / tenthBipsPerUnity;
return value * bips.value() / tenthBipsPerUnity.value();
}
/** The maximum management fee rate allowed by a loan broker in 1/10 bips.
Valid values are between 0 and 10% inclusive.
*/
std::uint16_t constexpr maxManagementFeeRate = percentageToTenthBips(10);
static_assert(maxManagementFeeRate == 10'000);
TenthBips16 constexpr maxManagementFeeRate(
unsafe_cast<std::uint16_t>(percentageToTenthBips(10).value()));
static_assert(maxManagementFeeRate == TenthBips16(std::uint16_t(10'000u)));
/** The maximum coverage rate required of a loan broker in 1/10 bips.
Valid values are between 0 and 100% inclusive.
*/
std::uint32_t constexpr maxCoverRate = percentageToTenthBips(100);
static_assert(maxCoverRate == 100'000);
TenthBips32 constexpr maxCoverRate = percentageToTenthBips(100);
static_assert(maxCoverRate == TenthBips32(100'000u));
/** The maximum overpayment fee on a loan in 1/10 bips.
*
Valid values are between 0 and 100% inclusive.
*/
std::uint32_t constexpr maxOverpaymentFee = percentageToTenthBips(100);
TenthBips32 constexpr maxOverpaymentFee = percentageToTenthBips(100);
/** The maximum premium added to the interest rate for late payments on a loan
* in 1/10 bips.
*
* Valid values are between 0 and 100% inclusive.
*/
std::uint32_t constexpr maxLateInterestRate = percentageToTenthBips(100);
TenthBips32 constexpr maxLateInterestRate = percentageToTenthBips(100);
/** The maximum interest rate charged for repaying a loan early in 1/10 bips.
/** The maximum close interest rate charged for repaying a loan early in 1/10
* bips.
*
* Valid values are between 0 and 100% inclusive.
*/
std::uint32_t constexpr maxCloseInterestRate = percentageToTenthBips(100);
TenthBips32 constexpr maxCloseInterestRate = percentageToTenthBips(100);
/** The maximum interest rate charged on loan overpayments in 1/10 bips.
/** The maximum overpayment interest rate charged on loan overpayments in 1/10
* bips.
*
* Valid values are between 0 and 100% inclusive.
*/
std::uint32_t constexpr maxOverpaymentInterestRate = percentageToTenthBips(100);
TenthBips32 constexpr maxOverpaymentInterestRate = percentageToTenthBips(100);
/** The maximum length of a URI inside an NFT */
std::size_t constexpr maxTokenURILength = 256;

View File

@@ -22,6 +22,7 @@
#include <xrpl/basics/safe_cast.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Units.h>
#include <cstdint>
#include <map>
@@ -122,267 +123,287 @@ field_code(SerializedTypeID id, int index)
return (safe_cast<int>(id) << 16) | index;
}
// constexpr
// constexpr
inline int
field_code(int id, int index)
{
return (id << 16) | index;
}
/** Identifies fields.
/** Identifies fields.
Fields are necessary to tag data in signed transactions so that
the binary format of the transaction can be canonicalized. All
SFields are created at compile time.
Fields are necessary to tag data in signed transactions so that
the binary format of the transaction can be canonicalized. All
SFields are created at compile time.
Each SField, once constructed, lives until program termination, and there
is only one instance per fieldType/fieldValue pair which serves the entire
application.
*/
class SField
{
public:
enum {
sMD_Never = 0x00,
sMD_ChangeOrig = 0x01, // original value when it changes
sMD_ChangeNew = 0x02, // new value when it changes
sMD_DeleteFinal = 0x04, // final value when it is deleted
sMD_Create = 0x08, // value when it's created
sMD_Always = 0x10, // value when node containing it is affected at all
sMD_BaseTen =
0x20, // value is treated as base 10, overriding default behavior
sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT
// _only_, then it is a pseudo-account
sMD_Default =
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
Each SField, once constructed, lives until program termination, and
there is only one instance per fieldType/fieldValue pair which serves the
entire application.
*/
class SField
{
public:
enum {
sMD_Never = 0x00,
sMD_ChangeOrig = 0x01, // original value when it changes
sMD_ChangeNew = 0x02, // new value when it changes
sMD_DeleteFinal = 0x04, // final value when it is deleted
sMD_Create = 0x08, // value when it's created
sMD_Always =
0x10, // value when node containing it is affected at all
sMD_BaseTen = 0x20, // value is treated as base 10, overriding
// default behavior
sMD_PseudoAccount =
0x40, // if this field is set in an ACCOUNT_ROOT
// _only_, then it is a pseudo-account
sMD_Default =
sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
};
enum class IsSigning : unsigned char { no, yes };
static IsSigning const notSigning = IsSigning::no;
int const fieldCode; // (type<<16)|index
SerializedTypeID const fieldType; // STI_*
int const fieldValue; // Code number for protocol
std::string const fieldName;
int const fieldMeta;
int const fieldNum;
IsSigning const signingField;
Json::StaticString const jsonName;
SField(SField const&) = delete;
SField&
operator=(SField const&) = delete;
SField(SField&&) = delete;
SField&
operator=(SField&&) = delete;
public:
struct private_access_tag_t; // public, but still an implementation
// detail
// These constructors can only be called from SField.cpp
SField(
private_access_tag_t,
SerializedTypeID tid,
int fv,
const char* fn,
int meta = sMD_Default,
IsSigning signing = IsSigning::yes);
explicit SField(private_access_tag_t, int fc);
static const SField&
getField(int fieldCode);
static const SField&
getField(std::string const& fieldName);
static const SField&
getField(int type, int value)
{
return getField(field_code(type, value));
}
static const SField&
getField(SerializedTypeID type, int value)
{
return getField(field_code(type, value));
}
std::string const&
getName() const
{
return fieldName;
}
bool
hasName() const
{
return fieldCode > 0;
}
Json::StaticString const&
getJsonName() const
{
return jsonName;
}
operator Json::StaticString const&() const
{
return jsonName;
}
bool
isInvalid() const
{
return fieldCode == -1;
}
bool
isUseful() const
{
return fieldCode > 0;
}
bool
isBinary() const
{
return fieldValue < 256;
}
// A discardable field is one that cannot be serialized, and
// should be discarded during serialization,like 'hash'.
// You cannot serialize an object's hash inside that object,
// but you can have it in the JSON representation.
bool
isDiscardable() const
{
return fieldValue > 256;
}
int
getCode() const
{
return fieldCode;
}
int
getNum() const
{
return fieldNum;
}
static int
getNumFields()
{
return num;
}
bool
shouldMeta(int c) const
{
return (fieldMeta & c) != 0;
}
bool
shouldInclude(bool withSigningField) const
{
return (fieldValue < 256) &&
(withSigningField || (signingField == IsSigning::yes));
}
bool
operator==(const SField& f) const
{
return fieldCode == f.fieldCode;
}
bool
operator!=(const SField& f) const
{
return fieldCode != f.fieldCode;
}
static int
compare(const SField& f1, const SField& f2);
static std::map<int, SField const*> const&
getKnownCodeToField()
{
return knownCodeToField;
}
private:
static int num;
static std::map<int, SField const*> knownCodeToField;
static std::map<std::string, SField const*> knownNameToField;
};
enum class IsSigning : unsigned char { no, yes };
static IsSigning const notSigning = IsSigning::no;
int const fieldCode; // (type<<16)|index
SerializedTypeID const fieldType; // STI_*
int const fieldValue; // Code number for protocol
std::string const fieldName;
int const fieldMeta;
int const fieldNum;
IsSigning const signingField;
Json::StaticString const jsonName;
SField(SField const&) = delete;
SField&
operator=(SField const&) = delete;
SField(SField&&) = delete;
SField&
operator=(SField&&) = delete;
public:
struct private_access_tag_t; // public, but still an implementation detail
// These constructors can only be called from SField.cpp
SField(
private_access_tag_t,
SerializedTypeID tid,
int fv,
const char* fn,
int meta = sMD_Default,
IsSigning signing = IsSigning::yes);
explicit SField(private_access_tag_t, int fc);
static const SField&
getField(int fieldCode);
static const SField&
getField(std::string const& fieldName);
static const SField&
getField(int type, int value)
/** A field with a type known at compile time. */
template <class T>
struct TypedField : SField
{
return getField(field_code(type, value));
using type = T;
template <class... Args>
explicit TypedField(private_access_tag_t pat, Args&&... args);
};
/** Indicate std::optional field semantics. */
template <class T>
struct OptionaledField
{
TypedField<T> const* f;
explicit OptionaledField(TypedField<T> const& f_) : f(&f_)
{
}
};
template <class T>
inline OptionaledField<T>
operator~(TypedField<T> const& f)
{
return OptionaledField<T>(f);
}
static const SField&
getField(SerializedTypeID type, int value)
{
return getField(field_code(type, value));
}
//------------------------------------------------------------------------------
std::string const&
getName() const
{
return fieldName;
}
//------------------------------------------------------------------------------
bool
hasName() const
{
return fieldCode > 0;
}
using SF_UINT8 = TypedField<STInteger<std::uint8_t>>;
using SF_UINT16 = TypedField<STInteger<std::uint16_t>>;
using SF_UINT32 = TypedField<STInteger<std::uint32_t>>;
using SF_UINT64 = TypedField<STInteger<std::uint64_t>>;
using SF_UINT96 = TypedField<STBitString<96>>;
using SF_UINT128 = TypedField<STBitString<128>>;
using SF_UINT160 = TypedField<STBitString<160>>;
using SF_UINT192 = TypedField<STBitString<192>>;
using SF_UINT256 = TypedField<STBitString<256>>;
using SF_UINT384 = TypedField<STBitString<384>>;
using SF_UINT512 = TypedField<STBitString<512>>;
Json::StaticString const&
getJsonName() const
{
return jsonName;
}
// These BIPS and TENTHBIPS values are serialized as the underlying type.
// The tag is only applied when deserialized.
//
// Basis points (bips) values:
using SF_BIPS16 = TypedField<STInteger<Bips16>>;
using SF_BIPS32 = TypedField<STInteger<Bips32>>;
// Tenth of a basis point values:
using SF_TENTHBIPS16 = TypedField<STInteger<TenthBips16>>;
using SF_TENTHBIPS32 = TypedField<STInteger<TenthBips32>>;
operator Json::StaticString const&() const
{
return jsonName;
}
using SF_ACCOUNT = TypedField<STAccount>;
using SF_AMOUNT = TypedField<STAmount>;
using SF_ISSUE = TypedField<STIssue>;
using SF_CURRENCY = TypedField<STCurrency>;
using SF_NUMBER = TypedField<STNumber>;
using SF_VL = TypedField<STBlob>;
using SF_VECTOR256 = TypedField<STVector256>;
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
bool
isInvalid() const
{
return fieldCode == -1;
}
//------------------------------------------------------------------------------
bool
isUseful() const
{
return fieldCode > 0;
}
bool
isBinary() const
{
return fieldValue < 256;
}
// A discardable field is one that cannot be serialized, and
// should be discarded during serialization,like 'hash'.
// You cannot serialize an object's hash inside that object,
// but you can have it in the JSON representation.
bool
isDiscardable() const
{
return fieldValue > 256;
}
int
getCode() const
{
return fieldCode;
}
int
getNum() const
{
return fieldNum;
}
static int
getNumFields()
{
return num;
}
bool
shouldMeta(int c) const
{
return (fieldMeta & c) != 0;
}
bool
shouldInclude(bool withSigningField) const
{
return (fieldValue < 256) &&
(withSigningField || (signingField == IsSigning::yes));
}
bool
operator==(const SField& f) const
{
return fieldCode == f.fieldCode;
}
bool
operator!=(const SField& f) const
{
return fieldCode != f.fieldCode;
}
static int
compare(const SField& f1, const SField& f2);
static std::map<int, SField const*> const&
getKnownCodeToField()
{
return knownCodeToField;
}
private:
static int num;
static std::map<int, SField const*> knownCodeToField;
static std::map<std::string, SField const*> knownNameToField;
};
/** A field with a type known at compile time. */
template <class T>
struct TypedField : SField
{
using type = T;
template <class... Args>
explicit TypedField(private_access_tag_t pat, Args&&... args);
};
/** Indicate std::optional field semantics. */
template <class T>
struct OptionaledField
{
TypedField<T> const* f;
explicit OptionaledField(TypedField<T> const& f_) : f(&f_)
{
}
};
template <class T>
inline OptionaledField<T>
operator~(TypedField<T> const& f)
{
return OptionaledField<T>(f);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using SF_UINT8 = TypedField<STInteger<std::uint8_t>>;
using SF_UINT16 = TypedField<STInteger<std::uint16_t>>;
using SF_UINT32 = TypedField<STInteger<std::uint32_t>>;
using SF_UINT64 = TypedField<STInteger<std::uint64_t>>;
using SF_UINT96 = TypedField<STBitString<96>>;
using SF_UINT128 = TypedField<STBitString<128>>;
using SF_UINT160 = TypedField<STBitString<160>>;
using SF_UINT192 = TypedField<STBitString<192>>;
using SF_UINT256 = TypedField<STBitString<256>>;
using SF_UINT384 = TypedField<STBitString<384>>;
using SF_UINT512 = TypedField<STBitString<512>>;
using SF_ACCOUNT = TypedField<STAccount>;
using SF_AMOUNT = TypedField<STAmount>;
using SF_ISSUE = TypedField<STIssue>;
using SF_CURRENCY = TypedField<STCurrency>;
using SF_NUMBER = TypedField<STNumber>;
using SF_VL = TypedField<STBlob>;
using SF_VECTOR256 = TypedField<STVector256>;
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
//------------------------------------------------------------------------------
// Use macros for most SField construction to enforce naming conventions.
// Use macros for most SField construction to enforce naming conventions.
#pragma push_macro("UNTYPED_SFIELD")
#undef UNTYPED_SFIELD
#pragma push_macro("TYPED_SFIELD")
#undef TYPED_SFIELD
#pragma push_macro("INTERPRETED_SFIELD")
#undef INTERPRETED_SFIELD
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
extern SField const sfName;
extern SField const sfName;
#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
extern SF_##stiSuffix const sfName;
extern SF_##stiSuffix const sfName;
#define INTERPRETED_SFIELD( \
sfName, stiSuffix, fieldValue, stiInterpreted, ...) \
extern SF_##stiInterpreted const sfName;
extern SField const sfInvalid;
extern SField const sfGeneric;
extern SField const sfInvalid;
extern SField const sfGeneric;
#include <xrpl/protocol/detail/sfields.macro>
#undef INTERPRETED_SFIELD
#pragma pop_macro("INTERPRETED_SFIELD")
#undef TYPED_SFIELD
#pragma pop_macro("TYPED_SFIELD")
#undef UNTYPED_SFIELD

View File

@@ -21,6 +21,7 @@
#define RIPPLE_PROTOCOL_STINTEGER_H_INCLUDED
#include <xrpl/basics/CountedObject.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STBase.h>
namespace ripple {
@@ -81,6 +82,13 @@ using STUInt16 = STInteger<std::uint16_t>;
using STUInt32 = STInteger<std::uint32_t>;
using STUInt64 = STInteger<std::uint64_t>;
// Basis points (bips) values:
using SFBips16 = STInteger<Bips16>;
using SFBips32 = STInteger<Bips32>;
// Tenth of a basis point values:
using SFTenthBips16 = STInteger<TenthBips16>;
using SFTenthBips32 = STInteger<TenthBips32>;
template <typename Integer>
inline STInteger<Integer>::STInteger(Integer v) : value_(v)
{
@@ -122,7 +130,7 @@ template <typename Integer>
inline bool
STInteger<Integer>::isDefault() const
{
return value_ == 0;
return value_ == Integer{};
}
template <typename Integer>

View File

@@ -25,7 +25,6 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/STAmount.h>
@@ -34,6 +33,7 @@
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/STPathSet.h>
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/detail/STVar.h>
#include <boost/iterator/transform_iterator.hpp>
@@ -518,7 +518,8 @@ protected:
// Constraint += and -= ValueProxy operators
// to value types that support arithmetic operations
template <typename U>
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount>;
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount> ||
std::is_same_v<U, STNumber> || std::is_same_v<U, Number>;
template <class T>
class STObject::ValueProxy : public Proxy<T>

View File

@@ -22,10 +22,10 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Units.h>
#include <cstdint>
#include <functional>

View File

@@ -16,8 +16,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef BASICS_FEES_H_INCLUDED
#define BASICS_FEES_H_INCLUDED
#ifndef PROTOCOL_UNITS_H_INCLUDED
#define PROTOCOL_UNITS_H_INCLUDED
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/Zero.h>
@@ -38,23 +38,23 @@
namespace ripple {
namespace feeunit {
namespace unit {
/** "drops" are the smallest divisible amount of XRP. This is what most
of the code uses. */
struct dropTag;
/** "fee units" calculations are a not-really-unitless value that is used
to express the cost of a given transaction vs. a reference transaction.
They are primarily used by the Transactor classes. */
struct feeunitTag;
/** "fee levels" are used by the transaction queue to compare the relative
cost of transactions that require different levels of effort to process.
See also: src/ripple/app/misc/FeeEscalation.md#fee-level */
struct feelevelTag;
/** unitless values are plain scalars wrapped in a TaggedFee. They are
/** unitless values are plain scalars wrapped in a ValueUnit. They are
used for calculations in this header. */
struct unitlessTag;
/** Units to represent basis points (bips) and 1/10 basis points */
class BipsTag;
class TenthBipsTag;
template <class T>
using enable_if_unit_t = typename std::enable_if_t<
std::is_class_v<T> && std::is_object_v<typename T::unit_type> &&
@@ -62,32 +62,33 @@ using enable_if_unit_t = typename std::enable_if_t<
/** `is_usable_unit_v` is checked to ensure that only values with
known valid type tags can be used (sometimes transparently) in
non-fee contexts. At the time of implementation, this includes
non-unit contexts. At the time of implementation, this includes
all known tags, but more may be added in the future, and they
should not be added automatically unless determined to be
appropriate.
*/
template <class T, class = enable_if_unit_t<T>>
constexpr bool is_usable_unit_v =
std::is_same_v<typename T::unit_type, feeunitTag> ||
std::is_same_v<typename T::unit_type, feelevelTag> ||
std::is_same_v<typename T::unit_type, unitlessTag> ||
std::is_same_v<typename T::unit_type, dropTag>;
std::is_same_v<typename T::unit_type, dropTag> ||
std::is_same_v<typename T::unit_type, BipsTag> ||
std::is_same_v<typename T::unit_type, TenthBipsTag>;
template <class UnitTag, class T>
class TaggedFee : private boost::totally_ordered<TaggedFee<UnitTag, T>>,
private boost::additive<TaggedFee<UnitTag, T>>,
private boost::equality_comparable<TaggedFee<UnitTag, T>, T>,
private boost::dividable<TaggedFee<UnitTag, T>, T>,
private boost::modable<TaggedFee<UnitTag, T>, T>,
private boost::unit_steppable<TaggedFee<UnitTag, T>>
class ValueUnit : private boost::totally_ordered<ValueUnit<UnitTag, T>>,
private boost::additive<ValueUnit<UnitTag, T>>,
private boost::equality_comparable<ValueUnit<UnitTag, T>, T>,
private boost::dividable<ValueUnit<UnitTag, T>, T>,
private boost::modable<ValueUnit<UnitTag, T>, T>,
private boost::unit_steppable<ValueUnit<UnitTag, T>>
{
public:
using unit_type = UnitTag;
using value_type = T;
private:
value_type fee_;
value_type value_;
protected:
template <class Other>
@@ -95,44 +96,44 @@ protected:
std::is_arithmetic_v<Other> && std::is_arithmetic_v<value_type> &&
std::is_convertible_v<Other, value_type>;
template <class OtherFee, class = enable_if_unit_t<OtherFee>>
static constexpr bool is_compatiblefee_v =
is_compatible_v<typename OtherFee::value_type> &&
std::is_same_v<UnitTag, typename OtherFee::unit_type>;
template <class OtherValue, class = enable_if_unit_t<OtherValue>>
static constexpr bool is_compatiblevalue_v =
is_compatible_v<typename OtherValue::value_type> &&
std::is_same_v<UnitTag, typename OtherValue::unit_type>;
template <class Other>
using enable_if_compatible_t =
typename std::enable_if_t<is_compatible_v<Other>>;
template <class OtherFee>
using enable_if_compatiblefee_t =
typename std::enable_if_t<is_compatiblefee_v<OtherFee>>;
template <class OtherValue>
using enable_if_compatiblevalue_t =
typename std::enable_if_t<is_compatiblevalue_v<OtherValue>>;
public:
TaggedFee() = default;
constexpr TaggedFee(TaggedFee const& other) = default;
constexpr TaggedFee&
operator=(TaggedFee const& other) = default;
ValueUnit() = default;
constexpr ValueUnit(ValueUnit const& other) = default;
constexpr ValueUnit&
operator=(ValueUnit const& other) = default;
constexpr explicit TaggedFee(beast::Zero) : fee_(0)
constexpr explicit ValueUnit(beast::Zero) : value_(0)
{
}
constexpr TaggedFee&
constexpr ValueUnit&
operator=(beast::Zero)
{
fee_ = 0;
value_ = 0;
return *this;
}
constexpr explicit TaggedFee(value_type fee) : fee_(fee)
constexpr explicit ValueUnit(value_type value) : value_(value)
{
}
TaggedFee&
operator=(value_type fee)
constexpr ValueUnit&
operator=(value_type value)
{
fee_ = fee;
value_ = value;
return *this;
}
@@ -144,153 +145,180 @@ public:
class = std::enable_if_t<
is_compatible_v<Other> &&
is_safetocasttovalue_v<value_type, Other>>>
constexpr TaggedFee(TaggedFee<unit_type, Other> const& fee)
: TaggedFee(safe_cast<value_type>(fee.fee()))
constexpr ValueUnit(ValueUnit<unit_type, Other> const& value)
: ValueUnit(safe_cast<value_type>(value.value()))
{
}
constexpr TaggedFee
constexpr ValueUnit
operator+(value_type const& rhs) const
{
return ValueUnit{value_ + rhs};
}
friend constexpr ValueUnit
operator+(value_type lhs, ValueUnit const& rhs)
{
// addition is commutative
return rhs + lhs;
}
constexpr ValueUnit
operator-(value_type const& rhs) const
{
return ValueUnit{value_ - rhs};
}
friend constexpr ValueUnit
operator-(value_type lhs, ValueUnit const& rhs)
{
// subtraction is NOT commutative, but (lhs + (-rhs)) is addition, which
// is
return -rhs + lhs;
}
constexpr ValueUnit
operator*(value_type const& rhs) const
{
return TaggedFee{fee_ * rhs};
return ValueUnit{value_ * rhs};
}
friend constexpr TaggedFee
operator*(value_type lhs, TaggedFee const& rhs)
friend constexpr ValueUnit
operator*(value_type lhs, ValueUnit const& rhs)
{
// multiplication is commutative
return rhs * lhs;
}
constexpr value_type
operator/(TaggedFee const& rhs) const
operator/(ValueUnit const& rhs) const
{
return fee_ / rhs.fee_;
return value_ / rhs.value_;
}
TaggedFee&
operator+=(TaggedFee const& other)
ValueUnit&
operator+=(ValueUnit const& other)
{
fee_ += other.fee();
value_ += other.value();
return *this;
}
TaggedFee&
operator-=(TaggedFee const& other)
ValueUnit&
operator-=(ValueUnit const& other)
{
fee_ -= other.fee();
value_ -= other.value();
return *this;
}
TaggedFee&
ValueUnit&
operator++()
{
++fee_;
++value_;
return *this;
}
TaggedFee&
ValueUnit&
operator--()
{
--fee_;
--value_;
return *this;
}
TaggedFee&
ValueUnit&
operator*=(value_type const& rhs)
{
fee_ *= rhs;
value_ *= rhs;
return *this;
}
TaggedFee&
ValueUnit&
operator/=(value_type const& rhs)
{
fee_ /= rhs;
value_ /= rhs;
return *this;
}
template <class transparent = value_type>
std::enable_if_t<std::is_integral_v<transparent>, TaggedFee&>
std::enable_if_t<std::is_integral_v<transparent>, ValueUnit&>
operator%=(value_type const& rhs)
{
fee_ %= rhs;
value_ %= rhs;
return *this;
}
TaggedFee
ValueUnit
operator-() const
{
static_assert(
std::is_signed_v<T>, "- operator illegal on unsigned fee types");
return TaggedFee{-fee_};
std::is_signed_v<T>, "- operator illegal on unsigned value types");
return ValueUnit{-value_};
}
bool
operator==(TaggedFee const& other) const
constexpr bool
operator==(ValueUnit const& other) const
{
return fee_ == other.fee_;
return value_ == other.value_;
}
template <class Other, class = enable_if_compatible_t<Other>>
bool
operator==(TaggedFee<unit_type, Other> const& other) const
constexpr bool
operator==(ValueUnit<unit_type, Other> const& other) const
{
return fee_ == other.fee();
return value_ == other.value();
}
bool
constexpr bool
operator==(value_type other) const
{
return fee_ == other;
return value_ == other;
}
template <class Other, class = enable_if_compatible_t<Other>>
bool
operator!=(TaggedFee<unit_type, Other> const& other) const
constexpr bool
operator!=(ValueUnit<unit_type, Other> const& other) const
{
return !operator==(other);
}
bool
operator<(TaggedFee const& other) const
constexpr bool
operator<(ValueUnit const& other) const
{
return fee_ < other.fee_;
return value_ < other.value_;
}
/** Returns true if the amount is not zero */
explicit constexpr
operator bool() const noexcept
{
return fee_ != 0;
return value_ != 0;
}
/** Return the sign of the amount */
constexpr int
signum() const noexcept
{
return (fee_ < 0) ? -1 : (fee_ ? 1 : 0);
return (value_ < 0) ? -1 : (value_ ? 1 : 0);
}
/** Returns the number of drops */
constexpr value_type
fee() const
{
return fee_;
return value_;
}
template <class Other>
constexpr double
decimalFromReference(TaggedFee<unit_type, Other> reference) const
decimalFromReference(ValueUnit<unit_type, Other> reference) const
{
return static_cast<double>(fee_) / reference.fee();
return static_cast<double>(value_) / reference.value();
}
// `is_usable_unit_v` is checked to ensure that only values with
// known valid type tags can be converted to JSON. At the time
// of implementation, that includes all known tags, but more may
// be added in the future.
std::enable_if_t<is_usable_unit_v<TaggedFee>, Json::Value>
std::enable_if_t<is_usable_unit_v<ValueUnit>, Json::Value>
jsonClipped() const
{
if constexpr (std::is_integral_v<value_type>)
@@ -303,15 +331,15 @@ public:
constexpr auto min = std::numeric_limits<jsontype>::min();
constexpr auto max = std::numeric_limits<jsontype>::max();
if (fee_ < min)
if (value_ < min)
return min;
if (fee_ > max)
if (value_ > max)
return max;
return static_cast<jsontype>(fee_);
return static_cast<jsontype>(value_);
}
else
{
return fee_;
return value_;
}
}
@@ -322,30 +350,30 @@ public:
constexpr value_type
value() const
{
return fee_;
return value_;
}
friend std::istream&
operator>>(std::istream& s, TaggedFee& val)
operator>>(std::istream& s, ValueUnit& val)
{
s >> val.fee_;
s >> val.value_;
return s;
}
};
// Output Fees as just their numeric value.
// Output Values as just their numeric value.
template <class Char, class Traits, class UnitTag, class T>
std::basic_ostream<Char, Traits>&
operator<<(std::basic_ostream<Char, Traits>& os, const TaggedFee<UnitTag, T>& q)
operator<<(std::basic_ostream<Char, Traits>& os, const ValueUnit<UnitTag, T>& q)
{
return os << q.value();
}
template <class UnitTag, class T>
std::string
to_string(TaggedFee<UnitTag, T> const& amount)
to_string(ValueUnit<UnitTag, T> const& amount)
{
return std::to_string(amount.fee());
return std::to_string(amount.value());
}
template <class Source, class = enable_if_unit_t<Source>>
@@ -408,10 +436,10 @@ using enable_muldiv_commute_t =
typename std::enable_if_t<can_muldiv_commute_v<Source1, Source2, Dest>>;
template <class T>
TaggedFee<unitlessTag, T>
ValueUnit<unitlessTag, T>
scalar(T value)
{
return TaggedFee<unitlessTag, T>{value};
return ValueUnit<unitlessTag, T>{value};
}
template <
@@ -422,18 +450,17 @@ template <
std::optional<Dest>
mulDivU(Source1 value, Dest mul, Source2 div)
{
// Fees can never be negative in any context.
// values can never be negative in any context.
if (value.value() < 0 || mul.value() < 0 || div.value() < 0)
{
// split the asserts so if one hits, the user can tell which
// without a debugger.
XRPL_ASSERT(
value.value() >= 0,
"ripple::feeunit::mulDivU : minimum value input");
value.value() >= 0, "ripple::unit::mulDivU : minimum value input");
XRPL_ASSERT(
mul.value() >= 0, "ripple::feeunit::mulDivU : minimum mul input");
mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input");
XRPL_ASSERT(
div.value() >= 0, "ripple::feeunit::mulDivU : minimum div input");
div.value() >= 0, "ripple::unit::mulDivU : minimum div input");
return std::nullopt;
}
@@ -466,46 +493,57 @@ mulDivU(Source1 value, Dest mul, Source2 div)
return Dest{static_cast<desttype>(quotient)};
}
} // namespace feeunit
} // namespace unit
// Fee Levels
template <class T>
using FeeLevel = feeunit::TaggedFee<feeunit::feelevelTag, T>;
using FeeLevel = unit::ValueUnit<unit::feelevelTag, T>;
using FeeLevel64 = FeeLevel<std::uint64_t>;
using FeeLevelDouble = FeeLevel<double>;
// Basis points (Bips)
template <class T>
using Bips = unit::ValueUnit<unit::BipsTag, T>;
using Bips16 = Bips<std::uint16_t>;
using Bips32 = Bips<std::uint32_t>;
template <class T>
using TenthBips = unit::ValueUnit<unit::TenthBipsTag, T>;
using TenthBips16 = TenthBips<std::uint16_t>;
using TenthBips32 = TenthBips<std::uint32_t>;
template <
class Source1,
class Source2,
class Dest,
class = feeunit::enable_muldiv_t<Source1, Source2, Dest>>
class = unit::enable_muldiv_t<Source1, Source2, Dest>>
std::optional<Dest>
mulDiv(Source1 value, Dest mul, Source2 div)
{
return feeunit::mulDivU(value, mul, div);
return unit::mulDivU(value, mul, div);
}
template <
class Source1,
class Source2,
class Dest,
class = feeunit::enable_muldiv_commute_t<Source1, Source2, Dest>>
class = unit::enable_muldiv_commute_t<Source1, Source2, Dest>>
std::optional<Dest>
mulDiv(Dest value, Source1 mul, Source2 div)
{
// Multiplication is commutative
return feeunit::mulDivU(mul, value, div);
return unit::mulDivU(mul, value, div);
}
template <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>>
template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
std::optional<Dest>
mulDiv(std::uint64_t value, Dest mul, std::uint64_t div)
{
// Give the scalars a non-tag so the
// unit-handling version gets called.
return feeunit::mulDivU(feeunit::scalar(value), mul, feeunit::scalar(div));
return unit::mulDivU(unit::scalar(value), mul, unit::scalar(div));
}
template <class Dest, class = feeunit::enable_muldiv_dest_t<Dest>>
template <class Dest, class = unit::enable_muldiv_dest_t<Dest>>
std::optional<Dest>
mulDiv(Dest value, std::uint64_t mul, std::uint64_t div)
{
@@ -516,13 +554,13 @@ mulDiv(Dest value, std::uint64_t mul, std::uint64_t div)
template <
class Source1,
class Source2,
class = feeunit::enable_muldiv_sources_t<Source1, Source2>>
class = unit::enable_muldiv_sources_t<Source1, Source2>>
std::optional<std::uint64_t>
mulDiv(Source1 value, std::uint64_t mul, Source2 div)
{
// Give the scalars a dimensionless unit so the
// unit-handling version gets called.
auto unitresult = feeunit::mulDivU(value, feeunit::scalar(mul), div);
auto unitresult = unit::mulDivU(value, unit::scalar(mul), div);
if (!unitresult)
return std::nullopt;
@@ -533,7 +571,7 @@ mulDiv(Source1 value, std::uint64_t mul, Source2 div)
template <
class Source1,
class Source2,
class = feeunit::enable_muldiv_sources_t<Source1, Source2>>
class = unit::enable_muldiv_sources_t<Source1, Source2>>
std::optional<std::uint64_t>
mulDiv(std::uint64_t value, Source1 mul, Source2 div)
{
@@ -567,4 +605,4 @@ unsafe_cast(Src s) noexcept
} // namespace ripple
#endif // BASICS_FEES_H_INCLUDED
#endif // PROTOCOL_UNITS_H_INCLUDED

View File

@@ -24,7 +24,7 @@
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Units.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/operators.hpp>
@@ -42,7 +42,7 @@ class XRPAmount : private boost::totally_ordered<XRPAmount>,
private boost::additive<XRPAmount, std::int64_t>
{
public:
using unit_type = feeunit::dropTag;
using unit_type = unit::dropTag;
using value_type = std::int64_t;
private:

View File

@@ -61,7 +61,7 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22)
INTERPRETED_SFIELD(sfManagementFeeRate, UINT16, 22, TENTHBIPS16)
// 32-bit integers (common)
TYPED_SFIELD(sfNetworkID, UINT32, 1)
@@ -123,12 +123,12 @@ TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 55)
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56)
TYPED_SFIELD(sfPaymentRemaining, UINT32, 57)
TYPED_SFIELD(sfPaymentTotal, UINT32, 58)
TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59)
TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60)
TYPED_SFIELD(sfInterestRate, UINT32, 61)
TYPED_SFIELD(sfLateInterestRate, UINT32, 62)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 63)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 64)
INTERPRETED_SFIELD(sfCoverRateMinimum, UINT32, 59, TENTHBIPS32)
INTERPRETED_SFIELD(sfCoverRateLiquidation, UINT32, 60, TENTHBIPS32)
INTERPRETED_SFIELD(sfInterestRate, UINT32, 61, TENTHBIPS32)
INTERPRETED_SFIELD(sfLateInterestRate, UINT32, 62, TENTHBIPS32)
INTERPRETED_SFIELD(sfCloseInterestRate, UINT32, 63, TENTHBIPS32)
INTERPRETED_SFIELD(sfOverpaymentInterestRate, UINT32, 64, TENTHBIPS32)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)

View File

@@ -739,9 +739,11 @@ TRANSACTION(ttLOAN_SET, 78, LoanSet, noPriv, ({
{sfLoanServiceFee, soeOPTIONAL},
{sfLatePaymentFee, soeOPTIONAL},
{sfClosePaymentFee, soeOPTIONAL},
{sfOverpaymentFee, soeOPTIONAL},
{sfInterestRate, soeOPTIONAL},
{sfLateInterestRate, soeOPTIONAL},
{sfCloseInterestRate, soeOPTIONAL},
{sfOverpaymentInterestRate, soeOPTIONAL},
{sfPrincipalRequested, soeREQUIRED},
{sfStartDate, soeREQUIRED},
{sfPaymentTotal, soeOPTIONAL},

View File

@@ -559,9 +559,12 @@ loanbroker(AccountID const& owner, std::uint32_t seq) noexcept
}
Keylet
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept
loan(
AccountID const& borrower,
uint256 loanBrokerID,
std::uint32_t seq) noexcept
{
return loan(indexHash(LedgerNameSpace::LOAN, loanBrokerID, seq));
return loan(indexHash(LedgerNameSpace::LOAN, borrower, loanBrokerID, seq));
}
Keylet

View File

@@ -54,6 +54,8 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
#undef UNTYPED_SFIELD
#pragma push_macro("TYPED_SFIELD")
#undef TYPED_SFIELD
#pragma push_macro("INTERPRETED_SFIELD")
#undef INTERPRETED_SFIELD
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) \
SField const sfName( \
@@ -69,6 +71,13 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
fieldValue, \
std::string_view(#sfName).substr(2).data(), \
##__VA_ARGS__);
#define INTERPRETED_SFIELD(sfName, stiSuffix, fieldValue, stiInterpreted, ...) \
SF_##stiInterpreted const sfName( \
access, \
STI_##stiSuffix, \
fieldValue, \
std::string_view(#sfName).substr(2).data(), \
##__VA_ARGS__);
// SFields which, for historical reasons, do not follow naming conventions.
SField const sfInvalid(access, -1);
@@ -80,6 +89,8 @@ SField const sfIndex(access, STI_UINT256, 258, "index");
#include <xrpl/protocol/detail/sfields.macro>
#undef INTERPRETED_SFIELD
#pragma pop_macro("INTERPRETED_SFIELD")
#undef TYPED_SFIELD
#pragma pop_macro("TYPED_SFIELD")
#undef UNTYPED_SFIELD

View File

@@ -686,7 +686,10 @@ STObject
STObject::getFieldObject(SField const& field) const
{
STObject const empty{field};
return getFieldByConstRef<STObject>(field, empty);
auto ret = getFieldByConstRef<STObject>(field, empty);
if (ret != empty)
ret.applyTemplateFromSField(field);
return ret;
}
const STArray&

View File

@@ -75,7 +75,14 @@ class LoanBroker_test : public beast::unit_test::suite
env(set(alice, keylet.key), fee(increment), ter(temDISABLED));
auto const brokerKeylet =
keylet::loanbroker(alice.id(), env.seq(alice));
// LoanBrokerDelete is disabled, too.
// Other LoanBroker transactions are disabled, too.
// 1. LoanBrokerCoverDeposit
env(coverDeposit(alice, brokerKeylet.key, asset(1000)),
ter(temDISABLED));
// 2. LoanBrokerCoverWithdraw
env(coverWithdraw(alice, brokerKeylet.key, asset(1000)),
ter(temDISABLED));
// 3. LoanBrokerDelete
env(del(alice, brokerKeylet.key), ter(temDISABLED));
};
failAll(all - featureMPTokensV1);
@@ -421,7 +428,7 @@ class LoanBroker_test : public beast::unit_test::suite
ter(tecNO_PERMISSION));
// sfManagementFeeRate: too big
env(set(evan, vault.vaultID),
managementFeeRate(maxManagementFeeRate + 1),
managementFeeRate(maxManagementFeeRate + TenthBips16(10)),
fee(increment),
ter(temINVALID));
// sfCoverRateMinimum: good value, bad account
@@ -555,10 +562,10 @@ class LoanBroker_test : public beast::unit_test::suite
return env.jt(
jv,
data(testData),
managementFeeRate(123),
managementFeeRate(TenthBips16(123)),
debtMaximum(Number(9)),
coverRateMinimum(100),
coverRateLiquidation(200));
coverRateMinimum(TenthBips32(100)),
coverRateLiquidation(TenthBips32(200)));
},
[&](SLE::const_ref broker) {
// Extra checks

View File

@@ -17,13 +17,13 @@
*/
#include <xrpl/beast/unit_test.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/Units.h>
namespace ripple {
namespace test {
class feeunits_test : public beast::unit_test::suite
class units_test : public beast::unit_test::suite
{
private:
void
@@ -35,16 +35,16 @@ private:
XRPAmount x{100};
BEAST_EXPECT(x.drops() == 100);
BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>));
(std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
auto y = 4u * x;
BEAST_EXPECT(y.value() == 400);
BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>));
(std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
auto z = 4 * y;
BEAST_EXPECT(z.value() == 1600);
BEAST_EXPECT(
(std::is_same_v<decltype(z)::unit_type, feeunit::dropTag>));
(std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
FeeLevel32 f{10};
FeeLevel32 baseFee{100};
@@ -55,7 +55,7 @@ private:
BEAST_EXPECT(drops.value() == 1000);
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>));
unit::dropTag>));
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>,
@@ -65,11 +65,11 @@ private:
XRPAmount x{100};
BEAST_EXPECT(x.value() == 100);
BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::dropTag>));
(std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
auto y = 4u * x;
BEAST_EXPECT(y.value() == 400);
BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>));
(std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
FeeLevel64 f{10};
FeeLevel64 baseFee{100};
@@ -80,7 +80,7 @@ private:
BEAST_EXPECT(drops.value() == 1000);
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>));
unit::dropTag>));
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>,
XRPAmount>));
@@ -89,12 +89,12 @@ private:
FeeLevel64 x{1024};
BEAST_EXPECT(x.value() == 1024);
BEAST_EXPECT(
(std::is_same_v<decltype(x)::unit_type, feeunit::feelevelTag>));
(std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
std::uint64_t m = 4;
auto y = m * x;
BEAST_EXPECT(y.value() == 4096);
BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::feelevelTag>));
(std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
XRPAmount basefee{10};
FeeLevel64 referencefee{256};
@@ -105,7 +105,7 @@ private:
BEAST_EXPECT(drops.value() == 40);
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>::unit_type,
feeunit::dropTag>));
unit::dropTag>));
BEAST_EXPECT((std::is_same_v<
std::remove_reference_t<decltype(*drops)>,
XRPAmount>));
@@ -181,7 +181,7 @@ private:
void
testFunctions()
{
// Explicitly test every defined function for the TaggedFee class
// Explicitly test every defined function for the ValueUnit class
// since some of them are templated, but not used anywhere else.
using FeeLevel32 = FeeLevel<std::uint32_t>;
@@ -191,8 +191,8 @@ private:
return FeeLevel64{x};
};
[[maybe_unused]]
FeeLevel64 defaulted;
(void)defaulted;
FeeLevel64 test{0};
BEAST_EXPECT(test.fee() == 0);
@@ -278,8 +278,8 @@ private:
return FeeLevelDouble{x};
};
[[maybe_unused]]
FeeLevelDouble defaulted;
(void)defaulted;
FeeLevelDouble test{0};
BEAST_EXPECT(test.fee() == 0);
@@ -371,7 +371,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(feeunits, ripple_basics, ripple);
BEAST_DEFINE_TESTSUITE(units, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -54,7 +54,9 @@ struct JTx
bool fill_sig = true;
bool fill_netid = true;
std::shared_ptr<STTx const> stx;
// TODO: Remove
std::function<void(Env&, JTx&)> signer;
std::vector<std::function<void(Env&, JTx&)>> signers;
JTx() = default;
JTx(JTx const&) = default;

View File

@@ -28,6 +28,7 @@
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/jss.h>
#include <vector>
@@ -164,6 +165,37 @@ struct blobField : public JTxField<SF_VL, std::string>
}
};
template <class SField, class UnitTag, class ValueType>
struct valueUnitField
: public JTxField<SField, unit::ValueUnit<UnitTag, ValueType>, ValueType>
{
using SF = SField;
using SV = unit::ValueUnit<UnitTag, ValueType>;
using OV = ValueType;
using base = JTxField<SF, SV, OV>;
static_assert(
std::is_same_v<SV, typename SField::type::value_type::value_type>);
static_assert(std::is_same_v<
OV,
typename SField::type::value_type::value_type::value_type>);
protected:
using base::value_;
public:
explicit valueUnitField(SF const& sfield, SV const& value)
: JTxField(sfield, value)
{
}
OV
value() const override
{
return value_.value();
}
};
template <class JTxField>
struct JTxFieldWrapper
{
@@ -221,6 +253,13 @@ public:
}
};
template <
class SField,
class UnitTag = typename SField::type::value_type::unit_type,
class ValueType = typename SField::type::value_type::value_type>
using valueUnitWrapper =
JTxFieldWrapper<valueUnitField<SField, UnitTag, ValueType>>;
template <class SField, class StoredValue = typename SField::type::value_type>
using simpleField = JTxFieldWrapper<JTxField<SField, StoredValue>>;
@@ -627,17 +666,32 @@ auto const loanBrokerID = JTxFieldWrapper<uint256Field>(sfLoanBrokerID);
auto const data = JTxFieldWrapper<blobField>(sfData);
auto const managementFeeRate = simpleField<SF_UINT16>(sfManagementFeeRate);
auto const managementFeeRate =
valueUnitWrapper<SF_TENTHBIPS16>(sfManagementFeeRate);
auto const debtMaximum = simpleField<SF_NUMBER>(sfDebtMaximum);
auto const coverRateMinimum = simpleField<SF_UINT32>(sfCoverRateMinimum);
auto const coverRateMinimum =
valueUnitWrapper<SF_TENTHBIPS32>(sfCoverRateMinimum);
auto const coverRateLiquidation =
simpleField<SF_UINT32>(sfCoverRateLiquidation);
simpleField<SF_TENTHBIPS32>(sfCoverRateLiquidation);
} // namespace loanBroker
/* Loan */
/******************************************************************************/
namespace loan {
Json::Value
set(AccountID const& account,
uint256 loanBrokerID,
Number principalRequested,
NetClock::time_point const& startDate,
uint32_t flags = 0);
}
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -24,9 +24,9 @@
#include <test/jtx/tags.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Units.h>
#include <cstdint>
#include <ostream>

View File

@@ -470,6 +470,28 @@ coverWithdraw(
} // namespace loanBroker
/* Loan */
/******************************************************************************/
namespace loan {
Json::Value
set(AccountID const& account,
uint256 loanBrokerID,
Number principalRequested,
NetClock::time_point const& startDate,
uint32_t flags)
{
Json::Value jv;
jv[sfTransactionType] = jss::LoanSet;
jv[sfAccount] = to_string(account);
jv[sfLoanBrokerID] = to_string(loanBrokerID);
jv[sfPrincipalRequested] = to_string(principalRequested);
jv[sfFlags] = flags;
jv[sfStartDate] = startDate.time_since_epoch().count();
return jv;
}
} // namespace loan
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -1382,7 +1382,11 @@ class Invariants_test : public beast::unit_test::suite
sle->at(sfOwner) = sle->at(sfAccount);
},
[](SLE::pointer& sle) {
sle->at(sfManagementFeeRate) += 1;
// The operator overloads aren't playing nice with the
// custom class, so just do it the hard way for now.
auto value = sle->at(sfManagementFeeRate).value();
++value;
sle->at(sfManagementFeeRate) = value;
},
[](SLE::pointer& sle) { sle->at(sfCoverRateMinimum) += 1; },
[](SLE::pointer& sle) {

View File

@@ -805,7 +805,7 @@ class Simulate_test : public beast::unit_test::suite
testTx(env, tx, testSimulation);
tx[sfSigningPubKey] = "";
tx[sfTxnSignature] = "";
tx[sfTxnSignature] = "x";
tx[sfSequence] = env.seq(env.master);
tx[sfFee] = env.current()->fees().base.jsonClipped().asString();

View File

@@ -23,7 +23,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Units.h>
#include <cstdint>

View File

@@ -30,10 +30,10 @@
#include <xrpl/basics/mulDiv.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/Units.h>
namespace ripple {

View File

@@ -26,11 +26,11 @@
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/FeeUnits.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/nftPageMask.h>
namespace ripple {

View File

@@ -125,6 +125,8 @@ LoanBrokerCoverDeposit::doApply()
auto const amount = tx[sfAmount];
auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const brokerPseudoID = broker->at(sfAccount);

View File

@@ -138,6 +138,8 @@ LoanBrokerCoverWithdraw::doApply()
auto const amount = tx[sfAmount];
auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerPseudoID = broker->at(sfAccount);

View File

@@ -96,8 +96,12 @@ LoanBrokerDelete::doApply()
// Delete the loan broker
auto broker = view().peek(keylet::loanbroker(brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultID = broker->at(sfVaultID);
auto const sleVault = view().read(keylet::vault(vaultID));
if (!sleVault)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoID = sleVault->at(sfAccount);
auto const vaultAsset = sleVault->at(sfAsset);

View File

@@ -155,6 +155,8 @@ LoanBrokerSet::doApply()
{
// Modify an existing LoanBroker
auto broker = view.peek(keylet::loanbroker(*brokerID));
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (auto const data = tx[~sfData])
broker->at(sfData) = *data;
@@ -172,6 +174,8 @@ LoanBrokerSet::doApply()
auto const sequence = tx.getSeqValue();
auto owner = view.peek(keylet::account(account_));
if (!owner)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto broker =
std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
@@ -180,10 +184,14 @@ LoanBrokerSet::doApply()
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
return ter;
/* We're already charging a higher fee, so we probably don't want to
also charge a reserve.
*
adjustOwnerCount(view, owner, 1, j_);
auto ownerCount = owner->at(sfOwnerCount);
if (mPriorBalance < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE;
*/
auto maybePseudo = createPseudoAccount(
view, broker->key(), PseudoAccountOwnerType::LoanBroker);

View File

@@ -33,6 +33,7 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -63,14 +64,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
auto const& tx = ctx.tx;
auto const counterPartySig = ctx.tx.getFieldObject(sfCounterpartySignature);
// Copied from preflight1
// TODO: Refactor into a helper function?
if (auto const spk = counterPartySig.getFieldVL(sfSigningPubKey);
!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
return temBAD_SIGNATURE;
}
if (auto const ret =
ripple::detail::preflightCheckSigningKey(counterPartySig, ctx.j))
return ret;
if (auto const data = tx[~sfData]; data && !data->empty() &&
!validDataLength(tx[~sfData], maxDataPayloadLength))
@@ -96,34 +92,9 @@ LoanSet::doPreflight(PreflightContext const& ctx)
return temINVALID;
// Copied from preflight2
// TODO: Refactor into a helper function?
if (ctx.flags & tapDRY_RUN) // simulation
{
if (!ctx.tx.getSignature(counterPartySig).empty())
{
// NOTE: This code should never be hit because it's checked in the
// `simulate` RPC
return temINVALID; // LCOV_EXCL_LINE
}
if (!counterPartySig.isFieldPresent(sfSigners))
{
// no signers, no signature - a valid simulation
return tesSUCCESS;
}
for (auto const& signer : counterPartySig.getFieldArray(sfSigners))
{
if (signer.isFieldPresent(sfTxnSignature) &&
!signer[sfTxnSignature].empty())
{
// NOTE: This code should never be hit because it's
// checked in the `simulate` RPC
return temINVALID; // LCOV_EXCL_LINE
}
}
return tesSUCCESS;
}
if (auto const ret = ripple::detail::preflightCheckSimulateKeys(
ctx.flags, counterPartySig, ctx.j))
return *ret;
return tesSUCCESS;
}
@@ -152,7 +123,6 @@ LoanSet::checkSign(PreclaimContext const& ctx)
auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
return Transactor::checkSign(ctx, *counterSigner, counterSig);
}
}
XRPAmount
LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
@@ -176,120 +146,247 @@ LoanSet::calculateBaseFee(ReadView const& view, STTx const& tx)
TER
LoanSet::preclaim(PreclaimContext const& ctx)
{
return temDISABLED;
auto const& tx = ctx.tx;
if (auto const startDate(tx[sfStartDate]); hasExpired(ctx.view, startDate))
{
JLOG(ctx.j.warn()) << "Start date is in the past.";
return tecEXPIRED;
}
auto const account = tx[sfAccount];
if (auto const ID = tx[~sfLoanID])
auto const brokerID = tx[sfLoanBrokerID];
auto const brokerSle = ctx.view.read(keylet::loanbroker(brokerID));
if (!brokerSle)
{
auto const sle = ctx.view.read(keylet::loan(*ID));
if (!sle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
return tecNO_ENTRY;
}
if (tx[sfVaultID] != sle->at(sfVaultID))
{
JLOG(ctx.j.warn()) << "Can not change VaultID on an existing Loan.";
return tecNO_PERMISSION;
}
if (account != sle->at(sfOwner))
{
JLOG(ctx.j.warn()) << "Account is not the owner of the Loan.";
return tecNO_PERMISSION;
}
JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
return tecNO_ENTRY;
}
else
auto const brokerOwner = brokerSle->at(sfOwner);
if (!tx.isFieldPresent(sfCounterparty) && account != brokerOwner)
{
auto const vaultID = tx[sfVaultID];
auto const sleVault = ctx.view.read(keylet::vault(vaultID));
if (!sleVault)
{
JLOG(ctx.j.warn()) << "Vault does not exist.";
return tecNO_ENTRY;
}
if (account != sleVault->at(sfOwner))
{
JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
return tecNO_PERMISSION;
}
JLOG(ctx.j.warn())
<< "Counterparty is not the owner of the LoanBroker.";
return tecNO_PERMISSION;
}
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
auto const borrower = counterparty == brokerOwner ? account : counterparty;
if (account != brokerOwner && counterparty != brokerOwner)
{
JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
"of the LoanBroker.";
return tecNO_PERMISSION;
}
if (auto const borrowerSle = ctx.view.read(keylet::account(borrower));
!borrowerSle)
{
JLOG(ctx.j.warn()) << "Borrower does not exist.";
return tecNO_ENTRY;
}
auto const brokerPseudo = brokerSle->at(sfAccount);
auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
if (!vault)
// Should be impossible
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const asset = vault->at(sfAsset);
if (isFrozen(ctx.view, brokerOwner, asset) ||
isFrozen(ctx.view, brokerPseudo, asset))
{
JLOG(ctx.j.warn()) << "One of the affected accounts is frozen.";
return asset.holds<Issue>() ? tecFROZEN : tecLOCKED;
}
if (asset.holds<Issue>())
{
auto const issue = asset.get<Issue>();
if (isDeepFrozen(ctx.view, borrower, issue.currency, issue.account))
return tecFROZEN;
}
auto const principalRequested = tx[sfPrincipalRequested];
if (auto const assetsAvailable = vault->at(sfAssetsAvailable);
assetsAvailable < principalRequested)
{
JLOG(ctx.j.warn())
<< "Insufficient assets available in the Vault to fund the loan.";
return tecINSUFFICIENT_FUNDS;
}
auto const debtTotal = brokerSle->at(sfDebtTotal);
if (brokerSle->at(sfDebtMaximum) < debtTotal + principalRequested)
{
JLOG(ctx.j.warn())
<< "Loan would exceed the maximum debt limit of the LoanBroker.";
return tecLIMIT_EXCEEDED;
}
if (brokerSle->at(sfCoverAvailable) <
tenthBipsOfValue(
debtTotal + principalRequested, brokerSle->at(sfCoverRateMinimum)))
{
JLOG(ctx.j.warn())
<< "Insufficient first-loss capital to cover the loan.";
return tecINSUFFICIENT_FUNDS;
}
return tesSUCCESS;
}
TER
LoanSet::doApply()
{
return temDISABLED;
auto const& tx = ctx_.tx;
auto& view = ctx_.view();
#if 0
if (auto const ID = tx[~sfLoanID])
auto const brokerID = tx[sfLoanBrokerID];
auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
if (!brokerSle)
tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerOwner = brokerSle->at(sfOwner);
auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudo = vaultSle->at(sfAccount);
auto const vaultAsset = vaultSle->at(sfAsset);
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
auto const borrowerSle = view.peek(keylet::account(borrower));
if (!borrowerSle)
{
// Modify an existing Loan
auto loan = view.peek(keylet::loan(*ID));
if (auto const data = tx[~sfData])
loan->at(sfData) = *data;
if (auto const debtMax = tx[~sfDebtMaximum])
loan->at(sfDebtMaximum) = *debtMax;
view.update();
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
else
auto const brokerPseudo = brokerSle->at(sfAccount);
auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
auto const principalRequested = tx[sfPrincipalRequested];
auto const interestRate = tx[~sfInterestRate].value_or(TenthBips32(0));
auto const originationFee = tx[~sfLoanOriginationFee];
auto const loanAssetsAvailable =
principalRequested - originationFee.value_or(Number{});
adjustOwnerCount(view, borrowerSle, 1, j_);
auto ownerCount = borrowerSle->at(sfOwnerCount);
if (mPriorBalance < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE;
// Create a holding for the borrower if one does not already exist.
// Account for the origination fee using two payments
//
// 1. Transfer (principalRequested - originationFee) from vault
// pseudo-account to LoanBroker pseudo-account.
//
// Create the holding if it doesn't already exist
if (auto const ter = addEmptyHolding(
view,
brokerPseudo,
brokerPseudoSle->at(sfBalance).value().xrp(),
vaultAsset,
j_);
ter != tesSUCCESS && ter != tecDUPLICATE)
// ignore tecDUPLICATE. That means the holding already exists, and is
// fine here
return ter;
if (auto const ter = accountSend(
view,
vaultPseudo,
brokerPseudo,
STAmount{vaultAsset, loanAssetsAvailable},
j_,
WaiveTransferFee::Yes))
return ter;
// 2. Transfer originationFee, if any, from vault pseudo-account to
// LoanBroker owner.
if (originationFee)
{
// Create a new Loan pointing back to the given Vault
auto const vaultID = tx[sfVaultID];
auto const sleVault = view.read(keylet::vault(vaultID));
auto const vaultPseudoID = sleVault->at(sfAccount);
auto const sequence = tx.getSeqValue();
auto owner = view.peek(keylet::account(account_));
auto loan = std::make_shared<SLE>(keylet::loan(account_, sequence));
if (auto const ter = dirLink(view, account_, ))
// Create the holding if it doesn't already exist
if (auto const ter = addEmptyHolding(
view,
brokerOwner,
brokerOwnerSle->at(sfBalance).value().xrp(),
vaultAsset,
j_);
ter != tesSUCCESS && ter != tecDUPLICATE)
// ignore tecDUPLICATE. That means the holding already exists, and
// is fine here
return ter;
if (auto const ter = dirLink(view, vaultPseudoID, , sfVaultNode))
if (auto const ter = accountSend(
view,
vaultPseudo,
brokerOwner,
STAmount{vaultAsset, *originationFee},
j_,
WaiveTransferFee::Yes))
return ter;
adjustOwnerCount(view, owner, 1, j_);
auto ownerCount = owner->at(sfOwnerCount);
if (mPriorBalance < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE;
auto maybePseudo =
createPseudoAccount(view, loan->key(), PseudoAccountOwnerType::Loan);
if (!maybePseudo)
return maybePseudo.error();
auto& pseudo = *maybePseudo;
auto pseudoId = pseudo->at(sfAccount);
if (auto ter = addEmptyHolding(
view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
return ter;
// Initialize data fields:
loan->at(sfSequence) = sequence;
loan->at(sfVaultID) = vaultID;
loan->at(sfOwner) = account_;
loan->at(sfAccount) = pseudoId;
if (auto const data = tx[~sfData])
loan->at(sfData) = *data;
if (auto const rate = tx[~sfManagementFeeRate])
loan->at(sfManagementFeeRate) = *rate;
if (auto const debtMax = tx[~sfDebtMaximum])
loan->at(sfDebtMaximum) = *debtMax;
if (auto const coverMin = tx[~sfCoverRateMinimum])
loan->at(sfCoverRateMinimum) = *coverMin;
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
loan->at(sfCoverRateLiquidation) = *coverLiq;
view.insert(loan);
}
#endif
auto const managementFeeRate = brokerSle->at(sfManagementFeeRate);
auto const loanInterest =
tenthBipsOfValue(principalRequested, interestRate);
auto const loanInterestToVault = loanInterest -
tenthBipsOfValue(loanInterest, managementFeeRate.value());
auto const loanSequence = tx.getSeqValue();
auto const startDate = tx[sfStartDate];
auto const paymentInterval =
tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
// Create the loan
auto loan =
std::make_shared<SLE>(keylet::loan(borrower, brokerID, loanSequence));
// Prevent copy/paste errors
auto setLoanField = [&loan, &tx](auto const& field) {
loan->at(field) = tx[field];
};
// Set required tx fields and pre-computed fields
loan->at(sfPrincipalRequested) = principalRequested;
loan->at(sfStartDate) = startDate;
loan->at(sfSequence) = loanSequence;
loan->at(sfLoanBrokerID) = brokerID;
loan->at(sfBorrower) = borrower;
loan->at(~sfLoanOriginationFee) = originationFee;
loan->at(sfPaymentInterval) = paymentInterval;
// Set all other transaction fields directly from the transaction
if (tx.isFlag(tfLoanOverpayment))
loan->setFlag(lsfLoanOverpayment);
setLoanField(~sfData);
setLoanField(~sfLoanServiceFee);
setLoanField(~sfLatePaymentFee);
setLoanField(~sfClosePaymentFee);
setLoanField(~sfOverpaymentFee);
setLoanField(~sfInterestRate);
setLoanField(~sfLateInterestRate);
setLoanField(~sfCloseInterestRate);
setLoanField(~sfOverpaymentInterestRate);
loan->at(sfGracePeriod) = tx[~sfGracePeriod].value_or(defaultGracePeriod);
// Set dynamic fields to their initial values
loan->at(sfPreviousPaymentDate) = 0;
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
loan->at(sfPaymentRemaining) =
tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
loan->at(sfAssetsAvailable) = loanAssetsAvailable;
loan->at(sfPrincipalOutstanding) = principalRequested;
// Update the balances in the vault
vaultSle->at(sfAssetsAvailable) -= principalRequested;
vaultSle->at(sfAssetsTotal) += loanInterestToVault;
view.update(vaultSle);
// Update the balances in the loan broker
brokerSle->at(sfDebtTotal) += principalRequested + loanInterestToVault;
adjustOwnerCount(view, brokerSle, 1, j_);
if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
return ter;
// Borrower is effectively the owner of the loan
if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
return ter;
view.update(brokerSle);
return tesSUCCESS;
}

View File

@@ -87,6 +87,22 @@ preflight0(PreflightContext const& ctx, std::uint32_t flagMask)
namespace detail {
/** Checks the validity of the transactor signing key.
*
* Normally called from preflight1.
*/
NotTEC
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
{
if (auto const spk = sigObject.getFieldVL(sfSigningPubKey);
!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(j.debug()) << "preflightCheckSigningKey: invalid signing key";
return temBAD_SIGNATURE;
}
return tesSUCCESS;
}
/** Performs early sanity checks on the account and fee fields */
NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
@@ -118,13 +134,8 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
return temBAD_FEE;
}
auto const spk = ctx.tx.getSigningPubKey();
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
return temBAD_SIGNATURE;
}
if (auto const ret = preflightCheckSigningKey(ctx.tx, ctx.j))
return ret;
// An AccountTxnID field constrains transaction ordering more than the
// Sequence field. Tickets, on the other hand, reduce ordering
@@ -139,26 +150,28 @@ preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
return tesSUCCESS;
}
/** Checks whether the signature appears valid */
NotTEC
preflight2(PreflightContext const& ctx)
std::optional<NotTEC>
preflightCheckSimulateKeys(
ApplyFlags flags,
STObject const& sigObject,
beast::Journal j)
{
if (ctx.flags & tapDRY_RUN) // simulation
if (flags & tapDRY_RUN) // simulation
{
if (!ctx.tx.getSignature().empty())
if (!sigObject.getFieldVL(sfTxnSignature).empty())
{
// NOTE: This code should never be hit because it's checked in the
// `simulate` RPC
return temINVALID; // LCOV_EXCL_LINE
}
if (!ctx.tx.isFieldPresent(sfSigners))
if (!sigObject.isFieldPresent(sfSigners))
{
// no signers, no signature - a valid simulation
return tesSUCCESS;
}
for (auto const& signer : ctx.tx.getFieldArray(sfSigners))
for (auto const& signer : sigObject.getFieldArray(sfSigners))
{
if (signer.isFieldPresent(sfTxnSignature) &&
!signer[sfTxnSignature].empty())
@@ -170,6 +183,17 @@ preflight2(PreflightContext const& ctx)
}
return tesSUCCESS;
}
return {};
}
/** Checks whether the signature appears valid */
NotTEC
preflight2(PreflightContext const& ctx)
{
if (auto const ret = preflightCheckSimulateKeys(ctx.flags, ctx.tx, ctx.j))
// Skips following checks if the transaction is being simulated,
// regardless of success or failure
return *ret;
auto const sigValid = checkValidity(
ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());

View File

@@ -262,6 +262,14 @@ NotTEC
preflight0(PreflightContext const& ctx, std::uint32_t flagMask);
namespace detail {
/** Checks the validity of the transactor signing key.
*
* Normally called from preflight1 with ctx.tx.
*/
NotTEC
preflightCheckSigningKey(STObject const& sigObject, beast::Journal j);
/** Performs early sanity checks on the account and fee fields.
(And passes flagMask to preflight0)
@@ -269,6 +277,16 @@ namespace detail {
NotTEC
preflight1(PreflightContext const& ctx, std::uint32_t flagMask);
/** Checks the special signing key state needed for simulation
*
* Normally called from preflight2 with ctx.tx.
*/
std::optional<NotTEC>
preflightCheckSimulateKeys(
ApplyFlags flags,
STObject const& sigObject,
beast::Journal j);
/** Checks whether the signature appears valid */
NotTEC
preflight2(PreflightContext const& ctx);