rippled
Loading...
Searching...
No Matches
AMMVote.cpp
1#include <xrpld/app/misc/AMMUtils.h>
2#include <xrpld/app/tx/detail/AMMVote.h>
3
4#include <xrpl/ledger/Sandbox.h>
5#include <xrpl/protocol/AMMCore.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/TxFlags.h>
8
9namespace ripple {
10
11bool
16
19{
20 if (auto const res = invalidAMMAssetPair(
21 ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
22 {
23 JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
24 return res;
25 }
26
27 if (ctx.tx[sfTradingFee] > TRADING_FEE_THRESHOLD)
28 {
29 JLOG(ctx.j.debug()) << "AMM Vote: invalid trading fee.";
30 return temBAD_FEE;
31 }
32
33 return tesSUCCESS;
34}
35
36TER
38{
39 if (auto const ammSle =
40 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
41 !ammSle)
42 {
43 JLOG(ctx.j.debug()) << "AMM Vote: Invalid asset pair.";
44 return terNO_AMM;
45 }
46 else if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::zero)
47 return tecAMM_EMPTY;
48 else if (auto const lpTokensNew =
49 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
50 lpTokensNew == beast::zero)
51 {
52 JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
54 }
55
56 return tesSUCCESS;
57}
58
61 ApplyContext& ctx_,
62 Sandbox& sb,
63 AccountID const& account_,
65{
66 auto const feeNew = ctx_.tx[sfTradingFee];
67 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
68 if (!ammSle)
69 return {tecINTERNAL, false};
70 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
71 auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
73 std::size_t minPos{0};
74 AccountID minAccount{0};
75 std::uint32_t minFee{0};
76 STArray updatedVoteSlots;
77 Number num{0};
78 Number den{0};
79 // Account already has vote entry
80 bool foundAccount = false;
81
82 // Iterate over the current vote entries and update each entry
83 // per current total tokens balance and each LP tokens balance.
84 // Find the entry with the least tokens and whether the account
85 // has the vote entry.
86 for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
87 {
88 auto const account = entry[sfAccount];
89 auto lpTokens = ammLPHolds(sb, *ammSle, account, ctx_.journal);
90 if (lpTokens == beast::zero)
91 {
92 JLOG(j_.debug())
93 << "AMMVote::applyVote, account " << account << " is not LP";
94 continue;
95 }
96 auto feeVal = entry[sfTradingFee];
97 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
98 // The account already has the vote entry.
99 if (account == account_)
100 {
101 lpTokens = lpTokensNew;
102 feeVal = feeNew;
103 foundAccount = true;
104 }
105 // Keep running numerator/denominator to calculate the updated fee.
106 num += feeVal * lpTokens;
107 den += lpTokens;
108 newEntry.setAccountID(sfAccount, account);
109 if (feeVal != 0)
110 newEntry.setFieldU16(sfTradingFee, feeVal);
111 newEntry.setFieldU32(
112 sfVoteWeight,
113 static_cast<std::int64_t>(
114 Number(lpTokens) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
115
116 // Find an entry with the least tokens/fee. Make the order deterministic
117 // if the tokens/fees are equal.
118 if (!minTokens ||
119 (lpTokens < *minTokens ||
120 (lpTokens == *minTokens &&
121 (feeVal < minFee || (feeVal == minFee && account < minAccount)))))
122 {
123 minTokens = lpTokens;
124 minPos = updatedVoteSlots.size();
125 minAccount = account;
126 minFee = feeVal;
127 }
128 updatedVoteSlots.push_back(std::move(newEntry));
129 }
130
131 // The account doesn't have the vote entry.
132 if (!foundAccount)
133 {
134 auto update = [&](std::optional<std::uint8_t> const& minPos =
135 std::nullopt) {
136 STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
137 if (feeNew != 0)
138 newEntry.setFieldU16(sfTradingFee, feeNew);
139 newEntry.setFieldU32(
140 sfVoteWeight,
141 static_cast<std::int64_t>(
142 Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR /
143 lptAMMBalance));
144 newEntry.setAccountID(sfAccount, account_);
145 num += feeNew * lpTokensNew;
146 den += lpTokensNew;
147 if (minPos)
148 *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
149 else
150 updatedVoteSlots.push_back(std::move(newEntry));
151 };
152 // Add new entry if the number of the vote entries
153 // is less than Max.
154 if (updatedVoteSlots.size() < VOTE_MAX_SLOTS)
155 update();
156 // Add the entry if the account has more tokens than
157 // the least token holder or same tokens and higher fee.
158 else if (
159 lpTokensNew > *minTokens ||
160 (lpTokensNew == *minTokens && feeNew > minFee))
161 {
162 auto const entry = updatedVoteSlots.begin() + minPos;
163 // Remove the least token vote entry.
164 num -= Number((*entry)[~sfTradingFee].value_or(0)) * *minTokens;
165 den -= *minTokens;
166 update(minPos);
167 }
168 // All slots are full and the account does not hold more LPTokens.
169 // Update anyway to refresh the slots.
170 else
171 {
172 JLOG(j_.debug()) << "AMMVote::applyVote, insufficient tokens to "
173 "override other votes";
174 }
175 }
176
177 XRPL_ASSERT(
178 !ctx_.view().rules().enabled(fixInnerObjTemplate) ||
179 ammSle->isFieldPresent(sfAuctionSlot),
180 "ripple::applyVote : has auction slot");
181
182 // Update the vote entries and the trading/discounted fee.
183 ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
184 if (auto const fee = static_cast<std::int64_t>(num / den))
185 {
186 ammSle->setFieldU16(sfTradingFee, fee);
187 if (ammSle->isFieldPresent(sfAuctionSlot))
188 {
189 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
190 if (auto const discountedFee =
192 auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
193 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
194 auctionSlot.makeFieldAbsent(sfDiscountedFee);
195 }
196 }
197 else
198 {
199 if (ammSle->isFieldPresent(sfTradingFee))
200 ammSle->makeFieldAbsent(sfTradingFee);
201 if (ammSle->isFieldPresent(sfAuctionSlot))
202 {
203 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
204 if (auctionSlot.isFieldPresent(sfDiscountedFee))
205 auctionSlot.makeFieldAbsent(sfDiscountedFee);
206 }
207 }
208 sb.update(ammSle);
209
210 return {tesSUCCESS, true};
211}
212
213TER
215{
216 // This is the ledger view that we work against. Transactions are applied
217 // as we go on processing transactions.
218 Sandbox sb(&ctx_.view());
219
220 auto const result = applyVote(ctx_, sb, account_, j_);
221 if (result.second)
222 sb.apply(ctx_.rawView());
223
224 return result.first;
225}
226
227} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream debug() const
Definition Journal.h:309
static NotTEC preflight(PreflightContext const &ctx)
Definition AMMVote.cpp:18
static TER preclaim(PreclaimContext const &ctx)
Definition AMMVote.cpp:37
TER doApply() override
Definition AMMVote.cpp:214
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition AMMVote.cpp:12
State information when applying a tx.
ApplyView & view()
beast::Journal const journal
A currency issued by an account.
Definition Issue.h:14
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:111
void push_back(STObject const &object)
Definition STArray.h:193
iterator begin()
Definition STArray.h:205
size_type size() const
Definition STArray.h:229
void setFieldU16(SField const &field, std::uint16_t)
Definition STObject.cpp:732
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:76
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:774
void setFieldU32(SField const &field, std::uint32_t)
Definition STObject.cpp:738
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
AccountID const account_
Definition Transactor.h:128
beast::Journal const j_
Definition Transactor.h:126
ApplyContext & ctx_
Definition Transactor.h:124
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:427
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
static std::pair< TER, bool > applyVote(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition AMMVote.cpp:60
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:110
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:94
@ tecAMM_EMPTY
Definition TER.h:314
@ tecINTERNAL
Definition TER.h:292
@ tecAMM_INVALID_TOKENS
Definition TER.h:313
std::uint32_t constexpr VOTE_WEIGHT_SCALE_FACTOR
Definition AMMCore.h:26
@ tesSUCCESS
Definition TER.h:226
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:61
std::uint16_t constexpr VOTE_MAX_SLOTS
Definition AMMCore.h:25
@ terNO_AMM
Definition TER.h:208
TERSubset< CanCvtToTER > TER
Definition TER.h:630
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:12
std::uint32_t constexpr AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition AMMCore.h:19
@ temBAD_FEE
Definition TER.h:73
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:16
beast::Journal const j
Definition Transactor.h:23