mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-26 13:37:45 +00:00
Compare commits
396
Commits
3.4.1
...
xrplf/sponsor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a85377730 | ||
|
|
710af21ce9 | ||
|
|
a5ac1d4944 | ||
|
|
6f40198823 | ||
|
|
8114636103 | ||
|
|
3760b70a5a | ||
|
|
8d379087e5 | ||
|
|
f2761d931b | ||
|
|
9abeb52051 | ||
|
|
a065ab6c09 | ||
|
|
6a6b97b529 | ||
|
|
152130082d | ||
|
|
28b7fb7cc7 | ||
|
|
e0c9cf8b97 | ||
|
|
c74d394e8f | ||
|
|
d324b7a731 | ||
|
|
03d333855f | ||
|
|
1c23b2acd5 | ||
|
|
63209d4d77 | ||
|
|
d8616d486c | ||
|
|
78e4bb17c8 | ||
|
|
2cfbf728bc | ||
|
|
ec77e15ac7 | ||
|
|
5b6ff52519 | ||
|
|
5be01f06ce | ||
|
|
6d8e7f45c5 | ||
|
|
a5cced5d39 | ||
|
|
47043763af | ||
|
|
ec0716b7ba | ||
|
|
9b930d5dea | ||
|
|
f514427847 | ||
|
|
5283e9dba1 | ||
|
|
a2f1418aa8 | ||
|
|
af58fdac3c | ||
|
|
7868d6ed79 | ||
|
|
70c16e8e1e | ||
|
|
5670cc1316 | ||
|
|
8a71f96f78 | ||
|
|
c738ad14ed | ||
|
|
60702858a1 | ||
|
|
c7b10a3153 | ||
|
|
cd5ce8dc50 | ||
|
|
12cf66259c | ||
|
|
ed65de1489 | ||
|
|
11e6d65b30 | ||
|
|
cecea82835 | ||
|
|
0240203060 | ||
|
|
ad5bc87d1f | ||
|
|
6c902777ee | ||
|
|
bd42a05275 | ||
|
|
1c9168b29f | ||
|
|
76acc2c92b | ||
|
|
61896fd53d | ||
|
|
cbf44b8436 | ||
|
|
909f7ff102 | ||
|
|
b664847d59 | ||
|
|
5c17e879f7 | ||
|
|
b590672929 | ||
|
|
b4bac1db65 | ||
|
|
dd489172c3 | ||
|
|
da31106aa5 | ||
|
|
d9c683d822 | ||
|
|
5b714a79c0 | ||
|
|
7817245314 | ||
|
|
730e526080 | ||
|
|
4e6f8f334f | ||
|
|
140d5ec297 | ||
|
|
efeedcdc60 | ||
|
|
09b5cd9879 | ||
|
|
f9af579690 | ||
|
|
5bb979b2e5 | ||
|
|
12cafc87db | ||
|
|
e42c7c8551 | ||
|
|
35e1a6ef25 | ||
|
|
5029c08f12 | ||
|
|
132ced7d51 | ||
|
|
03d4e30378 | ||
|
|
5eff397170 | ||
|
|
86484cfa39 | ||
|
|
ac86cdada7 | ||
|
|
c6889666c9 | ||
|
|
9a7d1a114c | ||
|
|
6ecf413c64 | ||
|
|
8c98b50de8 | ||
|
|
2b727cd292 | ||
|
|
05bb41056d | ||
|
|
51264617f3 | ||
|
|
05d3614a19 | ||
|
|
494391b4ab | ||
|
|
fba4ba6ebd | ||
|
|
018e736a3b | ||
|
|
4a3abc0407 | ||
|
|
1eef7473d3 | ||
|
|
c3b3bce35d | ||
|
|
d1a77b453e | ||
|
|
c2ab83c960 | ||
|
|
aac6be218a | ||
|
|
14986cc043 | ||
|
|
4fc781ef93 | ||
|
|
e52113956e | ||
|
|
f650d52ada | ||
|
|
64159eb040 | ||
|
|
942a950e93 | ||
|
|
d70eca3d7d | ||
|
|
7e5ac6f8cb | ||
|
|
03fb6d1e0c | ||
|
|
9ef1949309 | ||
|
|
595b612589 | ||
|
|
d561f0d84d | ||
|
|
3859fcad73 | ||
|
|
8d905ac21b | ||
|
|
0997b92de3 | ||
|
|
9e5b7281cf | ||
|
|
0bfdfa73ff | ||
|
|
39e819927f | ||
|
|
08357b196f | ||
|
|
bb4c443fe5 | ||
|
|
a669099f21 | ||
|
|
7624c18c76 | ||
|
|
ab7bbac8f9 | ||
|
|
960aaec91e | ||
|
|
f042cd8e25 | ||
|
|
9d4c4968ad | ||
|
|
b76652bdf4 | ||
|
|
405b314842 | ||
|
|
77ebc2921e | ||
|
|
c72fd59539 | ||
|
|
6f4365557d | ||
|
|
d792e03468 | ||
|
|
2b4ce0fc21 | ||
|
|
eb88c0ef6b | ||
|
|
50d5e2d996 | ||
|
|
bca6d8e515 | ||
|
|
86c90c8e5c | ||
|
|
0c683bfd66 | ||
|
|
33be9abbef | ||
|
|
aa70d6051b | ||
|
|
d057f07d0e | ||
|
|
688e568e39 | ||
|
|
3ad93af34d | ||
|
|
c5941d6b14 | ||
|
|
ee8d9ca73e | ||
|
|
462a0b2338 | ||
|
|
9b4ab67864 | ||
|
|
84bffad2bf | ||
|
|
b153213e83 | ||
|
|
c12eff6646 | ||
|
|
fbff1c065c | ||
|
|
e8deaa12d8 | ||
|
|
6ee071c5b0 | ||
|
|
381094498d | ||
|
|
c072b125a0 | ||
|
|
b2b2babe1e | ||
|
|
bb27479686 | ||
|
|
278e25d8ad | ||
|
|
773acf0af7 | ||
|
|
7639bd9061 | ||
|
|
207a33d3da | ||
|
|
52e7cdc24d | ||
|
|
dd82977e6c | ||
|
|
31bc25973a | ||
|
|
021eaa81bf | ||
|
|
84e84fbadf | ||
|
|
b6b317597f | ||
|
|
525adf122f | ||
|
|
16e4f24226 | ||
|
|
3df1e668da | ||
|
|
9264136b36 | ||
|
|
6bd2e911eb | ||
|
|
828b4753fe | ||
|
|
d524670bcd | ||
|
|
25de0ce1b1 | ||
|
|
4026af175b | ||
|
|
a115d73c56 | ||
|
|
71003699dc | ||
|
|
831f99862d | ||
|
|
4d2945323d | ||
|
|
700a528a25 | ||
|
|
07d0887efb | ||
|
|
3ea0067e6a | ||
|
|
5a195d2585 | ||
|
|
3f4e88c300 | ||
|
|
7c3ca98bd5 | ||
|
|
2505fa86f7 | ||
|
|
d0d4f1d153 | ||
|
|
41429dcc99 | ||
|
|
aacb8e4a68 | ||
|
|
98257d3b96 | ||
|
|
afcc3c6464 | ||
|
|
fd393685dd | ||
|
|
de64bc7855 | ||
|
|
1adaeac8a7 | ||
|
|
7ddda1b1e6 | ||
|
|
5280a185d0 | ||
|
|
64f699a974 | ||
|
|
968493f43b | ||
|
|
22bfc5e743 | ||
|
|
2f0adfae3b | ||
|
|
53bdfb2bc7 | ||
|
|
b695b4bf9b | ||
|
|
6d7ffcbb91 | ||
|
|
d1346fa3f6 | ||
|
|
d6803868a1 | ||
|
|
9a6432dd66 | ||
|
|
f8a8c2301c | ||
|
|
e40e15a866 | ||
|
|
6555001cd9 | ||
|
|
f13945eff9 | ||
|
|
b32ff18c21 | ||
|
|
8baec5634c | ||
|
|
37c95ead69 | ||
|
|
234d2e5414 | ||
|
|
024ab07eb4 | ||
|
|
d95b6e41e5 | ||
|
|
8d75f3bdc6 | ||
|
|
eb7e01de7e | ||
|
|
c79814a037 | ||
|
|
15be71469d | ||
|
|
c9666d7b45 | ||
|
|
839ba17dae | ||
|
|
85e1f2b16c | ||
|
|
8a05e3d93b | ||
|
|
8c8ecffe1f | ||
|
|
b9f1c97518 | ||
|
|
d1eb22b403 | ||
|
|
fb86735800 | ||
|
|
53f407e1bd | ||
|
|
99618877e4 | ||
|
|
062ece321b | ||
|
|
1ad6dd1846 | ||
|
|
4a91351bcd | ||
|
|
f86b255e73 | ||
|
|
178bb8f7e9 | ||
|
|
293394fbc6 | ||
|
|
dcfcb1f3fd | ||
|
|
2fd8e1be3c | ||
|
|
fe5fab00ed | ||
|
|
9cde57d284 | ||
|
|
9859ba85a8 | ||
|
|
f6d79f74d6 | ||
|
|
bf4fa37937 | ||
|
|
686f945909 | ||
|
|
c1e1be510f | ||
|
|
d1d613da27 | ||
|
|
392a913631 | ||
|
|
446681dc3d | ||
|
|
e1aee43359 | ||
|
|
fe075470f8 | ||
|
|
49c52695cb | ||
|
|
37f4ea94cb | ||
|
|
f8209087a3 | ||
|
|
d390bc7a87 | ||
|
|
4e87de7303 | ||
|
|
8d24afe59d | ||
|
|
527d7bbede | ||
|
|
4a205eb9d8 | ||
|
|
0a410024a5 | ||
|
|
8f8fc03800 | ||
|
|
40cf5993c1 | ||
|
|
7cdaa24e63 | ||
|
|
175a2dfd28 | ||
|
|
4cd8d0d178 | ||
|
|
5155a94015 | ||
|
|
c0de722cfa | ||
|
|
d7ab1b48e1 | ||
|
|
65768237cd | ||
|
|
5f385e0f9b | ||
|
|
ab1de456cc | ||
|
|
8be44c7fc4 | ||
|
|
87718bdb3b | ||
|
|
88e870b1c6 | ||
|
|
ce8049a17f | ||
|
|
990627fc9e | ||
|
|
1823d70b21 | ||
|
|
f333dd1b2e | ||
|
|
204138fb0e | ||
|
|
52ae31bb17 | ||
|
|
c3e5bcfafd | ||
|
|
3568df43c4 | ||
|
|
c321e1070d | ||
|
|
9e65dba253 | ||
|
|
5b91d815d1 | ||
|
|
f885f02ede | ||
|
|
1bc5a1bb43 | ||
|
|
ec05613472 | ||
|
|
f88e964dac | ||
|
|
ffb538242a | ||
|
|
1e8f6ce1b5 | ||
|
|
82b146bf03 | ||
|
|
0791ca164a | ||
|
|
dc325a93e7 | ||
|
|
edddf3fe4b | ||
|
|
cf03c4cb8f | ||
|
|
2813fea294 | ||
|
|
163a2acbf5 | ||
|
|
198f3f83db | ||
|
|
0f36de8ef5 | ||
|
|
ea29fe5ede | ||
|
|
f5e2195bbb | ||
|
|
e49d338cd8 | ||
|
|
d96998ce47 | ||
|
|
a9629ae325 | ||
|
|
7f0a940a43 | ||
|
|
c38cb902ad | ||
|
|
b5db59644a | ||
|
|
9d1b5eb373 | ||
|
|
af9d91bc72 | ||
|
|
3f9673510a | ||
|
|
a49cb91c73 | ||
|
|
fbf403aaa4 | ||
|
|
e03c7a9c96 | ||
|
|
cd62f7f4cf | ||
|
|
297083e27b | ||
|
|
13145f6db2 | ||
|
|
3e6d6c6f80 | ||
|
|
b4188a887a | ||
|
|
774917db22 | ||
|
|
965ecd3f27 | ||
|
|
d0bcca6bf1 | ||
|
|
4fe7e87b4f | ||
|
|
2337d340e5 | ||
|
|
5d597e3b69 | ||
|
|
d8dc000488 | ||
|
|
a65cf6e07d | ||
|
|
7f922c4919 | ||
|
|
754f597078 | ||
|
|
32cc7825f2 | ||
|
|
a6b0efff87 | ||
|
|
28c5cadbea | ||
|
|
9df0ad64d1 | ||
|
|
1441abcf01 | ||
|
|
8d32b0f856 | ||
|
|
d59772657e | ||
|
|
bc71d9c138 | ||
|
|
101b70f0e8 | ||
|
|
ee385e9d3b | ||
|
|
4126d53978 | ||
|
|
59c3d5bddc | ||
|
|
bc1ef1e839 | ||
|
|
5b64ff0dd6 | ||
|
|
1ee250881b | ||
|
|
75aaeb5aae | ||
|
|
b09666210d | ||
|
|
dbbfc1354c | ||
|
|
e82c300de7 | ||
|
|
d5a1314c47 | ||
|
|
699297f01a | ||
|
|
71b81b743e | ||
|
|
ec11763def | ||
|
|
cc4b07dbd6 | ||
|
|
b78885d98c | ||
|
|
0c93bd6f08 | ||
|
|
9694de92da | ||
|
|
c4b798e8ed | ||
|
|
350130911a | ||
|
|
ab4ac64c92 | ||
|
|
aeaf679deb | ||
|
|
ea72198a1d | ||
|
|
6eb453f07d | ||
|
|
d4947386d7 | ||
|
|
fed56b2eeb | ||
|
|
f7e1d4bbb9 | ||
|
|
613fe48d54 | ||
|
|
5dc63b64eb | ||
|
|
cc5e063438 | ||
|
|
453fd23512 | ||
|
|
89af81745b | ||
|
|
a43ae9a3ce | ||
|
|
2afe5707ab | ||
|
|
759f843020 | ||
|
|
5ac7bbf51b | ||
|
|
2a27280f1a | ||
|
|
6c0732ccbe | ||
|
|
20bce64946 | ||
|
|
110b222579 | ||
|
|
1cdf7bb745 | ||
|
|
4abd94e7ac | ||
|
|
6aa0331ffe | ||
|
|
4eea76ca92 | ||
|
|
8e895a3e7d | ||
|
|
e589b71ee0 | ||
|
|
02d8f9fbef | ||
|
|
9ff71aa109 | ||
|
|
2071b39d6a | ||
|
|
5e65813a52 | ||
|
|
abd8620c97 | ||
|
|
6e01f233e4 | ||
|
|
0bddc95444 | ||
|
|
67bdd099c8 | ||
|
|
f0addd81a1 | ||
|
|
a798c60a08 | ||
|
|
198ed72fe3 | ||
|
|
d40f8cc232 | ||
|
|
5b9c767b07 | ||
|
|
43c16f35f7 | ||
|
|
cad305ac7e |
No files matched your search
@@ -279,6 +279,8 @@ words:
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- sponsee
|
||||
- sponsees
|
||||
- SRPMS
|
||||
- sslws
|
||||
- statsd
|
||||
@@ -327,6 +329,7 @@ words:
|
||||
- unserviced
|
||||
- unshareable
|
||||
- unshares
|
||||
- unsponsored
|
||||
- unsquelch
|
||||
- unsquelched
|
||||
- unsquelching
|
||||
|
||||
@@ -595,7 +595,7 @@ template <std::size_t Bits, typename Tag>
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(BaseUInt<Bits, Tag> const& lhs, BaseUInt<Bits, Tag> const& rhs)
|
||||
{
|
||||
return (lhs <=> rhs) == 0;
|
||||
return (lhs <=> rhs) == 0; // NOLINT(modernize-use-nullptr)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h> // IWYU pragma: keep
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -288,7 +290,7 @@ public:
|
||||
// Called when the owner count changes
|
||||
// This is required to support PaymentSandbox
|
||||
virtual void
|
||||
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next)
|
||||
adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -411,6 +413,23 @@ public:
|
||||
emptyDirDelete(Keylet const& directory);
|
||||
};
|
||||
|
||||
/** Bundles the mutable ledger view and the transaction being applied.
|
||||
|
||||
Passed together to avoid threading two separate parameters through every
|
||||
helper that needs both the view (for state reads/writes) and the
|
||||
transaction (for field inspection and metadata).
|
||||
|
||||
Both members are non-owning references; the caller is responsible for
|
||||
ensuring that the referenced objects outlive the ApplyViewContext.
|
||||
|
||||
TODO: replace with ApplyContext after it's untangled with xrpl/tx
|
||||
*/
|
||||
struct ApplyViewContext
|
||||
{
|
||||
ApplyView& view;
|
||||
STTx const& tx;
|
||||
};
|
||||
|
||||
namespace directory {
|
||||
/** Helper functions for managing low-level directory operations.
|
||||
These are not part of the ApplyView interface.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STInteger.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
struct OwnerCounts
|
||||
{
|
||||
std::uint32_t owner = 0;
|
||||
std::uint32_t sponsored = 0;
|
||||
std::uint32_t sponsoring = 0;
|
||||
|
||||
OwnerCounts() = default;
|
||||
OwnerCounts(SLE::const_ref sle)
|
||||
: owner(sle->at(sfOwnerCount))
|
||||
, sponsored(sle->at(sfSponsoredOwnerCount))
|
||||
, sponsoring(sle->at(sfSponsoringOwnerCount))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
owner >= sponsored,
|
||||
"xrpl::OwnerCounts : OwnerCount must be greater than or equal to "
|
||||
"SponsoredOwnerCount");
|
||||
XRPL_ASSERT(sle->getType() == ltACCOUNT_ROOT, "xrpl::OwnerCounts : sle is AccountRoot");
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
count() const
|
||||
{
|
||||
std::int64_t const x = static_cast<std::int64_t>(owner) - sponsored + sponsoring;
|
||||
if (x < 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::OwnerCounts::count : count less than zero");
|
||||
return 0;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (x > std::numeric_limits<std::uint32_t>::max())
|
||||
return std::numeric_limits<std::uint32_t>::max(); // LCOV_EXCL_LINE
|
||||
return static_cast<std::uint32_t>(x);
|
||||
}
|
||||
|
||||
auto
|
||||
operator<=>(OwnerCounts const& o) const
|
||||
{
|
||||
if (auto cmp = count() <=> o.count(); cmp != 0) // NOLINT(modernize-use-nullptr)
|
||||
return cmp;
|
||||
if (auto cmp = owner <=> o.owner; cmp != 0) // NOLINT(modernize-use-nullptr)
|
||||
return cmp;
|
||||
if (auto cmp = sponsored <=> o.sponsored; cmp != 0) // NOLINT(modernize-use-nullptr)
|
||||
return cmp;
|
||||
return sponsoring <=> o.sponsoring;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(OwnerCounts const& o) const
|
||||
{
|
||||
return this == &o ||
|
||||
(owner == o.owner && sponsored == o.sponsored && sponsoring == o.sponsoring);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/detail/ApplyViewBase.h>
|
||||
@@ -107,12 +108,12 @@ public:
|
||||
issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance);
|
||||
|
||||
void
|
||||
ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next);
|
||||
ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next);
|
||||
|
||||
// Get the adjusted owner count. Since DeferredCredits is meant to be used
|
||||
// in payments, and payments only decrease owner counts, return the max
|
||||
// remembered owner count.
|
||||
[[nodiscard]] std::optional<std::uint32_t>
|
||||
[[nodiscard]] std::optional<OwnerCounts>
|
||||
ownerCount(AccountID const& id) const;
|
||||
|
||||
void
|
||||
@@ -124,7 +125,7 @@ private:
|
||||
|
||||
std::map<KeyIOU, ValueIOU> creditsIOU_;
|
||||
std::map<MPTID, IssuerValueMPT> creditsMPT_;
|
||||
std::map<AccountID, std::uint32_t> ownerCounts_;
|
||||
std::map<AccountID, OwnerCounts> ownerCounts_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@@ -218,10 +219,11 @@ public:
|
||||
override;
|
||||
|
||||
void
|
||||
adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override;
|
||||
adjustOwnerCountHook(AccountID const& account, OwnerCounts const& cur, OwnerCounts const& next)
|
||||
override;
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
ownerCountHook(AccountID const& account, std::uint32_t count) const override;
|
||||
[[nodiscard]] OwnerCounts
|
||||
ownerCountHook(AccountID const& account, OwnerCounts const& count) const override;
|
||||
|
||||
/** Apply changes to base view.
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/detail/ReadViewFwdRange.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
@@ -189,8 +191,8 @@ public:
|
||||
// changes that accounts make during a payment. `ownerCountHook` adjusts the
|
||||
// ownerCount so it returns the max value of the ownerCount so far.
|
||||
// This is required to support PaymentSandbox.
|
||||
[[nodiscard]] virtual std::uint32_t
|
||||
ownerCountHook(AccountID const& account, std::uint32_t count) const
|
||||
[[nodiscard]] virtual OwnerCounts
|
||||
ownerCountHook(AccountID const& account, OwnerCounts const& count) const
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -210,8 +210,7 @@ canWithdraw(ReadView const& view, STTx const& tx);
|
||||
|
||||
[[nodiscard]] TER
|
||||
doWithdraw(
|
||||
ApplyView& view,
|
||||
STTx const& tx,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& senderAcct,
|
||||
AccountID const& dstAcct,
|
||||
AccountID const& sourceAcct,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,21 +27,293 @@ namespace xrpl {
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
/** Calculate liquid XRP balance for an account.
|
||||
*
|
||||
* This function may be used to calculate the amount of XRP that
|
||||
* the holder is able to freely spend. It subtracts reserve requirements.
|
||||
*
|
||||
* ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
* before ledger entries are added or removed. Positive to add, negative
|
||||
* to subtract.
|
||||
*
|
||||
* @param view The ledger view to read from
|
||||
* @param id The account ID to check
|
||||
* @param ownerCountAdj Positive to add to count, negative to reduce count
|
||||
* @param j Journal for logging
|
||||
* @return The liquid XRP amount available to the account
|
||||
*/
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
struct Adjustment
|
||||
{
|
||||
std::int32_t ownerCountDelta = 0;
|
||||
std::int32_t accountCountDelta = 0;
|
||||
};
|
||||
|
||||
/** Returns the account reserve, in drops.
|
||||
*
|
||||
* Actual owner count can be adjusted by delta in ownerCountAdj
|
||||
* Actual reserve count can be adjusted by delta in accountCountAdj
|
||||
* The reserve is calculated as:
|
||||
* (ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) *
|
||||
* increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base"
|
||||
*
|
||||
* @param view The ledger view to read from
|
||||
* @param sle The ledger entry for the account
|
||||
* @param j Journal for logging
|
||||
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
|
||||
* subtract.
|
||||
* @return The account reserve amount in drops
|
||||
*/
|
||||
[[nodiscard]] XRPAmount
|
||||
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj = {});
|
||||
|
||||
/** Convenience overload that accepts AccountID instead of SLE.
|
||||
*
|
||||
* @param view The ledger view to read from
|
||||
* @param id The account ID
|
||||
* @param j Journal for logging
|
||||
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
|
||||
* subtract.
|
||||
* @return The account reserve amount in drops
|
||||
*/
|
||||
[[nodiscard]] inline XRPAmount
|
||||
accountReserve(ReadView const& view, AccountID const& id, beast::Journal j, Adjustment adj = {})
|
||||
{
|
||||
return accountReserve(view, view.read(keylet::account(id)), j, adj);
|
||||
}
|
||||
|
||||
/** Check if an account has sufficient reserve.
|
||||
*
|
||||
* @param view The ledger view to read from
|
||||
* @param tx The transaction being processed
|
||||
* @param accSle The account's ledger entry
|
||||
* @param accBalance The account's balance
|
||||
* @param sponsorSle The sponsor's ledger entry (if applicable)
|
||||
* @param adj Adjustment to the owner/account count (default: 0/0). Positive to add, negative to
|
||||
* subtract.
|
||||
* @param j Journal for logging (default: null sink)
|
||||
* @param insufReserveCode The transaction result code to return if the reserve is insufficient
|
||||
* (default: tecINSUFFICIENT_RESERVE).
|
||||
* @return Transaction result code
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkReserve(
|
||||
ApplyViewContext ctx,
|
||||
SLE::const_ref accSle,
|
||||
XRPAmount accBalance,
|
||||
SLE::const_ref sponsorSle,
|
||||
Adjustment adj,
|
||||
beast::Journal j,
|
||||
TER insufReserveCode = tecINSUFFICIENT_RESERVE);
|
||||
|
||||
/** Check if an account has sufficient reserve, deriving the sponsor internally.
|
||||
*
|
||||
* Equivalent to the overload above, but resolves the sponsor via
|
||||
* getEffectiveTxReserveSponsor(ctx, accSle) instead of taking it explicitly. Use this
|
||||
* in the common case where the sponsor is simply the transaction's reserve
|
||||
* sponsor for accSle. Callers that must force the account's-own-reserve branch
|
||||
* (passing a null sponsor) or supply a different sponsor should use the
|
||||
* explicit overload above.
|
||||
*
|
||||
* @param ctx The apply-view context (view + tx)
|
||||
* @param accSle The account's ledger entry
|
||||
* @param accBalance The account's balance
|
||||
* @param adj Reserve adjustments (owner/account count deltas)
|
||||
* @param j Journal for logging (default: null sink)
|
||||
* @return Transaction result code
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkReserve(
|
||||
ApplyViewContext ctx,
|
||||
SLE::const_ref accSle,
|
||||
XRPAmount accBalance,
|
||||
Adjustment adj,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
/** Return number of the objects which reserve is covered by the account(sle) (so called "owner
|
||||
* count"). Actual owner count can be adjusted by delta in ownerCountAdj.
|
||||
*
|
||||
* @param sle The account's ledger entry
|
||||
* @param j Journal for logging
|
||||
* @param ownerCountAdj Adjustment to the owner count (default: 0)
|
||||
* @return The adjusted owner count
|
||||
*/
|
||||
std::uint32_t
|
||||
ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj = 0);
|
||||
|
||||
/** Increase owner-count fields when the caller supplies the sponsor.
|
||||
*
|
||||
* This helper does not create a ledger object. It updates reserve accounting
|
||||
* after the caller has created/updated an object.
|
||||
* If sponsorSle is provided, this also adjusts the account's sponsored count
|
||||
* and the sponsor's sponsoring count.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param accountSle The account's ledger entry
|
||||
* @param sponsorSle The sponsor's ledger entry (if applicable)
|
||||
* @param count Amount to add to the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
void
|
||||
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j);
|
||||
increaseOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j);
|
||||
|
||||
/** Increase owner-count fields, deriving the tx reserve sponsor internally.
|
||||
*
|
||||
* Equivalent to the overload above, but resolves the sponsor via
|
||||
* getEffectiveTxReserveSponsor(ctx, accountSle) instead of taking it explicitly. Use
|
||||
* this when the sponsor is the transaction's reserve sponsor for accountSle
|
||||
* (the common create path). Deletion paths, which derive the sponsor from an
|
||||
* object's sfSponsor field, should keep using the explicit overload.
|
||||
*
|
||||
* @param ctx The apply-view context (view + tx)
|
||||
* @param accountSle The account's ledger entry
|
||||
* @param count Amount to add to the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
void
|
||||
increaseOwnerCount(
|
||||
ApplyViewContext ctx,
|
||||
SLE::ref accountSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j);
|
||||
|
||||
/** Convenience overload that accepts AccountID instead of SLE references.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param account The account ID
|
||||
* @param sponsor The optional sponsor account ID
|
||||
* @param count Amount to add to the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
inline void
|
||||
increaseOwnerCount(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
std::optional<AccountID> const& sponsor,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
increaseOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(account)),
|
||||
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
|
||||
count,
|
||||
j);
|
||||
}
|
||||
|
||||
/** Decrease owner-count fields when the caller supplies the sponsor.
|
||||
*
|
||||
* This helper does not delete a ledger object. It updates reserve accounting
|
||||
* after the caller has removed an owner-counted reserve, or for special
|
||||
* owner-count changes whose sponsor cannot be derived from an object's
|
||||
* sfSponsor field.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param accountSle The account's ledger entry
|
||||
* @param sponsorSle The sponsor's ledger entry (if applicable)
|
||||
* @param count Amount to remove from the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
void
|
||||
decreaseOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j);
|
||||
|
||||
/** Convenience overload that accepts AccountID instead of SLE references.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param account The account ID
|
||||
* @param sponsor The optional sponsor account ID
|
||||
* @param count Amount to remove from the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
inline void
|
||||
decreaseOwnerCount(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
std::optional<AccountID> const& sponsor,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
decreaseOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(account)),
|
||||
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
|
||||
count,
|
||||
j);
|
||||
}
|
||||
|
||||
/** Decrease owner-count fields for an existing ledger object.
|
||||
*
|
||||
* This helper derives the reserve sponsor from objectSle's sfSponsor field,
|
||||
* then updates the same owner-count fields as decreaseOwnerCount. Use this
|
||||
* when removing an existing object whose reserve sponsor is stored on that
|
||||
* object.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param accountSle The account's ledger entry
|
||||
* @param objectSle The object's ledger entry
|
||||
* @param count Amount to remove from the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
void
|
||||
decreaseOwnerCountForObject(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref objectSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j);
|
||||
|
||||
/** Convenience overload that accepts AccountID instead of account SLE reference.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param account The account ID
|
||||
* @param objectSle The object's ledger entry
|
||||
* @param count Amount to remove from the owner count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
inline void
|
||||
decreaseOwnerCountForObject(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
SLE::ref objectSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
SLE::ref accountSle = view.peek(keylet::account(account));
|
||||
decreaseOwnerCountForObject(view, accountSle, objectSle, count, j);
|
||||
}
|
||||
|
||||
/** Adjust a LoanBroker's owner count.
|
||||
*
|
||||
* A LoanBroker's sfOwnerCount tracks the number of outstanding loans on
|
||||
* that broker; it is not a reserve-backed owner count and is distinct
|
||||
* from the broker's pseudo-account's owner count. Loans can never carry a
|
||||
* reserve sponsor (LoanSet rejects reserve sponsorship at preflight), so
|
||||
* this never involves sponsor accounting and never invokes the
|
||||
* ownerCountHook used for ACCOUNT_ROOT reserve tracking.
|
||||
*
|
||||
* @param view The apply view for making changes
|
||||
* @param brokerSle The LoanBroker's ledger entry
|
||||
* @param delta Amount to add (positive) or remove (negative) from the count
|
||||
* @param j Journal for logging
|
||||
*/
|
||||
void
|
||||
adjustLoanBrokerOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref brokerSle,
|
||||
std::int32_t delta,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -22,17 +23,15 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockApplyHelper(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
Rate lockedRate,
|
||||
SLE::ref sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
XRPAmount xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
@@ -43,10 +42,10 @@ escrowUnlockApplyHelper(
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
Rate lockedRate,
|
||||
SLE::ref sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
XRPAmount xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
@@ -66,16 +65,26 @@ escrowUnlockApplyHelper<Issue>(
|
||||
if (receiverIssuer)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!view.exists(trustLineKey) && createAsset)
|
||||
if (!ctx.view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx,
|
||||
sleDest,
|
||||
xrpBalance,
|
||||
*sponsorSle,
|
||||
{.ownerCountDelta = 1},
|
||||
journal,
|
||||
tecNO_LINE_INSUF_RESERVE);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
JLOG(journal.trace()) << "Trust line does not exist. "
|
||||
"Insufficient reserve to create line.";
|
||||
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Currency const currency = issue.currency;
|
||||
@@ -83,7 +92,7 @@ escrowUnlockApplyHelper<Issue>(
|
||||
initialBalance.get<Issue>().account = noAccount();
|
||||
|
||||
if (TER const ter = trustCreate(
|
||||
view, // payment sandbox
|
||||
ctx.view, // payment sandbox
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
@@ -97,19 +106,20 @@ escrowUnlockApplyHelper<Issue>(
|
||||
Issue(currency, receiver), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
*sponsorSle, // sponsor
|
||||
journal); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
view.update(sleDest);
|
||||
ctx.view.update(sleDest);
|
||||
}
|
||||
|
||||
if (!view.exists(trustLineKey) && !receiverIssuer)
|
||||
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
|
||||
return tecNO_LINE;
|
||||
|
||||
auto const xferRate = transferRate(view, amount);
|
||||
auto const xferRate = transferRate(ctx.view, amount);
|
||||
// update if issuer rate is less than locked rate
|
||||
if (xferRate < lockedRate)
|
||||
lockedRate = xferRate;
|
||||
@@ -137,7 +147,7 @@ escrowUnlockApplyHelper<Issue>(
|
||||
// of the funds
|
||||
if (!createAsset)
|
||||
{
|
||||
auto const sleRippleState = view.peek(trustLineKey);
|
||||
auto const sleRippleState = ctx.view.peek(trustLineKey);
|
||||
if (!sleRippleState)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -163,7 +173,7 @@ escrowUnlockApplyHelper<Issue>(
|
||||
// if destination is not the issuer then transfer funds
|
||||
if (!receiverIssuer)
|
||||
{
|
||||
auto const ter = directSendNoFee(view, issuer, receiver, finalAmt, true, journal);
|
||||
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -173,10 +183,10 @@ escrowUnlockApplyHelper<Issue>(
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
Rate lockedRate,
|
||||
SLE::ref sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
XRPAmount xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
@@ -189,27 +199,32 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
|
||||
auto const mptID = amount.get<MPTIssue>().getMptID();
|
||||
auto const issuanceKey = keylet::mptokenIssuance(mptID);
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
|
||||
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
|
||||
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
|
||||
{
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
|
||||
if (auto const ret = checkReserve(
|
||||
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// update owner count.
|
||||
adjustOwnerCount(view, sleDest, 1, journal);
|
||||
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
|
||||
}
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
|
||||
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const xferRate = transferRate(view, amount);
|
||||
auto const xferRate = transferRate(ctx.view, amount);
|
||||
// update if issuer rate is less than locked rate
|
||||
if (xferRate < lockedRate)
|
||||
lockedRate = xferRate;
|
||||
@@ -232,11 +247,11 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
return unlockEscrowMPT(
|
||||
view,
|
||||
ctx.view,
|
||||
sender,
|
||||
receiver,
|
||||
finalAmt,
|
||||
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
journal);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
@@ -117,7 +117,7 @@ requireAuth(
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
@@ -203,7 +203,7 @@ canMPTTradeAndTransfer(
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
@@ -211,7 +211,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
@@ -243,6 +243,7 @@ createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t const flags);
|
||||
|
||||
TER
|
||||
@@ -250,6 +251,7 @@ checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
constexpr std::uint32_t kMinOracleReserveCount = 1;
|
||||
constexpr std::uint32_t kMaxOracleReserveCount = 2;
|
||||
constexpr std::size_t kOracleReserveCountThreshold = 5;
|
||||
|
||||
template <typename T>
|
||||
requires requires(T const& t) { t.size(); }
|
||||
inline std::uint32_t
|
||||
calculateOracleReserve(T const& priceDataSeries)
|
||||
{
|
||||
return priceDataSeries.size() > kOracleReserveCountThreshold ? kMaxOracleReserveCount
|
||||
: kMinOracleReserveCount;
|
||||
}
|
||||
|
||||
inline std::uint32_t
|
||||
calculateOracleReserve(SLE::const_ref oracleSle)
|
||||
{
|
||||
return calculateOracleReserve(oracleSle->getFieldArray(sfPriceDataSeries));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -156,6 +156,7 @@ trustCreate(
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -178,6 +179,7 @@ issueIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -235,7 +237,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
/// canAddHolding() in preflight with the same View and Asset
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
@@ -243,7 +245,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Whether the given transaction type may use reserve sponsorship (v1).
|
||||
*
|
||||
* Reserve sponsorship is restricted to an explicit allow-list of transaction
|
||||
* types; all others reject spfSponsorReserve at preflight.
|
||||
*/
|
||||
bool
|
||||
isReserveSponsorAllowed(TxType txType);
|
||||
|
||||
/** Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set). */
|
||||
inline bool
|
||||
isFeeSponsored(STTx const& tx)
|
||||
{
|
||||
return tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u);
|
||||
}
|
||||
|
||||
/** Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set). */
|
||||
inline bool
|
||||
isReserveSponsored(STTx const& tx)
|
||||
{
|
||||
return tx.isFieldPresent(sfSponsor) &&
|
||||
((tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u);
|
||||
}
|
||||
|
||||
/** Return the AccountID of the transaction's reserve sponsor, or nullopt if unsponsored. */
|
||||
std::optional<AccountID>
|
||||
getTxReserveSponsorID(STTx const& tx);
|
||||
|
||||
/** Return a mutable SLE for the transaction's reserve sponsor account.
|
||||
*
|
||||
* @param ctx The apply-view context (view + tx)
|
||||
* @return The sponsor account SLE, a null pointer if the tx is not
|
||||
* reserve-sponsored, or tecINTERNAL if the sponsor account cannot
|
||||
* be loaded (an already-checked invariant).
|
||||
*/
|
||||
std::expected<SLE::pointer, TER>
|
||||
getTxReserveSponsor(ApplyViewContext ctx);
|
||||
|
||||
/** Return a read-only SLE for the transaction's reserve sponsor account.
|
||||
*
|
||||
* @param view The ledger read view
|
||||
* @param tx The transaction to inspect
|
||||
* @return The sponsor account SLE, a null pointer if the tx is not
|
||||
* reserve-sponsored, or tecINTERNAL if the sponsor account cannot
|
||||
* be loaded (an already-checked invariant).
|
||||
*/
|
||||
std::expected<SLE::const_pointer, TER>
|
||||
getTxReserveSponsor(ReadView const& view, STTx const& tx);
|
||||
|
||||
/** The transaction's reserve sponsor for the given account, if applicable.
|
||||
*
|
||||
* A reserve sponsor only covers the transaction submitter's own objects, so
|
||||
* this returns the tx reserve sponsor SLE only when accountSle is the tx's own
|
||||
* (non-pseudo) account; otherwise it returns a null sponsor pointer. This is
|
||||
* the single source of truth for the "sponsor applies to tx.Account only" rule
|
||||
* that the sponsor-deriving helper overloads in AccountRootHelpers rely on.
|
||||
*
|
||||
* @param ctx The apply-view context (view + tx)
|
||||
* @param accountSle The account whose sponsor is being resolved
|
||||
* @return The sponsor SLE (nullptr if unsponsored), or tecINTERNAL if the
|
||||
* sponsor account cannot be loaded (an already-checked invariant)
|
||||
*/
|
||||
[[nodiscard]] std::expected<SLE::pointer, TER>
|
||||
getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle);
|
||||
|
||||
/** Return the AccountID stored in the given sponsor field of a ledger entry, or nullopt if absent.
|
||||
*/
|
||||
std::optional<AccountID>
|
||||
getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
/** Return a mutable SLE for the reserve sponsor recorded on a ledger entry.
|
||||
*
|
||||
* Reads the sponsor AccountID from @p field on @p sle and peeks the
|
||||
* corresponding account root in @p view.
|
||||
*
|
||||
* @param view The mutable apply view
|
||||
* @param sle The ledger entry whose sponsor field is inspected
|
||||
* @param field The field that holds the sponsor AccountID (defaults to sfSponsor)
|
||||
* @return The sponsor account SLE, or a null pointer if the entry is unsponsored.
|
||||
*/
|
||||
SLE::pointer
|
||||
getLedgerEntryReserveSponsor(
|
||||
ApplyView& view,
|
||||
SLE::const_ref sle,
|
||||
SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
/** Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
|
||||
*
|
||||
* Sets @p field on @p sle to the AccountID from @p sponsorSle. A no-op when
|
||||
* @p sponsorSle is null (unsponsored). For RippleState entries the field must
|
||||
* be sfHighSponsor or sfLowSponsor; for all other entry types it must be
|
||||
* sfSponsor.
|
||||
*
|
||||
* @param sle The ledger entry to stamp
|
||||
* @param sponsorSle The sponsor's account root SLE (null → no-op)
|
||||
* @param field The sponsor field to set (defaults to sfSponsor)
|
||||
*/
|
||||
void
|
||||
addSponsorToLedgerEntry(
|
||||
SLE::ref sle,
|
||||
SLE::const_ref sponsorSle,
|
||||
SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
/** Stamp the transaction's reserve sponsor onto a newly-created ledger entry.
|
||||
*
|
||||
* Equivalent to the overload above, but resolves the sponsor via
|
||||
* getTxReserveSponsor(ctx) instead of taking it explicitly. A no-op when the
|
||||
* transaction is not reserve-sponsored. The entry is assumed to be owned by
|
||||
* the transaction submitter, which is the only account a tx reserve sponsor
|
||||
* can cover.
|
||||
*/
|
||||
void
|
||||
addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
/** Remove the reserve sponsor field from a ledger entry.
|
||||
*
|
||||
* A no-op when @p field is not present on @p sle. For RippleState entries
|
||||
* the field must be sfHighSponsor or sfLowSponsor; for all other entry types
|
||||
* it must be sfSponsor.
|
||||
*
|
||||
* @param sle The ledger entry to modify
|
||||
* @param field The sponsor field to clear (defaults to sfSponsor)
|
||||
*/
|
||||
void
|
||||
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
/** Whether @p account is the owner of a ledger entry for sponsorship purposes.
|
||||
*
|
||||
* Ownership rules vary by entry type. For RippleState entries the owner is
|
||||
* whichever side of the trust line holds the reserve. For credentials, the
|
||||
* owner is the subject once accepted and the issuer before acceptance.
|
||||
*
|
||||
* @param view The ledger read view (used for SignerList lookup)
|
||||
* @param sle The ledger entry whose owner is checked
|
||||
* @param account The candidate account to match against
|
||||
* @return true if @p account owns @p sle, false otherwise.
|
||||
*/
|
||||
bool
|
||||
isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account);
|
||||
|
||||
/** Whether this ledger entry type can have a reserve sponsor attached to it. */
|
||||
bool
|
||||
isLedgerEntrySupportedBySponsorship(SLE const& sle);
|
||||
|
||||
/** Return the number of owner-count units the ledger entry consumes.
|
||||
*
|
||||
* Most entries cost 1. Exceptions: Oracles scale with their price-data series
|
||||
* size, Vaults cost 2 (vault + pseudo-account), and legacy SignerList entries
|
||||
* (pre-MultiSignReserve) cost 2 + signer count.
|
||||
*/
|
||||
std::uint32_t
|
||||
getLedgerEntryOwnerCount(SLE const& sle);
|
||||
|
||||
/** Return the SField used to store the reserve sponsor for @p owner on @p sle.
|
||||
*
|
||||
* For most entry types this is sfSponsor. RippleState entries use
|
||||
* sfHighSponsor or sfLowSponsor depending on which side of the trust line
|
||||
* @p owner holds.
|
||||
*
|
||||
* @param sle The ledger entry
|
||||
* @param owner The account whose sponsor field is needed
|
||||
* @return sfHighSponsor, sfLowSponsor, or sfSponsor as appropriate.
|
||||
*/
|
||||
SF_ACCOUNT const&
|
||||
getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
@@ -302,7 +303,7 @@ canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
@@ -310,7 +311,7 @@ addEmptyHolding(
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
@@ -371,6 +372,7 @@ accountSend(
|
||||
AccountID const& to,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle = {},
|
||||
WaiveTransferFee waiveFee = WaiveTransferFee::No,
|
||||
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
|
||||
|
||||
|
||||
@@ -74,9 +74,9 @@ operator==(Book const& lhs, Book const& rhs)
|
||||
[[nodiscard]] constexpr std::weak_ordering
|
||||
operator<=>(Book const& lhs, Book const& rhs)
|
||||
{
|
||||
if (auto const c{lhs.in <=> rhs.in}; c != 0)
|
||||
if (auto const c{lhs.in <=> rhs.in}; c != 0) // NOLINT(modernize-use-nullptr)
|
||||
return c;
|
||||
if (auto const c{lhs.out <=> rhs.out}; c != 0)
|
||||
if (auto const c{lhs.out <=> rhs.out}; c != 0) // NOLINT(modernize-use-nullptr)
|
||||
return c;
|
||||
|
||||
// Manually compare optionals
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -39,13 +38,13 @@ struct Fees
|
||||
|
||||
/** Returns the account reserve given the owner count, in drops.
|
||||
|
||||
The reserve is calculated as the reserve base plus
|
||||
the reserve increment times the number of increments.
|
||||
The reserve is calculated as the reserve base times the number of accounts plus the reserve
|
||||
increment times the number of increments.
|
||||
*/
|
||||
[[nodiscard]] XRPAmount
|
||||
accountReserve(std::size_t ownerCount) const
|
||||
accountReserve(std::uint32_t ownerCount, std::uint32_t accountCount) const
|
||||
{
|
||||
return reserve + ownerCount * increment;
|
||||
return (reserve * accountCount) + (increment * ownerCount);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -142,6 +142,10 @@ ticket(uint256 const& key)
|
||||
Keylet
|
||||
signerList(AccountID const& account) noexcept;
|
||||
|
||||
/** A Sponsorship */
|
||||
Keylet
|
||||
sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept;
|
||||
|
||||
/** A Check */
|
||||
/** @{ */
|
||||
Keylet
|
||||
|
||||
@@ -84,7 +84,7 @@ operator==(Issue const& lhs, Issue const& rhs)
|
||||
[[nodiscard]] constexpr std::weak_ordering
|
||||
operator<=>(Issue const& lhs, Issue const& rhs)
|
||||
{
|
||||
if (auto const c{lhs.currency <=> rhs.currency}; c != 0)
|
||||
if (auto const c{lhs.currency <=> rhs.currency}; c != 0) // NOLINT(modernize-use-nullptr)
|
||||
return c;
|
||||
|
||||
if (isXRP(lhs.currency))
|
||||
|
||||
@@ -208,7 +208,11 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LEDGER_OBJECT(Loan, \
|
||||
LSF_FLAG(lsfLoanDefault, 0x00010000) \
|
||||
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
|
||||
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
|
||||
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
|
||||
\
|
||||
LEDGER_OBJECT(Sponsorship, \
|
||||
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
|
||||
LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000))
|
||||
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -229,10 +229,10 @@ public:
|
||||
[[nodiscard]] AccountID
|
||||
getAccountID(SField const& field) const;
|
||||
|
||||
/** The account responsible for the fee and authorization: the delegate when
|
||||
/** The account responsible for the authorization: the delegate when
|
||||
sfDelegate is present, otherwise the account. */
|
||||
[[nodiscard]] AccountID
|
||||
getFeePayer() const;
|
||||
getInitiator() const;
|
||||
|
||||
[[nodiscard]] Blob
|
||||
getFieldVL(SField const& field) const;
|
||||
|
||||
@@ -141,6 +141,9 @@ public:
|
||||
[[nodiscard]] std::vector<uint256> const&
|
||||
getBatchTransactionIDs() const;
|
||||
|
||||
[[nodiscard]] AccountID
|
||||
getFeePayerID() const;
|
||||
|
||||
private:
|
||||
/** Check the signature.
|
||||
@param rules The current ledger rules.
|
||||
|
||||
@@ -225,6 +225,7 @@ enum TERcodes : TERUnderlyingType {
|
||||
// create a pseudo-account
|
||||
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
|
||||
terLOCKED, // MPT is locked
|
||||
terNO_PERMISSION, // No permission but retry
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -368,6 +369,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
// reclaimed after those networks reset.
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecBAD_PROOF = 199,
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -102,7 +102,8 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TRANSACTION(Payment, \
|
||||
TF_FLAG(tfNoRippleDirect, 0x00010000) \
|
||||
TF_FLAG(tfPartialPayment, 0x00020000) \
|
||||
TF_FLAG(tfLimitQuality, 0x00040000), \
|
||||
TF_FLAG(tfLimitQuality, 0x00040000) \
|
||||
TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(TrustSet, \
|
||||
@@ -141,7 +142,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
|
||||
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
|
||||
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \
|
||||
TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \
|
||||
TF_FLAG(tfMPTCanHoldConfidentialBalance, lsfMPTCanHoldConfidentialBalance), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(MPTokenAuthorize, \
|
||||
@@ -215,6 +216,20 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfLoanDefault, 0x00010000) \
|
||||
TF_FLAG(tfLoanImpair, 0x00020000) \
|
||||
TF_FLAG(tfLoanUnimpair, 0x00040000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(SponsorshipSet, \
|
||||
TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \
|
||||
TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \
|
||||
TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \
|
||||
TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \
|
||||
TF_FLAG(tfDeleteObject, 0x00100000), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(SponsorshipTransfer, \
|
||||
TF_FLAG(tfSponsorshipEnd, 0x00010000) \
|
||||
TF_FLAG(tfSponsorshipCreate, 0x00020000) \
|
||||
TF_FLAG(tfSponsorshipReassign, 0x00040000), \
|
||||
MASK_ADJ(0))
|
||||
|
||||
// clang-format on
|
||||
@@ -444,6 +459,12 @@ getAsfFlagMap()
|
||||
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
|
||||
#pragma pop_macro("ACCOUNTSET_FLAGS")
|
||||
|
||||
// Sponsor flags (spf)
|
||||
|
||||
inline constexpr FlagValue spfSponsorFee = 1;
|
||||
inline constexpr FlagValue spfSponsorReserve = 2;
|
||||
inline constexpr FlagValue spfSponsorFlagMask = ~(spfSponsorFee | spfSponsorReserve);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConfidentialTransfer, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -127,29 +127,32 @@ LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
|
||||
\sa keylet::account
|
||||
*/
|
||||
LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfAccount, SoeRequired},
|
||||
{sfSequence, SoeRequired},
|
||||
{sfBalance, SoeRequired},
|
||||
{sfOwnerCount, SoeRequired},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfAccountTxnID, SoeOptional},
|
||||
{sfRegularKey, SoeOptional},
|
||||
{sfEmailHash, SoeOptional},
|
||||
{sfWalletLocator, SoeOptional},
|
||||
{sfWalletSize, SoeOptional},
|
||||
{sfMessageKey, SoeOptional},
|
||||
{sfTransferRate, SoeOptional},
|
||||
{sfDomain, SoeOptional},
|
||||
{sfTickSize, SoeOptional},
|
||||
{sfTicketCount, SoeOptional},
|
||||
{sfNFTokenMinter, SoeOptional},
|
||||
{sfMintedNFTokens, SoeDefault},
|
||||
{sfBurnedNFTokens, SoeDefault},
|
||||
{sfFirstNFTokenSequence, SoeOptional},
|
||||
{sfAMMID, SoeOptional}, // pseudo-account designator
|
||||
{sfVaultID, SoeOptional}, // pseudo-account designator
|
||||
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
|
||||
{sfAccount, SoeRequired},
|
||||
{sfSequence, SoeRequired},
|
||||
{sfBalance, SoeRequired},
|
||||
{sfOwnerCount, SoeRequired},
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfAccountTxnID, SoeOptional},
|
||||
{sfRegularKey, SoeOptional},
|
||||
{sfEmailHash, SoeOptional},
|
||||
{sfWalletLocator, SoeOptional},
|
||||
{sfWalletSize, SoeOptional},
|
||||
{sfMessageKey, SoeOptional},
|
||||
{sfTransferRate, SoeOptional},
|
||||
{sfDomain, SoeOptional},
|
||||
{sfTickSize, SoeOptional},
|
||||
{sfTicketCount, SoeOptional},
|
||||
{sfNFTokenMinter, SoeOptional},
|
||||
{sfMintedNFTokens, SoeDefault},
|
||||
{sfBurnedNFTokens, SoeDefault},
|
||||
{sfFirstNFTokenSequence, SoeOptional},
|
||||
{sfSponsoredOwnerCount, SoeDefault},
|
||||
{sfSponsoringOwnerCount, SoeDefault},
|
||||
{sfSponsoringAccountCount, SoeDefault},
|
||||
{sfAMMID, SoeOptional}, // pseudo-account designator
|
||||
{sfVaultID, SoeOptional}, // pseudo-account designator
|
||||
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a list of object identifiers.
|
||||
@@ -286,6 +289,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
|
||||
{sfHighNode, SoeOptional},
|
||||
{sfHighQualityIn, SoeOptional},
|
||||
{sfHighQualityOut, SoeOptional},
|
||||
{sfHighSponsor, SoeOptional},
|
||||
{sfLowSponsor, SoeOptional},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists the network's fee settings.
|
||||
@@ -616,5 +621,20 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
|
||||
{sfLoanScale, SoeDefault},
|
||||
}))
|
||||
|
||||
/** A ledger object representing a sponsorship.
|
||||
\sa keylet::sponsorship
|
||||
*/
|
||||
LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfOwner, SoeRequired},
|
||||
{sfSponsee, SoeRequired},
|
||||
{sfFeeAmount, SoeOptional},
|
||||
{sfMaxFee, SoeOptional},
|
||||
{sfRemainingOwnerCount, SoeDefault},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
{sfSponseeNode, SoeRequired},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
@@ -114,6 +114,11 @@ TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bi
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
|
||||
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 70)
|
||||
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 71)
|
||||
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 72)
|
||||
TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -148,6 +153,7 @@ TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SFie
|
||||
TYPED_SFIELD(sfVaultNode, UINT64, 30)
|
||||
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
|
||||
TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::kSmdBaseTen|SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfSponseeNode, UINT64, 33)
|
||||
|
||||
// 128-bit
|
||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||
@@ -209,6 +215,7 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
|
||||
TYPED_SFIELD(sfLoanID, UINT256, 38)
|
||||
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
|
||||
TYPED_SFIELD(sfBlindingFactor, UINT256, 40)
|
||||
TYPED_SFIELD(sfObjectID, UINT256, 41)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -268,6 +275,8 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
|
||||
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
@@ -343,6 +352,11 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
|
||||
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
|
||||
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
|
||||
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
|
||||
TYPED_SFIELD(sfSponsor, ACCOUNT, 27)
|
||||
TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
|
||||
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
|
||||
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
|
||||
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
|
||||
|
||||
// vector of 256-bit
|
||||
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever)
|
||||
@@ -407,6 +421,7 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
|
||||
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
|
||||
UNTYPED_SFIELD(sfBook, OBJECT, 36)
|
||||
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning)
|
||||
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning)
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -1165,6 +1165,35 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
|
||||
{sfZKProof, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This transaction transfers sponsorship on an object/account. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
|
||||
#endif
|
||||
TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer,
|
||||
Delegation::NotDelegable,
|
||||
featureSponsor,
|
||||
NoPriv,
|
||||
({
|
||||
{sfObjectID, SoeOptional},
|
||||
{sfSponsee, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction creates a Sponsorship object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
|
||||
Delegation::Delegable,
|
||||
featureSponsor,
|
||||
NoPriv,
|
||||
({
|
||||
{sfCounterpartySponsor, SoeOptional},
|
||||
{sfSponsee, SoeOptional},
|
||||
{sfFeeAmount, SoeOptional},
|
||||
{sfMaxFee, SoeOptional},
|
||||
{sfRemainingOwnerCount, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
|
||||
@@ -558,6 +558,9 @@ JSS(source_account); // in: PathRequest, RipplePathFind
|
||||
JSS(source_amount); // in: PathRequest, RipplePathFind
|
||||
JSS(source_currencies); // in: PathRequest, RipplePathFind
|
||||
JSS(source_tag); // out: AccountChannels
|
||||
JSS(sponsee); // in: LedgerEntry
|
||||
JSS(sponsor); // in: LedgerEntry
|
||||
JSS(sponsored); // in: AccountObjects
|
||||
JSS(stand_alone); // out: NetworkOPs
|
||||
JSS(standard_deviation); // out: get_aggregate_price
|
||||
JSS(start); // in: TxHistory
|
||||
|
||||
@@ -447,6 +447,78 @@ public:
|
||||
return this->sle_->isFieldPresent(sfFirstNFTokenSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsoredOwnerCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getSponsoredOwnerCount() const
|
||||
{
|
||||
if (hasSponsoredOwnerCount())
|
||||
return this->sle_->at(sfSponsoredOwnerCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSponsoredOwnerCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSponsoredOwnerCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfSponsoredOwnerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsoringOwnerCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getSponsoringOwnerCount() const
|
||||
{
|
||||
if (hasSponsoringOwnerCount())
|
||||
return this->sle_->at(sfSponsoringOwnerCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSponsoringOwnerCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSponsoringOwnerCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfSponsoringOwnerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsoringAccountCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getSponsoringAccountCount() const
|
||||
{
|
||||
if (hasSponsoringAccountCount())
|
||||
return this->sle_->at(sfSponsoringAccountCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSponsoringAccountCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSponsoringAccountCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfSponsoringAccountCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAMMID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -786,6 +858,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoredOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoredOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoringOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoringOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsoringAccountCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
AccountRootBuilder&
|
||||
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsoringAccountCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAMMID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -243,6 +243,54 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfHighQualityOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHighSponsor (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getHighSponsor() const
|
||||
{
|
||||
if (hasHighSponsor())
|
||||
return this->sle_->at(sfHighSponsor);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfHighSponsor is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasHighSponsor() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfHighSponsor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfLowSponsor (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getLowSponsor() const
|
||||
{
|
||||
if (hasLowSponsor())
|
||||
return this->sle_->at(sfLowSponsor);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfLowSponsor is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasLowSponsor() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfLowSponsor);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -410,6 +458,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHighSponsor (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RippleStateBuilder&
|
||||
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHighSponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfLowSponsor (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
RippleStateBuilder&
|
||||
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfLowSponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed RippleState wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
|
||||
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::ledger_entries {
|
||||
|
||||
class SponsorshipBuilder;
|
||||
|
||||
/**
|
||||
* @brief Ledger Entry: Sponsorship
|
||||
*
|
||||
* Type: ltSPONSORSHIP (0x0090)
|
||||
* RPC Name: sponsorship
|
||||
*
|
||||
* Immutable wrapper around SLE providing type-safe field access.
|
||||
* Use SponsorshipBuilder to construct new ledger entries.
|
||||
*/
|
||||
class Sponsorship : public LedgerEntryBase
|
||||
{
|
||||
public:
|
||||
static constexpr LedgerEntryType entryType = ltSPONSORSHIP;
|
||||
|
||||
/**
|
||||
* @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
explicit Sponsorship(SLE::const_pointer sle)
|
||||
: LedgerEntryBase(std::move(sle))
|
||||
{
|
||||
// Verify ledger entry type
|
||||
if (sle_->getType() != entryType)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
|
||||
}
|
||||
}
|
||||
|
||||
// Ledger entry-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT256::type::value_type
|
||||
getPreviousTxnID() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT32::type::value_type
|
||||
getPreviousTxnLgrSeq() const
|
||||
{
|
||||
return this->sle_->at(sfPreviousTxnLgrSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwner (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getOwner() const
|
||||
{
|
||||
return this->sle_->at(sfOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsee (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_ACCOUNT::type::value_type
|
||||
getSponsee() const
|
||||
{
|
||||
return this->sle_->at(sfSponsee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFeeAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getFeeAmount() const
|
||||
{
|
||||
if (hasFeeAmount())
|
||||
return this->sle_->at(sfFeeAmount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFeeAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFeeAmount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfFeeAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMaxFee (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getMaxFee() const
|
||||
{
|
||||
if (hasMaxFee())
|
||||
return this->sle_->at(sfMaxFee);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMaxFee is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMaxFee() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfMaxFee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRemainingOwnerCount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getRemainingOwnerCount() const
|
||||
{
|
||||
if (hasRemainingOwnerCount())
|
||||
return this->sle_->at(sfRemainingOwnerCount);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRemainingOwnerCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRemainingOwnerCount() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfRemainingOwnerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfOwnerNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getOwnerNode() const
|
||||
{
|
||||
return this->sle_->at(sfOwnerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponseeNode (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT64::type::value_type
|
||||
getSponseeNode() const
|
||||
{
|
||||
return this->sle_->at(sfSponseeNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for Sponsorship ledger entries.
|
||||
*
|
||||
* Provides a fluent interface for constructing ledger entries with method chaining.
|
||||
* Uses STObject internally for flexible ledger entry construction.
|
||||
* Inherits common field setters from LedgerEntryBuilderBase.
|
||||
*/
|
||||
class SponsorshipBuilder : public LedgerEntryBuilderBase<SponsorshipBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new SponsorshipBuilder with required fields.
|
||||
* @param previousTxnID The sfPreviousTxnID field value.
|
||||
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
|
||||
* @param owner The sfOwner field value.
|
||||
* @param sponsee The sfSponsee field value.
|
||||
* @param ownerNode The sfOwnerNode field value.
|
||||
* @param sponseeNode The sfSponseeNode field value.
|
||||
*/
|
||||
SponsorshipBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
|
||||
: LedgerEntryBuilderBase<SponsorshipBuilder>(ltSPONSORSHIP)
|
||||
{
|
||||
setPreviousTxnID(previousTxnID);
|
||||
setPreviousTxnLgrSeq(previousTxnLgrSeq);
|
||||
setOwner(owner);
|
||||
setSponsee(sponsee);
|
||||
setOwnerNode(ownerNode);
|
||||
setSponseeNode(sponseeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a SponsorshipBuilder from an existing SLE object.
|
||||
* @param sle The existing ledger entry to copy from.
|
||||
* @throws std::runtime_error if the ledger entry type doesn't match.
|
||||
*/
|
||||
SponsorshipBuilder(SLE::const_pointer sle)
|
||||
{
|
||||
if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP)
|
||||
{
|
||||
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
|
||||
}
|
||||
object_ = *sle;
|
||||
}
|
||||
|
||||
/** @brief Ledger entry-specific field setters */
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfPreviousTxnLgrSeq] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwner (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwner] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaxFee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaxFee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRemainingOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfOwnerNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfOwnerNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponseeNode (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipBuilder&
|
||||
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponseeNode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed Sponsorship wrapper.
|
||||
* @param index The ledger entry index.
|
||||
* @return The constructed ledger entry wrapper.
|
||||
*/
|
||||
Sponsorship
|
||||
build(uint256 const& index)
|
||||
{
|
||||
return Sponsorship{std::make_shared<SLE>(std::move(object_), index)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::ledger_entries
|
||||
@@ -0,0 +1,290 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class SponsorshipSetBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: SponsorshipSet
|
||||
*
|
||||
* Type: ttSPONSORSHIP_SET (91)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureSponsor
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SponsorshipSetBuilder to construct new transactions.
|
||||
*/
|
||||
class SponsorshipSet : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET;
|
||||
|
||||
/**
|
||||
* @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit SponsorshipSet(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for SponsorshipSet");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfCounterpartySponsor (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getCounterpartySponsor() const
|
||||
{
|
||||
if (hasCounterpartySponsor())
|
||||
{
|
||||
return this->tx_->at(sfCounterpartySponsor);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfCounterpartySponsor is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasCounterpartySponsor() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCounterpartySponsor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsee (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getSponsee() const
|
||||
{
|
||||
if (hasSponsee())
|
||||
{
|
||||
return this->tx_->at(sfSponsee);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSponsee is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSponsee() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfSponsee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfFeeAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getFeeAmount() const
|
||||
{
|
||||
if (hasFeeAmount())
|
||||
{
|
||||
return this->tx_->at(sfFeeAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfFeeAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasFeeAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfFeeAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMaxFee (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
|
||||
getMaxFee() const
|
||||
{
|
||||
if (hasMaxFee())
|
||||
{
|
||||
return this->tx_->at(sfMaxFee);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMaxFee is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMaxFee() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMaxFee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfRemainingOwnerCount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getRemainingOwnerCount() const
|
||||
{
|
||||
if (hasRemainingOwnerCount())
|
||||
{
|
||||
return this->tx_->at(sfRemainingOwnerCount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfRemainingOwnerCount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasRemainingOwnerCount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfRemainingOwnerCount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for SponsorshipSet transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class SponsorshipSetBuilder : public TransactionBuilderBase<SponsorshipSetBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new SponsorshipSetBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<SponsorshipSetBuilder>(ttSPONSORSHIP_SET, account, sequence, fee)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a SponsorshipSetBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
SponsorshipSetBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttSPONSORSHIP_SET)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/** @brief Transaction-specific field setters */
|
||||
|
||||
/**
|
||||
* @brief Set sfCounterpartySponsor (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setCounterpartySponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfCounterpartySponsor] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfFeeAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfFeeAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMaxFee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMaxFee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfRemainingOwnerCount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipSetBuilder&
|
||||
setRemainingOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfRemainingOwnerCount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SponsorshipSet wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
SponsorshipSet
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return SponsorshipSet{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -0,0 +1,179 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class SponsorshipTransferBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: SponsorshipTransfer
|
||||
*
|
||||
* Type: ttSPONSORSHIP_TRANSFER (90)
|
||||
* Delegable: Delegation::NotDelegable
|
||||
* Amendment: featureSponsor
|
||||
* Privileges: NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use SponsorshipTransferBuilder to construct new transactions.
|
||||
*/
|
||||
class SponsorshipTransfer : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER;
|
||||
|
||||
/**
|
||||
* @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit SponsorshipTransfer(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for SponsorshipTransfer");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfObjectID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT256::type::value_type>
|
||||
getObjectID() const
|
||||
{
|
||||
if (hasObjectID())
|
||||
{
|
||||
return this->tx_->at(sfObjectID);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfObjectID is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasObjectID() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfObjectID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSponsee (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getSponsee() const
|
||||
{
|
||||
if (hasSponsee())
|
||||
{
|
||||
return this->tx_->at(sfSponsee);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfSponsee is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasSponsee() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfSponsee);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for SponsorshipTransfer transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class SponsorshipTransferBuilder : public TransactionBuilderBase<SponsorshipTransferBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new SponsorshipTransferBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<SponsorshipTransferBuilder>(ttSPONSORSHIP_TRANSFER, account, sequence, fee)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a SponsorshipTransferBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
SponsorshipTransferBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/** @brief Transaction-specific field setters */
|
||||
|
||||
/**
|
||||
* @brief Set sfObjectID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipTransferBuilder&
|
||||
setObjectID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfObjectID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSponsee (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SponsorshipTransferBuilder&
|
||||
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfSponsee] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SponsorshipTransfer wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
SponsorshipTransfer
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return SponsorshipTransfer{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -127,6 +127,15 @@ public:
|
||||
TER
|
||||
checkInvariants(TER const result, XRPAmount const fee);
|
||||
|
||||
ApplyViewContext
|
||||
getApplyViewContext()
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
view_.has_value(),
|
||||
"xrpl::ApplyContext::getApplyViewContext : view_ emplaced in constructor");
|
||||
return {.view = *view_, .tx = tx};
|
||||
}
|
||||
|
||||
private:
|
||||
static TER
|
||||
failInvariantCheck(TER const result);
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
@@ -126,6 +128,21 @@ struct PreflightResult;
|
||||
// Needed for preflight specialization
|
||||
class Change;
|
||||
|
||||
enum class FeePayerType {
|
||||
Account,
|
||||
Delegate,
|
||||
SponsorCoSigned,
|
||||
SponsorPreFunded,
|
||||
};
|
||||
|
||||
struct FeePayer
|
||||
{
|
||||
AccountID id;
|
||||
Keylet keylet;
|
||||
SF_AMOUNT const& balanceField;
|
||||
FeePayerType type{FeePayerType::Account};
|
||||
};
|
||||
|
||||
class Transactor
|
||||
{
|
||||
protected:
|
||||
@@ -298,6 +315,10 @@ public:
|
||||
|
||||
return T::checkGranularSemantics(view, tx, heldGranularPermissions);
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
checkSponsor(ReadView const& view, STTx const& tx);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// Interface used by AccountDelete
|
||||
@@ -459,6 +480,9 @@ private:
|
||||
std::pair<TER, XRPAmount>
|
||||
reset(XRPAmount fee);
|
||||
|
||||
static FeePayer
|
||||
getFeePayer(ReadView const& view, STTx const& tx);
|
||||
|
||||
TER
|
||||
consumeSeqProxy(SLE::pointer const& sleAccount);
|
||||
TER
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <xrpl/tx/invariants/NFTInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
|
||||
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
|
||||
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
|
||||
#include <xrpl/tx/invariants/VaultInvariant.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -441,7 +442,9 @@ using InvariantChecks = std::tuple<
|
||||
ValidMPTPayment,
|
||||
ValidAmounts,
|
||||
ValidMPTTransfer,
|
||||
ObjectHasPseudoAccount>;
|
||||
ObjectHasPseudoAccount,
|
||||
SponsorshipOwnerCountsMatch,
|
||||
SponsorshipAccountCountMatchesField>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Invariant: Sponsored owner counts are balanced.
|
||||
*
|
||||
* The following checks are made for every transaction:
|
||||
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
|
||||
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
|
||||
* - Account OwnerCount must be greater than or equal to SponsoredOwnerCount.
|
||||
* - The net delta of sponsored object owner counts (the owner-count
|
||||
* magnitude of sponsored ledger entries) equals the net delta of
|
||||
* `sfSponsoredOwnerCount`.
|
||||
*/
|
||||
class SponsorshipOwnerCountsMatch
|
||||
{
|
||||
std::int64_t deltaSponsoredOwnerCount_ = 0;
|
||||
std::int64_t deltaSponsoringOwnerCount_ = 0;
|
||||
std::int64_t deltaSponsoredObjectOwnerCount_ = 0;
|
||||
std::uint64_t ownerCountBelowSponsored_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Sponsoring account relationships tracked consistently.
|
||||
*
|
||||
* The following check is made for every transaction:
|
||||
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
|
||||
* the net delta of the count of ltACCOUNT_ROOT entries having
|
||||
* `sfSponsor` present (presence transitions only: add/remove).
|
||||
*/
|
||||
class SponsorshipAccountCountMatchesField
|
||||
{
|
||||
std::int64_t deltaSponsoringAccountCount_ = 0;
|
||||
std::int64_t deltaSponsorFieldPresence_ = 0;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -110,7 +111,10 @@ public:
|
||||
send(Args&&... args)
|
||||
{
|
||||
return accountSend(
|
||||
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
|
||||
std::forward<Args>(args)...,
|
||||
SLE::pointer(),
|
||||
WaiveTransferFee::Yes,
|
||||
AllowMPTOverflow::Yes);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
|
||||
@@ -234,7 +234,8 @@ template <typename... Args>
|
||||
TER
|
||||
TOffer<TIn, TOut>::send(Args&&... args)
|
||||
{
|
||||
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
|
||||
return accountSend(
|
||||
std::forward<Args>(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes);
|
||||
}
|
||||
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class SponsorshipSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class SponsorshipTransfer : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
beast::Journal const& j) override;
|
||||
|
||||
static std::expected<MPTID, TER>
|
||||
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
|
||||
create(ApplyViewContext ctx, beast::Journal journal, MPTCreateArgs const& args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
@@ -170,7 +171,7 @@ DeferredCredits::issuerSelfDebitMPT(
|
||||
}
|
||||
|
||||
void
|
||||
DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next)
|
||||
DeferredCredits::ownerCount(AccountID const& id, OwnerCounts const& cur, OwnerCounts const& next)
|
||||
{
|
||||
auto const v = std::max(cur, next);
|
||||
auto r = ownerCounts_.emplace(id, v);
|
||||
@@ -181,7 +182,7 @@ DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
std::optional<OwnerCounts>
|
||||
DeferredCredits::ownerCount(AccountID const& id) const
|
||||
{
|
||||
auto i = ownerCounts_.find(id);
|
||||
@@ -391,10 +392,10 @@ PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_
|
||||
return STAmount{issue};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const
|
||||
OwnerCounts
|
||||
PaymentSandbox::ownerCountHook(AccountID const& account, OwnerCounts const& count) const
|
||||
{
|
||||
std::uint32_t result = count;
|
||||
OwnerCounts result = count;
|
||||
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
|
||||
{
|
||||
if (auto adj = curSB->tab_.ownerCount(account))
|
||||
@@ -442,8 +443,8 @@ PaymentSandbox::issuerSelfDebitHookMPT(
|
||||
void
|
||||
PaymentSandbox::adjustOwnerCountHook(
|
||||
AccountID const& account,
|
||||
std::uint32_t cur,
|
||||
std::uint32_t next)
|
||||
OwnerCounts const& cur,
|
||||
OwnerCounts const& next)
|
||||
{
|
||||
tab_.ownerCount(account, cur, next);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -431,8 +432,7 @@ canWithdraw(ReadView const& view, STTx const& tx)
|
||||
|
||||
TER
|
||||
doWithdraw(
|
||||
ApplyView& view,
|
||||
STTx const& tx,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& senderAcct,
|
||||
AccountID const& dstAcct,
|
||||
AccountID const& sourceAcct,
|
||||
@@ -440,23 +440,24 @@ doWithdraw(
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const dstSle = ctx.view.read(keylet::account(dstAcct));
|
||||
|
||||
// Create trust line or MPToken for the receiving account
|
||||
if (dstAcct == senderAcct)
|
||||
{
|
||||
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
|
||||
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
|
||||
!isTesSuccess(ter) && ter != tecDUPLICATE)
|
||||
return ter;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dstSle = view.read(keylet::account(dstAcct));
|
||||
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
|
||||
if (auto err = verifyDepositPreauth(ctx.tx, ctx.view, senderAcct, dstAcct, dstSle, j))
|
||||
return err;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view,
|
||||
ctx.view,
|
||||
sourceAcct,
|
||||
amount.asset(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
@@ -469,9 +470,18 @@ doWithdraw(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// A reserve sponsor only covers tx.Account's own objects, so resolve the
|
||||
// sponsor against the destination. accountSend can auto-create a holding
|
||||
// for dstAcct; keying on the destination ensures a third-party destination's
|
||||
// holding is never stamped with the tx's reserve sponsor.
|
||||
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, dstSle);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
// Move the funds directly from the broker's pseudo-account to the
|
||||
// dstAcct
|
||||
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
|
||||
return accountSend(
|
||||
ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -15,6 +17,7 @@
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
@@ -41,46 +44,226 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer)
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
// owner count, clamp the owner count at 0. Similarly for overflow. This
|
||||
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
|
||||
// If id != std::nullopt, then do error reporting.
|
||||
//
|
||||
// Returns adjusted owner count.
|
||||
static std::uint32_t
|
||||
std::uint32_t
|
||||
confineOwnerCount(
|
||||
std::uint32_t current,
|
||||
std::int32_t adjustment,
|
||||
std::uint32_t currentOwnerCount,
|
||||
std::int32_t ownerCountAdj,
|
||||
std::optional<AccountID> const& id = std::nullopt,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
{
|
||||
std::uint32_t adjusted{current + adjustment};
|
||||
if (adjustment > 0)
|
||||
std::uint32_t totalOwnerCount{currentOwnerCount + ownerCountAdj};
|
||||
if (ownerCountAdj > 0)
|
||||
{
|
||||
// Overflow is well defined on unsigned
|
||||
if (adjusted < current)
|
||||
if (totalOwnerCount < currentOwnerCount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
|
||||
}
|
||||
adjusted = std::numeric_limits<std::uint32_t>::max();
|
||||
totalOwnerCount = std::numeric_limits<std::uint32_t>::max();
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Underflow is well defined on unsigned
|
||||
if (adjusted > current)
|
||||
if (totalOwnerCount > currentOwnerCount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
|
||||
}
|
||||
adjusted = 0;
|
||||
totalOwnerCount = 0;
|
||||
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
return adjusted;
|
||||
return totalOwnerCount;
|
||||
}
|
||||
|
||||
// Returns the number of account reserves funded by this account: 1 for itself (0 if sponsored by
|
||||
// another account) plus the count of accounts it sponsors.
|
||||
std::uint32_t
|
||||
accountCountImpl(SLE::const_ref sle, std::int32_t accountCountAdj, beast::Journal j)
|
||||
{
|
||||
bool const isSponsored = sle->isFieldPresent(sfSponsor);
|
||||
std::int64_t const sponsoringAccountCount = sle->getFieldU32(sfSponsoringAccountCount);
|
||||
std::int64_t const currentAccountCount = (isSponsored ? 0 : 1) + sponsoringAccountCount;
|
||||
|
||||
std::int64_t totalAccountCount{currentAccountCount + accountCountAdj};
|
||||
if (totalAccountCount > std::numeric_limits<std::uint32_t>::max())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Reserve count exceeds max!";
|
||||
totalAccountCount = std::numeric_limits<std::uint32_t>::max();
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else if (totalAccountCount < 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::accountCountImpl : Reserve count set below 0");
|
||||
JLOG(j.fatal()) << "Reserve count set below 0";
|
||||
totalAccountCount = 0;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return totalAccountCount;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
adjustOwnerCountImpl(
|
||||
ApplyView& view,
|
||||
SLE::ref sle,
|
||||
SF_UINT32 const& sfield,
|
||||
AccountID const& accID,
|
||||
std::int32_t ownerCountAdj,
|
||||
beast::Journal j)
|
||||
{
|
||||
std::uint32_t const currentOwnerCount = sle->at(sfield);
|
||||
std::uint32_t const totalOwnerCount =
|
||||
confineOwnerCount(currentOwnerCount, ownerCountAdj, accID, j);
|
||||
sle->at(sfield) = totalOwnerCount;
|
||||
view.update(sle);
|
||||
return totalOwnerCount;
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCountSigned(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref sponsorSle,
|
||||
std::int32_t adjustment,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (view.rules().enabled(featureSponsor))
|
||||
{
|
||||
XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle");
|
||||
if (!accountSle)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
auto const accountID = accountSle->getAccountID(sfAccount);
|
||||
bool const validType = accountSle->getType() == ltACCOUNT_ROOT;
|
||||
XRPL_ASSERT(validType, "xrpl::adjustOwnerCountSigned : valid account sle type");
|
||||
if (!validType)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCountSigned : nonzero adjustment input");
|
||||
|
||||
OwnerCounts const currentOwnerCount(accountSle);
|
||||
OwnerCounts totalOwnerCount(currentOwnerCount);
|
||||
|
||||
if (sponsorSle)
|
||||
{
|
||||
bool const validSponsorType = sponsorSle->getType() == ltACCOUNT_ROOT;
|
||||
XRPL_ASSERT(validSponsorType, "xrpl::adjustOwnerCountSigned : valid sponsor sle type");
|
||||
if (!validSponsorType)
|
||||
return; // LCOV_EXCL_LINE
|
||||
auto const sponsorID = sponsorSle->getAccountID(sfAccount);
|
||||
|
||||
totalOwnerCount.sponsored = adjustOwnerCountImpl(
|
||||
view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j);
|
||||
|
||||
{
|
||||
OwnerCounts const sponsorCurrent(sponsorSle);
|
||||
OwnerCounts sponsorAdjustment(sponsorCurrent);
|
||||
sponsorAdjustment.sponsoring = adjustOwnerCountImpl(
|
||||
view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j);
|
||||
view.adjustOwnerCountHook(sponsorID, sponsorCurrent, sponsorAdjustment);
|
||||
}
|
||||
|
||||
auto sponsorshipSle = view.peek(keylet::sponsorship(sponsorID, accountID));
|
||||
if (sponsorshipSle && adjustment > 0)
|
||||
{
|
||||
// Only decrease the pre-funded ReserveCount on Sponsorship if we assign new
|
||||
// objects. Removing/reassigning ownership of the object doesn't increase
|
||||
// RemainingOwnerCount back. Don't call hook because this counter is not something
|
||||
// that requires reserve (like other sf...OwnerCounts do).
|
||||
adjustOwnerCountImpl(
|
||||
view, sponsorshipSle, sfRemainingOwnerCount, sponsorID, -adjustment, j);
|
||||
}
|
||||
}
|
||||
|
||||
totalOwnerCount.owner =
|
||||
adjustOwnerCountImpl(view, accountSle, sfOwnerCount, accountID, adjustment, j);
|
||||
view.adjustOwnerCountHook(accountID, currentOwnerCount, totalOwnerCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(accountSle, "xrpl::adjustOwnerCountSigned : valid account sle");
|
||||
if (!accountSle)
|
||||
return;
|
||||
// the remaining are only asserts to preserve existing behavior
|
||||
XRPL_ASSERT(sponsorSle == nullptr, "xrpl::adjustOwnerCountSigned : sponsor not enabled");
|
||||
XRPL_ASSERT(
|
||||
accountSle->getType() == ltACCOUNT_ROOT,
|
||||
"xrpl::adjustOwnerCountSigned : valid account sle type");
|
||||
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input");
|
||||
std::uint32_t const current{accountSle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*accountSle)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, adjustment, id, j);
|
||||
|
||||
OwnerCounts const currentOwnerCount(accountSle);
|
||||
OwnerCounts finalOwnerCount(currentOwnerCount);
|
||||
finalOwnerCount.owner = adjusted;
|
||||
|
||||
view.adjustOwnerCountHook(id, currentOwnerCount, finalOwnerCount);
|
||||
accountSle->at(sfOwnerCount) = adjusted;
|
||||
view.update(accountSle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::uint32_t
|
||||
ownerCount(SLE::const_ref sle, beast::Journal j, std::int32_t ownerCountAdj)
|
||||
{
|
||||
XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::ownerCount : sle is account root");
|
||||
|
||||
AccountID const id = sle->getAccountID(sfAccount);
|
||||
std::uint32_t const currentOwnerCount = sle->at(sfOwnerCount);
|
||||
std::uint32_t const sponsoredOwnerCount = sle->at(sfSponsoredOwnerCount);
|
||||
std::uint32_t const sponsoringOwnerCount = sle->at(sfSponsoringOwnerCount);
|
||||
|
||||
XRPL_ASSERT(
|
||||
currentOwnerCount >= sponsoredOwnerCount,
|
||||
"xrpl::ownerCount : OwnerCount must be greater than or equal to SponsoredOwnerCount");
|
||||
|
||||
std::int64_t deltaCount =
|
||||
static_cast<std::int64_t>(ownerCountAdj) - sponsoredOwnerCount + sponsoringOwnerCount;
|
||||
|
||||
if (deltaCount > std::numeric_limits<std::int32_t>::max())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
deltaCount = std::numeric_limits<std::int32_t>::max();
|
||||
JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, "
|
||||
<< "adjustment: " << ownerCountAdj
|
||||
<< ", sponsoredCount: " << sponsoredOwnerCount
|
||||
<< ", sponsoringOwnerCount: " << sponsoringOwnerCount;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
else if (deltaCount < std::numeric_limits<std::int32_t>::min())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
deltaCount = std::numeric_limits<std::int32_t>::min();
|
||||
JLOG(j.fatal()) << "Account " << id << " delta count is below min, "
|
||||
<< "adjustment: " << ownerCountAdj
|
||||
<< ", sponsoredCount: " << sponsoredOwnerCount
|
||||
<< ", sponsoringCount: " << sponsoringOwnerCount;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return confineOwnerCount(currentOwnerCount, deltaCount);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -91,12 +274,14 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
|
||||
return beast::kZero;
|
||||
|
||||
// Return balance minus reserve
|
||||
std::uint32_t const ownerCount =
|
||||
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
|
||||
std::uint32_t const currentOwnerCount =
|
||||
confineOwnerCount(view.ownerCountHook(id, OwnerCounts(sle)).count(), ownerCountAdj);
|
||||
std::uint32_t const currentAccountCount = accountCountImpl(sle, 0, j);
|
||||
|
||||
// Pseudo-accounts have no reserve requirement
|
||||
auto const reserve =
|
||||
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
|
||||
auto const reserve = isPseudoAccount(sle)
|
||||
? XRPAmount{0}
|
||||
: view.fees().accountReserve(currentOwnerCount, currentAccountCount);
|
||||
|
||||
auto const fullBalance = sle->getFieldAmount(sfBalance);
|
||||
|
||||
@@ -108,7 +293,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText() << " reserve=" << reserve
|
||||
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
|
||||
<< " ownerCount=" << currentOwnerCount << " ownerCountAdj=" << ownerCountAdj;
|
||||
|
||||
return amount.xrp();
|
||||
}
|
||||
@@ -125,19 +310,193 @@ transferRate(ReadView const& view, AccountID const& issuer)
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j)
|
||||
increaseOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!sle)
|
||||
return;
|
||||
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
|
||||
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*sle)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
|
||||
view.adjustOwnerCountHook(id, current, adjusted);
|
||||
sle->at(sfOwnerCount) = adjusted;
|
||||
view.update(sle);
|
||||
XRPL_ASSERT(
|
||||
count != 0 && count <= std::numeric_limits<std::int32_t>::max(),
|
||||
"xrpl::increaseOwnerCount : count in signed delta range");
|
||||
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCountSigned(view, accountSle, sponsorSle, static_cast<std::int32_t>(count), j);
|
||||
}
|
||||
|
||||
void
|
||||
increaseOwnerCount(ApplyViewContext ctx, SLE::ref accountSle, std::uint32_t count, beast::Journal j)
|
||||
{
|
||||
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accountSle);
|
||||
|
||||
// The sponsor's existence is validated by checkReserve/checkSponsor before
|
||||
// any owner-count mutation, so loading it here cannot fail.
|
||||
XRPL_ASSERT(
|
||||
sponsorExp.has_value(), "xrpl::increaseOwnerCount : sponsor validated before mutation");
|
||||
|
||||
increaseOwnerCount(ctx.view, accountSle, sponsorExp ? *sponsorExp : SLE::pointer(), count, j);
|
||||
}
|
||||
|
||||
void
|
||||
decreaseOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
count != 0 && count <= std::numeric_limits<std::int32_t>::max(),
|
||||
"xrpl::decreaseOwnerCount : count in signed delta range");
|
||||
if (count == 0 || count > std::numeric_limits<std::int32_t>::max())
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCountSigned(view, accountSle, sponsorSle, -static_cast<std::int32_t>(count), j);
|
||||
}
|
||||
|
||||
void
|
||||
decreaseOwnerCountForObject(
|
||||
ApplyView& view,
|
||||
SLE::ref accountSle,
|
||||
SLE::ref objectSle,
|
||||
std::uint32_t count,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(objectSle, "xrpl::decreaseOwnerCountForObject : valid object sle");
|
||||
if (!objectSle)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
bool const validObjectType = objectSle->getType() != ltACCOUNT_ROOT;
|
||||
XRPL_ASSERT(validObjectType, "xrpl::decreaseOwnerCountForObject : valid object sle type");
|
||||
if (!validObjectType)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
|
||||
decreaseOwnerCount(view, accountSle, sponsorSle, count, j);
|
||||
}
|
||||
|
||||
void
|
||||
adjustLoanBrokerOwnerCount(
|
||||
ApplyView& view,
|
||||
SLE::ref brokerSle,
|
||||
std::int32_t delta,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
brokerSle && brokerSle->getType() == ltLOAN_BROKER,
|
||||
"xrpl::adjustLoanBrokerOwnerCount : valid loan broker sle");
|
||||
if (!brokerSle || brokerSle->getType() != ltLOAN_BROKER)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(delta != 0, "xrpl::adjustLoanBrokerOwnerCount : nonzero delta input");
|
||||
if (delta == 0)
|
||||
return; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCountImpl(
|
||||
view, brokerSle, sfOwnerCount, brokerSle->getAccountID(sfAccount), delta, j);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj)
|
||||
{
|
||||
XRPL_ASSERT(sle && sle->getType() == ltACCOUNT_ROOT, "xrpl::accountReserve : valid sle");
|
||||
|
||||
if (!view.rules().enabled(featureSponsor))
|
||||
{
|
||||
XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::accountReserve : no account count delta");
|
||||
return view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1);
|
||||
}
|
||||
std::uint32_t const currentOwnerCount = ownerCount(sle, j, adj.ownerCountDelta);
|
||||
std::uint32_t const currentAccountCount = accountCountImpl(sle, adj.accountCountDelta, j);
|
||||
|
||||
return view.fees().accountReserve(currentOwnerCount, currentAccountCount);
|
||||
}
|
||||
|
||||
TER
|
||||
checkReserve(
|
||||
ApplyViewContext ctx,
|
||||
SLE::const_ref accSle,
|
||||
XRPAmount accBalance,
|
||||
SLE::const_ref sponsorSle,
|
||||
Adjustment adj,
|
||||
beast::Journal j,
|
||||
TER insufReserveCode)
|
||||
{
|
||||
// TODO: swap to assert after fixCleanup3_2_0 is retired
|
||||
if (!accSle || accSle->getType() != ltACCOUNT_ROOT)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
XRPL_ASSERT(
|
||||
!isTesSuccess(insufReserveCode), "xrpl::checkReserve : insufReserveCode is not tesSUCCESS");
|
||||
if (ctx.view.rules().enabled(featureSponsor))
|
||||
{
|
||||
if (sponsorSle)
|
||||
{
|
||||
if (sponsorSle->getType() != ltACCOUNT_ROOT)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sle = ctx.view.read(
|
||||
keylet::sponsorship(
|
||||
sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
|
||||
|
||||
// A reserve-sponsored tx must carry a sponsor signature
|
||||
// (cosigning path) and/or have a pre-existing sponsorship SLE
|
||||
// (prefunded path). Absence of both is an internal invariant break.
|
||||
if (isReserveSponsored(ctx.tx) && !sle && !ctx.tx.isFieldPresent(sfSponsorSignature))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sle)
|
||||
{
|
||||
auto const ownerCountAllowed = sle->getFieldU32(sfRemainingOwnerCount);
|
||||
if (adj.ownerCountDelta > 0 &&
|
||||
ownerCountAllowed < static_cast<std::uint32_t>(adj.ownerCountDelta))
|
||||
return insufReserveCode;
|
||||
}
|
||||
|
||||
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance).xrp();
|
||||
XRPAmount const sponsorReserve = accountReserve(ctx.view, sponsorSle, j, adj);
|
||||
|
||||
if (sponsorBalance < sponsorReserve)
|
||||
return insufReserveCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPAmount const reserve = accountReserve(ctx.view, accSle, j, adj);
|
||||
if (accBalance < reserve)
|
||||
return insufReserveCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!sponsorSle,
|
||||
"xrpl::checkReserve : featureSponsor disabled and sponsorSle not provided");
|
||||
XRPL_ASSERT(adj.accountCountDelta == 0, "xrpl::checkReserve : accountCountDelta is 0");
|
||||
auto const reserve = ctx.view.fees().accountReserve(
|
||||
accSle->getFieldU32(sfOwnerCount) + adj.ownerCountDelta, 1);
|
||||
if (accBalance < reserve)
|
||||
return insufReserveCode;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkReserve(
|
||||
ApplyViewContext ctx,
|
||||
SLE::const_ref accSle,
|
||||
XRPAmount accBalance,
|
||||
Adjustment adj,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, accSle);
|
||||
if (!sponsorExp)
|
||||
return sponsorExp.error(); // LCOV_EXCL_LINE
|
||||
return checkReserve(ctx, accSle, accBalance, *sponsorExp, adj, j);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
|
||||
AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
|
||||
{
|
||||
|
||||
@@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
|
||||
}
|
||||
|
||||
if (isOwner)
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
decreaseOwnerCountForObject(view, sleAccount, sleCredential, 1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -11,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -125,29 +125,29 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mpt = view.peek(keylet::mptokenIssuance(mptID));
|
||||
auto const mpt = ctx.view.peek(keylet::mptokenIssuance(mptID));
|
||||
if (!mpt)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (mpt->isFlag(lsfMPTLocked))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (view.peek(keylet::mptoken(mptID, accountID)))
|
||||
if (ctx.view.peek(keylet::mptoken(mptID, accountID)))
|
||||
return tecDUPLICATE;
|
||||
if (accountID == mptIssue.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
|
||||
return authorizeMPToken(ctx, priorBalance, mptID, accountID, journal);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
@@ -155,7 +155,7 @@ authorizeMPToken(
|
||||
std::uint32_t flags,
|
||||
std::optional<AccountID> holderID)
|
||||
{
|
||||
auto const sleAcct = view.peek(keylet::account(account));
|
||||
auto const sleAcct = ctx.view.peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -170,19 +170,19 @@ authorizeMPToken(
|
||||
if ((flags & tfMPTUnauthorize) != 0u)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleMpt = view.peek(mptokenKey);
|
||||
auto const sleMpt = ctx.view.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
|
||||
(view.rules().enabled(fixCleanup3_1_3) &&
|
||||
(ctx.view.rules().enabled(fixCleanup3_1_3) &&
|
||||
(*sleMpt)[~sfLockedAmount].valueOr(0) != 0))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!view.dirRemove(
|
||||
if (!ctx.view.dirRemove(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleAcct, -1, journal);
|
||||
decreaseOwnerCountForObject(ctx.view, sleAcct, sleMpt, 1, journal);
|
||||
|
||||
view.erase(sleMpt);
|
||||
ctx.view.erase(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -190,47 +190,57 @@ authorizeMPToken(
|
||||
// - add the new mptokenKey to the owner directory
|
||||
// - create the MPToken object for the holder
|
||||
|
||||
// A reserve sponsor only covers tx.Account's own objects.
|
||||
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleAcct);
|
||||
if (!sponsorExp)
|
||||
return sponsorExp.error(); // LCOV_EXCL_LINE
|
||||
auto const sponsorSle = *sponsorExp;
|
||||
|
||||
// The reserve that is required to create the MPToken. Note
|
||||
// that although the reserve increases with every item
|
||||
// an account owns, in the case of MPTokens we only
|
||||
// *enforce* a reserve if the user owns more than two
|
||||
// items. This is similar to the reserve requirements of trust lines.
|
||||
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
|
||||
XRPAmount const reserveCreate(
|
||||
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
|
||||
: view.fees().accountReserve(uOwnerCount + 1));
|
||||
|
||||
if (priorBalance < reserveCreate)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
// The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on
|
||||
// the tx — the sponsor must always cover the reserve (via balance or prefunded
|
||||
// budget), so this check always runs for sponsored transactions.
|
||||
if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
|
||||
{
|
||||
if (auto const ret = checkReserve(
|
||||
ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Defensive check before we attempt to create MPToken for the issuer
|
||||
auto const mpt = view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
auto const mpt = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!mpt || mpt->getAccountID(sfIssuer) == account)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
|
||||
if (view.rules().enabled(featureLendingProtocol))
|
||||
if (ctx.view.rules().enabled(featureLendingProtocol))
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
if (auto ter = dirLink(view, account, mptoken))
|
||||
if (auto ter = dirLink(ctx.view, account, mptoken))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
view.insert(mptoken);
|
||||
ctx.view.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||
increaseOwnerCount(ctx.view, sleAcct, sponsorSle, 1, journal);
|
||||
addSponsorToLedgerEntry(mptoken, sponsorSle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const sleMptIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
auto const sleMptIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleMptIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -240,7 +250,7 @@ authorizeMPToken(
|
||||
if (account != (*sleMptIssuance)[sfIssuer])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
auto const sleMpt = ctx.view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
if (!sleMpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -263,13 +273,13 @@ authorizeMPToken(
|
||||
if (flagsIn != flagsOut)
|
||||
sleMpt->setFieldU32(sfFlags, flagsOut);
|
||||
|
||||
view.update(sleMpt);
|
||||
ctx.view.update(sleMpt);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
@@ -279,7 +289,7 @@ removeEmptyHolding(
|
||||
// a token does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
|
||||
auto const mptoken = ctx.view.peek(keylet::mptoken(mptID, accountID));
|
||||
if (!mptoken)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
// Unlike a trust line, if the account is the issuer, and the token has a
|
||||
@@ -287,7 +297,7 @@ removeEmptyHolding(
|
||||
// accounting out of balance, so fail. Since this should be impossible
|
||||
// anyway, I'm not going to put any effort into it.
|
||||
if (mptoken->at(sfMPTAmount) != 0 ||
|
||||
(view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
|
||||
(ctx.view.rules().enabled(fixCleanup3_1_3) && (*mptoken)[~sfLockedAmount].valueOr(0) != 0))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Don't delete if the token still has confidential balances
|
||||
@@ -300,7 +310,7 @@ removeEmptyHolding(
|
||||
}
|
||||
|
||||
return authorizeMPToken(
|
||||
view,
|
||||
ctx,
|
||||
{}, // priorBalance
|
||||
mptID,
|
||||
accountID,
|
||||
@@ -419,13 +429,13 @@ requireAuth(
|
||||
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const sleIssuance = view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -437,7 +447,7 @@ enforceMPTokenAuthorization(
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const keylet = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleToken = view.read(keylet); // NOTE: might be null
|
||||
auto const sleToken = ctx.view.read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
bool expired = false;
|
||||
bool const authorizedByDomain = [&]() -> bool {
|
||||
@@ -445,7 +455,7 @@ enforceMPTokenAuthorization(
|
||||
if (!maybeDomainID.has_value())
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
|
||||
auto const ter = verifyValidDomain(ctx.view, account, *maybeDomainID, j);
|
||||
if (isTesSuccess(ter))
|
||||
return true;
|
||||
if (ter == tecEXPIRED)
|
||||
@@ -500,7 +510,7 @@ enforceMPTokenAuthorization(
|
||||
maybeDomainID.has_value() && sleToken == nullptr,
|
||||
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
|
||||
if (auto const err = authorizeMPToken(
|
||||
view,
|
||||
ctx,
|
||||
priorBalance, // priorBalance
|
||||
mptIssuanceID, // mptIssuanceID
|
||||
account, // account
|
||||
@@ -915,6 +925,7 @@ createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t const flags)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
@@ -931,6 +942,8 @@ createMPToken(
|
||||
(*mptoken)[sfFlags] = flags;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
|
||||
addSponsorToLedgerEntry(mptoken, sponsorSle);
|
||||
|
||||
view.insert(mptoken);
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -941,6 +954,7 @@ checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (mptIssue.getIssuer() == holder)
|
||||
@@ -950,7 +964,7 @@ checkCreateMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||
if (!view.exists(mptokenID))
|
||||
{
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -960,7 +974,8 @@ checkCreateMPT(
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
adjustOwnerCount(view, sleAcct, 1, j);
|
||||
|
||||
increaseOwnerCount(view, sleAcct, sponsorSle, 1, j);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -269,11 +269,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
|
||||
// the NFT.
|
||||
SLE::pointer const page =
|
||||
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
|
||||
});
|
||||
|
||||
if (!page)
|
||||
@@ -411,21 +407,17 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
|
||||
curr->setFieldArray(sfNFTokens, arr);
|
||||
view.update(curr);
|
||||
|
||||
int cnt = 0;
|
||||
std::uint32_t cnt = 0;
|
||||
|
||||
if (prev && mergePages(view, prev, curr))
|
||||
cnt--;
|
||||
++cnt;
|
||||
|
||||
if (next && mergePages(view, curr, next))
|
||||
cnt--;
|
||||
++cnt;
|
||||
|
||||
if (cnt != 0)
|
||||
{
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()});
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -460,11 +452,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
|
||||
curr->makeFieldAbsent(sfPreviousPageMin);
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
view.update(curr);
|
||||
view.erase(prev);
|
||||
@@ -502,7 +490,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
|
||||
|
||||
view.erase(curr);
|
||||
|
||||
int cnt = 1;
|
||||
uint32_t cnt = 1;
|
||||
|
||||
// Since we're here, try to consolidate the previous and current pages
|
||||
// of the page we removed (if any) into one. mergePages() _should_
|
||||
@@ -519,11 +507,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
|
||||
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
|
||||
cnt++;
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1 * cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -639,8 +623,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer)
|
||||
false))
|
||||
return false;
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
|
||||
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
view.erase(offer);
|
||||
return true;
|
||||
@@ -743,9 +726,11 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
|
||||
auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink));
|
||||
if (!newPrev)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
Throw<std::runtime_error>(
|
||||
"NFTokenPage directory for " + to_string(owner) +
|
||||
" cannot be repaired. Unexpected link problem.");
|
||||
" cannot be repaired. Unexpected link problem.");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
newPrev->at(sfNextPageMin) = nextPage->key();
|
||||
view.update(newPrev);
|
||||
@@ -931,7 +916,7 @@ tokenOfferCreateApply(
|
||||
{
|
||||
Keylet const acctKeylet = keylet::account(acctID);
|
||||
if (auto const acct = view.read(acctKeylet);
|
||||
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
|
||||
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());
|
||||
@@ -983,7 +968,7 @@ tokenOfferCreateApply(
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
|
||||
increaseOwnerCount(view, acctID, {}, 1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j)
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
|
||||
decreaseOwnerCountForObject(view, owner, sle, 1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
decreaseOwnerCountForObject(view, sle, slep, 1, j);
|
||||
view.update(sle);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
@@ -195,6 +196,7 @@ trustCreate(
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j)
|
||||
{
|
||||
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
|
||||
@@ -281,7 +283,9 @@ trustCreate(
|
||||
}
|
||||
|
||||
sleRippleState->setFieldU32(sfFlags, uFlags);
|
||||
adjustOwnerCount(view, sleAccount, 1, j);
|
||||
increaseOwnerCount(view, sleAccount, sponsorSle, 1, j);
|
||||
|
||||
addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor);
|
||||
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
@@ -317,6 +321,9 @@ trustDelete(
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
|
||||
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
|
||||
|
||||
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
|
||||
view.erase(sleRippleState);
|
||||
|
||||
@@ -369,11 +376,15 @@ updateTrustLine(
|
||||
{
|
||||
// VFALCO Where is the line being deleted?
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
auto const currentSponsor =
|
||||
getLedgerEntryReserveSponsor(view, state, bSenderHigh ? sfHighSponsor : sfLowSponsor);
|
||||
decreaseOwnerCount(view, sle, currentSponsor, 1, j);
|
||||
|
||||
// Clear reserve flag.
|
||||
state->clearFlag(senderReserveFlag);
|
||||
|
||||
removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
|
||||
|
||||
// Balance is zero, receiver reserve is clear.
|
||||
if (!after && !state->isFlag(receiverReserveFlag))
|
||||
return true;
|
||||
@@ -381,12 +392,14 @@ updateTrustLine(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only used in tests
|
||||
TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -472,6 +485,7 @@ issueIOU(
|
||||
limit,
|
||||
0,
|
||||
0,
|
||||
sponsorSle,
|
||||
j);
|
||||
}
|
||||
|
||||
@@ -620,7 +634,7 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
@@ -632,30 +646,45 @@ addEmptyHolding(
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& currency = issue.currency;
|
||||
if (isGlobalFrozen(view, issuerId))
|
||||
if (isGlobalFrozen(ctx.view, issuerId))
|
||||
return tecFROZEN; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& srcId = issuerId;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::trustLine(srcId, dstId, currency);
|
||||
auto const sleSrc = view.peek(keylet::account(srcId));
|
||||
auto const sleDst = view.peek(keylet::account(dstId));
|
||||
auto const sleSrc = ctx.view.peek(keylet::account(srcId));
|
||||
auto const sleDst = ctx.view.peek(keylet::account(dstId));
|
||||
if (!sleDst || !sleSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (!sleSrc->isFlag(lsfDefaultRipple))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// If the line already exists, don't create it again.
|
||||
if (view.read(index))
|
||||
if (ctx.view.read(index))
|
||||
return tecDUPLICATE;
|
||||
|
||||
// A reserve sponsor only covers tx.Account's own objects.
|
||||
auto const sponsorExp = getEffectiveTxReserveSponsor(ctx, sleDst);
|
||||
if (!sponsorExp)
|
||||
return sponsorExp.error(); // LCOV_EXCL_LINE
|
||||
auto const sponsorSle = *sponsorExp;
|
||||
|
||||
// Can the account cover the trust line reserve ?
|
||||
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
|
||||
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
if (auto const ret = checkReserve(
|
||||
ctx,
|
||||
sleDst,
|
||||
priorBalance,
|
||||
sponsorSle,
|
||||
{.ownerCountDelta = 1},
|
||||
journal,
|
||||
tecNO_LINE_INSUF_RESERVE);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
ctx.view,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
@@ -669,19 +698,20 @@ addEmptyHolding(
|
||||
/*saLimit=*/STAmount{Issue{currency, dstId}},
|
||||
/*uQualityIn=*/0,
|
||||
/*uQualityOut=*/0,
|
||||
sponsorSle,
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (issue.native())
|
||||
{
|
||||
auto const sle = view.read(keylet::account(accountID));
|
||||
auto const sle = ctx.view.read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -696,7 +726,7 @@ removeEmptyHolding(
|
||||
// If the account is the issuer, then no line should exist. Check anyway.
|
||||
// If a line does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == issue.account;
|
||||
auto const line = view.peek(keylet::trustLine(accountID, issue));
|
||||
auto const line = ctx.view.peek(keylet::trustLine(accountID, issue));
|
||||
if (!line)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero)
|
||||
@@ -706,33 +736,43 @@ removeEmptyHolding(
|
||||
if (line->isFlag(lsfLowReserve))
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
|
||||
auto sleLowAccount = ctx.view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
|
||||
if (!sleLowAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleLowAccount, -1, journal);
|
||||
auto const currentLowSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfLowSponsor);
|
||||
|
||||
decreaseOwnerCount(ctx.view, sleLowAccount, currentLowSponsor, 1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
line->clearFlag(lsfLowReserve);
|
||||
removeSponsorFromLedgerEntry(line, sfLowSponsor);
|
||||
}
|
||||
|
||||
if (line->isFlag(lsfHighReserve))
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
|
||||
auto sleHighAccount = ctx.view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
|
||||
if (!sleHighAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleHighAccount, -1, journal);
|
||||
auto const currentHighSponsor = getLedgerEntryReserveSponsor(ctx.view, line, sfHighSponsor);
|
||||
|
||||
decreaseOwnerCount(ctx.view, sleHighAccount, currentHighSponsor, 1, journal);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
line->clearFlag(lsfHighReserve);
|
||||
removeSponsorFromLedgerEntry(line, sfHighSponsor);
|
||||
}
|
||||
|
||||
return trustDelete(
|
||||
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
|
||||
ctx.view,
|
||||
line,
|
||||
line->at(sfLowLimit)->getIssuer(),
|
||||
line->at(sfHighLimit)->getIssuer(),
|
||||
journal);
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -768,6 +808,9 @@ deleteAMMTrustLine(
|
||||
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
|
||||
return terNO_AMM;
|
||||
|
||||
auto const sponsorSle =
|
||||
getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor);
|
||||
|
||||
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
|
||||
@@ -778,7 +821,7 @@ deleteAMMTrustLine(
|
||||
if (!sleState->isFlag(uFlags))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
|
||||
decreaseOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, 1, j);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
isReserveSponsorAllowed(TxType txType)
|
||||
{
|
||||
// Transaction types explicitly allow-listed for reserve sponsorship, for
|
||||
// v1. Lazily-initialized function-local static: constructed once on first
|
||||
// use, with no startup cost paid by clients that never call this.
|
||||
static std::unordered_set<TxType> const kReserveSponsorAllowed = {
|
||||
ttDELEGATE_SET,
|
||||
ttDEPOSIT_PREAUTH,
|
||||
ttPAYMENT,
|
||||
ttSIGNER_LIST_SET,
|
||||
ttCHECK_CANCEL,
|
||||
ttCHECK_CASH,
|
||||
ttCHECK_CREATE,
|
||||
ttESCROW_CANCEL,
|
||||
ttESCROW_CREATE,
|
||||
ttESCROW_FINISH,
|
||||
ttPAYCHAN_CLAIM,
|
||||
ttPAYCHAN_CREATE,
|
||||
ttPAYCHAN_FUND,
|
||||
ttCLAWBACK,
|
||||
ttMPTOKEN_AUTHORIZE,
|
||||
ttMPTOKEN_ISSUANCE_CREATE,
|
||||
ttMPTOKEN_ISSUANCE_DESTROY,
|
||||
ttMPTOKEN_ISSUANCE_SET,
|
||||
ttTRUST_SET,
|
||||
ttCREDENTIAL_ACCEPT,
|
||||
ttCREDENTIAL_CREATE,
|
||||
ttCREDENTIAL_DELETE,
|
||||
ttACCOUNT_SET,
|
||||
ttREGULAR_KEY_SET,
|
||||
ttSPONSORSHIP_TRANSFER,
|
||||
};
|
||||
return kReserveSponsorAllowed.contains(txType);
|
||||
}
|
||||
|
||||
std::optional<AccountID>
|
||||
getTxReserveSponsorID(STTx const& tx)
|
||||
{
|
||||
if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
|
||||
featureSponsor),
|
||||
"xrpl::getTxReserveSponsorID : sponsor exists + Sponsor enabled");
|
||||
return tx.getAccountID(sfSponsor);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<SLE::pointer, TER>
|
||||
getTxReserveSponsor(ApplyViewContext ctx)
|
||||
{
|
||||
auto const sponsorID = getTxReserveSponsorID(ctx.tx);
|
||||
if (sponsorID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
ctx.view.rules().enabled(featureSponsor),
|
||||
"xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled");
|
||||
auto sle = ctx.view.peek(keylet::account(*sponsorID));
|
||||
|
||||
// already checked in Transactor::checkSponsor
|
||||
if (!sle)
|
||||
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return sle;
|
||||
}
|
||||
return SLE::pointer();
|
||||
}
|
||||
|
||||
std::expected<SLE::const_pointer, TER>
|
||||
getTxReserveSponsor(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
auto const sponsorID = getTxReserveSponsorID(tx);
|
||||
if (sponsorID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
view.rules().enabled(featureSponsor),
|
||||
"xrpl::getTxReserveSponsor : sponsor exists + Sponsor enabled");
|
||||
auto sle = view.read(keylet::account(*sponsorID));
|
||||
|
||||
// already checked in Transactor::checkSponsor
|
||||
if (!sle)
|
||||
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return sle;
|
||||
}
|
||||
return SLE::pointer();
|
||||
}
|
||||
|
||||
std::expected<SLE::pointer, TER>
|
||||
getEffectiveTxReserveSponsor(ApplyViewContext ctx, SLE::const_ref accountSle)
|
||||
{
|
||||
// A reserve sponsor only covers tx.Account's own objects.
|
||||
if (ctx.view.rules().enabled(fixCleanup3_2_0))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
accountSle && accountSle->getType() == ltACCOUNT_ROOT,
|
||||
"xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type");
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
accountSle &&
|
||||
((accountSle->getType() == ltACCOUNT_ROOT) || (accountSle->getType() == ltESCROW)),
|
||||
"xrpl::getEffectiveTxReserveSponsor : accountSle exists and is account type");
|
||||
}
|
||||
|
||||
if (isPseudoAccount(accountSle) || accountSle->getAccountID(sfAccount) != ctx.tx[sfAccount])
|
||||
return SLE::pointer();
|
||||
return getTxReserveSponsor(ctx);
|
||||
}
|
||||
|
||||
std::optional<AccountID>
|
||||
getLedgerEntryReserveSponsorID(SLE::const_ref sle, SF_ACCOUNT const& field)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(sle &&
|
||||
((sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
|
||||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor))),
|
||||
"xrpl::getLedgerEntryReserveSponsorID : correct sfield");
|
||||
|
||||
if (sle->isFieldPresent(field))
|
||||
return sle->getAccountID(field);
|
||||
return {};
|
||||
}
|
||||
|
||||
SLE::pointer
|
||||
getLedgerEntryReserveSponsor(ApplyView& view, SLE::const_ref sle, SF_ACCOUNT const& field)
|
||||
{
|
||||
auto const sponsorID = getLedgerEntryReserveSponsorID(sle, field);
|
||||
if (sponsorID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
view.rules().enabled(featureSponsor),
|
||||
"xrpl::getLedgerEntryReserveSponsor : sponsor exists + Sponsor enabled");
|
||||
return view.peek(keylet::account(*sponsorID));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const& field)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
|
||||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
|
||||
"addSponsorToLedgerEntry : Invalid field to the LedgerEntry");
|
||||
if (sponsorSle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
|
||||
featureSponsor),
|
||||
"xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled");
|
||||
sle->setAccountID(field, sponsorSle->getAccountID(sfAccount));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
addSponsorToLedgerEntry(ApplyViewContext ctx, SLE::ref sle, SF_ACCOUNT const& field)
|
||||
{
|
||||
// getTxReserveSponsor yields a null pointer when the tx is not
|
||||
// reserve-sponsored, so addSponsorToLedgerEntry becomes a no-op then. The
|
||||
// error case (tecINTERNAL) is an already-checked invariant; skip stamping.
|
||||
auto const sponsorSle = getTxReserveSponsor(ctx);
|
||||
if (sponsorSle && *sponsorSle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
ctx.view.rules().enabled(featureSponsor),
|
||||
"xrpl::addSponsorToLedgerEntry : sponsor exists + Sponsor enabled");
|
||||
addSponsorToLedgerEntry(sle, *sponsorSle, field);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
|
||||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
|
||||
"removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry");
|
||||
if (sle->isFieldPresent(field))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
getCurrentTransactionRules()->enabled( // NOLINT(bugprone-unchecked-optional-access)
|
||||
featureSponsor),
|
||||
"xrpl::removeSponsorFromLedgerEntry : sponsor exists + Sponsor enabled");
|
||||
sle->makeFieldAbsent(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& account)
|
||||
{
|
||||
switch (sle.getType())
|
||||
{
|
||||
case ltCHECK:
|
||||
case ltESCROW:
|
||||
case ltPAYCHAN:
|
||||
case ltMPTOKEN:
|
||||
case ltDELEGATE:
|
||||
case ltDEPOSIT_PREAUTH:
|
||||
return sle.getAccountID(sfAccount) == account;
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
return sle.getAccountID(sfIssuer) == account;
|
||||
case ltSIGNER_LIST: {
|
||||
auto const signerList = view.read(keylet::signerList(account));
|
||||
if (!signerList)
|
||||
return false;
|
||||
return signerList->key() == sle.key();
|
||||
}
|
||||
case ltCREDENTIAL: {
|
||||
auto const& ownerField = sle.isFlag(lsfAccepted) ? sfSubject : sfIssuer;
|
||||
return sle.getAccountID(ownerField) == account;
|
||||
}
|
||||
case ltRIPPLE_STATE: {
|
||||
if (sle.isFlag(lsfHighReserve))
|
||||
{
|
||||
auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer();
|
||||
if (highAccount == account)
|
||||
return true;
|
||||
}
|
||||
if (sle.isFlag(lsfLowReserve))
|
||||
{
|
||||
auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer();
|
||||
if (lowAccount == account)
|
||||
return true;
|
||||
}
|
||||
// Reachable: the sponsee may be a third party or the side of the
|
||||
// line that holds no reserve (e.g. the issuer). Callers map this
|
||||
// to tecNO_PERMISSION.
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::isLedgerEntryOwner : object is not supported by sponsorship.");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
}
|
||||
|
||||
bool
|
||||
isLedgerEntrySupportedBySponsorship(SLE const& sle)
|
||||
{
|
||||
switch (sle.getType())
|
||||
{
|
||||
case ltCHECK:
|
||||
case ltESCROW:
|
||||
case ltPAYCHAN:
|
||||
case ltMPTOKEN:
|
||||
case ltDELEGATE:
|
||||
case ltDEPOSIT_PREAUTH:
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
case ltSIGNER_LIST:
|
||||
case ltCREDENTIAL:
|
||||
case ltRIPPLE_STATE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getLedgerEntryOwnerCount(SLE const& sle)
|
||||
{
|
||||
switch (sle.getType())
|
||||
{
|
||||
case ltORACLE: {
|
||||
return calculateOracleReserve(sle.getFieldArray(sfPriceDataSeries));
|
||||
}
|
||||
// Vaults require 2 owner counts (the vault and a pseudo-account)
|
||||
case ltVAULT:
|
||||
return 2;
|
||||
case ltSIGNER_LIST: {
|
||||
// Mirror SignerListSet's owner-count accounting so that create and
|
||||
// delete agree. Modern lists (post-MultiSignReserve) carry the
|
||||
// lsfOneOwnerCount flag and cost a single owner count. Legacy
|
||||
// pre-MultiSignReserve lists cost 2 + signer_count owner counts
|
||||
if (sle.isFlag(lsfOneOwnerCount))
|
||||
return 1;
|
||||
return 2 + static_cast<std::uint32_t>(sle.getFieldArray(sfSignerEntries).size());
|
||||
}
|
||||
case ltACCOUNT_ROOT:
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("AccountRoots are not supported by object sponsorship.");
|
||||
return 0;
|
||||
// LCOV_EXCL_STOP
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
SF_ACCOUNT const&
|
||||
getLedgerEntrySponsorField(SLE const& sle, AccountID const& owner)
|
||||
{
|
||||
switch (sle.getType())
|
||||
{
|
||||
case ltRIPPLE_STATE: {
|
||||
if (sle.isFlag(lsfHighReserve))
|
||||
{
|
||||
auto const highAccount = sle.getFieldAmount(sfHighLimit).getIssuer();
|
||||
if (highAccount == owner)
|
||||
return sfHighSponsor;
|
||||
}
|
||||
if (sle.isFlag(lsfLowReserve))
|
||||
{
|
||||
auto const lowAccount = sle.getFieldAmount(sfLowLimit).getIssuer();
|
||||
if (lowAccount == owner)
|
||||
return sfLowSponsor;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::getLedgerEntrySponsorField : unknown owner for RippleState");
|
||||
return sfSponsor;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
default:
|
||||
return sfSponsor;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -571,7 +572,7 @@ canAddHolding(ReadView const& view, Asset const& asset)
|
||||
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
@@ -579,21 +580,21 @@ addEmptyHolding(
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
|
||||
return addEmptyHolding(ctx, accountID, priorBalance, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
ApplyViewContext ctx,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return removeEmptyHolding(view, accountID, issue, journal);
|
||||
return removeEmptyHolding(ctx, accountID, issue, journal);
|
||||
},
|
||||
asset.value());
|
||||
}
|
||||
@@ -647,6 +648,7 @@ directSendNoFeeIOU(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
bool bCheckIssuer,
|
||||
SLE::ref sponsorSle,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const& issuer = saAmount.getIssuer();
|
||||
@@ -717,7 +719,12 @@ directSendNoFeeIOU(
|
||||
// Sender quality out is 0.
|
||||
{
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
|
||||
auto const currentSponsor = getLedgerEntryReserveSponsor(
|
||||
view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
|
||||
decreaseOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, 1, j);
|
||||
|
||||
removeSponsorFromLedgerEntry(
|
||||
sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
|
||||
|
||||
// Clear reserve flag.
|
||||
sleRippleState->clearFlag(senderReserveFlag);
|
||||
@@ -780,6 +787,7 @@ directSendNoFeeIOU(
|
||||
saReceiverLimit,
|
||||
0,
|
||||
0,
|
||||
sponsorSle,
|
||||
j);
|
||||
}
|
||||
|
||||
@@ -794,6 +802,7 @@ directSendNoLimitIOU(
|
||||
STAmount const& saAmount,
|
||||
STAmount& saActual,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle,
|
||||
WaiveTransferFee waiveFee)
|
||||
{
|
||||
auto const& issuer = saAmount.getIssuer();
|
||||
@@ -806,7 +815,8 @@ directSendNoLimitIOU(
|
||||
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
|
||||
{
|
||||
// Direct send: redeeming IOUs and/or sending own IOUs.
|
||||
auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j);
|
||||
auto const ter =
|
||||
directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter;
|
||||
saActual = saAmount;
|
||||
@@ -824,10 +834,12 @@ directSendNoLimitIOU(
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
<< " cost=" << saActual.getFullText();
|
||||
|
||||
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j);
|
||||
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j);
|
||||
{
|
||||
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
|
||||
}
|
||||
|
||||
return terResult;
|
||||
}
|
||||
@@ -870,7 +882,8 @@ directSendNoLimitMultiIOU(
|
||||
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
|
||||
{
|
||||
// Direct send: redeeming IOUs and/or sending own IOUs.
|
||||
if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j);
|
||||
if (auto const ter =
|
||||
directSendNoFeeIOU(view, senderID, receiverID, amount, false, {}, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
actual += amount;
|
||||
@@ -894,14 +907,14 @@ directSendNoLimitMultiIOU(
|
||||
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
|
||||
<< " cost=" << actual.getFullText();
|
||||
|
||||
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j))
|
||||
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, {}, j))
|
||||
return terResult;
|
||||
}
|
||||
|
||||
if (senderID != issuer && takeFromSender)
|
||||
{
|
||||
if (TER const terResult =
|
||||
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j))
|
||||
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, {}, j))
|
||||
return terResult;
|
||||
}
|
||||
|
||||
@@ -915,6 +928,7 @@ accountSendIOU(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle,
|
||||
WaiveTransferFee waiveFee)
|
||||
{
|
||||
if (view.rules().enabled(fixAMMv1_1))
|
||||
@@ -946,7 +960,8 @@ accountSendIOU(
|
||||
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
|
||||
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
|
||||
|
||||
return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
|
||||
return directSendNoLimitIOU(
|
||||
view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
|
||||
}
|
||||
|
||||
/* XRP send which does not check reserve and can do pure adjustment.
|
||||
@@ -1475,7 +1490,7 @@ directSendNoFee(
|
||||
{
|
||||
return saAmount.asset().visit(
|
||||
[&](Issue const&) {
|
||||
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
|
||||
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
|
||||
},
|
||||
[&](MPTIssue const&) {
|
||||
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
|
||||
@@ -1490,12 +1505,13 @@ accountSend(
|
||||
AccountID const& uReceiverID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j,
|
||||
SLE::ref sponsorSle,
|
||||
WaiveTransferFee waiveFee,
|
||||
AllowMPTOverflow allowOverflow)
|
||||
{
|
||||
return saAmount.asset().visit(
|
||||
[&](Issue const&) {
|
||||
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
|
||||
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
|
||||
},
|
||||
[&](MPTIssue const&) {
|
||||
return accountSendMPT(
|
||||
|
||||
@@ -84,6 +84,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
Vault = 'V',
|
||||
LoanBroker = 'l', // lower-case L
|
||||
Loan = 'L',
|
||||
Sponsorship = '>',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
|
||||
Contract [[deprecated]] = 'c',
|
||||
@@ -326,6 +327,12 @@ signerList(AccountID const& account) noexcept
|
||||
return signerList(account, 0);
|
||||
}
|
||||
|
||||
Keylet
|
||||
sponsorship(AccountID const& sponsor, AccountID const& sponsee) noexcept
|
||||
{
|
||||
return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept
|
||||
{
|
||||
|
||||
@@ -160,6 +160,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfTxnSignature, SoeOptional},
|
||||
{sfSigners, SoeOptional},
|
||||
});
|
||||
|
||||
add(sfSponsorSignature.jsonName.cStr(),
|
||||
sfSponsorSignature.getCode(),
|
||||
{
|
||||
{sfSigningPubKey, SoeOptional},
|
||||
{sfTxnSignature, SoeOptional},
|
||||
{sfSigners, SoeOptional},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -15,6 +15,7 @@ LedgerFormats::getCommonFields()
|
||||
{sfLedgerIndex, SoeOptional},
|
||||
{sfLedgerEntryType, SoeRequired},
|
||||
{sfFlags, SoeRequired},
|
||||
{sfSponsor, SoeOptional},
|
||||
};
|
||||
return kCommonFields;
|
||||
}
|
||||
|
||||
@@ -634,16 +634,16 @@ STObject::getAccountID(SField const& field) const
|
||||
}
|
||||
|
||||
AccountID
|
||||
STObject::getFeePayer() const
|
||||
STObject::getInitiator() const
|
||||
{
|
||||
// If sfDelegate is present, the delegate account is the payer
|
||||
// If sfDelegate is present, the delegate account is the initiator
|
||||
// note: if a delegate is specified, its authorization to act on behalf of the account is
|
||||
// enforced in `Transactor::invokeCheckPermission`
|
||||
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
|
||||
if (isFieldPresent(sfDelegate))
|
||||
return getAccountID(sfDelegate);
|
||||
|
||||
// Default payer
|
||||
// Default initiator
|
||||
return getAccountID(sfAccount);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
@@ -271,6 +272,13 @@ STTx::checkSign(Rules const& rules) const
|
||||
return std::unexpected("Counterparty: " + ret.error());
|
||||
}
|
||||
|
||||
if (isFieldPresent(sfSponsorSignature))
|
||||
{
|
||||
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
|
||||
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
|
||||
return std::unexpected("Sponsor: " + ret.error());
|
||||
}
|
||||
|
||||
// Verify the batch signer signatures here too, so they are cached with the
|
||||
// rest of signature checking (checkValidity / SF_SIGGOOD) and stay out of
|
||||
// the transaction engine. Gated on a batch (batchTxnIds_ seated) that
|
||||
@@ -551,7 +559,7 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
|
||||
// For delegated transactions sfDelegate is the account whose signer list is checked,
|
||||
// the delegate account itself can not be among the signers.
|
||||
auto const txnAccountID =
|
||||
&sigObject != this ? std::nullopt : std::optional<AccountID>(getFeePayer());
|
||||
&sigObject != this ? std::nullopt : std::optional<AccountID>(getInitiator());
|
||||
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
// pre-constructing part of the data that we hash. Fill a Serializer
|
||||
@@ -603,6 +611,15 @@ STTx::getBatchTransactionIDs() const
|
||||
return *batchTxnIds_;
|
||||
}
|
||||
|
||||
AccountID
|
||||
STTx::getFeePayerID() const
|
||||
{
|
||||
if (isFieldPresent(sfSponsor) && ((getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u))
|
||||
return at(sfSponsor);
|
||||
|
||||
return getInitiator();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static bool
|
||||
|
||||
@@ -107,6 +107,7 @@ transResults()
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
|
||||
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -220,6 +221,7 @@ transResults()
|
||||
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
|
||||
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(terLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(terNO_PERMISSION, "No permission to perform requested operation."),
|
||||
|
||||
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
|
||||
};
|
||||
|
||||
@@ -30,6 +30,9 @@ TxFormats::getCommonFields()
|
||||
{sfSigners, SoeOptional}, // submit_multisigned
|
||||
{sfNetworkID, SoeOptional},
|
||||
{sfDelegate, SoeOptional},
|
||||
{sfSponsor, SoeOptional},
|
||||
{sfSponsorFlags, SoeOptional},
|
||||
{sfSponsorSignature, SoeOptional},
|
||||
};
|
||||
return kCommonFields;
|
||||
}
|
||||
|
||||
+315
-22
@@ -17,6 +17,7 @@
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -41,6 +42,7 @@
|
||||
#include <xrpl/tx/apply.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
@@ -167,6 +169,58 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J
|
||||
|
||||
} // namespace detail
|
||||
|
||||
static NotTEC
|
||||
preflight1Sponsor(PreflightContext const& ctx)
|
||||
{
|
||||
bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
|
||||
bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
|
||||
bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
|
||||
|
||||
if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
|
||||
return temDISABLED;
|
||||
|
||||
if (hasSponsor != hasSponsorFlags)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (hasSponsorFlags)
|
||||
{
|
||||
auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
|
||||
if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
// Reserve sponsorship is only permitted for an explicit allow-list of
|
||||
// transaction types, for v1. All other tx types reject spfSponsorReserve here.
|
||||
if (isReserveSponsored(ctx.tx))
|
||||
{
|
||||
if (!isReserveSponsorAllowed(ctx.tx.getTxnType()))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "preflight1: spfSponsorReserve not allowed for this transaction type";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == ctx.tx.getAccountID(sfAccount))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the same as the account";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Performs early sanity checks on the account and fee fields */
|
||||
NotTEC
|
||||
Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
@@ -230,6 +284,9 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
|
||||
if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value())
|
||||
return temINVALID_INNER_BATCH;
|
||||
|
||||
if (auto const ter = preflight1Sponsor(ctx); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -343,6 +400,44 @@ Transactor::checkPermission(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (!tx.isFieldPresent(sfSponsor))
|
||||
return tesSUCCESS;
|
||||
|
||||
// Reserve sponsorship with permissioned delegation is disallowed.
|
||||
if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx))
|
||||
return temINVALID;
|
||||
|
||||
if (!view.exists(keylet::account(tx.getAccountID(sfSponsor))))
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Skip Sponsorship existence checks if the sponsor has signed the transaction - this
|
||||
// transaction is valid regardless of the Sponsorship object.
|
||||
// The use of the Sponsorship object is properly handled in
|
||||
// getFeePayer/checkReserve/increaseOwnerCount/decreaseOwnerCount.
|
||||
if (tx.isFieldPresent(sfSponsorSignature))
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the transaction contains sfDelegate, the Sponsorship object should be
|
||||
// between the sponsor and the delegate.
|
||||
auto const sponsorshipSle =
|
||||
view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator()));
|
||||
|
||||
// sponsorship object missing for pre-funded (no co-signing) tx
|
||||
if (!sponsorshipSle)
|
||||
return terNO_PERMISSION;
|
||||
|
||||
if (isFeeSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee))
|
||||
return terNO_PERMISSION;
|
||||
|
||||
if (isReserveSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve))
|
||||
return terNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
@@ -351,6 +446,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
// The computation has two parts:
|
||||
// * The base fee, which is the same for most transactions.
|
||||
// * The additional cost of each multisignature on the transaction.
|
||||
// * The additional cost of each multisignature on the sponsor.
|
||||
XRPAmount const baseFee = view.fees().base;
|
||||
|
||||
// Each signer adds one more baseFee to the minimum required fee
|
||||
@@ -358,7 +454,15 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
std::size_t const signerCount =
|
||||
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
|
||||
|
||||
return baseFee + (signerCount * baseFee);
|
||||
std::size_t sponsorSignerCount = 0;
|
||||
if (tx.isFieldPresent(sfSponsorSignature))
|
||||
{
|
||||
auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
|
||||
if (sponsorObj.isFieldPresent(sfSigners))
|
||||
sponsorSignerCount += sponsorObj.getFieldArray(sfSigners).size();
|
||||
}
|
||||
|
||||
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -437,12 +541,51 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
if (feePaid == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const id = ctx.tx.getFeePayer();
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
auto const feePayer = getFeePayer(ctx.view, ctx.tx);
|
||||
auto const payerSle = ctx.view.read(feePayer.keylet);
|
||||
|
||||
auto const balance = (*sle)[sfBalance].xrp();
|
||||
if (!payerSle)
|
||||
{
|
||||
if (feePayer.type == FeePayerType::SponsorPreFunded)
|
||||
{
|
||||
// Sanity check: already checked in checkSponsor
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
|
||||
XRPAmount maxSpendable = beast::kZero;
|
||||
|
||||
if (feePayer.type == FeePayerType::SponsorPreFunded)
|
||||
{
|
||||
if (payerSle->getType() != ltSPONSORSHIP)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (payerSle->isFieldPresent(feePayer.balanceField))
|
||||
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
|
||||
if (payerSle->isFieldPresent(sfMaxFee))
|
||||
{
|
||||
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
|
||||
maxSpendable = std::min(maxSpendable, cap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (payerSle->getType() != ltACCOUNT_ROOT)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (feePayer.type == FeePayerType::SponsorCoSigned)
|
||||
{
|
||||
auto const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j);
|
||||
maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Because preclaim evaluates against a static readview, it
|
||||
// does not reflect fee deductions from other transactions paid by
|
||||
@@ -451,12 +594,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
// transactions, this check may pass optimistically.
|
||||
// The fee shortfall will be handled by the Transactor::reset mechanism,
|
||||
// which caps the fee to the remaining actual balance.
|
||||
if (balance < feePaid)
|
||||
if (maxSpendable < feePaid)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
|
||||
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
|
||||
<< " paid=" << to_string(feePaid);
|
||||
|
||||
if ((balance > beast::kZero) && !ctx.view.open())
|
||||
if ((maxSpendable > beast::kZero) && !ctx.view.open())
|
||||
{
|
||||
// Closed ledger, non-zero balance, less than fee
|
||||
return tecINSUFF_FEE;
|
||||
@@ -473,16 +616,72 @@ Transactor::payFee()
|
||||
{
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const feePayer = ctx_.tx.getFeePayer();
|
||||
auto const sle = view().peek(keylet::account(feePayer));
|
||||
auto const feePayer = getFeePayer(view(), ctx_.tx);
|
||||
auto const sle = view().peek(feePayer.keylet);
|
||||
|
||||
JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.id);
|
||||
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
|
||||
if (feePayer != accountID_)
|
||||
view().update(sle); // done in `apply()` for the account
|
||||
if (feePaid == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
|
||||
XRPAmount balance = beast::kZero;
|
||||
if (sle->isFieldPresent(feePayer.balanceField))
|
||||
{
|
||||
balance = sle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
}
|
||||
else if (feePayer.balanceField != sfFeeAmount)
|
||||
{
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// A co-signed sponsor pays the fee out of its own account balance, but must
|
||||
// never be charged into its account reserve, and a pre-funded sponsorship's
|
||||
// fee is capped by sfMaxFee. Mirror the spendable amount computed in
|
||||
// checkFee() so both limits are enforced on the apply path too.
|
||||
XRPAmount spendable = balance;
|
||||
if (feePayer.type == FeePayerType::SponsorCoSigned)
|
||||
{
|
||||
auto const sponsorReserve = accountReserve(view(), sle, j_);
|
||||
// max(balance - reserve, 0) with overflow handling
|
||||
spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
|
||||
}
|
||||
else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee))
|
||||
{
|
||||
auto const cap = sle->getFieldAmount(sfMaxFee).xrp();
|
||||
spendable = std::min(spendable, cap);
|
||||
}
|
||||
|
||||
// Only sponsor fee-payers reject here on insufficient funds. For an
|
||||
// ordinary account, the fee falls through and is capped by reset(), which
|
||||
// caps to the account's balance. That capping is wrong for sponsors: a
|
||||
// co-signed sponsor would be charged into its own reserve, and a prefunded
|
||||
// sponsorship's fee amount should be rejected rather than partially spent.
|
||||
if (feePaid > spendable &&
|
||||
(feePayer.type == FeePayerType::SponsorPreFunded ||
|
||||
feePayer.type == FeePayerType::SponsorCoSigned))
|
||||
{
|
||||
if ((spendable > beast::kZero) && !view().open())
|
||||
return tecINSUFF_FEE;
|
||||
|
||||
return terINSUF_FEE_B;
|
||||
}
|
||||
|
||||
auto const feeAmountAfter = balance - feePaid;
|
||||
|
||||
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
|
||||
{
|
||||
// Because ltSponsorship.sfFeeAmount is soeOptional
|
||||
sle->makeFieldAbsent(feePayer.balanceField);
|
||||
}
|
||||
else
|
||||
{
|
||||
sle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
|
||||
}
|
||||
|
||||
view().update(sle);
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
return tesSUCCESS;
|
||||
@@ -656,7 +855,7 @@ Transactor::ticketDelete(
|
||||
}
|
||||
|
||||
// Update the Ticket owner's reserve.
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j);
|
||||
|
||||
// Remove Ticket from ledger.
|
||||
view.erase(sleTicket);
|
||||
@@ -750,6 +949,21 @@ Transactor::checkSign(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (sigObject.isFieldPresent(sfSponsorSignature))
|
||||
{
|
||||
// Co-signed sponsorship
|
||||
|
||||
// Sanity check: already checked in preflight1
|
||||
if (!sigObject.isFieldPresent(sfSponsor))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsorID = sigObject.getAccountID(sfSponsor);
|
||||
auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
|
||||
if (auto const ret = checkSign(view, flags, std::nullopt, sponsorID, sponsorSignature, j);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If the pk is empty and not simulate or simulate and signers,
|
||||
// then we must be multi-signing.
|
||||
if (sigObject.isFieldPresent(sfSigners))
|
||||
@@ -1079,11 +1293,38 @@ Transactor::reset(XRPAmount fee)
|
||||
if (!txnAcct)
|
||||
return {tefINTERNAL, beast::kZero};
|
||||
|
||||
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
|
||||
auto const feePayer = getFeePayer(view(), ctx_.tx);
|
||||
auto const payerSle = view().peek(feePayer.keylet);
|
||||
|
||||
if (!payerSle)
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
|
||||
XRPAmount balance = beast::kZero;
|
||||
if (payerSle->isFieldPresent(feePayer.balanceField))
|
||||
{
|
||||
balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
}
|
||||
else if (feePayer.balanceField != sfFeeAmount)
|
||||
{
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
|
||||
{
|
||||
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
|
||||
fee = std::min(fee, cap);
|
||||
}
|
||||
|
||||
// A co-signed sponsor must never be charged into its own account reserve,
|
||||
// so the fee is capped to the balance above the reserve rather than to the
|
||||
// full balance.
|
||||
XRPAmount spendable = balance;
|
||||
if (feePayer.type == FeePayerType::SponsorCoSigned)
|
||||
{
|
||||
auto const sponsorReserve = accountReserve(view(), payerSle, j_);
|
||||
// max(balance - reserve, 0) with overflow handling
|
||||
spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
|
||||
}
|
||||
|
||||
// balance should have already been checked in checkFee / preFlight.
|
||||
XRPL_ASSERT(
|
||||
@@ -1093,8 +1334,8 @@ Transactor::reset(XRPAmount fee)
|
||||
// We retry/reject the transaction if the account balance is zero or
|
||||
// we're applying against an open ledger and the balance is less than
|
||||
// the fee
|
||||
if (fee > balance)
|
||||
fee = balance;
|
||||
if (fee > spendable)
|
||||
fee = spendable;
|
||||
|
||||
// Since we reset the context, we need to charge the fee and update
|
||||
// the account's sequence number (or consume the Ticket) again.
|
||||
@@ -1102,7 +1343,17 @@ Transactor::reset(XRPAmount fee)
|
||||
// If for some reason we are unable to consume the ticket or sequence
|
||||
// then the ledger is corrupted. Rather than make things worse we
|
||||
// reject the transaction.
|
||||
payerSle->setFieldAmount(sfBalance, balance - fee);
|
||||
auto const feeAmountAfter = balance - fee;
|
||||
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
|
||||
{
|
||||
// Because ltSponsorship.sfFeeAmount is soeOptional
|
||||
payerSle->makeFieldAbsent(feePayer.balanceField);
|
||||
}
|
||||
else
|
||||
{
|
||||
payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
|
||||
}
|
||||
|
||||
TER const ter{consumeSeqProxy(txnAcct)};
|
||||
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
|
||||
|
||||
@@ -1116,6 +1367,48 @@ Transactor::reset(XRPAmount fee)
|
||||
return {ter, fee};
|
||||
}
|
||||
|
||||
FeePayer
|
||||
Transactor::getFeePayer(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx))
|
||||
{
|
||||
auto const sponsorID = tx.getAccountID(sfSponsor);
|
||||
auto const sponseeID = tx.getInitiator();
|
||||
auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
|
||||
|
||||
// if pre-funded sponsorship exists, prefer it
|
||||
if (view.exists(sponsorshipKeylet))
|
||||
{
|
||||
// pre funded
|
||||
return FeePayer{
|
||||
.id = sponsorID,
|
||||
.keylet = sponsorshipKeylet,
|
||||
.balanceField = sfFeeAmount,
|
||||
.type = FeePayerType::SponsorPreFunded};
|
||||
}
|
||||
|
||||
// Checked in Transactor::checkSponsor
|
||||
XRPL_ASSERT(
|
||||
tx.isFieldPresent(sfSponsorSignature),
|
||||
"xrpl::getFeePayer has sponsor signature without a sponsorship object");
|
||||
|
||||
// co-signed
|
||||
return FeePayer{
|
||||
.id = sponsorID,
|
||||
.keylet = keylet::account(sponsorID),
|
||||
.balanceField = sfBalance,
|
||||
.type = FeePayerType::SponsorCoSigned};
|
||||
}
|
||||
|
||||
AccountID const payerID = tx.getInitiator();
|
||||
auto const payerAccountKeylet = keylet::account(payerID);
|
||||
auto const payerType =
|
||||
tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account;
|
||||
|
||||
return FeePayer{
|
||||
.id = payerID, .keylet = payerAccountKeylet, .balanceField = sfBalance, .type = payerType};
|
||||
}
|
||||
|
||||
// The sole purpose of this function is to provide a convenient, named
|
||||
// location to set a breakpoint, to be used when replaying transactions.
|
||||
void
|
||||
|
||||
@@ -181,6 +181,9 @@ invokePreclaim(PreclaimContext const& ctx)
|
||||
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx))
|
||||
return result;
|
||||
|
||||
if (NotTEC const result =
|
||||
Transactor::invokeCheckPermission<T>(ctx.view, ctx.tx))
|
||||
return result;
|
||||
|
||||
@@ -148,6 +148,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (before->isFieldPresent(sfFeeAmount))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
isXRP((*before)[sfFeeAmount]),
|
||||
"XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP");
|
||||
drops_ -= (*before)[sfFeeAmount].xrp().drops();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -168,6 +177,15 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += (*after)[sfAmount].xrp().drops();
|
||||
break;
|
||||
case ltSPONSORSHIP:
|
||||
if (!isDelete && after->isFieldPresent(sfFeeAmount))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
isXRP((*after)[sfFeeAmount]),
|
||||
"XRPNotCreated::visitEntry : Sponsorship.FeeAmount is XRP");
|
||||
drops_ += (*after)[sfFeeAmount].xrp().drops();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -515,6 +533,20 @@ AccountRootsDeletedClean::finalize(
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
// An account should not be deleted with sponsorship fields
|
||||
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
|
||||
after->isFieldPresent(sfSponsoringOwnerCount) ||
|
||||
after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: account deletion left "
|
||||
"behind a sponsorship field";
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"xrpl::AccountRootsDeletedClean::finalize : "
|
||||
"deleted account has no sponsorship fields");
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
// Simple types
|
||||
for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets)
|
||||
{
|
||||
@@ -881,8 +913,10 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
|
||||
// 1. Exactly one of the pseudo-account fields is set.
|
||||
// 2. The sequence number is not changed.
|
||||
// 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
|
||||
// flags are set.
|
||||
// flags are set.
|
||||
// 4. The RegularKey is not set.
|
||||
// 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor
|
||||
// fields are not set.
|
||||
{
|
||||
std::vector<SField const*> const& fields = getPseudoAccountFields();
|
||||
|
||||
@@ -908,6 +942,12 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
|
||||
{
|
||||
errors_.emplace_back("pseudo-account has a regular key");
|
||||
}
|
||||
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
|
||||
after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) ||
|
||||
after->isFieldPresent(sfSponsoringAccountCount))
|
||||
{
|
||||
errors_.emplace_back("pseudo-account has a sponsorship field");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
|
||||
//
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Add new sponsorship-related invariants implementations
|
||||
void
|
||||
SponsorshipOwnerCountsMatch::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
auto getSponsored = [](SLE::const_ref sle) -> std::uint32_t {
|
||||
if (sle && sle->getType() == ltACCOUNT_ROOT)
|
||||
return sle->getFieldU32(sfSponsoredOwnerCount);
|
||||
return 0;
|
||||
};
|
||||
auto getSponsoring = [](SLE::const_ref sle) -> std::uint32_t {
|
||||
if (sle && sle->getType() == ltACCOUNT_ROOT)
|
||||
return sle->getFieldU32(sfSponsoringOwnerCount);
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto getOwnerCount = [](SLE::const_ref sle) -> std::uint32_t {
|
||||
if (sle && sle->getType() == ltACCOUNT_ROOT)
|
||||
return sle->getFieldU32(sfOwnerCount);
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto getSponsoredObjectOwnerCount = [&](SLE::const_ref sle) -> std::uint32_t {
|
||||
if (!sle)
|
||||
return 0;
|
||||
switch (sle->getType())
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
return 0;
|
||||
case ltRIPPLE_STATE: {
|
||||
// A trust line can be reserve-sponsored independently on each
|
||||
// side, so it may contribute up to two sponsored owner counts.
|
||||
uint32_t ownerCount = 0;
|
||||
if (sle->isFieldPresent(sfHighSponsor))
|
||||
ownerCount++;
|
||||
if (sle->isFieldPresent(sfLowSponsor))
|
||||
ownerCount++;
|
||||
return ownerCount;
|
||||
}
|
||||
default:
|
||||
// Every other supported type carries a single sfSponsor field
|
||||
// and contributes its full owner-count magnitude only when it is
|
||||
// sponsored.
|
||||
if (!sle->isFieldPresent(sfSponsor))
|
||||
return 0;
|
||||
return getLedgerEntryOwnerCount(*sle);
|
||||
}
|
||||
};
|
||||
|
||||
// The values are implicitly casted to std::int64_t to calculate deltas.
|
||||
std::int64_t const beforeSponsored = getSponsored(before);
|
||||
std::int64_t const afterSponsored = getSponsored(after);
|
||||
std::int64_t const beforeSponsoring = getSponsoring(before);
|
||||
std::int64_t const afterSponsoring = getSponsoring(after);
|
||||
|
||||
std::int64_t const beforeSponsoredObjectOwnerCount = getSponsoredObjectOwnerCount(before);
|
||||
std::int64_t const afterSponsoredObjectOwnerCount =
|
||||
isDelete ? 0 : getSponsoredObjectOwnerCount(after);
|
||||
|
||||
deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored);
|
||||
deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring);
|
||||
|
||||
deltaSponsoredObjectOwnerCount_ +=
|
||||
(afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount);
|
||||
|
||||
if (getOwnerCount(after) < getSponsored(after))
|
||||
ownerCountBelowSponsored_ += 1;
|
||||
}
|
||||
|
||||
bool
|
||||
SponsorshipOwnerCountsMatch::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not "
|
||||
"equal SponsoringOwnerCount delta.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ownerCountBelowSponsored_ > 0)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not "
|
||||
"equal SponsoredOwnerCount delta.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SponsorshipAccountCountMatchesField::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
auto getSponsoringAccountCount = [](SLE::const_ref sle) -> std::uint32_t {
|
||||
if (sle && sle->getType() == ltACCOUNT_ROOT)
|
||||
return sle->getFieldU32(sfSponsoringAccountCount);
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto hasSponsorField = [](SLE::const_ref sle) -> bool {
|
||||
return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor);
|
||||
};
|
||||
|
||||
std::int64_t const beforeCount = getSponsoringAccountCount(before);
|
||||
std::int64_t const afterCount = getSponsoringAccountCount(after);
|
||||
deltaSponsoringAccountCount_ += (afterCount - beforeCount);
|
||||
|
||||
int const beforePresent = hasSponsorField(before) ? 1 : 0;
|
||||
int const afterPresent = hasSponsorField(after) ? 1 : 0;
|
||||
deltaSponsorFieldPresence_ += (afterPresent - beforePresent);
|
||||
}
|
||||
|
||||
bool
|
||||
SponsorshipAccountCountMatchesField::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not "
|
||||
"match net delta of sfSponsor presence.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -222,7 +222,10 @@ ValidVault::deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const
|
||||
if (!ret.has_value() || !vaultAsset.native())
|
||||
return ret;
|
||||
|
||||
if (auto const delegate = tx[~sfDelegate]; delegate.has_value() && *delegate != tx[sfAccount])
|
||||
// Only add the fee back if tx[sfAccount] actually paid it. When the fee is
|
||||
// paid by someone else (a delegate or a fee sponsor), the
|
||||
// account's XRP balance moved only by the vault amount.
|
||||
if (tx.getFeePayerID() != tx[sfAccount])
|
||||
return ret;
|
||||
|
||||
ret->delta += fee.drops();
|
||||
@@ -1044,10 +1047,8 @@ ValidVault::finalize(
|
||||
|
||||
case ttLOAN_SET:
|
||||
case ttLOAN_MANAGE:
|
||||
case ttLOAN_PAY: {
|
||||
// TBD
|
||||
case ttLOAN_PAY:
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
// LCOV_EXCL_START
|
||||
|
||||
@@ -731,7 +731,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
// Create MPToken for the offer's owner. No need to check
|
||||
// for the reserve since the offer is removed if it is consumed.
|
||||
// Therefore, the owner count remains the same.
|
||||
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
|
||||
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, {}, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -410,7 +410,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio
|
||||
// for the reserve since the offer doesn't go on the books
|
||||
// if crossed. Insufficient reserve is allowed if the offer
|
||||
// crossed. See CreateOffer::applyGuts() for reserve check.
|
||||
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
|
||||
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, {}, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
|
||||
resetCache(srcDebtDir);
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
AccountDelete::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -268,6 +267,15 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
if (cp)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if (sleAccount->isFieldPresent(sfSponsor))
|
||||
{
|
||||
if (dst != sleAccount->getAccountID(sfSponsor))
|
||||
return tecNO_SPONSOR_PERMISSION;
|
||||
}
|
||||
if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) ||
|
||||
sleAccount->isFieldPresent(sfSponsoringAccountCount))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// We don't allow an account to be deleted if its sequence number
|
||||
// is within 256 of the current ledger. This prevents replay of old
|
||||
// transactions if this account is resurrected after it is deleted.
|
||||
@@ -394,6 +402,35 @@ AccountDelete::doApply()
|
||||
(*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
|
||||
ctx_.deliver(remainingBalance);
|
||||
|
||||
if (src->isFieldPresent(sfSponsor))
|
||||
{
|
||||
auto const sponsorID = src->getAccountID(sfSponsor);
|
||||
auto sponsorSle = view().peek(keylet::account(sponsorID));
|
||||
|
||||
if (!sponsorSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
|
||||
|
||||
XRPL_ASSERT(
|
||||
sponsoringAccountCount != 0,
|
||||
"xrpl::AccountDelete::doApply : sponsoring account count is present");
|
||||
if (sponsoringAccountCount == 0)
|
||||
{
|
||||
// sanity check
|
||||
// Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be
|
||||
// present with a value of 0.
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1;
|
||||
view().update(sponsorSle);
|
||||
|
||||
// Following line might look redundant, but without it, sfSponsor
|
||||
// would end up remaining in after-ltAccountRoot during the
|
||||
// InvariantCheck.
|
||||
src->makeFieldAbsent(sfSponsor);
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
(*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -147,9 +148,7 @@ SignerListSet::preCompute()
|
||||
Transactor::preCompute();
|
||||
}
|
||||
|
||||
// The return type is signed so it is compatible with the 3rd argument
|
||||
// of adjustOwnerCount() (which must be signed).
|
||||
static int
|
||||
static std::uint32_t
|
||||
signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
|
||||
{
|
||||
// We always compute the full change in OwnerCount, taking into account:
|
||||
@@ -165,7 +164,7 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
|
||||
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
|
||||
//
|
||||
// The static_cast should always be safe since entryCount should always
|
||||
// be in the range from 1 to 32.
|
||||
// be in the range from 1 to 32, so the result is always positive.
|
||||
// We've got a lot of room to grow.
|
||||
XRPL_ASSERT(
|
||||
entryCount >= STTx::kMinMultiSigners,
|
||||
@@ -196,12 +195,11 @@ removeSignersFromLedger(
|
||||
// There are two different ways that the OwnerCount could be managed.
|
||||
// If the lsfOneOwnerCount bit is set then remove just one owner count.
|
||||
// Otherwise use the pre-MultiSignReserve amendment calculation.
|
||||
int removeFromOwnerCount = -1;
|
||||
std::uint32_t removeFromOwnerCount = 1;
|
||||
if (!signers->isFlag(lsfOneOwnerCount))
|
||||
{
|
||||
STArray const& actualList = signers->getFieldArray(sfSignerEntries);
|
||||
removeFromOwnerCount =
|
||||
signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1;
|
||||
removeFromOwnerCount = signerCountBasedOwnerCountDelta(actualList.size(), view.rules());
|
||||
}
|
||||
|
||||
// Remove the node from the account directory.
|
||||
@@ -215,8 +213,8 @@ removeSignersFromLedger(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
|
||||
decreaseOwnerCountForObject(
|
||||
view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View"));
|
||||
|
||||
view.erase(signers);
|
||||
|
||||
@@ -315,19 +313,20 @@ SignerListSet::replaceSignerList()
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
|
||||
|
||||
static constexpr int kAddedOwnerCount = 1;
|
||||
std::uint32_t const flags{lsfOneOwnerCount};
|
||||
|
||||
XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)};
|
||||
|
||||
// We check the reserve against the starting balance because we want to
|
||||
// allow dipping into the reserve to pay fees. This behavior is consistent
|
||||
// with TicketCreate.
|
||||
if (preFeeBalance_ < newReserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sle,
|
||||
preFeeBalance_,
|
||||
{.ownerCountDelta = kAddedOwnerCount},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
auto signerList = std::make_shared<SLE>(signerListKeylet);
|
||||
@@ -349,7 +348,8 @@ SignerListSet::replaceSignerList()
|
||||
|
||||
// If we succeeded, the new entry counts against the
|
||||
// creator's reserve.
|
||||
adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, kAddedOwnerCount, viewJ);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), signerList);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -435,8 +435,7 @@ transferHelper(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
|
||||
auto const reserve = psb.fees().accountReserve(ownerCount);
|
||||
auto const reserve = accountReserve(psb, sleSrc, j);
|
||||
|
||||
auto const availableBalance = [&]() -> STAmount {
|
||||
STAmount curBal = (*sleSrc)[sfBalance];
|
||||
@@ -726,9 +725,8 @@ finalizeClaimHelper(
|
||||
}
|
||||
|
||||
// Remove the claim id from the ledger
|
||||
decreaseOwnerCountForObject(outerSb, sleOwner, sleClaimID, 1, j);
|
||||
outerSb.erase(sleClaimID);
|
||||
|
||||
adjustOwnerCount(outerSb, sleOwner, -1, j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,7 +1026,7 @@ applyCreateAccountAttestations(
|
||||
|
||||
// Check reserve
|
||||
auto const balance = (*sleDoor)[sfBalance];
|
||||
auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
|
||||
auto const reserve = accountReserve(psb, sleDoor, j, {.ownerCountDelta = 1});
|
||||
|
||||
if (balance < reserve)
|
||||
return std::unexpected(tecINSUFFICIENT_RESERVE);
|
||||
@@ -1137,7 +1135,7 @@ applyCreateAccountAttestations(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Reserve was already checked
|
||||
adjustOwnerCount(psb, sleDoor, 1, j);
|
||||
increaseOwnerCount(psb, sleDoor, {}, 1, j);
|
||||
psb.insert(createdSleClaimID);
|
||||
psb.update(sleDoor);
|
||||
}
|
||||
@@ -1439,7 +1437,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -1484,7 +1482,7 @@ XChainCreateBridge::doApply()
|
||||
(*sleBridge)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
|
||||
|
||||
ctx_.view().insert(sleBridge);
|
||||
ctx_.view().update(sleAcct);
|
||||
@@ -1987,8 +1985,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
|
||||
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -2046,7 +2043,7 @@ XChainCreateClaimID::doApply()
|
||||
(*sleClaimID)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.view(), sleAcct, {}, 1, ctx_.journal);
|
||||
|
||||
ctx_.view().insert(sleClaimID);
|
||||
ctx_.view().update(sleBridge);
|
||||
|
||||
@@ -91,8 +91,7 @@ CheckCancel::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
auto const sleSrc = view().peek(keylet::account(srcId));
|
||||
adjustOwnerCount(view(), sleSrc, -1, viewJ);
|
||||
decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
view().erase(sleCheck);
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -30,7 +32,7 @@
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -175,7 +177,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// once the check is cashed, since the check's reserve will no
|
||||
// longer be required. So, if we're dealing in XRP, we add one
|
||||
// reserve's worth to the available funds.
|
||||
if (value.native())
|
||||
if (value.native() && !sleCheck->isFieldPresent(sfSponsor))
|
||||
availableFunds += XRPAmount{ctx.view.fees().increment};
|
||||
|
||||
if (value > availableFunds)
|
||||
@@ -307,6 +309,8 @@ CheckCash::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
|
||||
|
||||
// Preclaim already checked that source has at least the requested
|
||||
// funds.
|
||||
//
|
||||
@@ -335,7 +339,7 @@ CheckCash::doApply()
|
||||
// from src's directory, we allow them to send that additional
|
||||
// incremental reserve amount in the transfer. Hence the -1
|
||||
// argument.
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)};
|
||||
|
||||
// Now, how much do they need in order to be successful?
|
||||
STAmount const xrpDeliver{
|
||||
@@ -385,14 +389,25 @@ CheckCash::doApply()
|
||||
STAmount const flowDeliver{
|
||||
optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)};
|
||||
|
||||
auto applyViewContext = ApplyViewContext({.view = psb, .tx = ctx_.tx});
|
||||
auto const sponsorSle = getTxReserveSponsor(applyViewContext);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
// Check reserve. Return destination account SLE if enough reserve,
|
||||
// otherwise return nullptr.
|
||||
auto checkReserve = [&]() -> SLE::pointer {
|
||||
auto checkDstReserve = [&]() -> SLE::pointer {
|
||||
auto sleDst = psb.peek(keylet::account(accountID_));
|
||||
|
||||
// Can the account cover the trust line's or MPT reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
|
||||
if (auto const ret = checkReserve(
|
||||
applyViewContext,
|
||||
sleDst,
|
||||
preFeeBalance_,
|
||||
*sponsorSle,
|
||||
{.ownerCountDelta = 1},
|
||||
j_);
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||
"Insufficient reserve to create line.";
|
||||
@@ -426,7 +441,7 @@ CheckCash::doApply()
|
||||
// a. this (destination) account and
|
||||
// b. issuing account (not sending account).
|
||||
|
||||
auto const sleDst = checkReserve();
|
||||
auto const sleDst = checkDstReserve();
|
||||
if (sleDst == nullptr)
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
@@ -449,6 +464,7 @@ CheckCash::doApply()
|
||||
Issue(currency, accountID_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
*sponsorSle, // sponsor
|
||||
viewJ); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
@@ -491,11 +507,12 @@ CheckCash::doApply()
|
||||
auto const mptokenKey = keylet::mptoken(mptID, accountID_);
|
||||
if (!psb.exists(mptokenKey))
|
||||
{
|
||||
auto sleDst = checkReserve();
|
||||
auto sleDst = checkDstReserve();
|
||||
if (sleDst == nullptr)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
|
||||
if (auto const err =
|
||||
checkCreateMPT(psb, mptID, accountID_, *sponsorSle, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -581,7 +598,7 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
|
||||
decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
psb.erase(sleCheck);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -194,13 +195,10 @@ CheckCreate::doApply()
|
||||
// A check counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(), sle, preFeeBalance_, {.ownerCountDelta = 1}, ctx_.journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqValue();
|
||||
@@ -253,7 +251,9 @@ CheckCreate::doApply()
|
||||
sleCheck->setFieldU64(sfOwnerNode, *page);
|
||||
}
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sle, 1, viewJ);
|
||||
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -93,12 +93,19 @@ CredentialAccept::doApply()
|
||||
if (!sleSubject || !sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
auto txSponsorSle = getTxReserveSponsor(ctx_.getApplyViewContext());
|
||||
if (!txSponsorSle)
|
||||
return txSponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sleSubject,
|
||||
preFeeBalance_,
|
||||
*txSponsorSle,
|
||||
{.ownerCountDelta = 1},
|
||||
j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(accountID_, issuer, credType);
|
||||
@@ -115,10 +122,16 @@ CredentialAccept::doApply()
|
||||
}
|
||||
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, -1, j_);
|
||||
adjustOwnerCount(view(), sleSubject, 1, j_);
|
||||
// Release the original creation sponsor from the credential (it covered
|
||||
// the issuer's reserve), then assign the accept tx's sponsor (if any) so
|
||||
// the credential reflects whoever is now covering the subject's reserve.
|
||||
decreaseOwnerCountForObject(view(), sleIssuer, sleCred, 1, j_);
|
||||
removeSponsorFromLedgerEntry(sleCred);
|
||||
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sleSubject, 1, j_);
|
||||
view().update(sleCred);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h> // IWYU pragma: keep
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -130,12 +130,10 @@ CredentialCreate::doApply()
|
||||
if (!sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(), sleIssuer, preFeeBalance_, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
sleCred->setAccountID(sfSubject, subject);
|
||||
sleCred->setAccountID(sfIssuer, accountID_);
|
||||
@@ -153,7 +151,8 @@ CredentialCreate::doApply()
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfIssuerNode, *page);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sleIssuer, 1, j_);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCred);
|
||||
}
|
||||
|
||||
if (subject == accountID_)
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -98,11 +98,14 @@ DelegateSet::doApply()
|
||||
if (permissions.empty())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const reserve{
|
||||
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
if (auto const ret = checkReserve(
|
||||
ctx_.getApplyViewContext(),
|
||||
sleOwner,
|
||||
preFeeBalance_,
|
||||
{.ownerCountDelta = 1},
|
||||
ctx_.journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
sle = std::make_shared<SLE>(delegateKey);
|
||||
sle->setAccountID(sfAccount, accountID_);
|
||||
@@ -130,7 +133,8 @@ DelegateSet::doApply()
|
||||
(*sle)[sfDestinationNode] = *destPage;
|
||||
|
||||
ctx_.view().insert(sle);
|
||||
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sleOwner, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -170,7 +174,7 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j)
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -332,17 +332,30 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
|
||||
return err;
|
||||
}
|
||||
|
||||
if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err))
|
||||
if (auto const err = createMPToken(sb, mptID, accountId, {}, flags);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
// Don't adjust AMM owner count.
|
||||
// It's irrelevant for pseudo-account like AMM.
|
||||
return accountSend(
|
||||
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes);
|
||||
sb,
|
||||
account,
|
||||
accountId,
|
||||
amount,
|
||||
ctx.journal,
|
||||
{}, // don't sponsor for AMM Trustline
|
||||
WaiveTransferFee::Yes);
|
||||
},
|
||||
// Set AMM flag on AMM trustline
|
||||
[&](Issue const& issue) -> TER {
|
||||
if (auto const res = accountSend(
|
||||
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes))
|
||||
sb,
|
||||
account,
|
||||
accountId,
|
||||
amount,
|
||||
ctx.journal,
|
||||
{}, // don't sponsor for AMM Trustline
|
||||
WaiveTransferFee::Yes))
|
||||
return res;
|
||||
// Set AMM flag on AMM trustline
|
||||
if (!isXRP(amount))
|
||||
|
||||
@@ -618,7 +618,13 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
auto res = accountSend(
|
||||
view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
view,
|
||||
accountID_,
|
||||
ammAccount,
|
||||
amountDepositActual,
|
||||
ctx_.journal,
|
||||
{}, // don't sponsor for AMM Trustline
|
||||
WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
|
||||
@@ -642,6 +648,7 @@ AMMDeposit::deposit(
|
||||
ammAccount,
|
||||
*amount2DepositActual,
|
||||
ctx_.journal,
|
||||
{}, // don't sponsor for AMM Trustline
|
||||
WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
@@ -658,15 +659,15 @@ AMMWithdraw::withdraw(
|
||||
auto sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
STAmount const balance = (*sleAccount)[sfBalance];
|
||||
std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
|
||||
|
||||
auto const balance = (*sleAccount)[sfBalance]->xrp();
|
||||
// See also TrustSet::doApply() and MPTokenAuthorize::authorize()
|
||||
XRPAmount const reserve(
|
||||
(ownerCount < 2) ? XRPAmount(beast::kZero)
|
||||
: view.fees().accountReserve(ownerCount + 1));
|
||||
(ownerCount(sleAccount, journal) < 2)
|
||||
? XRPAmount(beast::kZero)
|
||||
: accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1}));
|
||||
|
||||
auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
|
||||
auto const balanceAdj = isIssue ? std::max(priorBalance, balance) : priorBalance;
|
||||
if (balanceAdj < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -683,7 +684,7 @@ AMMWithdraw::withdraw(
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
if (auto const err = checkCreateMPT(view, mptIssue, account, journal);
|
||||
if (auto const err = checkCreateMPT(view, mptIssue, account, {}, journal);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -700,7 +701,7 @@ AMMWithdraw::withdraw(
|
||||
|
||||
// Withdraw amountWithdraw
|
||||
auto res = accountSend(
|
||||
view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
|
||||
view, ammAccount, account, amountWithdrawActual, journal, {}, WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -719,7 +720,7 @@ AMMWithdraw::withdraw(
|
||||
return {res, STAmount{}, STAmount{}, STAmount{}};
|
||||
|
||||
res = accountSend(
|
||||
view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
|
||||
view, ammAccount, account, *amount2WithdrawActual, journal, {}, WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
|
||||
@@ -833,9 +833,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
return {tefINTERNAL, false};
|
||||
|
||||
{
|
||||
XRPAmount const reserve =
|
||||
sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
|
||||
|
||||
XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1});
|
||||
if (preFeeBalance_ < reserve)
|
||||
{
|
||||
// If we are here, the signing account had an insufficient reserve
|
||||
@@ -869,7 +867,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(sb, sleCreator, 1, viewJ);
|
||||
increaseOwnerCount(sb, sleCreator, {}, 1, viewJ);
|
||||
|
||||
JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : "
|
||||
<< to_string(saTakerGets.asset())
|
||||
|
||||
@@ -50,8 +50,7 @@ DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, b
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
view.update(sleOwner);
|
||||
decreaseOwnerCountForObject(view, sleOwner, sle, 1, j);
|
||||
|
||||
// Remove object from ledger
|
||||
view.erase(sle);
|
||||
|
||||
@@ -72,7 +72,8 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
|
||||
// Check reserve availability for new object creation
|
||||
{
|
||||
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
|
||||
auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
|
||||
auto const reserve =
|
||||
accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1});
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -89,7 +90,7 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
}
|
||||
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
|
||||
increaseOwnerCount(ctx.view(), sleAccount, {}, 1, ctx.journal);
|
||||
ctx.view().update(sleAccount);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -181,7 +181,7 @@ EscrowCancel::doApply()
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
ctx_.getApplyViewContext(),
|
||||
kParityRate,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
|
||||
preFeeBalance_,
|
||||
@@ -209,8 +209,7 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
@@ -434,16 +435,33 @@ EscrowCreate::doApply()
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance).xrp();
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
// First check: whoever is on the hook for the new owner increment
|
||||
// can cover it. When sponsored this hits the sponsor branch and
|
||||
// validates the sponsor's reserve + remaining credit. When
|
||||
// unsponsored this hits the source branch and validates the
|
||||
// source's pre-lock balance against base + (currentOC+1)*increment.
|
||||
if (auto const ret =
|
||||
checkReserve(ctx_.getApplyViewContext(), sle, balance, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount))
|
||||
{
|
||||
if (balance < reserve + STAmount(amount).xrp())
|
||||
// Second check (XRP escrow only): after locking the escrowed
|
||||
// amount, the source must still meet its own reserve floor. This is
|
||||
// always the source's own balance against the source's own reserve —
|
||||
// the sponsor's reserve was already validated above, and a sponsor
|
||||
// never covers the locked funds. We compare directly (rather than via
|
||||
// checkReserve) because that helper diverts to the sponsor's balance
|
||||
// when a sponsor is present and would ignore the source's post-lock
|
||||
// balance entirely. ownerCountDelta differs by case:
|
||||
// - sponsored: 0 — sponsor covers the new owner increment, so the
|
||||
// source only owes reserve for its current owners.
|
||||
// - unsponsored: 1 — source owes reserve including the new increment.
|
||||
auto const sourceReserve = accountReserve(
|
||||
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
|
||||
if (balance - STAmount(amount).xrp() < sourceReserve)
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
@@ -535,7 +553,8 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -340,6 +340,16 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// With the Sponsor amendment, release the escrow reserve before delivery.
|
||||
// Token delivery can auto-create a destination holding, and the same
|
||||
// sponsor (or the same account, for a self-escrow) may cover both the
|
||||
// escrow being removed and the holding being created. Without the
|
||||
// amendment, keep the legacy order: releasing early changes the reserve
|
||||
// arithmetic for self-escrows and would break consensus if not gated.
|
||||
bool const sponsorEnabled = ctx_.view().rules().enabled(featureSponsor);
|
||||
if (sponsorEnabled)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
if (isXRP(amount))
|
||||
@@ -359,7 +369,7 @@ EscrowFinish::doApply()
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
ctx_.getApplyViewContext(),
|
||||
lockedRate,
|
||||
sled,
|
||||
preFeeBalance_,
|
||||
@@ -389,10 +399,9 @@ EscrowFinish::doApply()
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
// Adjust source owner count (legacy position, pre-Sponsor)
|
||||
if (!sponsorEnabled)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
@@ -372,7 +372,7 @@ LoanBrokerCoverClawback::doApply()
|
||||
associateAsset(*sleBroker, vaultAsset);
|
||||
|
||||
// Transfer assets from pseudo-account to depositor.
|
||||
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
|
||||
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, {}, WaiveTransferFee::Yes);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -176,7 +176,7 @@ LoanBrokerCoverDeposit::doApply()
|
||||
|
||||
// Transfer assets from depositor to pseudo-account.
|
||||
if (auto ter =
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, {}, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
|
||||
// Increase the LoanBroker's CoverAvailable by Amount
|
||||
|
||||
@@ -211,7 +211,14 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
|
||||
return doWithdraw(
|
||||
ctx_.getApplyViewContext(),
|
||||
accountID_,
|
||||
dstAcct,
|
||||
brokerPseudoID,
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
j_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -155,11 +155,11 @@ LoanBrokerDelete::doApply()
|
||||
{
|
||||
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
|
||||
if (auto const ter = accountSend(
|
||||
view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes))
|
||||
view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
|
||||
if (auto ter = removeEmptyHolding(ctx_.getApplyViewContext(), brokerPseudoID, vaultAsset, j_))
|
||||
return ter;
|
||||
|
||||
auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
|
||||
@@ -186,8 +186,6 @@ LoanBrokerDelete::doApply()
|
||||
|
||||
view().erase(brokerPseudoSLE);
|
||||
|
||||
view().erase(broker);
|
||||
|
||||
{
|
||||
auto owner = view().peek(keylet::account(accountID_));
|
||||
if (!owner)
|
||||
@@ -195,9 +193,11 @@ LoanBrokerDelete::doApply()
|
||||
|
||||
// Decreases the owner count by two: one for the LoanBroker object, and
|
||||
// one for the pseudo-account.
|
||||
adjustOwnerCount(view(), owner, -2, j_);
|
||||
decreaseOwnerCountForObject(view(), owner, broker, 2, j_);
|
||||
}
|
||||
|
||||
view().erase(broker);
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -238,9 +238,8 @@ LoanBrokerSet::doApply()
|
||||
|
||||
// Increases the owner count by two: one for the LoanBroker object, and
|
||||
// one for the pseudo-account.
|
||||
adjustOwnerCount(view, owner, 2, j_);
|
||||
auto const ownerCount = owner->at(sfOwnerCount);
|
||||
if (preFeeBalance_ < view.fees().accountReserve(ownerCount))
|
||||
increaseOwnerCount(view, owner, {}, 2, j_);
|
||||
if (preFeeBalance_ < accountReserve(view, owner, j_))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
|
||||
@@ -249,7 +248,8 @@ LoanBrokerSet::doApply()
|
||||
auto& pseudo = *maybePseudo;
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
|
||||
if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
|
||||
if (auto ter = addEmptyHolding(
|
||||
ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
|
||||
return ter;
|
||||
|
||||
// Initialize data fields:
|
||||
|
||||
@@ -107,9 +107,8 @@ LoanDelete::doApply()
|
||||
view.erase(loanSle);
|
||||
|
||||
// Decrement the LoanBroker's owner count.
|
||||
// The broker's owner count is solely for the number of outstanding loans,
|
||||
// and is distinct from the broker's pseudo-account's owner count
|
||||
adjustOwnerCount(view, brokerSle, -1, j_);
|
||||
adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_);
|
||||
|
||||
// If there are no loans left, then any remaining debt must be forgiven,
|
||||
// because there is no other way to pay it back.
|
||||
if (brokerSle->at(sfOwnerCount) == 0)
|
||||
@@ -129,7 +128,7 @@ LoanDelete::doApply()
|
||||
}
|
||||
}
|
||||
// Decrement the borrower's owner count
|
||||
adjustOwnerCount(view, borrowerSle, -1, j_);
|
||||
decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_);
|
||||
|
||||
// These associations shouldn't do anything, but do them just to be safe
|
||||
associateAsset(*loanSle, vaultAsset);
|
||||
|
||||
@@ -292,6 +292,7 @@ LoanManage::defaultLoan(
|
||||
vaultSle->at(sfAccount),
|
||||
STAmount{vaultAsset, defaultCovered},
|
||||
j,
|
||||
{},
|
||||
WaiveTransferFee::Yes);
|
||||
}
|
||||
|
||||
|
||||
@@ -625,7 +625,11 @@ LoanPay::doApply()
|
||||
{
|
||||
// The broker may have deleted their holding. Recreate it if needed
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset, j_);
|
||||
ctx_.getApplyViewContext(),
|
||||
brokerPayee,
|
||||
brokerPayeeSle->at(sfBalance).value().xrp(),
|
||||
asset,
|
||||
j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx)
|
||||
|
||||
auto const& tx = ctx.tx;
|
||||
|
||||
if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
// Special case for Batch inner transactions
|
||||
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) &&
|
||||
!tx.isFieldPresent(sfCounterparty))
|
||||
@@ -512,12 +519,12 @@ LoanSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, borrowerSle, 1, j_);
|
||||
increaseOwnerCount(view, borrowerSle, {}, 1, j_);
|
||||
|
||||
{
|
||||
auto const ownerCount = borrowerSle->at(sfOwnerCount);
|
||||
auto const balance =
|
||||
accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
|
||||
if (balance < view.fees().accountReserve(ownerCount))
|
||||
if (balance < accountReserve(view, borrowerSle, j_))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
@@ -531,8 +538,9 @@ LoanSet::doApply()
|
||||
borrower == accountID_ || borrower == counterparty,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"borrower signed transaction");
|
||||
auto applyViewContext = ctx_.getApplyViewContext();
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
|
||||
applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists, and
|
||||
@@ -555,7 +563,11 @@ LoanSet::doApply()
|
||||
"broker owner signed transaction");
|
||||
|
||||
if (auto const ter = addEmptyHolding(
|
||||
view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
|
||||
applyViewContext,
|
||||
brokerOwner,
|
||||
brokerOwnerSle->at(sfBalance).value().xrp(),
|
||||
vaultAsset,
|
||||
j_);
|
||||
ter && ter != tecDUPLICATE)
|
||||
{
|
||||
// ignore tecDUPLICATE. That means the holding already exists,
|
||||
@@ -631,9 +643,7 @@ LoanSet::doApply()
|
||||
|
||||
// Update the balances in the loan broker
|
||||
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
|
||||
// The broker's owner count is solely for the number of outstanding loans,
|
||||
// and is distinct from the broker's pseudo-account's owner count
|
||||
adjustOwnerCount(view, brokerSle, 1, j_);
|
||||
adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_);
|
||||
loanSequenceProxy += 1;
|
||||
// The sequence should be extremely unlikely to roll over, but fail if it
|
||||
// does
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -387,8 +388,7 @@ NFTokenAcceptOffer::transferNFToken(
|
||||
auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
|
||||
if (buyerOwnerCountAfter > buyerOwnerCountBefore)
|
||||
{
|
||||
if (auto const reserve = view().fees().accountReserve(buyerOwnerCountAfter);
|
||||
buyerBalance < reserve)
|
||||
if (buyerBalance < accountReserve(view(), sleBuyer, j_))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -331,12 +332,14 @@ NFTokenMint::doApply()
|
||||
// allows NFTs to be added to the page (and burn fees) without
|
||||
// requiring the reserve to be met each time. The reserve is
|
||||
// only managed when a new NFT page or sell offer is added.
|
||||
if (auto const ownerCountAfter =
|
||||
view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount);
|
||||
auto const sleAccount = view().read(keylet::account(accountID_));
|
||||
if (!sleAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount);
|
||||
ownerCountAfter > ownerCountBefore)
|
||||
{
|
||||
if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
|
||||
preFeeBalance_ < reserve)
|
||||
if (preFeeBalance_ < accountReserve(view(), sleAccount, j_))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -13,6 +14,8 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
@@ -68,10 +71,8 @@ OracleDelete::deleteOracle(
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const count = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? -2 : -1;
|
||||
|
||||
adjustOwnerCount(view, sleOwner, count, j);
|
||||
|
||||
std::uint32_t const count = calculateOracleReserve(sle);
|
||||
decreaseOwnerCountForObject(view, sleOwner, sle, count, j);
|
||||
view.erase(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <xrpl/tx/transactors/oracle/OracleSet.h>
|
||||
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OracleHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
@@ -22,6 +24,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -150,8 +153,9 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
if (!pairsDel.empty())
|
||||
return tecTOKEN_PAIR_NOT_FOUND;
|
||||
|
||||
auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
|
||||
auto const newCount = pairs.size() > 5 ? 2 : 1;
|
||||
auto const oldCount = calculateOracleReserve(sle);
|
||||
auto const newCount = calculateOracleReserve(pairs);
|
||||
|
||||
adjustReserve = newCount - oldCount;
|
||||
}
|
||||
else
|
||||
@@ -160,7 +164,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass))
|
||||
return temMALFORMED;
|
||||
adjustReserve = pairs.size() > 5 ? 2 : 1;
|
||||
adjustReserve = calculateOracleReserve(pairs);
|
||||
}
|
||||
|
||||
if (pairs.empty())
|
||||
@@ -169,7 +173,7 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecARRAY_TOO_LARGE;
|
||||
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
|
||||
accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve});
|
||||
auto const& balance = sleSetter->getFieldAmount(sfBalance);
|
||||
|
||||
if (balance < reserve)
|
||||
@@ -179,11 +183,22 @@ OracleSet::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static bool
|
||||
adjustOwnerCount(ApplyContext& ctx, int count)
|
||||
adjustOracleOwnerCount(ApplyContext& ctx, int count)
|
||||
{
|
||||
XRPL_ASSERT(std::abs(count) <= 2, "xrpl::adjustOracleOwnerCount abs(counter) <= 2");
|
||||
|
||||
if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
|
||||
{
|
||||
adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
|
||||
if (count > 0)
|
||||
{
|
||||
increaseOwnerCount(
|
||||
ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(count), ctx.journal);
|
||||
}
|
||||
else if (count < 0)
|
||||
{
|
||||
decreaseOwnerCount(
|
||||
ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(-count), ctx.journal);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,7 +243,7 @@ OracleSet::doApply()
|
||||
priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
|
||||
pairs.emplace(tokenPairKey(entry), std::move(priceData));
|
||||
}
|
||||
auto const oldCount = pairs.size() > 5 ? 2 : 1;
|
||||
auto const oldCount = calculateOracleReserve(pairs);
|
||||
// update/add/delete pairs
|
||||
for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
|
||||
{
|
||||
@@ -266,9 +281,10 @@ OracleSet::doApply()
|
||||
(*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
|
||||
}
|
||||
|
||||
auto const newCount = pairs.size() > 5 ? 2 : 1;
|
||||
auto const adjust = newCount - oldCount;
|
||||
if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
|
||||
auto const newCount = calculateOracleReserve(pairs);
|
||||
int32_t const adjust = newCount - oldCount;
|
||||
|
||||
if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
ctx_.view().update(sle);
|
||||
@@ -317,8 +333,8 @@ OracleSet::doApply()
|
||||
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
|
||||
auto const count = series.size() > 5 ? 2 : 1;
|
||||
if (!adjustOwnerCount(ctx_, count))
|
||||
auto const count = calculateOracleReserve(series);
|
||||
if (!adjustOracleOwnerCount(ctx_, count))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
ctx_.view().insert(sle);
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -151,6 +151,7 @@ DepositPreauth::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
DepositPreauth::doApply()
|
||||
{
|
||||
auto applyViewContext = ctx_.getApplyViewContext();
|
||||
if (ctx_.tx.isFieldPresent(sfAuthorize))
|
||||
{
|
||||
auto const sleOwner = view().peek(keylet::account(accountID_));
|
||||
@@ -160,13 +161,10 @@ DepositPreauth::doApply()
|
||||
// A preauth counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
if (auto const ret = checkReserve(
|
||||
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Preclaim already verified that the Preauth entry does not yet exist.
|
||||
// Create and populate the Preauth entry.
|
||||
@@ -190,7 +188,8 @@ DepositPreauth::doApply()
|
||||
slePreauth->setFieldU64(sfOwnerNode, *page);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sleOwner, 1, j_);
|
||||
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
|
||||
addSponsorToLedgerEntry(applyViewContext, slePreauth);
|
||||
}
|
||||
else if (ctx_.tx.isFieldPresent(sfUnauthorize))
|
||||
{
|
||||
@@ -207,13 +206,10 @@ DepositPreauth::doApply()
|
||||
// A preauth counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
if (auto const ret = checkReserve(
|
||||
applyViewContext, sleOwner, preFeeBalance_, {.ownerCountDelta = 1}, j_);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Preclaim already verified that the Preauth entry does not yet exist.
|
||||
// Create and populate the Preauth entry.
|
||||
@@ -251,7 +247,8 @@ DepositPreauth::doApply()
|
||||
slePreauth->setFieldU64(sfOwnerNode, *page);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sleOwner, 1, j_);
|
||||
increaseOwnerCount(applyViewContext, sleOwner, 1, j_);
|
||||
addSponsorToLedgerEntry(applyViewContext, slePreauth);
|
||||
}
|
||||
else if (ctx_.tx.isFieldPresent(sfUnauthorizeCredentials))
|
||||
{
|
||||
@@ -289,8 +286,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b
|
||||
if (!sleOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
|
||||
decreaseOwnerCountForObject(view, sleOwner, slePreauth, 1, j);
|
||||
// Remove DepositPreauth from ledger.
|
||||
view.erase(slePreauth);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -37,6 +38,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
@@ -120,6 +122,21 @@ Payment::preflight(PreflightContext const& ctx)
|
||||
if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT)
|
||||
return temDISABLED;
|
||||
|
||||
if (tx.isFlag(tfSponsorCreatedAccount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSponsor))
|
||||
return temDISABLED;
|
||||
|
||||
if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
|
||||
return temINVALID;
|
||||
|
||||
if (!dstAmount.native())
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
|
||||
return temMALFORMED;
|
||||
|
||||
@@ -383,15 +400,28 @@ Payment::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// accountReserve is the minimum amount that an account can have.
|
||||
// Reserve is not scaled by load.
|
||||
JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
|
||||
<< "Insufficent payment to create account.";
|
||||
if (!ctx.tx.isFlag(tfSponsorCreatedAccount))
|
||||
{
|
||||
// The minimum amount when creating a Sponsored Account is 1 drop.
|
||||
// Since the reserve is covered by the sponsor, you don't need to hold the
|
||||
// 1-increment reserve yourself.
|
||||
JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
|
||||
<< "Insufficient payment to create account.";
|
||||
|
||||
// TODO: de-dupe
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST_INSUF_XRP;
|
||||
// TODO: de-dupe
|
||||
// Another transaction could create the account and then this
|
||||
// transaction would succeed.
|
||||
return tecNO_DST_INSUF_XRP;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ctx.tx.isFlag(tfSponsorCreatedAccount))
|
||||
{
|
||||
// The tfSponsorCreatedAccount flag is specific to account creation via
|
||||
// sponsorship. If the destination account already exists, applying this
|
||||
// flag is invalid.
|
||||
return tecNO_SPONSOR_PERMISSION;
|
||||
}
|
||||
else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
@@ -470,6 +500,27 @@ Payment::doApply()
|
||||
sleDst->setFieldU32(sfSequence, view().seq());
|
||||
sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero));
|
||||
|
||||
if (ctx_.tx.isFlag(tfSponsorCreatedAccount))
|
||||
{
|
||||
auto const sponsor = view().peek(keylet::account(accountID_));
|
||||
if (!sponsor)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
auto const currentSponsoringAccountCount =
|
||||
sponsor->getFieldU32(sfSponsoringAccountCount);
|
||||
if (currentSponsoringAccountCount == std::numeric_limits<std::uint32_t>::max())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Sponsoring account count overflow for account "
|
||||
<< to_string(accountID_);
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
|
||||
|
||||
addSponsorToLedgerEntry(sleDst, sponsor);
|
||||
view().update(sponsor);
|
||||
}
|
||||
|
||||
view().insert(sleDst);
|
||||
}
|
||||
else
|
||||
@@ -636,16 +687,12 @@ Payment::doApply()
|
||||
if (!sleSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// ownerCount is the number of entries in this ledger for this
|
||||
// account that require a reserve.
|
||||
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
|
||||
// the number of reserves in this ledger for this account that require a
|
||||
// reserve.
|
||||
auto const reserve = accountReserve(view(), sleSrc, j_);
|
||||
|
||||
// This is the total reserve in drops.
|
||||
auto const reserve = view().fees().accountReserve(ownerCount);
|
||||
|
||||
// In a delegated payment, the fee payer is the delegated account,
|
||||
// not the source account (accountID_).
|
||||
bool const accountIsPayer = (ctx_.tx.getFeePayer() == accountID_);
|
||||
// In a delegated / fee sponsored payment, the fee payer is not the source account (accountID_).
|
||||
bool const accountIsPayer = ctx_.tx.getFeePayerID() == accountID_;
|
||||
|
||||
// preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees
|
||||
// were charged. If source account is the fee payer, it must also cover the fee.
|
||||
|
||||
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user