Compare commits

...

13 Commits

Author SHA1 Message Date
Valentin Balaschenko
ab0a72ad93 Merge branch 'vlntb/taggedcache-lock-per-partition' into vlntb/lock-contention-analysis 2025-06-27 13:13:51 +01:00
Valentin Balaschenko
83ae5e3050 Merge branch 'develop' into vlntb/lock-contention-analysis 2025-06-27 11:35:07 +01:00
Vlad
e18f27f5f7 test: switch some unit tests to doctest (#5383)
This change moves some tests from the current unit tests that are compiled into the rippled binary to using the doctest framework.
2025-06-26 19:35:31 +00:00
Jingchen
df6daf0d8f Add XRPL_ABANDON and use it to abandon OwnerPaysFee (#5510) 2025-06-26 12:09:05 -04:00
Jingchen
e9d46f0bfc Remove OwnerPaysFee as it's never fully supported (#5435)
The OwnerPaysFee amendment was never fully supported, and this change removes the feature to the extent possible.
2025-06-24 18:56:58 +00:00
Bart
42fd74b77b Removes release notes from codebase (#5508) 2025-06-24 13:10:00 -04:00
tequ
c55ea56c5e Add nftoken_id, nftoken_ids, offer_id to meta for transaction stream (#5230) 2025-06-24 09:02:22 -04:00
Valentin Balaschenko
56f5189a2b lock cont annotations 2025-06-20 15:22:40 +01:00
Valentin Balaschenko
da694c8304 wip lock per partition 2025-06-13 18:33:00 +01:00
Valentin Balaschenko
d0f836581b remove mutex and dead references 2025-06-13 14:45:27 +01:00
Valentin Balaschenko
984c70955a clang 2025-06-11 16:33:06 +01:00
Valentin Balaschenko
3effb54e49 wip atomics for counters 2025-06-11 16:31:48 +01:00
Valentin Balaschenko
316f9535e3 wip removing mutex and dependencies 2025-06-11 14:31:26 +01:00
47 changed files with 1346 additions and 6288 deletions

View File

@@ -96,4 +96,7 @@ jobs:
run: |
n=$(nproc)
echo "Using $n test jobs"
${build_dir}/rippled --unittest --unittest-jobs $n
cd ${build_dir}
./rippled --unittest --unittest-jobs $n
ctest -j $n --output-on-failure

View File

@@ -163,7 +163,9 @@ jobs:
cmake-args: "-Dassert=TRUE -Dwerr=TRUE ${{ matrix.cmake-args }}"
- name: test
run: |
${build_dir}/rippled --unittest --unittest-jobs $(nproc)
cd ${build_dir}
./rippled --unittest --unittest-jobs $(nproc)
ctest -j $(nproc) --output-on-failure
reference-fee-test:
strategy:
@@ -217,8 +219,9 @@ jobs:
cmake-args: "-Dassert=TRUE -Dwerr=TRUE ${{ matrix.cmake-args }}"
- name: test
run: |
${build_dir}/rippled --unittest --unittest-jobs $(nproc)
cd ${build_dir}
./rippled --unittest --unittest-jobs $(nproc)
ctest -j $(nproc) --output-on-failure
coverage:
strategy:
fail-fast: false
@@ -441,3 +444,4 @@ jobs:
run: |
cd ${BUILD_DIR}
./rippled -u --unittest-jobs $(( $(nproc)/4 ))
ctest -j $(nproc) --output-on-failure

View File

@@ -95,5 +95,6 @@ jobs:
shell: bash
if: ${{ matrix.configuration.tests }}
run: |
${build_dir}/${{ matrix.configuration.type }}/rippled --unittest \
--unittest-jobs $(nproc)
cd ${build_dir}/${{ matrix.configuration.type }}
./rippled --unittest --unittest-jobs $(nproc)
ctest -j $(nproc) --output-on-failure

View File

@@ -132,6 +132,7 @@ test.shamap > xrpl.protocol
test.toplevel > test.csf
test.toplevel > xrpl.json
test.unit_test > xrpl.basics
tests.libxrpl > xrpl.basics
xrpl.json > xrpl.basics
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json

View File

@@ -90,6 +90,11 @@ set_target_properties(OpenSSL::SSL PROPERTIES
INTERFACE_COMPILE_DEFINITIONS OPENSSL_NO_SSL2
)
set(SECP256K1_INSTALL TRUE)
set(SECP256K1_BUILD_BENCHMARK FALSE)
set(SECP256K1_BUILD_TESTS FALSE)
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS FALSE)
set(SECP256K1_BUILD_CTIME_TESTS FALSE)
set(SECP256K1_BUILD_EXAMPLES FALSE)
add_subdirectory(external/secp256k1)
add_library(secp256k1::secp256k1 ALIAS secp256k1)
add_subdirectory(external/ed25519-donna)
@@ -144,3 +149,8 @@ set(PROJECT_EXPORT_SET RippleExports)
include(RippledCore)
include(RippledInstall)
include(RippledValidatorKeys)
if(tests)
include(CTest)
add_subdirectory(src/tests/libxrpl)
endif()

File diff suppressed because it is too large Load Diff

41
cmake/xrpl_add_test.cmake Normal file
View File

@@ -0,0 +1,41 @@
include(isolate_headers)
function(xrpl_add_test name)
set(target ${PROJECT_NAME}.test.${name})
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
)
add_executable(${target} EXCLUDE_FROM_ALL ${ARGN} ${sources})
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/tests/${name}"
PRIVATE
)
# Make sure the test isn't optimized away in unity builds
set_target_properties(${target} PROPERTIES
UNITY_BUILD_MODE GROUP
UNITY_BUILD_BATCH_SIZE 0) # Adjust as needed
add_test(NAME ${target} COMMAND ${target})
set_tests_properties(
${target} PROPERTIES
FIXTURES_REQUIRED ${target}_fixture
)
add_test(
NAME ${target}.build
COMMAND
${CMAKE_COMMAND}
--build ${CMAKE_BINARY_DIR}
--config $<CONFIG>
--target ${target}
)
set_tests_properties(${target}.build PROPERTIES
FIXTURES_SETUP ${target}_fixture
)
endfunction()

View File

@@ -24,6 +24,7 @@ class Xrpl(ConanFile):
}
requires = [
'doctest/2.4.11',
'grpc/1.50.1',
'libarchive/3.7.6',
'nudb/2.0.8',

View File

@@ -21,7 +21,6 @@
#define RIPPLE_BASICS_SHAMAP_HASH_H_INCLUDED
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/partitioned_unordered_map.h>
#include <ostream>

View File

@@ -170,9 +170,6 @@ public:
bool
retrieve(key_type const& key, T& data);
mutex_type&
peekMutex();
std::vector<key_type>
getKeys() const;
@@ -193,11 +190,14 @@ public:
private:
SharedPointerType
initialFetch(key_type const& key, std::lock_guard<mutex_type> const& l);
initialFetch(key_type const& key);
void
collect_metrics();
Mutex&
lockPartition(key_type const& key) const;
private:
struct Stats
{
@@ -300,8 +300,8 @@ private:
[[maybe_unused]] clock_type::time_point const& now,
typename KeyValueCacheType::map_type& partition,
SweptPointersVector& stuffToSweep,
std::atomic<int>& allRemovals,
std::lock_guard<std::recursive_mutex> const&);
std::atomic<int>& allRemoval,
Mutex& partitionLock);
[[nodiscard]] std::thread
sweepHelper(
@@ -310,14 +310,12 @@ private:
typename KeyOnlyCacheType::map_type& partition,
SweptPointersVector&,
std::atomic<int>& allRemovals,
std::lock_guard<std::recursive_mutex> const&);
Mutex& partitionLock);
beast::Journal m_journal;
clock_type& m_clock;
Stats m_stats;
mutex_type mutable m_mutex;
// Used for logging
std::string m_name;
@@ -328,10 +326,11 @@ private:
clock_type::duration const m_target_age;
// Number of items cached
int m_cache_count;
std::atomic<int> m_cache_count;
cache_type m_cache; // Hold strong reference to recent objects
std::uint64_t m_hits;
std::uint64_t m_misses;
std::atomic<std::uint64_t> m_hits;
std::atomic<std::uint64_t> m_misses;
mutable std::vector<mutex_type> partitionLocks_;
};
} // namespace ripple

View File

@@ -22,6 +22,7 @@
#include <xrpl/basics/IntrusivePointer.ipp>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/beast/core/CurrentThreadName.h>
namespace ripple {
@@ -60,6 +61,7 @@ inline TaggedCache<
, m_hits(0)
, m_misses(0)
{
partitionLocks_ = std::vector<mutex_type>(m_cache.partitions());
}
template <
@@ -105,8 +107,13 @@ TaggedCache<
KeyEqual,
Mutex>::size() const
{
std::lock_guard lock(m_mutex);
return m_cache.size();
std::size_t totalSize = 0;
for (size_t i = 0; i < partitionLocks_.size(); ++i)
{
std::lock_guard<Mutex> lock(partitionLocks_[i]);
totalSize += m_cache.map()[i].size();
}
return totalSize;
}
template <
@@ -129,8 +136,7 @@ TaggedCache<
KeyEqual,
Mutex>::getCacheSize() const
{
std::lock_guard lock(m_mutex);
return m_cache_count;
return m_cache_count.load(std::memory_order_relaxed);
}
template <
@@ -153,8 +159,7 @@ TaggedCache<
KeyEqual,
Mutex>::getTrackSize() const
{
std::lock_guard lock(m_mutex);
return m_cache.size();
return size();
}
template <
@@ -177,9 +182,10 @@ TaggedCache<
KeyEqual,
Mutex>::getHitRate()
{
std::lock_guard lock(m_mutex);
auto const total = static_cast<float>(m_hits + m_misses);
return m_hits * (100.0f / std::max(1.0f, total));
auto hits = m_hits.load(std::memory_order_relaxed);
auto misses = m_misses.load(std::memory_order_relaxed);
float total = float(hits + misses);
return hits * (100.0f / std::max(1.0f, total));
}
template <
@@ -202,9 +208,12 @@ TaggedCache<
KeyEqual,
Mutex>::clear()
{
std::lock_guard lock(m_mutex);
for (auto& mutex : partitionLocks_)
mutex.lock();
m_cache.clear();
m_cache_count = 0;
for (auto& mutex : partitionLocks_)
mutex.unlock();
m_cache_count.store(0, std::memory_order_relaxed);
}
template <
@@ -227,11 +236,14 @@ TaggedCache<
KeyEqual,
Mutex>::reset()
{
std::lock_guard lock(m_mutex);
for (auto& mutex : partitionLocks_)
mutex.lock();
m_cache.clear();
m_cache_count = 0;
m_hits = 0;
m_misses = 0;
for (auto& mutex : partitionLocks_)
mutex.unlock();
m_cache_count.store(0, std::memory_order_relaxed);
m_hits.store(0, std::memory_order_relaxed);
m_misses.store(0, std::memory_order_relaxed);
}
template <
@@ -255,7 +267,7 @@ TaggedCache<
KeyEqual,
Mutex>::touch_if_exists(KeyComparable const& key)
{
std::lock_guard lock(m_mutex);
std::lock_guard<Mutex> lock(lockPartition(key));
auto const iter(m_cache.find(key));
if (iter == m_cache.end())
{
@@ -297,8 +309,6 @@ TaggedCache<
auto const start = std::chrono::steady_clock::now();
{
std::lock_guard lock(m_mutex);
if (m_target_size == 0 ||
(static_cast<int>(m_cache.size()) <= m_target_size))
{
@@ -330,12 +340,13 @@ TaggedCache<
m_cache.map()[p],
allStuffToSweep[p],
allRemovals,
lock));
partitionLocks_[p]));
}
for (std::thread& worker : workers)
worker.join();
m_cache_count -= allRemovals;
int removals = allRemovals.load(std::memory_order_relaxed);
m_cache_count.fetch_sub(removals, std::memory_order_relaxed);
}
// At this point allStuffToSweep will go out of scope outside the lock
// and decrement the reference count on each strong pointer.
@@ -369,7 +380,8 @@ TaggedCache<
{
// Remove from cache, if !valid, remove from map too. Returns true if
// removed from cache
std::lock_guard lock(m_mutex);
std::lock_guard<Mutex> lock(lockPartition(key));
auto cit = m_cache.find(key);
@@ -382,7 +394,7 @@ TaggedCache<
if (entry.isCached())
{
--m_cache_count;
m_cache_count.fetch_sub(1, std::memory_order_relaxed);
entry.ptr.convertToWeak();
ret = true;
}
@@ -420,17 +432,16 @@ TaggedCache<
{
// Return canonical value, store if needed, refresh in cache
// Return values: true=we had the data already
std::lock_guard lock(m_mutex);
std::lock_guard<Mutex> lock(lockPartition(key));
auto cit = m_cache.find(key);
if (cit == m_cache.end())
{
m_cache.emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(m_clock.now(), data));
++m_cache_count;
m_cache_count.fetch_add(1, std::memory_order_relaxed);
return false;
}
@@ -479,12 +490,12 @@ TaggedCache<
data = cachedData;
}
++m_cache_count;
m_cache_count.fetch_add(1, std::memory_order_relaxed);
return true;
}
entry.ptr = data;
++m_cache_count;
m_cache_count.fetch_add(1, std::memory_order_relaxed);
return false;
}
@@ -560,10 +571,11 @@ TaggedCache<
KeyEqual,
Mutex>::fetch(key_type const& key)
{
std::lock_guard<mutex_type> l(m_mutex);
auto ret = initialFetch(key, l);
std::lock_guard<Mutex> lock(lockPartition(key));
auto ret = initialFetch(key);
if (!ret)
++m_misses;
m_misses.fetch_add(1, std::memory_order_relaxed);
return ret;
}
@@ -627,8 +639,8 @@ TaggedCache<
Mutex>::insert(key_type const& key)
-> std::enable_if_t<IsKeyCache, ReturnType>
{
std::lock_guard lock(m_mutex);
clock_type::time_point const now(m_clock.now());
std::lock_guard<Mutex> lock(lockPartition(key));
auto [it, inserted] = m_cache.emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
@@ -668,29 +680,6 @@ TaggedCache<
return true;
}
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
inline auto
TaggedCache<
Key,
T,
IsKeyCache,
SharedWeakUnionPointer,
SharedPointerType,
Hash,
KeyEqual,
Mutex>::peekMutex() -> mutex_type&
{
return m_mutex;
}
template <
class Key,
class T,
@@ -714,10 +703,13 @@ TaggedCache<
std::vector<key_type> v;
{
std::lock_guard lock(m_mutex);
v.reserve(m_cache.size());
for (auto const& _ : m_cache)
v.push_back(_.first);
for (std::size_t i = 0; i < partitionLocks_.size(); ++i)
{
std::lock_guard<Mutex> lock(partitionLocks_[i]);
for (auto const& entry : m_cache.map()[i])
v.push_back(entry.first);
}
}
return v;
@@ -743,11 +735,12 @@ TaggedCache<
KeyEqual,
Mutex>::rate() const
{
std::lock_guard lock(m_mutex);
auto const tot = m_hits + m_misses;
auto hits = m_hits.load(std::memory_order_relaxed);
auto misses = m_misses.load(std::memory_order_relaxed);
auto const tot = hits + misses;
if (tot == 0)
return 0;
return double(m_hits) / tot;
return 0.0;
return double(hits) / tot;
}
template <
@@ -771,18 +764,16 @@ TaggedCache<
KeyEqual,
Mutex>::fetch(key_type const& digest, Handler const& h)
{
{
std::lock_guard l(m_mutex);
if (auto ret = initialFetch(digest, l))
return ret;
}
std::lock_guard<Mutex> lock(lockPartition(digest));
if (auto ret = initialFetch(digest))
return ret;
auto sle = h();
if (!sle)
return {};
std::lock_guard l(m_mutex);
++m_misses;
m_misses.fetch_add(1, std::memory_order_relaxed);
auto const [it, inserted] =
m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle)));
if (!inserted)
@@ -809,9 +800,10 @@ TaggedCache<
SharedPointerType,
Hash,
KeyEqual,
Mutex>::
initialFetch(key_type const& key, std::lock_guard<mutex_type> const& l)
Mutex>::initialFetch(key_type const& key)
{
std::lock_guard<Mutex> lock(lockPartition(key));
auto cit = m_cache.find(key);
if (cit == m_cache.end())
return {};
@@ -819,7 +811,7 @@ TaggedCache<
Entry& entry = cit->second;
if (entry.isCached())
{
++m_hits;
m_hits.fetch_add(1, std::memory_order_relaxed);
entry.touch(m_clock.now());
return entry.ptr.getStrong();
}
@@ -827,12 +819,13 @@ TaggedCache<
if (entry.isCached())
{
// independent of cache size, so not counted as a hit
++m_cache_count;
m_cache_count.fetch_add(1, std::memory_order_relaxed);
entry.touch(m_clock.now());
return entry.ptr.getStrong();
}
m_cache.erase(cit);
m_cache.erase(cit); // TODO: if this erase happens on fetch, what is left
// for a sweep?
return {};
}
@@ -861,10 +854,11 @@ TaggedCache<
{
beast::insight::Gauge::value_type hit_rate(0);
{
std::lock_guard lock(m_mutex);
auto const total(m_hits + m_misses);
auto const hits = m_hits.load(std::memory_order_relaxed);
auto const misses = m_misses.load(std::memory_order_relaxed);
auto const total = hits + misses;
if (total != 0)
hit_rate = (m_hits * 100) / total;
hit_rate = (hits * 100) / total;
}
m_stats.hit_rate.set(hit_rate);
}
@@ -895,12 +889,16 @@ TaggedCache<
typename KeyValueCacheType::map_type& partition,
SweptPointersVector& stuffToSweep,
std::atomic<int>& allRemovals,
std::lock_guard<std::recursive_mutex> const&)
Mutex& partitionLock)
{
return std::thread([&, this]() {
beast::setCurrentThreadName("sweep-1");
int cacheRemovals = 0;
int mapRemovals = 0;
std::lock_guard<Mutex> lock(partitionLock);
// Keep references to all the stuff we sweep
// so that we can destroy them outside the lock.
stuffToSweep.reserve(partition.size());
@@ -984,12 +982,16 @@ TaggedCache<
typename KeyOnlyCacheType::map_type& partition,
SweptPointersVector&,
std::atomic<int>& allRemovals,
std::lock_guard<std::recursive_mutex> const&)
Mutex& partitionLock)
{
return std::thread([&, this]() {
beast::setCurrentThreadName("sweep-2");
int cacheRemovals = 0;
int mapRemovals = 0;
std::lock_guard<Mutex> lock(partitionLock);
// Keep references to all the stuff we sweep
// so that we can destroy them outside the lock.
{
@@ -1024,6 +1026,29 @@ TaggedCache<
});
}
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
inline Mutex&
TaggedCache<
Key,
T,
IsKeyCache,
SharedWeakUnionPointer,
SharedPointerType,
Hash,
KeyEqual,
Mutex>::lockPartition(key_type const& key) const
{
return partitionLocks_[m_cache.partition_index(key)];
}
} // namespace ripple
#endif

View File

@@ -277,6 +277,12 @@ public:
return map_;
}
partition_map_type const&
map() const
{
return map_;
}
iterator
begin()
{
@@ -321,6 +327,12 @@ public:
return cend();
}
std::size_t
partition_index(key_type const& key) const
{
return partitioner(key);
}
private:
template <class T>
void
@@ -380,7 +392,7 @@ public:
clear()
{
for (auto& p : map_)
p.clear();
p.clear(); // TODO make sure that it is locked inside
}
iterator
@@ -406,7 +418,7 @@ public:
{
std::size_t ret = 0;
for (auto& p : map_)
ret += p.size();
ret += p.size(); // TODO make sure that it is locked inside
return ret;
}

View File

@@ -55,6 +55,18 @@
* `VoteBehavior::DefaultYes`. The communication process is beyond
* the scope of these instructions.
*
* 5) A feature marked as Obsolete can mean either:
* 1) It is in the ledger (marked as Supported::yes) and it is on its way to
* become Retired
* 2) The feature is not in the ledger (has always been marked as
* Supported::no) and the code to support it has been removed
*
* If we want to discontinue a feature that we've never fully supported and
* the feature has never been enabled, we should remove all the related
* code, and mark the feature as "abandoned". To do this:
*
* 1) Open features.macro, move the feature to the abandoned section and
* change the macro to XRPL_ABANDON
*
* When a feature has been enabled for several years, the conditional code
* may be removed, and the feature "retired". To retire a feature:
@@ -88,10 +100,13 @@ namespace detail {
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) +1
#define XRPL_FIX(name, supported, vote) +1
#define XRPL_RETIRE(name) +1
#define XRPL_ABANDON(name) +1
// This value SHOULD be equal to the number of amendments registered in
// Feature.cpp. Because it's only used to reserve storage, and determine how
@@ -108,6 +123,8 @@ static constexpr std::size_t numFeatures =
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -349,10 +366,13 @@ foreachFeature(FeatureBitset bs, F&& f)
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
#define XRPL_RETIRE(name)
#define XRPL_ABANDON(name)
#include <xrpl/protocol/detail/features.macro>
@@ -362,6 +382,8 @@ foreachFeature(FeatureBitset bs, F&& f)
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
} // namespace ripple

View File

@@ -28,6 +28,8 @@
namespace ripple {
namespace RPC {
/**
Adds common synthetic fields to transaction-related JSON responses
@@ -40,6 +42,7 @@ insertNFTSyntheticInJson(
TxMeta const&);
/** @} */
} // namespace RPC
} // namespace ripple
#endif

View File

@@ -22,7 +22,6 @@
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/partitioned_unordered_map.h>
#include <cstdint>

View File

@@ -26,6 +26,9 @@
#if !defined(XRPL_RETIRE)
#error "undefined macro: XRPL_RETIRE"
#endif
#if !defined(XRPL_ABANDON)
#error "undefined macro: XRPL_ABANDON"
#endif
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
@@ -114,7 +117,6 @@ XRPL_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYe
XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::DefaultNo)
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.
@@ -132,6 +134,11 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
// The following amendments were never supported, never enabled, and
// we've abanded them. These features should never be in the ledger,
// and we've removed all the related code.
XRPL_ABANDON(OwnerPaysFee)
// The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated

View File

@@ -254,7 +254,7 @@ FeatureCollections::registerFeature(
{
check(!readOnly, "Attempting to register a feature after startup.");
check(
support == Supported::yes || vote == VoteBehavior::DefaultNo,
support == Supported::yes || vote != VoteBehavior::DefaultYes,
"Invalid feature parameters. Must be supported to be up-voted.");
Feature const* i = getByName(name);
if (!i)
@@ -268,7 +268,7 @@ FeatureCollections::registerFeature(
features.emplace_back(name, f);
auto const getAmendmentSupport = [=]() {
if (vote == VoteBehavior::Obsolete)
if (vote == VoteBehavior::Obsolete && support == Supported::yes)
return AmendmentSupport::Retired;
return support == Supported::yes ? AmendmentSupport::Supported
: AmendmentSupport::Unsupported;
@@ -398,6 +398,14 @@ retireFeature(std::string const& name)
return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
}
// Abandoned features are not in the ledger and have no code controlled by the
// feature. They were never supported, and cannot be voted on.
uint256
abandonFeature(std::string const& name)
{
return registerFeature(name, Supported::no, VoteBehavior::Obsolete);
}
/** Tell FeatureCollections when registration is complete. */
bool
registrationIsDone()
@@ -432,6 +440,8 @@ featureToName(uint256 const& f)
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) \
uint256 const feature##name = registerFeature(#name, supported, vote);
@@ -443,6 +453,11 @@ featureToName(uint256 const& f)
[[deprecated("The referenced amendment has been retired")]] \
[[maybe_unused]] \
uint256 const retired##name = retireFeature(#name);
#define XRPL_ABANDON(name) \
[[deprecated("The referenced amendment has been abandoned")]] \
[[maybe_unused]] \
uint256 const abandoned##name = abandonFeature(#name);
// clang-format on
#include <xrpl/protocol/detail/features.macro>
@@ -453,6 +468,8 @@ featureToName(uint256 const& f)
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
// All of the features should now be registered, since variables in a cpp file
// are initialized from top to bottom.

View File

@@ -28,6 +28,7 @@
#include <memory>
namespace ripple {
namespace RPC {
void
insertNFTSyntheticInJson(
@@ -39,4 +40,5 @@ insertNFTSyntheticInJson(
insertNFTokenOfferID(response[jss::meta], transaction, transactionMeta);
}
} // namespace RPC
} // namespace ripple

View File

@@ -1183,9 +1183,7 @@ private:
using namespace jtx;
// The problem was identified when featureOwnerPaysFee was enabled,
// so make sure that gets included.
Env env{*this, features | featureOwnerPaysFee};
Env env{*this, features};
// The fee that's charged for transactions.
auto const fee = env.current()->fees().base;
@@ -2217,271 +2215,6 @@ private:
}
}
void
testTransferRate(FeatureBitset features)
{
testcase("Transfer Rate");
using namespace jtx;
{
// transfer fee on AMM
Env env(*this, features);
fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(1'000)});
env(rate(gw, 1.25));
env.close();
AMM ammBob(env, bob, XRP(100), USD(150));
// no transfer fee on create
BEAST_EXPECT(expectLine(env, bob, USD(1000 - 150)));
env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
env.close();
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 150)));
BEAST_EXPECT(
ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
BEAST_EXPECT(expectLedgerEntryRoot(
env, alice, xrpMinusFee(env, 10'000 - 50)));
BEAST_EXPECT(expectLine(env, carol, USD(1'050)));
}
{
// Transfer fee AMM and offer
Env env(*this, features);
fund(
env,
gw,
{alice, bob, carol},
XRP(10'000),
{USD(1'000), EUR(1'000)});
env(rate(gw, 1.25));
env.close();
AMM ammBob(env, bob, XRP(100), USD(140));
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
env(offer(bob, USD(50), EUR(50)));
// alice buys 40EUR with 40XRP
env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
// 40XRP is swapped in for 40USD
BEAST_EXPECT(
ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
// 40USD buys 40EUR via bob's offer. 40EUR delivered to carol
// and bob pays 25% on 40EUR, 40EUR*0.25=10EUR
BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 40 - 40 * 0.25)));
// bob gets 40USD back from the offer
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
BEAST_EXPECT(expectLedgerEntryRoot(
env, alice, xrpMinusFee(env, 10'000 - 40)));
BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
BEAST_EXPECT(expectOffers(env, bob, 1, {{USD(10), EUR(10)}}));
}
{
// Transfer fee two consecutive AMM
Env env(*this, features);
fund(
env,
gw,
{alice, bob, carol},
XRP(10'000),
{USD(1'000), EUR(1'000)});
env(rate(gw, 1.25));
env.close();
AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
AMM ammBobUSD_EUR(env, bob, USD(100), EUR(140));
BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
// alice buys 40EUR with 40XRP
env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
// 40XRP is swapped in for 40USD
BEAST_EXPECT(ammBobXRP_USD.expectBalances(
XRP(140), USD(100), ammBobXRP_USD.tokens()));
// 40USD is swapped in for 40EUR
BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
USD(140), EUR(100), ammBobUSD_EUR.tokens()));
// no other charges on bob
BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
BEAST_EXPECT(expectLedgerEntryRoot(
env, alice, xrpMinusFee(env, 10'000 - 40)));
BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
}
{
// Payment via AMM with limit quality, deliver less
// than requested
Env env(*this, features);
fund(
env,
gw,
{alice, bob, carol},
XRP(1'000),
{USD(1'200), GBP(1'200)});
env(rate(gw, 1.25));
env.close();
AMM amm(env, bob, GBP(1'000), USD(1'100));
// requested quality limit is 90USD/110GBP = 0.8181
// trade quality is 77.2727USD/94.4444GBP = 0.8181
env(pay(alice, carol, USD(90)),
path(~USD),
sendmax(GBP(110)),
txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.close();
if (!features[fixAMMv1_1])
{
// alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
// on 75.5555GBP
// 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'105'555555555555), -12}));
// 75.5555GBP is swapped in for 77.7272USD
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'075'555555555556), -12},
STAmount{USD, UINT64_C(1'022'727272727272), -12},
amm.tokens()));
}
else
{
// alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
// on 75.5555GBP
// 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
// 75.5555GBP is swapped in for 77.7272USD
BEAST_EXPECT(amm.expectBalances(
STAmount{GBP, UINT64_C(1'075'555555555557), -12},
STAmount{USD, UINT64_C(1'022'727272727272), -12},
amm.tokens()));
}
BEAST_EXPECT(expectLine(
env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
}
{
// AMM offer crossing
Env env(*this, features);
fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'200), EUR(1'200)});
env(rate(gw, 1.25));
env.close();
AMM amm(env, bob, USD(1'000), EUR(1'150));
env(offer(alice, EUR(100), USD(100)));
env.close();
if (!features[fixAMMv1_1])
{
// 95.2380USD is swapped in for 100EUR
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(1'095'238095238095), -12},
EUR(1'050),
amm.tokens()));
// alice pays 25% tr fee on 95.2380USD
// 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{USD, UINT64_C(1'080'952380952381), -12},
EUR(1'300)));
}
else
{
// 95.2380USD is swapped in for 100EUR
BEAST_EXPECT(amm.expectBalances(
STAmount{USD, UINT64_C(1'095'238095238096), -12},
EUR(1'050),
amm.tokens()));
// alice pays 25% tr fee on 95.2380USD
// 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
BEAST_EXPECT(expectLine(
env,
alice,
STAmount{USD, UINT64_C(1'080'95238095238), -11},
EUR(1'300)));
}
BEAST_EXPECT(expectOffers(env, alice, 0));
}
{
// First pass through a strand redeems, second pass issues,
// through an offer limiting step is not an endpoint
Env env(*this, features);
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
Account const dan("dan");
env.fund(XRP(10'000), bob, carol, dan, gw);
fund(env, {alice}, XRP(10'000));
env(rate(gw, 1.25));
env.trust(USD(2'000), alice, bob, carol, dan);
env.trust(EUR(2'000), carol, dan);
env.trust(USDA(1'000), bob);
env.trust(USDB(1'000), gw);
env(pay(gw, bob, USD(50)));
env(pay(gw, dan, EUR(1'050)));
env(pay(gw, dan, USD(1'000)));
AMM ammDan(env, dan, USD(1'000), EUR(1'050));
if (!features[fixAMMv1_1])
{
// alice -> bob -> gw -> carol. $50 should have transfer fee;
// $10, no fee
env(pay(alice, carol, EUR(50)),
path(bob, gw, ~EUR),
sendmax(USDA(60)),
txflags(tfNoRippleDirect));
BEAST_EXPECT(ammDan.expectBalances(
USD(1'050), EUR(1'000), ammDan.tokens()));
BEAST_EXPECT(expectLine(env, dan, USD(0)));
BEAST_EXPECT(expectLine(env, dan, EUR(0)));
BEAST_EXPECT(expectLine(env, bob, USD(-10)));
BEAST_EXPECT(expectLine(env, bob, USDA(60)));
BEAST_EXPECT(expectLine(env, carol, EUR(50)));
}
else
{
// alice -> bob -> gw -> carol. $50 should have transfer fee;
// $10, no fee
env(pay(alice, carol, EUR(50)),
path(bob, gw, ~EUR),
sendmax(USDA(60.1)),
txflags(tfNoRippleDirect));
BEAST_EXPECT(ammDan.expectBalances(
STAmount{USD, UINT64_C(1'050'000000000001), -12},
EUR(1'000),
ammDan.tokens()));
BEAST_EXPECT(expectLine(env, dan, USD(0)));
BEAST_EXPECT(expectLine(env, dan, EUR(0)));
BEAST_EXPECT(expectLine(
env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
BEAST_EXPECT(expectLine(
env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
BEAST_EXPECT(expectLine(env, carol, EUR(50)));
}
}
}
void
testTransferRateNoOwnerFee(FeatureBitset features)
{
@@ -4057,13 +3790,9 @@ private:
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
testFalseDry(all);
testBookStep(all);
testBookStep(all | ownerPaysFee);
testTransferRate(all | ownerPaysFee);
testTransferRate((all - fixAMMv1_1 - fixAMMv1_3) | ownerPaysFee);
testTransferRateNoOwnerFee(all);
testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
testLimitQuality();

View File

@@ -599,158 +599,18 @@ struct Flow_test : public beast::unit_test::suite
Account const bob("bob");
Account const carol("carol");
{
// Simple payment through a gateway with a
// transfer rate
Env env(*this, features);
// Offer where the owner is also the issuer, sender pays fee
Env env(*this, features);
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol);
env(pay(gw, alice, USD(50)));
env.require(balance(alice, USD(50)));
env(pay(alice, bob, USD(40)), sendmax(USD(50)));
env.require(balance(bob, USD(40)), balance(alice, USD(0)));
}
{
// transfer rate is not charged when issuer is src or dst
Env env(*this, features);
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol);
env(pay(gw, alice, USD(50)));
env.require(balance(alice, USD(50)));
env(pay(alice, gw, USD(40)), sendmax(USD(40)));
env.require(balance(alice, USD(10)));
}
{
// transfer fee on an offer
Env env(*this, features);
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol);
env(pay(gw, bob, USD(65)));
env(offer(bob, XRP(50), USD(50)));
env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
env.require(
balance(alice, xrpMinusFee(env, 10000 - 50)),
balance(bob, USD(2.5)), // owner pays transfer fee
balance(carol, USD(50)));
}
{
// Transfer fee two consecutive offers
Env env(*this, features);
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol);
env.trust(EUR(1000), alice, bob, carol);
env(pay(gw, bob, USD(50)));
env(pay(gw, bob, EUR(50)));
env(offer(bob, XRP(50), USD(50)));
env(offer(bob, USD(50), EUR(50)));
env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
env.require(
balance(alice, xrpMinusFee(env, 10000 - 40)),
balance(bob, USD(40)),
balance(bob, EUR(0)),
balance(carol, EUR(40)));
}
{
// First pass through a strand redeems, second pass issues, no
// offers limiting step is not an endpoint
Env env(*this, features);
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
env.fund(XRP(10000), alice, bob, carol, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol);
env.trust(USDA(1000), bob);
env.trust(USDB(1000), gw);
env(pay(gw, bob, USD(50)));
// alice -> bob -> gw -> carol. $50 should have transfer fee; $10,
// no fee
env(pay(alice, carol, USD(50)), path(bob), sendmax(USDA(60)));
env.require(
balance(bob, USD(-10)),
balance(bob, USDA(60)),
balance(carol, USD(50)));
}
{
// First pass through a strand redeems, second pass issues, through
// an offer limiting step is not an endpoint
Env env(*this, features);
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
Account const dan("dan");
env.fund(XRP(10000), alice, bob, carol, dan, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob, carol, dan);
env.trust(EUR(1000), carol, dan);
env.trust(USDA(1000), bob);
env.trust(USDB(1000), gw);
env(pay(gw, bob, USD(50)));
env(pay(gw, dan, EUR(100)));
env(offer(dan, USD(100), EUR(100)));
// alice -> bob -> gw -> carol. $50 should have transfer fee; $10,
// no fee
env(pay(alice, carol, EUR(50)),
path(bob, gw, ~EUR),
sendmax(USDA(60)),
txflags(tfNoRippleDirect));
env.require(
balance(bob, USD(-10)),
balance(bob, USDA(60)),
balance(dan, USD(50)),
balance(dan, EUR(37.5)),
balance(carol, EUR(50)));
}
{
// Offer where the owner is also the issuer, owner pays fee
Env env(*this, features);
env.fund(XRP(10000), alice, bob, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob);
env(offer(gw, XRP(100), USD(100)));
env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
env.require(
balance(alice, xrpMinusFee(env, 10000 - 100)),
balance(bob, USD(100)));
}
if (!features[featureOwnerPaysFee])
{
// Offer where the owner is also the issuer, sender pays fee
Env env(*this, features);
env.fund(XRP(10000), alice, bob, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob);
env(offer(gw, XRP(125), USD(125)));
env(pay(alice, bob, USD(100)), sendmax(XRP(200)));
env.require(
balance(alice, xrpMinusFee(env, 10000 - 125)),
balance(bob, USD(100)));
}
env.fund(XRP(10000), alice, bob, gw);
env.close();
env(rate(gw, 1.25));
env.trust(USD(1000), alice, bob);
env(offer(gw, XRP(125), USD(125)));
env(pay(alice, bob, USD(100)), sendmax(XRP(200)));
env.require(
balance(alice, xrpMinusFee(env, 10000 - 125)),
balance(bob, USD(100)));
}
void
@@ -1445,7 +1305,6 @@ struct Flow_test : public beast::unit_test::suite
testWithFeats(FeatureBitset features)
{
using namespace jtx;
FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
FeatureBitset const reducedOffersV2(fixReducedOffersV2);
testLineQuality(features);
@@ -1453,9 +1312,7 @@ struct Flow_test : public beast::unit_test::suite
testBookStep(features - reducedOffersV2);
testDirectStep(features);
testBookStep(features);
testDirectStep(features | ownerPaysFee);
testBookStep(features | ownerPaysFee);
testTransferRate(features | ownerPaysFee);
testTransferRate(features);
testSelfPayment1(features);
testSelfPayment2(features);
testSelfFundedXRPEndpoint(false, features);

View File

@@ -3643,9 +3643,7 @@ public:
using namespace jtx;
// The problem was identified when featureOwnerPaysFee was enabled,
// so make sure that gets included.
Env env{*this, features | featureOwnerPaysFee};
Env env{*this, features};
// The fee that's charged for transactions.
auto const fee = env.current()->fees().base;

View File

@@ -264,7 +264,7 @@ class TheoreticalQuality_test : public beast::unit_test::suite
sendMaxIssue,
rcp.paths,
/*defaultPaths*/ rcp.paths.empty(),
sb.rules().enabled(featureOwnerPaysFee),
false,
OfferCrossing::no,
ammContext,
std::nullopt,

View File

@@ -1,144 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/RangeSet.h>
#include <xrpl/beast/unit_test.h>
namespace ripple {
class RangeSet_test : public beast::unit_test::suite
{
public:
void
testPrevMissing()
{
testcase("prevMissing");
// Set will include:
// [ 0, 5]
// [10,15]
// [20,25]
// etc...
RangeSet<std::uint32_t> set;
for (std::uint32_t i = 0; i < 10; ++i)
set.insert(range(10 * i, 10 * i + 5));
for (std::uint32_t i = 1; i < 100; ++i)
{
std::optional<std::uint32_t> expected;
// no prev missing in domain for i <= 6
if (i > 6)
{
std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1;
expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange;
}
BEAST_EXPECT(prevMissing(set, i) == expected);
}
}
void
testToString()
{
testcase("toString");
RangeSet<std::uint32_t> set;
BEAST_EXPECT(to_string(set) == "empty");
set.insert(1);
BEAST_EXPECT(to_string(set) == "1");
set.insert(range(4u, 6u));
BEAST_EXPECT(to_string(set) == "1,4-6");
set.insert(2);
BEAST_EXPECT(to_string(set) == "1-2,4-6");
set.erase(range(4u, 5u));
BEAST_EXPECT(to_string(set) == "1-2,6");
}
void
testFromString()
{
testcase("fromString");
RangeSet<std::uint32_t> set;
BEAST_EXPECT(!from_string(set, ""));
BEAST_EXPECT(boost::icl::length(set) == 0);
BEAST_EXPECT(!from_string(set, "#"));
BEAST_EXPECT(boost::icl::length(set) == 0);
BEAST_EXPECT(!from_string(set, ","));
BEAST_EXPECT(boost::icl::length(set) == 0);
BEAST_EXPECT(!from_string(set, ",-"));
BEAST_EXPECT(boost::icl::length(set) == 0);
BEAST_EXPECT(!from_string(set, "1,,2"));
BEAST_EXPECT(boost::icl::length(set) == 0);
BEAST_EXPECT(from_string(set, "1"));
BEAST_EXPECT(boost::icl::length(set) == 1);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(from_string(set, "1,1"));
BEAST_EXPECT(boost::icl::length(set) == 1);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(from_string(set, "1-1"));
BEAST_EXPECT(boost::icl::length(set) == 1);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(from_string(set, "1,4-6"));
BEAST_EXPECT(boost::icl::length(set) == 4);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(!boost::icl::contains(set, 2));
BEAST_EXPECT(!boost::icl::contains(set, 3));
BEAST_EXPECT(boost::icl::contains(set, 4));
BEAST_EXPECT(boost::icl::contains(set, 5));
BEAST_EXPECT(boost::icl::last(set) == 6);
BEAST_EXPECT(from_string(set, "1-2,4-6"));
BEAST_EXPECT(boost::icl::length(set) == 5);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(boost::icl::contains(set, 2));
BEAST_EXPECT(boost::icl::contains(set, 4));
BEAST_EXPECT(boost::icl::last(set) == 6);
BEAST_EXPECT(from_string(set, "1-2,6"));
BEAST_EXPECT(boost::icl::length(set) == 3);
BEAST_EXPECT(boost::icl::first(set) == 1);
BEAST_EXPECT(boost::icl::contains(set, 2));
BEAST_EXPECT(boost::icl::last(set) == 6);
}
void
run() override
{
testPrevMissing();
testToString();
testFromString();
}
};
BEAST_DEFINE_TESTSUITE(RangeSet, ripple_basics, ripple);
} // namespace ripple

View File

@@ -1,116 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github0.com/ripple/rippled
Copyright (c) 2012-2016 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/unit_test.h>
#include <array>
#include <cstdint>
namespace ripple {
namespace test {
struct Slice_test : beast::unit_test::suite
{
void
run() override
{
std::uint8_t const data[] = {
0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a,
0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c,
0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3};
{
testcase("Equality & Inequality");
Slice const s0{};
BEAST_EXPECT(s0.size() == 0);
BEAST_EXPECT(s0.data() == nullptr);
BEAST_EXPECT(s0 == s0);
// Test slices of equal and unequal size pointing to same data:
for (std::size_t i = 0; i != sizeof(data); ++i)
{
Slice const s1{data, i};
BEAST_EXPECT(s1.size() == i);
BEAST_EXPECT(s1.data() != nullptr);
if (i == 0)
BEAST_EXPECT(s1 == s0);
else
BEAST_EXPECT(s1 != s0);
for (std::size_t j = 0; j != sizeof(data); ++j)
{
Slice const s2{data, j};
if (i == j)
BEAST_EXPECT(s1 == s2);
else
BEAST_EXPECT(s1 != s2);
}
}
// Test slices of equal size but pointing to different data:
std::array<std::uint8_t, sizeof(data)> a;
std::array<std::uint8_t, sizeof(data)> b;
for (std::size_t i = 0; i != sizeof(data); ++i)
a[i] = b[i] = data[i];
BEAST_EXPECT(makeSlice(a) == makeSlice(b));
b[7]++;
BEAST_EXPECT(makeSlice(a) != makeSlice(b));
a[7]++;
BEAST_EXPECT(makeSlice(a) == makeSlice(b));
}
{
testcase("Indexing");
Slice const s{data, sizeof(data)};
for (std::size_t i = 0; i != sizeof(data); ++i)
BEAST_EXPECT(s[i] == data[i]);
}
{
testcase("Advancing");
for (std::size_t i = 0; i < sizeof(data); ++i)
{
for (std::size_t j = 0; i + j < sizeof(data); ++j)
{
Slice s(data + i, sizeof(data) - i);
s += j;
BEAST_EXPECT(s.data() == data + i + j);
BEAST_EXPECT(s.size() == sizeof(data) - i - j);
}
}
}
}
};
BEAST_DEFINE_TESTSUITE(Slice, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,82 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2018 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#include <xrpl/basics/base64.h>
#include <xrpl/beast/unit_test.h>
namespace ripple {
class base64_test : public beast::unit_test::suite
{
public:
void
check(std::string const& in, std::string const& out)
{
auto const encoded = base64_encode(in);
BEAST_EXPECT(encoded == out);
BEAST_EXPECT(base64_decode(encoded) == in);
}
void
run() override
{
check("", "");
check("f", "Zg==");
check("fo", "Zm8=");
check("foo", "Zm9v");
check("foob", "Zm9vYg==");
check("fooba", "Zm9vYmE=");
check("foobar", "Zm9vYmFy");
check(
"Man is distinguished, not only by his reason, but by this "
"singular passion from "
"other animals, which is a lust of the mind, that by a "
"perseverance of delight "
"in the continued and indefatigable generation of knowledge, "
"exceeds the short "
"vehemence of any carnal pleasure.",
"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dC"
"BieSB0aGlz"
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG"
"x1c3Qgb2Yg"
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aG"
"UgY29udGlu"
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleG"
"NlZWRzIHRo"
"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
std::string const notBase64 = "not_base64!!";
std::string const truncated = "not";
BEAST_EXPECT(base64_decode(notBase64) == base64_decode(truncated));
}
};
BEAST_DEFINE_TESTSUITE(base64, ripple_basics, ripple);
} // namespace ripple

View File

@@ -1,62 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/mulDiv.h>
#include <xrpl/beast/unit_test.h>
namespace ripple {
namespace test {
struct mulDiv_test : beast::unit_test::suite
{
void
run() override
{
auto const max = std::numeric_limits<std::uint64_t>::max();
std::uint64_t const max32 = std::numeric_limits<std::uint32_t>::max();
auto result = mulDiv(85, 20, 5);
BEAST_EXPECT(result && *result == 340);
result = mulDiv(20, 85, 5);
BEAST_EXPECT(result && *result == 340);
result = mulDiv(0, max - 1, max - 3);
BEAST_EXPECT(result && *result == 0);
result = mulDiv(max - 1, 0, max - 3);
BEAST_EXPECT(result && *result == 0);
result = mulDiv(max, 2, max / 2);
BEAST_EXPECT(result && *result == 4);
result = mulDiv(max, 1000, max / 1000);
BEAST_EXPECT(result && *result == 1000000);
result = mulDiv(max, 1000, max / 1001);
BEAST_EXPECT(result && *result == 1001000);
result = mulDiv(max32 + 1, max32 + 1, 5);
BEAST_EXPECT(result && *result == 3689348814741910323);
// Overflow
result = mulDiv(max - 1, max - 2, 5);
BEAST_EXPECT(!result);
}
};
BEAST_DEFINE_TESTSUITE(mulDiv, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,193 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github0.com/ripple/rippled
Copyright (c) 2021 Ripple Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/scope.h>
#include <xrpl/beast/unit_test.h>
namespace ripple {
namespace test {
struct scope_test : beast::unit_test::suite
{
void
test_scope_exit()
{
// scope_exit always executes the functor on destruction,
// unless release() is called
int i = 0;
{
scope_exit x{[&i]() { i = 1; }};
}
BEAST_EXPECT(i == 1);
{
scope_exit x{[&i]() { i = 2; }};
x.release();
}
BEAST_EXPECT(i == 1);
{
scope_exit x{[&i]() { i += 2; }};
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 3);
{
scope_exit x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 3);
{
try
{
scope_exit x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 5);
{
try
{
scope_exit x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 5);
}
void
test_scope_fail()
{
// scope_fail executes the functor on destruction only
// if an exception is unwinding, unless release() is called
int i = 0;
{
scope_fail x{[&i]() { i = 1; }};
}
BEAST_EXPECT(i == 0);
{
scope_fail x{[&i]() { i = 2; }};
x.release();
}
BEAST_EXPECT(i == 0);
{
scope_fail x{[&i]() { i = 3; }};
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 0);
{
scope_fail x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 0);
{
try
{
scope_fail x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 5);
{
try
{
scope_fail x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 5);
}
void
test_scope_success()
{
// scope_success executes the functor on destruction only
// if an exception is not unwinding, unless release() is called
int i = 0;
{
scope_success x{[&i]() { i = 1; }};
}
BEAST_EXPECT(i == 1);
{
scope_success x{[&i]() { i = 2; }};
x.release();
}
BEAST_EXPECT(i == 1);
{
scope_success x{[&i]() { i += 2; }};
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 3);
{
scope_success x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
BEAST_EXPECT(i == 3);
{
try
{
scope_success x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 3);
{
try
{
scope_success x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
BEAST_EXPECT(i == 3);
}
void
run() override
{
test_scope_exit();
test_scope_fail();
test_scope_success();
}
};
BEAST_DEFINE_TESTSUITE(scope, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,258 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright 2014, Nikolaos D. Bougalis <nikb@bougalis.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/tagged_integer.h>
#include <xrpl/beast/unit_test.h>
#include <type_traits>
namespace ripple {
namespace test {
class tagged_integer_test : public beast::unit_test::suite
{
private:
struct Tag1
{
};
struct Tag2
{
};
// Static checks that types are not interoperable
using TagUInt1 = tagged_integer<std::uint32_t, Tag1>;
using TagUInt2 = tagged_integer<std::uint32_t, Tag2>;
using TagUInt3 = tagged_integer<std::uint64_t, Tag1>;
// Check construction of tagged_integers
static_assert(
std::is_constructible<TagUInt1, std::uint32_t>::value,
"TagUInt1 should be constructible using a std::uint32_t");
static_assert(
!std::is_constructible<TagUInt1, std::uint64_t>::value,
"TagUInt1 should not be constructible using a std::uint64_t");
static_assert(
std::is_constructible<TagUInt3, std::uint32_t>::value,
"TagUInt3 should be constructible using a std::uint32_t");
static_assert(
std::is_constructible<TagUInt3, std::uint64_t>::value,
"TagUInt3 should be constructible using a std::uint64_t");
// Check assignment of tagged_integers
static_assert(
!std::is_assignable<TagUInt1, std::uint32_t>::value,
"TagUInt1 should not be assignable with a std::uint32_t");
static_assert(
!std::is_assignable<TagUInt1, std::uint64_t>::value,
"TagUInt1 should not be assignable with a std::uint64_t");
static_assert(
!std::is_assignable<TagUInt3, std::uint32_t>::value,
"TagUInt3 should not be assignable with a std::uint32_t");
static_assert(
!std::is_assignable<TagUInt3, std::uint64_t>::value,
"TagUInt3 should not be assignable with a std::uint64_t");
static_assert(
std::is_assignable<TagUInt1, TagUInt1>::value,
"TagUInt1 should be assignable with a TagUInt1");
static_assert(
!std::is_assignable<TagUInt1, TagUInt2>::value,
"TagUInt1 should not be assignable with a TagUInt2");
static_assert(
std::is_assignable<TagUInt3, TagUInt3>::value,
"TagUInt3 should be assignable with a TagUInt1");
static_assert(
!std::is_assignable<TagUInt1, TagUInt3>::value,
"TagUInt1 should not be assignable with a TagUInt3");
static_assert(
!std::is_assignable<TagUInt3, TagUInt1>::value,
"TagUInt3 should not be assignable with a TagUInt1");
// Check convertibility of tagged_integers
static_assert(
!std::is_convertible<std::uint32_t, TagUInt1>::value,
"std::uint32_t should not be convertible to a TagUInt1");
static_assert(
!std::is_convertible<std::uint32_t, TagUInt3>::value,
"std::uint32_t should not be convertible to a TagUInt3");
static_assert(
!std::is_convertible<std::uint64_t, TagUInt3>::value,
"std::uint64_t should not be convertible to a TagUInt3");
static_assert(
!std::is_convertible<std::uint64_t, TagUInt2>::value,
"std::uint64_t should not be convertible to a TagUInt2");
static_assert(
!std::is_convertible<TagUInt1, TagUInt2>::value,
"TagUInt1 should not be convertible to TagUInt2");
static_assert(
!std::is_convertible<TagUInt1, TagUInt3>::value,
"TagUInt1 should not be convertible to TagUInt3");
static_assert(
!std::is_convertible<TagUInt2, TagUInt3>::value,
"TagUInt2 should not be convertible to a TagUInt3");
public:
void
run() override
{
using TagInt = tagged_integer<std::int32_t, Tag1>;
{
testcase("Comparison Operators");
TagInt const zero(0);
TagInt const one(1);
BEAST_EXPECT(one == one);
BEAST_EXPECT(!(one == zero));
BEAST_EXPECT(one != zero);
BEAST_EXPECT(!(one != one));
BEAST_EXPECT(zero < one);
BEAST_EXPECT(!(one < zero));
BEAST_EXPECT(one > zero);
BEAST_EXPECT(!(zero > one));
BEAST_EXPECT(one >= one);
BEAST_EXPECT(one >= zero);
BEAST_EXPECT(!(zero >= one));
BEAST_EXPECT(zero <= one);
BEAST_EXPECT(zero <= zero);
BEAST_EXPECT(!(one <= zero));
}
{
testcase("Increment/Decrement Operators");
TagInt const zero(0);
TagInt const one(1);
TagInt a{0};
++a;
BEAST_EXPECT(a == one);
--a;
BEAST_EXPECT(a == zero);
a++;
BEAST_EXPECT(a == one);
a--;
BEAST_EXPECT(a == zero);
}
{
testcase("Arithmetic Operators");
TagInt a{-2};
BEAST_EXPECT(+a == TagInt{-2});
BEAST_EXPECT(-a == TagInt{2});
BEAST_EXPECT(TagInt{-3} + TagInt{4} == TagInt{1});
BEAST_EXPECT(TagInt{-3} - TagInt{4} == TagInt{-7});
BEAST_EXPECT(TagInt{-3} * TagInt{4} == TagInt{-12});
BEAST_EXPECT(TagInt{8} / TagInt{4} == TagInt{2});
BEAST_EXPECT(TagInt{7} % TagInt{4} == TagInt{3});
BEAST_EXPECT(~TagInt{8} == TagInt{~TagInt::value_type{8}});
BEAST_EXPECT((TagInt{6} & TagInt{3}) == TagInt{2});
BEAST_EXPECT((TagInt{6} | TagInt{3}) == TagInt{7});
BEAST_EXPECT((TagInt{6} ^ TagInt{3}) == TagInt{5});
BEAST_EXPECT((TagInt{4} << TagInt{2}) == TagInt{16});
BEAST_EXPECT((TagInt{16} >> TagInt{2}) == TagInt{4});
}
{
testcase("Assignment Operators");
TagInt a{-2};
TagInt b{0};
b = a;
BEAST_EXPECT(b == TagInt{-2});
// -3 + 4 == 1
a = TagInt{-3};
a += TagInt{4};
BEAST_EXPECT(a == TagInt{1});
// -3 - 4 == -7
a = TagInt{-3};
a -= TagInt{4};
BEAST_EXPECT(a == TagInt{-7});
// -3 * 4 == -12
a = TagInt{-3};
a *= TagInt{4};
BEAST_EXPECT(a == TagInt{-12});
// 8/4 == 2
a = TagInt{8};
a /= TagInt{4};
BEAST_EXPECT(a == TagInt{2});
// 7 % 4 == 3
a = TagInt{7};
a %= TagInt{4};
BEAST_EXPECT(a == TagInt{3});
// 6 & 3 == 2
a = TagInt{6};
a /= TagInt{3};
BEAST_EXPECT(a == TagInt{2});
// 6 | 3 == 7
a = TagInt{6};
a |= TagInt{3};
BEAST_EXPECT(a == TagInt{7});
// 6 ^ 3 == 5
a = TagInt{6};
a ^= TagInt{3};
BEAST_EXPECT(a == TagInt{5});
// 4 << 2 == 16
a = TagInt{4};
a <<= TagInt{2};
BEAST_EXPECT(a == TagInt{16});
// 16 >> 2 == 4
a = TagInt{16};
a >>= TagInt{2};
BEAST_EXPECT(a == TagInt{4});
}
}
};
BEAST_DEFINE_TESTSUITE(tagged_integer, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -139,7 +139,8 @@ class Feature_test : public beast::unit_test::suite
// Test a random sampling of the variables. If any of these get retired
// or removed, swap out for any other feature.
BEAST_EXPECT(featureToName(featureOwnerPaysFee) == "OwnerPaysFee");
BEAST_EXPECT(
featureToName(fixTrustLinesToSelf) == "fixTrustLinesToSelf");
BEAST_EXPECT(featureToName(featureFlow) == "Flow");
BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
BEAST_EXPECT(featureToName(fix1578) == "fix1578");

View File

@@ -1354,6 +1354,225 @@ public:
}));
}
void
testNFToken(FeatureBitset features)
{
// `nftoken_id` is added for `transaction` stream in the `subscribe`
// response for NFTokenMint and NFTokenAcceptOffer.
//
// `nftoken_ids` is added for `transaction` stream in the `subscribe`
// response for NFTokenCancelOffer
//
// `offer_id` is added for `transaction` stream in the `subscribe`
// response for NFTokenCreateOffer
//
// The values of these fields are dependent on the NFTokenID/OfferID
// changed in its corresponding transaction. We want to validate each
// response to make sure the synethic fields hold the right values.
testcase("Test synthetic fields from Subscribe response");
using namespace test::jtx;
using namespace std::chrono_literals;
Account const alice{"alice"};
Account const bob{"bob"};
Account const broker{"broker"};
Env env{*this, features};
env.fund(XRP(10000), alice, bob, broker);
env.close();
auto wsc = test::makeWSClient(env.app().config());
Json::Value stream;
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("transactions");
auto jv = wsc->invoke("subscribe", stream);
// Verify `nftoken_id` value equals to the NFTokenID that was
// changed in the most recent NFTokenMint or NFTokenAcceptOffer
// transaction
auto verifyNFTokenID = [&](uint256 const& actualNftID) {
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
uint256 nftID;
BEAST_EXPECT(
nftID.parseHex(jv[jss::meta][jss::nftoken_id].asString()));
return nftID == actualNftID;
}));
};
// Verify `nftoken_ids` value equals to the NFTokenIDs that were
// changed in the most recent NFTokenCancelOffer transaction
auto verifyNFTokenIDsInCancelOffer =
[&](std::vector<uint256> actualNftIDs) {
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
std::vector<uint256> metaIDs;
std::transform(
jv[jss::meta][jss::nftoken_ids].begin(),
jv[jss::meta][jss::nftoken_ids].end(),
std::back_inserter(metaIDs),
[this](Json::Value id) {
uint256 nftID;
BEAST_EXPECT(nftID.parseHex(id.asString()));
return nftID;
});
// Sort both array to prepare for comparison
std::sort(metaIDs.begin(), metaIDs.end());
std::sort(actualNftIDs.begin(), actualNftIDs.end());
// Make sure the expect number of NFTs is correct
BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
// Check the value of NFT ID in the meta with the
// actual values
for (size_t i = 0; i < metaIDs.size(); ++i)
BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
return true;
}));
};
// Verify `offer_id` value equals to the offerID that was
// changed in the most recent NFTokenCreateOffer tx
auto verifyNFTokenOfferID = [&](uint256 const& offerID) {
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
uint256 metaOfferID;
BEAST_EXPECT(metaOfferID.parseHex(
jv[jss::meta][jss::offer_id].asString()));
return metaOfferID == offerID;
}));
};
// Check new fields in tx meta when for all NFTtransactions
{
// Alice mints 2 NFTs
// Verify the NFTokenIDs are correct in the NFTokenMint tx meta
uint256 const nftId1{
token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
verifyNFTokenID(nftId1);
uint256 const nftId2{
token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
verifyNFTokenID(nftId2);
// Alice creates one sell offer for each NFT
// Verify the offer indexes are correct in the NFTokenCreateOffer tx
// meta
uint256 const aliceOfferIndex1 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId1, drops(1)),
txflags(tfSellNFToken));
env.close();
verifyNFTokenOfferID(aliceOfferIndex1);
uint256 const aliceOfferIndex2 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId2, drops(1)),
txflags(tfSellNFToken));
env.close();
verifyNFTokenOfferID(aliceOfferIndex2);
// Alice cancels two offers she created
// Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
// meta
env(token::cancelOffer(
alice, {aliceOfferIndex1, aliceOfferIndex2}));
env.close();
verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
// Bobs creates a buy offer for nftId1
// Verify the offer id is correct in the NFTokenCreateOffer tx meta
auto const bobBuyOfferIndex =
keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice));
env.close();
verifyNFTokenOfferID(bobBuyOfferIndex);
// Alice accepts bob's buy offer
// Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
env.close();
verifyNFTokenID(nftId1);
}
// Check `nftoken_ids` in brokered mode
{
// Alice mints a NFT
uint256 const nftId{
token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
verifyNFTokenID(nftId);
// Alice creates sell offer and set broker as destination
uint256 const offerAliceToBroker =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, drops(1)),
token::destination(broker),
txflags(tfSellNFToken));
env.close();
verifyNFTokenOfferID(offerAliceToBroker);
// Bob creates buy offer
uint256 const offerBobToBroker =
keylet::nftoffer(bob, env.seq(bob)).key;
env(token::createOffer(bob, nftId, drops(1)), token::owner(alice));
env.close();
verifyNFTokenOfferID(offerBobToBroker);
// Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
env(token::brokerOffers(
broker, offerBobToBroker, offerAliceToBroker));
env.close();
verifyNFTokenID(nftId);
}
// Check if there are no duplicate nft id in Cancel transactions where
// multiple offers are cancelled for the same NFT
{
// Alice mints a NFT
uint256 const nftId{
token::getNextID(env, alice, 0u, tfTransferable)};
env(token::mint(alice, 0u), txflags(tfTransferable));
env.close();
verifyNFTokenID(nftId);
// Alice creates 2 sell offers for the same NFT
uint256 const aliceOfferIndex1 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, drops(1)),
txflags(tfSellNFToken));
env.close();
verifyNFTokenOfferID(aliceOfferIndex1);
uint256 const aliceOfferIndex2 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, drops(1)),
txflags(tfSellNFToken));
env.close();
verifyNFTokenOfferID(aliceOfferIndex2);
// Make sure the metadata only has 1 nft id, since both offers are
// for the same nft
env(token::cancelOffer(
alice, {aliceOfferIndex1, aliceOfferIndex2}));
env.close();
verifyNFTokenIDsInCancelOffer({nftId});
}
if (features[featureNFTokenMintOffer])
{
uint256 const aliceMintWithOfferIndex1 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::mint(alice), token::amount(XRP(0)));
env.close();
verifyNFTokenOfferID(aliceMintWithOfferIndex1);
}
}
void
run() override
{
@@ -1373,6 +1592,8 @@ public:
testSubByUrl();
testHistoryTxStream();
testSubBookChanges();
testNFToken(all);
testNFToken(all - featureNFTokenMintOffer);
}
};

4
src/tests/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Unit tests
This directory contains unit tests for the project. The difference from existing `src/test` folder
is that we switch to 3rd party testing framework (doctest). We intend to gradually move existing tests
from our own framework to doctest and such tests will be moved to this new folder.

View File

@@ -0,0 +1,14 @@
include(xrpl_add_test)
# Test requirements.
find_package(doctest REQUIRED)
# Common library dependencies for the rest of the tests.
add_library(xrpl.imports.test INTERFACE)
target_link_libraries(xrpl.imports.test INTERFACE doctest::doctest xrpl.libxrpl)
# One test for each module.
xrpl_add_test(basics)
target_link_libraries(xrpl.test.basics PRIVATE xrpl.imports.test)
xrpl_add_test(crypto)
target_link_libraries(xrpl.test.crypto PRIVATE xrpl.imports.test)

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/RangeSet.h>
#include <doctest/doctest.h>
#include <cstdint>
#include <optional>
using namespace ripple;
TEST_SUITE_BEGIN("RangeSet");
TEST_CASE("prevMissing")
{
// Set will include:
// [ 0, 5]
// [10,15]
// [20,25]
// etc...
RangeSet<std::uint32_t> set;
for (std::uint32_t i = 0; i < 10; ++i)
set.insert(range(10 * i, 10 * i + 5));
for (std::uint32_t i = 1; i < 100; ++i)
{
std::optional<std::uint32_t> expected;
// no prev missing in domain for i <= 6
if (i > 6)
{
std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1;
expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange;
}
CHECK(prevMissing(set, i) == expected);
}
}
TEST_CASE("toString")
{
RangeSet<std::uint32_t> set;
CHECK(to_string(set) == "empty");
set.insert(1);
CHECK(to_string(set) == "1");
set.insert(range(4u, 6u));
CHECK(to_string(set) == "1,4-6");
set.insert(2);
CHECK(to_string(set) == "1-2,4-6");
set.erase(range(4u, 5u));
CHECK(to_string(set) == "1-2,6");
}
TEST_CASE("fromString")
{
RangeSet<std::uint32_t> set;
CHECK(!from_string(set, ""));
CHECK(boost::icl::length(set) == 0);
CHECK(!from_string(set, "#"));
CHECK(boost::icl::length(set) == 0);
CHECK(!from_string(set, ","));
CHECK(boost::icl::length(set) == 0);
CHECK(!from_string(set, ",-"));
CHECK(boost::icl::length(set) == 0);
CHECK(!from_string(set, "1,,2"));
CHECK(boost::icl::length(set) == 0);
CHECK(from_string(set, "1"));
CHECK(boost::icl::length(set) == 1);
CHECK(boost::icl::first(set) == 1);
CHECK(from_string(set, "1,1"));
CHECK(boost::icl::length(set) == 1);
CHECK(boost::icl::first(set) == 1);
CHECK(from_string(set, "1-1"));
CHECK(boost::icl::length(set) == 1);
CHECK(boost::icl::first(set) == 1);
CHECK(from_string(set, "1,4-6"));
CHECK(boost::icl::length(set) == 4);
CHECK(boost::icl::first(set) == 1);
CHECK(!boost::icl::contains(set, 2));
CHECK(!boost::icl::contains(set, 3));
CHECK(boost::icl::contains(set, 4));
CHECK(boost::icl::contains(set, 5));
CHECK(boost::icl::last(set) == 6);
CHECK(from_string(set, "1-2,4-6"));
CHECK(boost::icl::length(set) == 5);
CHECK(boost::icl::first(set) == 1);
CHECK(boost::icl::contains(set, 2));
CHECK(boost::icl::contains(set, 4));
CHECK(boost::icl::last(set) == 6);
CHECK(from_string(set, "1-2,6"));
CHECK(boost::icl::length(set) == 3);
CHECK(boost::icl::first(set) == 1);
CHECK(boost::icl::contains(set, 2));
CHECK(boost::icl::last(set) == 6);
}
TEST_SUITE_END();

View File

@@ -0,0 +1,105 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/Slice.h>
#include <doctest/doctest.h>
#include <array>
#include <cstdint>
using namespace ripple;
static std::uint8_t const data[] = {
0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a,
0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c,
0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3};
TEST_SUITE_BEGIN("Slice");
TEST_CASE("equality & inequality")
{
Slice const s0{};
CHECK(s0.size() == 0);
CHECK(s0.data() == nullptr);
CHECK(s0 == s0);
// Test slices of equal and unequal size pointing to same data:
for (std::size_t i = 0; i != sizeof(data); ++i)
{
Slice const s1{data, i};
CHECK(s1.size() == i);
CHECK(s1.data() != nullptr);
if (i == 0)
CHECK(s1 == s0);
else
CHECK(s1 != s0);
for (std::size_t j = 0; j != sizeof(data); ++j)
{
Slice const s2{data, j};
if (i == j)
CHECK(s1 == s2);
else
CHECK(s1 != s2);
}
}
// Test slices of equal size but pointing to different data:
std::array<std::uint8_t, sizeof(data)> a;
std::array<std::uint8_t, sizeof(data)> b;
for (std::size_t i = 0; i != sizeof(data); ++i)
a[i] = b[i] = data[i];
CHECK(makeSlice(a) == makeSlice(b));
b[7]++;
CHECK(makeSlice(a) != makeSlice(b));
a[7]++;
CHECK(makeSlice(a) == makeSlice(b));
}
TEST_CASE("indexing")
{
Slice const s{data, sizeof(data)};
for (std::size_t i = 0; i != sizeof(data); ++i)
CHECK(s[i] == data[i]);
}
TEST_CASE("advancing")
{
for (std::size_t i = 0; i < sizeof(data); ++i)
{
for (std::size_t j = 0; i + j < sizeof(data); ++j)
{
Slice s(data + i, sizeof(data) - i);
s += j;
CHECK(s.data() == data + i + j);
CHECK(s.size() == sizeof(data) - i - j);
}
}
}
TEST_SUITE_END();

View File

@@ -0,0 +1,67 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/base64.h>
#include <doctest/doctest.h>
#include <string>
using namespace ripple;
static void
check(std::string const& in, std::string const& out)
{
auto const encoded = base64_encode(in);
CHECK(encoded == out);
CHECK(base64_decode(encoded) == in);
}
TEST_CASE("base64")
{
check("", "");
check("f", "Zg==");
check("fo", "Zm8=");
check("foo", "Zm9v");
check("foob", "Zm9vYg==");
check("fooba", "Zm9vYmE=");
check("foobar", "Zm9vYmFy");
check(
"Man is distinguished, not only by his reason, but by this "
"singular passion from "
"other animals, which is a lust of the mind, that by a "
"perseverance of delight "
"in the continued and indefatigable generation of knowledge, "
"exceeds the short "
"vehemence of any carnal pleasure.",
"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dC"
"BieSB0aGlz"
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG"
"x1c3Qgb2Yg"
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aG"
"UgY29udGlu"
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleG"
"NlZWRzIHRo"
"ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
std::string const notBase64 = "not_base64!!";
std::string const truncated = "not";
CHECK(base64_decode(notBase64) == base64_decode(truncated));
}

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Copyright (c) 2012 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,46 +18,39 @@
//==============================================================================
#include <xrpl/basics/contract.h>
#include <xrpl/beast/unit_test.h>
#include <doctest/doctest.h>
#include <stdexcept>
#include <string>
namespace ripple {
using namespace ripple;
class contract_test : public beast::unit_test::suite
TEST_CASE("contract")
{
public:
void
run() override
try
{
Throw<std::runtime_error>("Throw test");
}
catch (std::runtime_error const& e1)
{
CHECK(std::string(e1.what()) == "Throw test");
try
{
Throw<std::runtime_error>("Throw test");
Rethrow();
}
catch (std::runtime_error const& e1)
catch (std::runtime_error const& e2)
{
BEAST_EXPECT(std::string(e1.what()) == "Throw test");
try
{
Rethrow();
}
catch (std::runtime_error const& e2)
{
BEAST_EXPECT(std::string(e2.what()) == "Throw test");
}
catch (...)
{
BEAST_EXPECT(false);
}
CHECK(std::string(e2.what()) == "Throw test");
}
catch (...)
{
BEAST_EXPECT(false);
CHECK(false);
}
}
};
BEAST_DEFINE_TESTSUITE(contract, basics, ripple);
} // namespace ripple
catch (...)
{
CHECK(false);
}
}

View File

@@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

View File

@@ -0,0 +1,64 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/mulDiv.h>
#include <doctest/doctest.h>
#include <cstdint>
#include <limits>
using namespace ripple;
TEST_CASE("mulDiv")
{
auto const max = std::numeric_limits<std::uint64_t>::max();
std::uint64_t const max32 = std::numeric_limits<std::uint32_t>::max();
auto result = mulDiv(85, 20, 5);
REQUIRE(result);
CHECK(*result == 340);
result = mulDiv(20, 85, 5);
REQUIRE(result);
CHECK(*result == 340);
result = mulDiv(0, max - 1, max - 3);
REQUIRE(result);
CHECK(*result == 0);
result = mulDiv(max - 1, 0, max - 3);
REQUIRE(result);
CHECK(*result == 0);
result = mulDiv(max, 2, max / 2);
REQUIRE(result);
CHECK(*result == 4);
result = mulDiv(max, 1000, max / 1000);
REQUIRE(result);
CHECK(*result == 1000000);
result = mulDiv(max, 1000, max / 1001);
REQUIRE(result);
CHECK(*result == 1001000);
result = mulDiv(max32 + 1, max32 + 1, 5);
REQUIRE(result);
CHECK(*result == 3689348814741910323);
// Overflow
result = mulDiv(max - 1, max - 2, 5);
CHECK(!result);
}

View File

@@ -0,0 +1,174 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2021 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/scope.h>
#include <doctest/doctest.h>
using namespace ripple;
TEST_CASE("scope_exit")
{
// scope_exit always executes the functor on destruction,
// unless release() is called
int i = 0;
{
scope_exit x{[&i]() { i = 1; }};
}
CHECK(i == 1);
{
scope_exit x{[&i]() { i = 2; }};
x.release();
}
CHECK(i == 1);
{
scope_exit x{[&i]() { i += 2; }};
auto x2 = std::move(x);
}
CHECK(i == 3);
{
scope_exit x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
CHECK(i == 3);
{
try
{
scope_exit x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
CHECK(i == 5);
{
try
{
scope_exit x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
CHECK(i == 5);
}
TEST_CASE("scope_fail")
{
// scope_fail executes the functor on destruction only
// if an exception is unwinding, unless release() is called
int i = 0;
{
scope_fail x{[&i]() { i = 1; }};
}
CHECK(i == 0);
{
scope_fail x{[&i]() { i = 2; }};
x.release();
}
CHECK(i == 0);
{
scope_fail x{[&i]() { i = 3; }};
auto x2 = std::move(x);
}
CHECK(i == 0);
{
scope_fail x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
CHECK(i == 0);
{
try
{
scope_fail x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
CHECK(i == 5);
{
try
{
scope_fail x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
CHECK(i == 5);
}
TEST_CASE("scope_success")
{
// scope_success executes the functor on destruction only
// if an exception is not unwinding, unless release() is called
int i = 0;
{
scope_success x{[&i]() { i = 1; }};
}
CHECK(i == 1);
{
scope_success x{[&i]() { i = 2; }};
x.release();
}
CHECK(i == 1);
{
scope_success x{[&i]() { i += 2; }};
auto x2 = std::move(x);
}
CHECK(i == 3);
{
scope_success x{[&i]() { i = 4; }};
x.release();
auto x2 = std::move(x);
}
CHECK(i == 3);
{
try
{
scope_success x{[&i]() { i = 5; }};
throw 1;
}
catch (...)
{
}
}
CHECK(i == 3);
{
try
{
scope_success x{[&i]() { i = 6; }};
x.release();
throw 1;
}
catch (...)
{
}
}
CHECK(i == 3);
}

View File

@@ -0,0 +1,247 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/tagged_integer.h>
#include <doctest/doctest.h>
#include <type_traits>
using namespace ripple;
struct Tag1
{
};
struct Tag2
{
};
// Static checks that types are not interoperable
using TagUInt1 = tagged_integer<std::uint32_t, Tag1>;
using TagUInt2 = tagged_integer<std::uint32_t, Tag2>;
using TagUInt3 = tagged_integer<std::uint64_t, Tag1>;
// Check construction of tagged_integers
static_assert(
std::is_constructible<TagUInt1, std::uint32_t>::value,
"TagUInt1 should be constructible using a std::uint32_t");
static_assert(
!std::is_constructible<TagUInt1, std::uint64_t>::value,
"TagUInt1 should not be constructible using a std::uint64_t");
static_assert(
std::is_constructible<TagUInt3, std::uint32_t>::value,
"TagUInt3 should be constructible using a std::uint32_t");
static_assert(
std::is_constructible<TagUInt3, std::uint64_t>::value,
"TagUInt3 should be constructible using a std::uint64_t");
// Check assignment of tagged_integers
static_assert(
!std::is_assignable<TagUInt1, std::uint32_t>::value,
"TagUInt1 should not be assignable with a std::uint32_t");
static_assert(
!std::is_assignable<TagUInt1, std::uint64_t>::value,
"TagUInt1 should not be assignable with a std::uint64_t");
static_assert(
!std::is_assignable<TagUInt3, std::uint32_t>::value,
"TagUInt3 should not be assignable with a std::uint32_t");
static_assert(
!std::is_assignable<TagUInt3, std::uint64_t>::value,
"TagUInt3 should not be assignable with a std::uint64_t");
static_assert(
std::is_assignable<TagUInt1, TagUInt1>::value,
"TagUInt1 should be assignable with a TagUInt1");
static_assert(
!std::is_assignable<TagUInt1, TagUInt2>::value,
"TagUInt1 should not be assignable with a TagUInt2");
static_assert(
std::is_assignable<TagUInt3, TagUInt3>::value,
"TagUInt3 should be assignable with a TagUInt1");
static_assert(
!std::is_assignable<TagUInt1, TagUInt3>::value,
"TagUInt1 should not be assignable with a TagUInt3");
static_assert(
!std::is_assignable<TagUInt3, TagUInt1>::value,
"TagUInt3 should not be assignable with a TagUInt1");
// Check convertibility of tagged_integers
static_assert(
!std::is_convertible<std::uint32_t, TagUInt1>::value,
"std::uint32_t should not be convertible to a TagUInt1");
static_assert(
!std::is_convertible<std::uint32_t, TagUInt3>::value,
"std::uint32_t should not be convertible to a TagUInt3");
static_assert(
!std::is_convertible<std::uint64_t, TagUInt3>::value,
"std::uint64_t should not be convertible to a TagUInt3");
static_assert(
!std::is_convertible<std::uint64_t, TagUInt2>::value,
"std::uint64_t should not be convertible to a TagUInt2");
static_assert(
!std::is_convertible<TagUInt1, TagUInt2>::value,
"TagUInt1 should not be convertible to TagUInt2");
static_assert(
!std::is_convertible<TagUInt1, TagUInt3>::value,
"TagUInt1 should not be convertible to TagUInt3");
static_assert(
!std::is_convertible<TagUInt2, TagUInt3>::value,
"TagUInt2 should not be convertible to a TagUInt3");
TEST_SUITE_BEGIN("tagged_integer");
using TagInt = tagged_integer<std::int32_t, Tag1>;
TEST_CASE("comparison operators")
{
TagInt const zero(0);
TagInt const one(1);
CHECK(one == one);
CHECK(!(one == zero));
CHECK(one != zero);
CHECK(!(one != one));
CHECK(zero < one);
CHECK(!(one < zero));
CHECK(one > zero);
CHECK(!(zero > one));
CHECK(one >= one);
CHECK(one >= zero);
CHECK(!(zero >= one));
CHECK(zero <= one);
CHECK(zero <= zero);
CHECK(!(one <= zero));
}
TEST_CASE("increment / decrement operators")
{
TagInt const zero(0);
TagInt const one(1);
TagInt a{0};
++a;
CHECK(a == one);
--a;
CHECK(a == zero);
a++;
CHECK(a == one);
a--;
CHECK(a == zero);
}
TEST_CASE("arithmetic operators")
{
TagInt a{-2};
CHECK(+a == TagInt{-2});
CHECK(-a == TagInt{2});
CHECK(TagInt{-3} + TagInt{4} == TagInt{1});
CHECK(TagInt{-3} - TagInt{4} == TagInt{-7});
CHECK(TagInt{-3} * TagInt{4} == TagInt{-12});
CHECK(TagInt{8} / TagInt{4} == TagInt{2});
CHECK(TagInt{7} % TagInt{4} == TagInt{3});
CHECK(~TagInt{8} == TagInt{~TagInt::value_type{8}});
CHECK((TagInt{6} & TagInt{3}) == TagInt{2});
CHECK((TagInt{6} | TagInt{3}) == TagInt{7});
CHECK((TagInt{6} ^ TagInt{3}) == TagInt{5});
CHECK((TagInt{4} << TagInt{2}) == TagInt{16});
CHECK((TagInt{16} >> TagInt{2}) == TagInt{4});
}
TEST_CASE("assignment operators")
{
TagInt a{-2};
TagInt b{0};
b = a;
CHECK(b == TagInt{-2});
// -3 + 4 == 1
a = TagInt{-3};
a += TagInt{4};
CHECK(a == TagInt{1});
// -3 - 4 == -7
a = TagInt{-3};
a -= TagInt{4};
CHECK(a == TagInt{-7});
// -3 * 4 == -12
a = TagInt{-3};
a *= TagInt{4};
CHECK(a == TagInt{-12});
// 8/4 == 2
a = TagInt{8};
a /= TagInt{4};
CHECK(a == TagInt{2});
// 7 % 4 == 3
a = TagInt{7};
a %= TagInt{4};
CHECK(a == TagInt{3});
// 6 & 3 == 2
a = TagInt{6};
a /= TagInt{3};
CHECK(a == TagInt{2});
// 6 | 3 == 7
a = TagInt{6};
a |= TagInt{3};
CHECK(a == TagInt{7});
// 6 ^ 3 == 5
a = TagInt{6};
a ^= TagInt{3};
CHECK(a == TagInt{5});
// 4 << 2 == 16
a = TagInt{4};
a <<= TagInt{2};
CHECK(a == TagInt{16});
// 16 >> 2 == 4
a = TagInt{16};
a >>= TagInt{2};
CHECK(a == TagInt{4});
}
TEST_SUITE_END();

View File

@@ -17,44 +17,18 @@
*/
//==============================================================================
#include <test/jtx/Env.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/crypto/csprng.h>
namespace ripple {
#include <doctest/doctest.h>
class CryptoPRNG_test : public beast::unit_test::suite
using namespace ripple;
TEST_CASE("get values")
{
void
testGetValues()
{
testcase("Get Values");
try
{
auto& engine = crypto_prng();
auto rand_val = engine();
BEAST_EXPECT(rand_val >= engine.min());
BEAST_EXPECT(rand_val <= engine.max());
uint16_t twoByte{0};
engine(&twoByte, sizeof(uint16_t));
pass();
}
catch (std::exception&)
{
fail();
}
}
public:
void
run() override
{
testGetValues();
}
};
BEAST_DEFINE_TESTSUITE(CryptoPRNG, core, ripple);
} // namespace ripple
auto& engine = crypto_prng();
auto rand_val = engine();
CHECK(rand_val >= engine.min());
CHECK(rand_val <= engine.max());
uint16_t twoByte{0};
engine(&twoByte, sizeof(uint16_t));
}

View File

@@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

View File

@@ -63,8 +63,8 @@ LedgerHistory::insert(
ledger->stateMap().getHash().isNonZero(),
"ripple::LedgerHistory::insert : nonzero hash");
std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// TODOL merge the below into a single call to avoid lock and race
// conditions, i.e. - return alreadyHad on assignment somehow.
bool const alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(
ledger->info().hash, ledger);
if (validated)
@@ -76,7 +76,7 @@ LedgerHistory::insert(
LedgerHash
LedgerHistory::getLedgerHash(LedgerIndex index)
{
std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// TODO: is it safe to get iterator without lock here?
if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
return it->second;
return {};
@@ -86,13 +86,12 @@ std::shared_ptr<Ledger const>
LedgerHistory::getLedgerBySeq(LedgerIndex index)
{
{
std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// TODO: this lock is not needed
auto it = mLedgersByIndex.find(index);
if (it != mLedgersByIndex.end())
{
uint256 hash = it->second;
sl.unlock();
return getLedgerByHash(hash);
}
}
@@ -108,7 +107,8 @@ LedgerHistory::getLedgerBySeq(LedgerIndex index)
{
// Add this ledger to the local tracking by index
std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// TODO: make sure that canonicalize_replace_client lock the partition
XRPL_ASSERT(
ret->isImmutable(),
@@ -458,7 +458,8 @@ LedgerHistory::builtLedger(
XRPL_ASSERT(
!hash.isZero(), "ripple::LedgerHistory::builtLedger : nonzero hash");
std::unique_lock sl(m_consensus_validated.peekMutex());
// std::unique_lock sl(m_consensus_validated.peekMutex());
// TODO: make sure that canonicalize_replace_client lock the partition
auto entry = std::make_shared<cv_entry>();
m_consensus_validated.canonicalize_replace_client(index, entry);
@@ -500,7 +501,8 @@ LedgerHistory::validatedLedger(
!hash.isZero(),
"ripple::LedgerHistory::validatedLedger : nonzero hash");
std::unique_lock sl(m_consensus_validated.peekMutex());
// std::unique_lock sl(m_consensus_validated.peekMutex());
// TODO: make sure that canonicalize_replace_client lock the partition
auto entry = std::make_shared<cv_entry>();
m_consensus_validated.canonicalize_replace_client(index, entry);
@@ -535,7 +537,9 @@ LedgerHistory::validatedLedger(
bool
LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
{
std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// std::unique_lock sl(m_ledgers_by_hash.peekMutex());
// TODO: how to ensure that? "Ensure m_ledgers_by_hash doesn't have the
// wrong hash for a particular index"
auto it = mLedgersByIndex.find(ledgerIndex);
if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))

View File

@@ -63,6 +63,7 @@
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/MultiApiJson.h>
#include <xrpl/protocol/NFTSyntheticSerializer.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
@@ -3258,6 +3259,7 @@ NetworkOPsImp::transJson(
jvObj[jss::meta] = meta->get().getJson(JsonOptions::none);
RPC::insertDeliveredAmount(
jvObj[jss::meta], *ledger, transaction, meta->get());
RPC::insertNFTSyntheticInJson(jvObj, transaction, meta->get());
RPC::insertMPTokenIssuanceID(
jvObj[jss::meta], transaction, meta->get());
}

View File

@@ -95,9 +95,6 @@ RippleCalc::rippleCalculate(
return std::nullopt;
}();
bool const ownerPaysTransferFee =
view.rules().enabled(featureOwnerPaysFee);
try
{
flowOut = flow(
@@ -108,7 +105,7 @@ RippleCalc::rippleCalculate(
spsPaths,
defaultPaths,
partialPayment,
ownerPaysTransferFee,
false,
OfferCrossing::no,
limitQuality,
sendMax,

View File

@@ -348,7 +348,7 @@ populateJsonResponse(
txnMeta->getJson(JsonOptions::include_date);
insertDeliveredAmount(
jvObj[jss::meta], context, txn, *txnMeta);
insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
RPC::insertNFTSyntheticInJson(jvObj, sttx, *txnMeta);
RPC::insertMPTokenIssuanceID(
jvObj[jss::meta], sttx, *txnMeta);
}

View File

@@ -270,7 +270,7 @@ populateJsonResponse(
response[jss::meta] = meta->getJson(JsonOptions::none);
insertDeliveredAmount(
response[jss::meta], context, result.txn, *meta);
insertNFTSyntheticInJson(response, sttx, *meta);
RPC::insertNFTSyntheticInJson(response, sttx, *meta);
RPC::insertMPTokenIssuanceID(response[jss::meta], sttx, *meta);
}
}