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(
199 !ctx.view().rules().enabled(fixInnerObjTemplate) || ammSle->isFieldPresent(sfAuctionSlot),
200 "xrpl::applyVote : has auction slot");
201
202 // Update the vote entries and the trading/discounted fee.
203 ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
204 if (auto const fee = static_cast<std::int64_t>(num / den))
205 {
206 ammSle->setFieldU16(sfTradingFee, fee);
207 if (ammSle->isFieldPresent(sfAuctionSlot))
208 {
209 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
210 if (auto const discountedFee = fee / kAuctionSlotDiscountedFeeFraction)
211 {
212 auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
213 }
214 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
215 {
216 auctionSlot.makeFieldAbsent(sfDiscountedFee);
217 }
218 }
219 }
220 else
221 {
222 if (ammSle->isFieldPresent(sfTradingFee))
223 ammSle->makeFieldAbsent(sfTradingFee);
224 if (ammSle->isFieldPresent(sfAuctionSlot))
225 {
226 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
227 if (auctionSlot.isFieldPresent(sfDiscountedFee))
228 auctionSlot.makeFieldAbsent(sfDiscountedFee);
229 }
230 }
231 sb.update(ammSle);
232
233 return {tesSUCCESS, true};
234}
235
236TER
238{
239 // This is the ledger view that we work against. Transactions are applied
240 // as we go on processing transactions.
241 Sandbox sb(&ctx_.view());
242
243 auto const result = applyVote(ctx_, sb, accountID_, j_);
244 if (result.second)
245 sb.apply(ctx_.rawView());
246
247 return result.first;
248}
249
250void
252{
253 // No transaction-specific invariants yet (future work).
254}
255
256bool
258{
259 // No transaction-specific invariants yet (future work).
260 return true;
261}
262
263} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
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:251
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:257
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:237
State information when applying a tx.
STTx const & tx
beast::Journal const journal
ApplyView & view()
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
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:171
size_type size() const
Definition STArray.h:240
iterator begin()
Definition STArray.h:216
void pushBack(STObject const &object)
Definition STArray.h:204
std::shared_ptr< STLedgerEntry const > const & const_ref
void setFieldU32(SField const &field, std::uint32_t)
Definition STObject.cpp:733
void setFieldU16(SField const &field, std::uint16_t)
Definition STObject.cpp:727
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:74
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:775
Discardable, editable view to a ledger.
Definition Sandbox.h:15
void apply(RawView &to)
Definition Sandbox.h:35
beast::Journal const j_
Definition Transactor.h:118
AccountID const accountID_
Definition Transactor.h:120
ApplyContext & ctx_
Definition Transactor.h:116
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.
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:425
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:24
@ terNO_AMM
Definition TER.h:219
constexpr std::uint16_t kTradingFeeThreshold
Definition AMMCore.h:11
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:18
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
Definition AMMCore.cpp:128
NotTEC invalidAMMAssetPair(Asset const &asset1, Asset const &asset2, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt)
Definition AMMCore.cpp:82
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
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:28
@ temBAD_FEE
Definition TER.h:78
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecAMM_EMPTY
Definition TER.h:330
@ tecAMM_INVALID_TOKENS
Definition TER.h:329
@ tecINTERNAL
Definition TER.h:308
constexpr std::uint32_t kVoteWeightScaleFactor
Definition AMMCore.h:25
@ tesSUCCESS
Definition TER.h:240
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25