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 (auto const lpTokensNew =
73  ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
74  lpTokensNew == beast::zero)
75  {
76  JLOG(ctx.j.debug()) << "AMM Vote: account is not LP.";
77  return tecAMM_INVALID_TOKENS;
78  }
79 
80  return tesSUCCESS;
81 }
82 
85  ApplyContext& ctx_,
86  Sandbox& sb,
87  AccountID const& account_,
88  beast::Journal j_)
89 {
90  auto const feeNew = ctx_.tx[sfTradingFee];
91  auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
92  if (!ammSle)
93  return {tecINTERNAL, false};
94  STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
95  auto const lpTokensNew = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
96  std::optional<STAmount> minTokens;
97  std::size_t minPos{0};
98  AccountID minAccount{0};
99  std::uint32_t minFee{0};
100  STArray updatedVoteSlots;
101  Number num{0};
102  Number den{0};
103  // Account already has vote entry
104  bool foundAccount = false;
105  // Iterate over the current vote entries and update each entry
106  // per current total tokens balance and each LP tokens balance.
107  // Find the entry with the least tokens and whether the account
108  // has the vote entry.
109  for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
110  {
111  auto const account = entry[sfAccount];
112  auto lpTokens = ammLPHolds(sb, *ammSle, account, ctx_.journal);
113  if (lpTokens == beast::zero)
114  {
115  JLOG(j_.debug())
116  << "AMMVote::applyVote, account " << account << " is not LP";
117  continue;
118  }
119  auto feeVal = entry[sfTradingFee];
120  STObject newEntry{sfVoteEntry};
121  // The account already has the vote entry.
122  if (account == account_)
123  {
124  lpTokens = lpTokensNew;
125  feeVal = feeNew;
126  foundAccount = true;
127  }
128  // Keep running numerator/denominator to calculate the updated fee.
129  num += feeVal * lpTokens;
130  den += lpTokens;
131  newEntry.setAccountID(sfAccount, account);
132  if (feeVal != 0)
133  newEntry.setFieldU16(sfTradingFee, feeVal);
134  newEntry.setFieldU32(
135  sfVoteWeight,
136  static_cast<std::int64_t>(
137  Number(lpTokens) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
138 
139  // Find an entry with the least tokens/fee. Make the order deterministic
140  // if the tokens/fees are equal.
141  if (!minTokens ||
142  (lpTokens < *minTokens ||
143  (lpTokens == *minTokens &&
144  (feeVal < minFee || (feeVal == minFee && account < minAccount)))))
145  {
146  minTokens = lpTokens;
147  minPos = updatedVoteSlots.size();
148  minAccount = account;
149  minFee = feeVal;
150  }
151  updatedVoteSlots.push_back(std::move(newEntry));
152  }
153 
154  // The account doesn't have the vote entry.
155  if (!foundAccount)
156  {
157  auto update = [&](std::optional<std::uint8_t> const& minPos =
158  std::nullopt) {
159  STObject newEntry{sfVoteEntry};
160  if (feeNew != 0)
161  newEntry.setFieldU16(sfTradingFee, feeNew);
162  newEntry.setFieldU32(
163  sfVoteWeight,
164  static_cast<std::int64_t>(
165  Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR /
166  lptAMMBalance));
167  newEntry.setAccountID(sfAccount, account_);
168  num += feeNew * lpTokensNew;
169  den += lpTokensNew;
170  if (minPos)
171  *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
172  else
173  updatedVoteSlots.push_back(std::move(newEntry));
174  };
175  // Add new entry if the number of the vote entries
176  // is less than Max.
177  if (updatedVoteSlots.size() < VOTE_MAX_SLOTS)
178  update();
179  // Add the entry if the account has more tokens than
180  // the least token holder or same tokens and higher fee.
181  else if (
182  lpTokensNew > *minTokens ||
183  (lpTokensNew == *minTokens && feeNew > minFee))
184  {
185  auto const entry = updatedVoteSlots.begin() + minPos;
186  // Remove the least token vote entry.
187  num -= Number((*entry)[~sfTradingFee].value_or(0)) * *minTokens;
188  den -= *minTokens;
189  update(minPos);
190  }
191  // All slots are full and the account does not hold more LPTokens.
192  // Update anyway to refresh the slots.
193  else
194  {
195  JLOG(j_.debug()) << "AMMVote::applyVote, insufficient tokens to "
196  "override other votes";
197  }
198  }
199 
200  // Update the vote entries and the trading/discounted fee.
201  ammSle->setFieldArray(sfVoteSlots, updatedVoteSlots);
202  if (auto const fee = static_cast<std::int64_t>(num / den))
203  {
204  ammSle->setFieldU16(sfTradingFee, fee);
205  if (ammSle->isFieldPresent(sfAuctionSlot))
206  {
207  auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
208  if (auto const discountedFee =
210  auctionSlot.setFieldU16(sfDiscountedFee, discountedFee);
211  else
212  auctionSlot.makeFieldAbsent(sfDiscountedFee);
213  }
214  }
215  else
216  {
217  ammSle->makeFieldAbsent(sfTradingFee);
218  if (ammSle->isFieldPresent(sfAuctionSlot))
219  {
220  auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
221  auctionSlot.makeFieldAbsent(sfDiscountedFee);
222  }
223  }
224  sb.update(ammSle);
225 
226  return {tesSUCCESS, true};
227 }
228 
229 TER
231 {
232  // This is the ledger view that we work against. Transactions are applied
233  // as we go on processing transactions.
234  Sandbox sb(&ctx_.view());
235 
236  auto const result = applyVote(ctx_, sb, account_, j_);
237  if (result.second)
238  sb.apply(ctx_.rawView());
239 
240  return result.first;
241 }
242 
243 } // 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:84
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:130
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:604
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:375
ripple::detail::ApplyViewBase::update
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
Definition: ApplyViewBase.cpp:146
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:78
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:109
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:301
ripple::TERSubset< CanCvtToTER >
ripple::AMMVote::doApply
TER doApply() override
Definition: AMMVote.cpp:230
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:575
ripple::STAmount
Definition: STAmount.h:45
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:280
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:90
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:112
ripple::terNO_AMM
@ terNO_AMM
Definition: TER.h:210
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:315
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:225
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:535