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/tx/detail/AMMVote.h>
21
22#include <xrpld/app/misc/AMMHelpers.h>
23#include <xrpld/app/misc/AMMUtils.h>
24#include <xrpld/ledger/Sandbox.h>
25#include <xrpl/protocol/AMMCore.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/STAccount.h>
28#include <xrpl/protocol/TxFlags.h>
29
30namespace ripple {
31
34{
35 if (!ammEnabled(ctx.rules))
36 return temDISABLED;
37
38 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
39 return ret;
40
41 if (auto const res = invalidAMMAssetPair(
42 ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
43 {
44 JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
45 return res;
46 }
47
48 if (ctx.tx.getFlags() & tfUniversalMask)
49 {
50 JLOG(ctx.j.debug()) << "AMM Vote: invalid flags.";
51 return temINVALID_FLAG;
52 }
53
54 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
55 {
56 JLOG(ctx.j.debug()) << "AMM Vote: invalid trading fee.";
57 return temBAD_FEE;
58 }
59
60 return preflight2(ctx);
61}
62
63TER
65{
66 if (auto const ammSle =
67 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
68 !ammSle)
69 {
70 JLOG(ctx.j.debug()) << "AMM Vote: Invalid asset pair.";
71 return terNO_AMM;
72 }
73 else if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::zero)
74 return tecAMM_EMPTY;
75 else if (auto const lpTokensNew =
76 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
77 lpTokensNew == beast::zero)
78 {
79 JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
81 }
82
83 return tesSUCCESS;
84}
85
88 ApplyContext& ctx_,
89 Sandbox& sb,
90 AccountID const& account_,
92{
93 auto const feeNew = ctx_.tx[sfTradingFee];
94 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
95 if (!ammSle)
96 return {tecINTERNAL, false};
97 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
98 auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
100 std::size_t minPos{0};
101 AccountID minAccount{0};
102 std::uint32_t minFee{0};
103 STArray updatedVoteSlots;
104 Number num{0};
105 Number den{0};
106 // Account already has vote entry
107 bool foundAccount = false;
108
109 // Iterate over the current vote entries and update each entry
110 // per current total tokens balance and each LP tokens balance.
111 // Find the entry with the least tokens and whether the account
112 // has the vote entry.
113 for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
114 {
115 auto const account = entry[sfAccount];
116 auto lpTokens = ammLPHolds(sb, *ammSle, account, ctx_.journal);
117 if (lpTokens == beast::zero)
118 {
119 JLOG(j_.debug())
120 << "AMMVote::applyVote, account " << account << " is not LP";
121 continue;
122 }
123 auto feeVal = entry[sfTradingFee];
124 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
125 // The account already has the vote entry.
126 if (account == account_)
127 {
128 lpTokens = lpTokensNew;
129 feeVal = feeNew;
130 foundAccount = true;
131 }
132 // Keep running numerator/denominator to calculate the updated fee.
133 num += feeVal * lpTokens;
134 den += lpTokens;
135 newEntry.setAccountID(sfAccount, account);
136 if (feeVal != 0)
137 newEntry.setFieldU16(sfTradingFee, feeVal);
138 newEntry.setFieldU32(
139 sfVoteWeight,
140 static_cast<std::int64_t>(
141 Number(lpTokens) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
142
143 // Find an entry with the least tokens/fee. Make the order deterministic
144 // if the tokens/fees are equal.
145 if (!minTokens ||
146 (lpTokens < *minTokens ||
147 (lpTokens == *minTokens &&
148 (feeVal < minFee || (feeVal == minFee && account < minAccount)))))
149 {
150 minTokens = lpTokens;
151 minPos = updatedVoteSlots.size();
152 minAccount = account;
153 minFee = feeVal;
154 }
155 updatedVoteSlots.push_back(std::move(newEntry));
156 }
157
158 // The account doesn't have the vote entry.
159 if (!foundAccount)
160 {
161 auto update = [&](std::optional<std::uint8_t> const& minPos =
162 std::nullopt) {
163 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
164 if (feeNew != 0)
165 newEntry.setFieldU16(sfTradingFee, feeNew);
166 newEntry.setFieldU32(
167 sfVoteWeight,
168 static_cast<std::int64_t>(
169 Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR /
170 lptAMMBalance));
171 newEntry.setAccountID(sfAccount, account_);
172 num += feeNew * lpTokensNew;
173 den += lpTokensNew;
174 if (minPos)
175 *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
176 else
177 updatedVoteSlots.push_back(std::move(newEntry));
178 };
179 // Add new entry if the number of the vote entries
180 // is less than Max.
181 if (updatedVoteSlots.size() < VOTE_MAX_SLOTS)
182 update();
183 // Add the entry if the account has more tokens than
184 // the least token holder or same tokens and higher fee.
185 else if (
186 lpTokensNew > *minTokens ||
187 (lpTokensNew == *minTokens && feeNew > minFee))
188 {
189 auto const entry = updatedVoteSlots.begin() + minPos;
190 // Remove the least token vote entry.
191 num -= Number((*entry)[~sfTradingFee].value_or(0)) * *minTokens;
192 den -= *minTokens;
193 update(minPos);
194 }
195 // All slots are full and the account does not hold more LPTokens.
196 // Update anyway to refresh the slots.
197 else
198 {
199 JLOG(j_.debug()) << "AMMVote::applyVote, insufficient tokens to "
200 "override other votes";
201 }
202 }
203
204 XRPL_ASSERT(
205 !ctx_.view().rules().enabled(fixInnerObjTemplate) ||
206 ammSle->isFieldPresent(sfAuctionSlot),
207 "ripple::applyVote : has auction slot");
208
209 // Update the vote entries and the trading/discounted fee.
210 ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
211 if (auto const fee = static_cast<std::int64_t>(num / den))
212 {
213 ammSle->setFieldU16(sfTradingFee, fee);
214 if (ammSle->isFieldPresent(sfAuctionSlot))
215 {
216 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
217 if (auto const discountedFee =
219 auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
220 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
221 auctionSlot.makeFieldAbsent(sfDiscountedFee);
222 }
223 }
224 else
225 {
226 if (ammSle->isFieldPresent(sfTradingFee))
227 ammSle->makeFieldAbsent(sfTradingFee);
228 if (ammSle->isFieldPresent(sfAuctionSlot))
229 {
230 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
231 if (auctionSlot.isFieldPresent(sfDiscountedFee))
232 auctionSlot.makeFieldAbsent(sfDiscountedFee);
233 }
234 }
235 sb.update(ammSle);
236
237 return {tesSUCCESS, true};
238}
239
240TER
242{
243 // This is the ledger view that we work against. Transactions are applied
244 // as we go on processing transactions.
245 Sandbox sb(&ctx_.view());
246
247 auto const result = applyVote(ctx_, sb, account_, j_);
248 if (result.second)
249 sb.apply(ctx_.rawView());
250
251 return result.first;
252}
253
254} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream debug() const
Definition: Journal.h:327
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMVote.cpp:33
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMVote.cpp:64
TER doApply() override
Definition: AMMVote.cpp:241
State information when applying a tx.
Definition: ApplyContext.h:36
RawView & rawView()
Definition: ApplyContext.h:67
ApplyView & view()
Definition: ApplyContext.h:54
beast::Journal const journal
Definition: ApplyContext.h:51
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:122
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:705
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:65
void setAccountID(SField const &field, AccountID const &)
Definition: STObject.cpp:741
void setFieldU32(SField const &field, std::uint32_t)
Definition: STObject.cpp:711
std::uint32_t getFlags() const
Definition: STObject.cpp:507
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
AccountID const account_
Definition: Transactor.h:91
beast::Journal const j_
Definition: Transactor.h:89
ApplyContext & ctx_
Definition: Transactor.h:88
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.
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:422
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:87
bool isTesSuccess(TER x)
Definition: TER.h:656
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:128
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
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:111
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
@ tecAMM_EMPTY
Definition: TER.h:319
@ tecINTERNAL
Definition: TER.h:297
@ tecAMM_INVALID_TOKENS
Definition: TER.h:318
std::uint32_t constexpr VOTE_WEIGHT_SCALE_FACTOR
Definition: AMMCore.h:45
@ tesSUCCESS
Definition: TER.h:242
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:79
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:627
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition: AMMCore.h:31
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
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:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
beast::Journal const j
Definition: Transactor.h:38