rippled
Loading...
Searching...
No Matches
AMMVote.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/misc/AMMUtils.h>
21#include <xrpld/app/tx/detail/AMMVote.h>
22#include <xrpld/ledger/Sandbox.h>
23
24#include <xrpl/protocol/AMMCore.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/TxFlags.h>
27
28namespace ripple {
29
32{
33 if (!ammEnabled(ctx.rules))
34 return temDISABLED;
35
36 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
37 return ret;
38
39 if (auto const res = invalidAMMAssetPair(
40 ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
41 {
42 JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
43 return res;
44 }
45
46 if (ctx.tx.getFlags() & tfUniversalMask)
47 {
48 JLOG(ctx.j.debug()) << "AMM Vote: invalid flags.";
49 return temINVALID_FLAG;
50 }
51
52 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
53 {
54 JLOG(ctx.j.debug()) << "AMM Vote: invalid trading fee.";
55 return temBAD_FEE;
56 }
57
58 return preflight2(ctx);
59}
60
61TER
63{
64 if (auto const ammSle =
65 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
66 !ammSle)
67 {
68 JLOG(ctx.j.debug()) << "AMM Vote: Invalid asset pair.";
69 return terNO_AMM;
70 }
71 else if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::zero)
72 return tecAMM_EMPTY;
73 else if (auto const lpTokensNew =
74 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
75 lpTokensNew == beast::zero)
76 {
77 JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
79 }
80
81 return tesSUCCESS;
82}
83
86 ApplyContext& ctx_,
87 Sandbox& sb,
88 AccountID const& account_,
90{
91 auto const feeNew = ctx_.tx[sfTradingFee];
92 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
93 if (!ammSle)
94 return {tecINTERNAL, false};
95 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
96 auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
98 std::size_t minPos{0};
99 AccountID minAccount{0};
100 std::uint32_t minFee{0};
101 STArray updatedVoteSlots;
102 Number num{0};
103 Number den{0};
104 // Account already has vote entry
105 bool foundAccount = false;
106
107 // Iterate over the current vote entries and update each entry
108 // per current total tokens balance and each LP tokens balance.
109 // Find the entry with the least tokens and whether the account
110 // has the vote entry.
111 for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
112 {
113 auto const account = entry[sfAccount];
114 auto lpTokens = ammLPHolds(sb, *ammSle, account, ctx_.journal);
115 if (lpTokens == beast::zero)
116 {
117 JLOG(j_.debug())
118 << "AMMVote::applyVote, account " << account << " is not LP";
119 continue;
120 }
121 auto feeVal = entry[sfTradingFee];
122 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
123 // The account already has the vote entry.
124 if (account == account_)
125 {
126 lpTokens = lpTokensNew;
127 feeVal = feeNew;
128 foundAccount = true;
129 }
130 // Keep running numerator/denominator to calculate the updated fee.
131 num += feeVal * lpTokens;
132 den += lpTokens;
133 newEntry.setAccountID(sfAccount, account);
134 if (feeVal != 0)
135 newEntry.setFieldU16(sfTradingFee, feeVal);
136 newEntry.setFieldU32(
137 sfVoteWeight,
138 static_cast<std::int64_t>(
139 Number(lpTokens) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
140
141 // Find an entry with the least tokens/fee. Make the order deterministic
142 // if the tokens/fees are equal.
143 if (!minTokens ||
144 (lpTokens < *minTokens ||
145 (lpTokens == *minTokens &&
146 (feeVal < minFee || (feeVal == minFee && account < minAccount)))))
147 {
148 minTokens = lpTokens;
149 minPos = updatedVoteSlots.size();
150 minAccount = account;
151 minFee = feeVal;
152 }
153 updatedVoteSlots.push_back(std::move(newEntry));
154 }
155
156 // The account doesn't have the vote entry.
157 if (!foundAccount)
158 {
159 auto update = [&](std::optional<std::uint8_t> const& minPos =
160 std::nullopt) {
161 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
162 if (feeNew != 0)
163 newEntry.setFieldU16(sfTradingFee, feeNew);
164 newEntry.setFieldU32(
165 sfVoteWeight,
166 static_cast<std::int64_t>(
167 Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR /
168 lptAMMBalance));
169 newEntry.setAccountID(sfAccount, account_);
170 num += feeNew * lpTokensNew;
171 den += lpTokensNew;
172 if (minPos)
173 *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
174 else
175 updatedVoteSlots.push_back(std::move(newEntry));
176 };
177 // Add new entry if the number of the vote entries
178 // is less than Max.
179 if (updatedVoteSlots.size() < VOTE_MAX_SLOTS)
180 update();
181 // Add the entry if the account has more tokens than
182 // the least token holder or same tokens and higher fee.
183 else if (
184 lpTokensNew > *minTokens ||
185 (lpTokensNew == *minTokens && feeNew > minFee))
186 {
187 auto const entry = updatedVoteSlots.begin() + minPos;
188 // Remove the least token vote entry.
189 num -= Number((*entry)[~sfTradingFee].value_or(0)) * *minTokens;
190 den -= *minTokens;
191 update(minPos);
192 }
193 // All slots are full and the account does not hold more LPTokens.
194 // Update anyway to refresh the slots.
195 else
196 {
197 JLOG(j_.debug()) << "AMMVote::applyVote, insufficient tokens to "
198 "override other votes";
199 }
200 }
201
202 XRPL_ASSERT(
203 !ctx_.view().rules().enabled(fixInnerObjTemplate) ||
204 ammSle->isFieldPresent(sfAuctionSlot),
205 "ripple::applyVote : has auction slot");
206
207 // Update the vote entries and the trading/discounted fee.
208 ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
209 if (auto const fee = static_cast<std::int64_t>(num / den))
210 {
211 ammSle->setFieldU16(sfTradingFee, fee);
212 if (ammSle->isFieldPresent(sfAuctionSlot))
213 {
214 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
215 if (auto const discountedFee =
217 auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
218 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
219 auctionSlot.makeFieldAbsent(sfDiscountedFee);
220 }
221 }
222 else
223 {
224 if (ammSle->isFieldPresent(sfTradingFee))
225 ammSle->makeFieldAbsent(sfTradingFee);
226 if (ammSle->isFieldPresent(sfAuctionSlot))
227 {
228 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
229 if (auctionSlot.isFieldPresent(sfDiscountedFee))
230 auctionSlot.makeFieldAbsent(sfDiscountedFee);
231 }
232 }
233 sb.update(ammSle);
234
235 return {tesSUCCESS, true};
236}
237
238TER
240{
241 // This is the ledger view that we work against. Transactions are applied
242 // as we go on processing transactions.
243 Sandbox sb(&ctx_.view());
244
245 auto const result = applyVote(ctx_, sb, account_, j_);
246 if (result.second)
247 sb.apply(ctx_.rawView());
248
249 return result.first;
250}
251
252} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream debug() const
Definition: Journal.h:328
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMVote.cpp:31
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMVote.cpp:62
TER doApply() override
Definition: AMMVote.cpp:239
State information when applying a tx.
Definition: ApplyContext.h:37
RawView & rawView()
Definition: ApplyContext.h:68
ApplyView & view()
Definition: ApplyContext.h:55
beast::Journal const journal
Definition: ApplyContext.h:52
A currency issued by an account.
Definition: Issue.h:36
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
void push_back(STObject const &object)
Definition: STArray.h:212
iterator begin()
Definition: STArray.h:224
size_type size() const
Definition: STArray.h:248
void setFieldU16(SField const &field, std::uint16_t)
Definition: STObject.cpp:735
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:95
void setAccountID(SField const &field, AccountID const &)
Definition: STObject.cpp:771
void setFieldU32(SField const &field, std::uint32_t)
Definition: STObject.cpp:741
std::uint32_t getFlags() const
Definition: STObject.cpp:537
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
AccountID const account_
Definition: Transactor.h:93
beast::Journal const j_
Definition: Transactor.h:91
ApplyContext & ctx_
Definition: Transactor.h:90
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Set the fee on a JTx.
Definition: fee.h:37
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:439
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static std::pair< TER, bool > applyVote(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMVote.cpp:85
bool isTesSuccess(TER x)
Definition: TER.h:672
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:129
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:83
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition: AMMUtils.cpp:112
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:144
@ tecAMM_EMPTY
Definition: TER.h:332
@ tecINTERNAL
Definition: TER.h:310
@ tecAMM_INVALID_TOKENS
Definition: TER.h:331
std::uint32_t constexpr VOTE_WEIGHT_SCALE_FACTOR
Definition: AMMCore.h:45
@ tesSUCCESS
Definition: TER.h:244
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:80
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
std::uint16_t constexpr VOTE_MAX_SLOTS
Definition: AMMCore.h:44
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition: AMMCore.h:31
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:603
std::uint32_t constexpr AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition: AMMCore.h:38
@ temBAD_FEE
Definition: TER.h:92
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:55
ReadView const & view
Definition: Transactor.h:58
beast::Journal const j
Definition: Transactor.h:62
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:40