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.
ApplyView & view()
beast::Journal const journal
A currency issued by an account.
Definition Issue.h:33
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:143
beast::Journal const j_
Definition Transactor.h:141
ApplyContext & ctx_
Definition Transactor.h:140
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.
T is_same_v
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
static std::pair< TER, bool > applyVote(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition AMMVote.cpp:85
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.
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:113
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
@ 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
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
std::uint16_t constexpr VOTE_MAX_SLOTS
Definition AMMCore.h:44
@ terNO_AMM
Definition TER.h:227
TERSubset< CanCvtToTER > TER
Definition TER.h:645
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:31
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
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:79
ReadView const & view
Definition Transactor.h:82
beast::Journal const j
Definition Transactor.h:87
State information when preflighting a tx.
Definition Transactor.h:34
beast::Journal const j
Definition Transactor.h:41