xrpld
Loading...
Searching...
No Matches
AMMVote.cpp
1#include <xrpl/tx/transactors/dex/AMMVote.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/ledger/Sandbox.h>
8#include <xrpl/ledger/helpers/AMMHelpers.h>
9#include <xrpl/protocol/AMMCore.h>
10#include <xrpl/protocol/AccountID.h>
11#include <xrpl/protocol/Feature.h>
12#include <xrpl/protocol/Indexes.h>
13#include <xrpl/protocol/MPTIssue.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/STArray.h>
16#include <xrpl/protocol/STLedgerEntry.h>
17#include <xrpl/protocol/STTx.h>
18#include <xrpl/protocol/TER.h>
19#include <xrpl/protocol/XRPAmount.h>
20#include <xrpl/tx/ApplyContext.h>
21#include <xrpl/tx/Transactor.h>
22
23#include <cstddef>
24#include <cstdint>
25#include <optional>
26#include <utility>
27
28namespace xrpl {
29
30bool
32{
33 if (!ammEnabled(ctx.rules))
34 return false;
35
36 return ctx.rules.enabled(featureMPTokensV2) ||
37 (!ctx.tx[sfAsset].holds<MPTIssue>() && !ctx.tx[sfAsset2].holds<MPTIssue>());
38}
39
42{
43 if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2]))
44 {
45 JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
46 return res;
47 }
48
49 if (ctx.tx[sfTradingFee] > kTradingFeeThreshold)
50 {
51 JLOG(ctx.j.debug()) << "AMM Vote: invalid trading fee.";
52 return temBAD_FEE;
53 }
54
55 return tesSUCCESS;
56}
57
58TER
60{
61 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
62 if (!ammSle)
63 {
64 JLOG(ctx.j.debug()) << "AMM Vote: Invalid asset pair.";
65 return terNO_AMM;
66 }
67 if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::kZero)
68 {
69 return tecAMM_EMPTY;
70 }
71 if (auto const lpTokensNew = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
72 lpTokensNew == beast::kZero)
73 {
74 JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
76 }
77
78 return tesSUCCESS;
79}
80
82applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& accountID, beast::Journal j)
83{
84 auto const feeNew = ctx.tx[sfTradingFee];
85 auto ammSle = sb.peek(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
86 if (!ammSle)
87 return {tecINTERNAL, false};
88 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
89 auto const lpTokensNew = ammLPHolds(sb, *ammSle, accountID, ctx.journal);
91 std::size_t minPos{0};
92 AccountID minAccount{0};
93 std::uint32_t minFee{0};
94 STArray updatedVoteSlots;
95 Number num{0};
96 Number den{0};
97 // Account already has vote entry
98 bool foundAccount = false;
99
100 // Iterate over the current vote entries and update each entry
101 // per current total tokens balance and each LP tokens balance.
102 // Find the entry with the least tokens and whether the account
103 // has the vote entry.
104 for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
105 {
106 auto const entryAccount = entry[sfAccount];
107 auto lpTokens = ammLPHolds(sb, *ammSle, entryAccount, ctx.journal);
108 if (lpTokens == beast::kZero)
109 {
110 JLOG(j.debug()) << "AMMVote::applyVote, accountID " << entryAccount << " is not LP";
111 continue;
112 }
113 auto feeVal = entry[sfTradingFee];
114 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
115 // The account already has the vote entry.
116 if (entryAccount == accountID)
117 {
118 lpTokens = lpTokensNew;
119 feeVal = feeNew;
120 foundAccount = true;
121 }
122 // Keep running numerator/denominator to calculate the updated fee.
123 num += feeVal * lpTokens;
124 den += lpTokens;
125 newEntry.setAccountID(sfAccount, entryAccount);
126 if (feeVal != 0)
127 newEntry.setFieldU16(sfTradingFee, feeVal);
128 newEntry.setFieldU32(
129 sfVoteWeight,
130 static_cast<std::int64_t>(Number(lpTokens) * kVoteWeightScaleFactor / lptAMMBalance));
131
132 // Find an entry with the least tokens/fee. Make the order deterministic
133 // if the tokens/fees are equal.
134 if (!minTokens ||
135 (lpTokens < *minTokens ||
136 (lpTokens == *minTokens &&
137 (feeVal < minFee || (feeVal == minFee && entryAccount < minAccount)))))
138 {
139 minTokens = lpTokens;
140 minPos = updatedVoteSlots.size();
141 minAccount = entryAccount;
142 minFee = feeVal;
143 }
144 updatedVoteSlots.pushBack(std::move(newEntry));
145 }
146
147 // The account doesn't have the vote entry.
148 if (!foundAccount)
149 {
150 auto update = [&](std::optional<std::uint8_t> const& minPos = std::nullopt) {
151 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
152 if (feeNew != 0)
153 newEntry.setFieldU16(sfTradingFee, feeNew);
154 newEntry.setFieldU32(
155 sfVoteWeight,
156 static_cast<std::int64_t>(
157 Number(lpTokensNew) * kVoteWeightScaleFactor / lptAMMBalance));
158 newEntry.setAccountID(sfAccount, accountID);
159 num += feeNew * lpTokensNew;
160 den += lpTokensNew;
161 if (minPos)
162 {
163 *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
164 }
165 else
166 {
167 updatedVoteSlots.pushBack(std::move(newEntry));
168 }
169 };
170 // Add new entry if the number of the vote entries
171 // is less than Max.
172 if (updatedVoteSlots.size() < kVoteMaxSlots)
173 {
174 update();
175 // Add the entry if the account has more tokens than
176 // the least token holder or same tokens and higher fee.
177 }
178 // NOLINTBEGIN(bugprone-unchecked-optional-access) slots full means loop ran, minTokens is
179 // set
180 else if (lpTokensNew > *minTokens || (lpTokensNew == *minTokens && feeNew > minFee))
181 {
182 auto const entry = updatedVoteSlots.begin() + minPos;
183 // Remove the least token vote entry.
184 num -= Number((*entry)[~sfTradingFee].valueOr(0)) * *minTokens;
185 den -= *minTokens;
186 update(minPos);
187 }
188 // NOLINTEND(bugprone-unchecked-optional-access)
189 // All slots are full and the account does not hold more LPTokens.
190 // Update anyway to refresh the slots.
191 else
192 {
193 JLOG(j.debug()) << "AMMVote::applyVote, insufficient tokens to "
194 "override other votes";
195 }
196 }
197
198 XRPL_ASSERT(ammSle->isFieldPresent(sfAuctionSlot), "xrpl::applyVote : has auction slot");
199
200 // Update the vote entries and the trading/discounted fee.
201 ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
202 if (auto const fee = static_cast<std::int64_t>(num / den))
203 {
204 ammSle->setFieldU16(sfTradingFee, fee);
205 if (ammSle->isFieldPresent(sfAuctionSlot))
206 {
207 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
208 if (auto const discountedFee = fee / kAuctionSlotDiscountedFeeFraction)
209 {
210 auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
211 }
212 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
213 {
214 auctionSlot.makeFieldAbsent(sfDiscountedFee);
215 }
216 }
217 }
218 else
219 {
220 if (ammSle->isFieldPresent(sfTradingFee))
221 ammSle->makeFieldAbsent(sfTradingFee);
222 if (ammSle->isFieldPresent(sfAuctionSlot))
223 {
224 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
225 if (auctionSlot.isFieldPresent(sfDiscountedFee))
226 auctionSlot.makeFieldAbsent(sfDiscountedFee);
227 }
228 }
229 sb.update(ammSle);
230
231 return {tesSUCCESS, true};
232}
233
234TER
236{
237 // This is the ledger view that we work against. Transactions are applied
238 // as we go on processing transactions.
239 Sandbox sb(&ctx_.view());
240
241 auto const result = applyVote(ctx_, sb, accountID_, j_);
242 if (result.second)
243 sb.apply(ctx_.rawView());
244
245 return result.first;
246}
247
248void
250{
251 // No transaction-specific invariants yet (future work).
252}
253
254bool
256{
257 // No transaction-specific invariants yet (future work).
258 return true;
259}
260
261} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
static NotTEC preflight(PreflightContext const &ctx)
Definition AMMVote.cpp:41
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
Definition AMMVote.cpp:249
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
Definition AMMVote.cpp:255
static TER preclaim(PreclaimContext const &ctx)
Definition AMMVote.cpp:59
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition AMMVote.cpp:31
TER doApply() override
Definition AMMVote.cpp:235
State information when applying a tx.
STTx const & tx
beast::Journal const journal
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
size_type size() const
Definition STArray.h:248
iterator begin()
Definition STArray.h:224
void pushBack(STObject const &object)
Definition STArray.h:212
std::shared_ptr< STLedgerEntry const > const & const_ref
void setFieldU32(SField const &field, std::uint32_t)
Definition STObject.cpp:743
void setFieldU16(SField const &field, std::uint16_t)
Definition STObject.cpp:737
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:79
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:785
Discardable, editable view to a ledger.
Definition Sandbox.h:18
void apply(RawView &to)
Definition Sandbox.h:38
beast::Journal const j_
Definition Transactor.h:155
AccountID const accountID_
Definition Transactor.h:157
ApplyContext & ctx_
Definition Transactor.h:153
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
void update(SLE::ref sle) override
Indicate changes to a peeked SLE.
constexpr Zero kZero
Definition Zero.h:30
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:441
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::uint16_t kVoteMaxSlots
Definition AMMCore.h:29
@ terNO_AMM
Definition TER.h:223
constexpr std::uint16_t kTradingFeeThreshold
Definition AMMCore.h:16
STAmount ammLPHolds(ReadView const &view, Asset const &asset1, Asset const &asset2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
constexpr std::uint32_t kAuctionSlotDiscountedFeeFraction
Definition AMMCore.h:23
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
Definition AMMCore.cpp:129
NotTEC invalidAMMAssetPair(Asset const &asset1, Asset const &asset2, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt)
Definition AMMCore.cpp:83
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
static std::pair< TER, bool > applyVote(ApplyContext &ctx, Sandbox &sb, AccountID const &accountID, beast::Journal j)
Definition AMMVote.cpp:82
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temBAD_FEE
Definition TER.h:80
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecAMM_EMPTY
Definition TER.h:335
@ tecAMM_INVALID_TOKENS
Definition TER.h:334
@ tecINTERNAL
Definition TER.h:313
constexpr std::uint32_t kVoteWeightScaleFactor
Definition AMMCore.h:30
@ tesSUCCESS
Definition TER.h:245
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
beast::Journal const j
Definition Transactor.h:45