rippled
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 <ripple/app/tx/impl/AMMVote.h>
21 
22 #include <ripple/app/misc/AMMHelpers.h>
23 #include <ripple/app/misc/AMMUtils.h>
24 #include <ripple/ledger/Sandbox.h>
25 #include <ripple/protocol/AMMCore.h>
26 #include <ripple/protocol/Feature.h>
27 #include <ripple/protocol/STAccount.h>
28 #include <ripple/protocol/TxFlags.h>
29 
30 namespace ripple {
31 
32 NotTEC
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(ctx.tx[sfAsset], ctx.tx[sfAsset2]))
42  {
43  JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair.";
44  return res;
45  }
46 
47  if (ctx.tx.getFlags() & tfUniversalMask)
48  {
49  JLOG(ctx.j.debug()) << "AMM Vote: invalid flags.";
50  return temINVALID_FLAG;
51  }
52 
54  {
55  JLOG(ctx.j.debug()) << "AMM Vote: invalid trading fee.";
56  return temBAD_FEE;
57  }
58 
59  return preflight2(ctx);
60 }
61 
62 TER
64 {
65  if (auto const ammSle =
66  ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
67  !ammSle)
68  {
69  JLOG(ctx.j.debug()) << "AMM Vote: Invalid asset pair.";
70  return terNO_AMM;
71  }
72  else if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::zero)
73  return tecAMM_EMPTY;
74  else if (auto const lpTokensNew =
75  ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
76  lpTokensNew == beast::zero)
77  {
78  JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
79  return tecAMM_INVALID_TOKENS;
80  }
81 
82  return tesSUCCESS;
83 }
84 
87  ApplyContext& ctx_,
88  Sandbox& sb,
89  AccountID const& account_,
90  beast::Journal j_)
91 {
92  auto const feeNew = ctx_.tx[sfTradingFee];
93  auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
94  if (!ammSle)
95  return {tecINTERNAL, false};
96  STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
97  auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
98  std::optional<STAmount> minTokens;
99  std::size_t minPos{0};
100  AccountID minAccount{0};
101  std::uint32_t minFee{0};
102  STArray updatedVoteSlots;
103  Number num{0};
104  Number den{0};
105  // Account already has vote entry
106  bool foundAccount = false;
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{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{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  // 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 =
212  auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
213  else if (auctionSlot.isFieldPresent(sfDiscountedFee))
214  auctionSlot.makeFieldAbsent(sfDiscountedFee);
215  }
216  }
217  else
218  {
219  if (ammSle->isFieldPresent(sfTradingFee))
220  ammSle->makeFieldAbsent(sfTradingFee);
221  if (ammSle->isFieldPresent(sfAuctionSlot))
222  {
223  auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
224  if (auctionSlot.isFieldPresent(sfDiscountedFee))
225  auctionSlot.makeFieldAbsent(sfDiscountedFee);
226  }
227  }
228  sb.update(ammSle);
229 
230  return {tesSUCCESS, true};
231 }
232 
233 TER
235 {
236  // This is the ledger view that we work against. Transactions are applied
237  // as we go on processing transactions.
238  Sandbox sb(&ctx_.view());
239 
240  auto const result = applyVote(ctx_, sb, account_, j_);
241  if (result.second)
242  sb.apply(ctx_.rawView());
243 
244  return result.first;
245 }
246 
247 } // namespace ripple
ripple::STArray::size
size_type size() const
Definition: STArray.h:248
ripple::applyVote
static std::pair< TER, bool > applyVote(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMVote.cpp:86
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
ripple::sfDiscountedFee
const SF_UINT16 sfDiscountedFee
ripple::sfAsset
const SF_ISSUE sfAsset
ripple::PreclaimContext::view
ReadView const & view
Definition: Transactor.h:56
ripple::VOTE_MAX_SLOTS
constexpr std::uint16_t VOTE_MAX_SLOTS
Definition: AMMCore.h:44
ripple::PreclaimContext::j
const beast::Journal j
Definition: Transactor.h:60
ripple::VOTE_WEIGHT_SCALE_FACTOR
constexpr std::uint32_t VOTE_WEIGHT_SCALE_FACTOR
Definition: AMMCore.h:45
ripple::Transactor::j_
const beast::Journal j_
Definition: Transactor.h:89
ripple::isTesSuccess
bool isTesSuccess(TER x)
Definition: TER.h:637
ripple::Sandbox::apply
void apply(RawView &to)
Definition: Sandbox.h:55
ripple::sfVoteSlots
const SField sfVoteSlots
std::pair
ripple::sfVoteEntry
const SField sfVoteEntry
ripple::sfLPTokenBalance
const SF_AMOUNT sfLPTokenBalance
ripple::AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
constexpr std::uint32_t AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition: AMMCore.h:38
ripple::AMMVote::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMVote.cpp:63
ripple::ammEnabled
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:126
ripple::keylet::amm
Keylet amm(Issue const &issue1, Issue const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:383
ripple::detail::ApplyViewBase::update
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
Definition: ApplyViewBase.cpp:146
ripple::tecAMM_EMPTY
@ tecAMM_EMPTY
Definition: TER.h:313
ripple::STArray::push_back
void push_back(STObject const &object)
Definition: STArray.h:212
ripple::ApplyContext::journal
const beast::Journal journal
Definition: ApplyContext.h:51
ripple::ApplyContext::rawView
RawView & rawView()
Definition: ApplyContext.h:67
ripple::PreflightContext::j
const beast::Journal j
Definition: Transactor.h:38
ripple::sfVoteWeight
const SF_UINT32 sfVoteWeight
ripple::preflight1
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
ripple::sfTradingFee
const SF_UINT16 sfTradingFee
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:82
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:110
ripple::AMMVote::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMVote.cpp:33
ripple::sfAsset2
const SF_ISSUE sfAsset2
ripple::Number
Definition: Number.h:36
ripple::tecAMM_INVALID_TOKENS
@ tecAMM_INVALID_TOKENS
Definition: TER.h:312
ripple::TERSubset< CanCvtToTER >
ripple::AMMVote::doApply
TER doApply() override
Definition: AMMVote.cpp:234
ripple::STArray
Definition: STArray.h:28
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:34
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:608
ripple::STAmount
Definition: STAmount.h:46
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:291
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:481
ripple::ApplyContext
State information when applying a tx.
Definition: ApplyContext.h:35
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::invalidAMMAssetPair
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue >> const &pair=std::nullopt)
Definition: AMMCore.cpp:79
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:91
ripple::ReadView::read
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
ripple::ApplyContext::view
ApplyView & view()
Definition: ApplyContext.h:54
ripple::PreclaimContext::tx
STTx const & tx
Definition: Transactor.h:58
ripple::PreclaimContext
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
ripple::STArray::begin
iterator begin()
Definition: STArray.h:224
ripple::STObject
Definition: STObject.h:52
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::TRADING_FEE_THRESHOLD
constexpr std::uint16_t TRADING_FEE_THRESHOLD
Definition: AMMCore.h:31
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:113
ripple::terNO_AMM
@ terNO_AMM
Definition: TER.h:220
ripple::ammLPHolds
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:104
ripple::Transactor::ctx_
ApplyContext & ctx_
Definition: Transactor.h:88
std::optional< STAmount >
beast::Journal::debug
Stream debug() const
Definition: Journal.h:314
std::size_t
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::detail::ApplyViewBase::peek
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Definition: ApplyViewBase.cpp:128
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:35
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::PreflightContext::rules
const Rules rules
Definition: Transactor.h:36
ripple::tfUniversalMask
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:60
ripple::sfAuctionSlot
const SField sfAuctionSlot
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:236
ripple::Transactor::account_
const AccountID account_
Definition: Transactor.h:91
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:568