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  auto const& rules = ctx_.view().rules();
108  // Iterate over the current vote entries and update each entry
109  // per current total tokens balance and each LP tokens balance.
110  // Find the entry with the least tokens and whether the account
111  // has the vote entry.
112  for (auto const& entry : ammSle->getFieldArray(sfVoteSlots))
113  {
114  auto const account = entry[sfAccount];
115  auto lpTokens = ammLPHolds(sb, *ammSle, account, ctx_.journal);
116  if (lpTokens == beast::zero)
117  {
118  JLOG(j_.debug())
119  << "AMMVote::applyVote, account " << account << " is not LP";
120  continue;
121  }
122  auto feeVal = entry[sfTradingFee];
124  // The account already has the vote entry.
125  if (account == account_)
126  {
127  lpTokens = lpTokensNew;
128  feeVal = feeNew;
129  foundAccount = true;
130  }
131  // Keep running numerator/denominator to calculate the updated fee.
132  num += feeVal * lpTokens;
133  den += lpTokens;
134  newEntry.setAccountID(sfAccount, account);
135  if (feeVal != 0)
136  newEntry.setFieldU16(sfTradingFee, feeVal);
137  newEntry.setFieldU32(
138  sfVoteWeight,
139  static_cast<std::int64_t>(
140  Number(lpTokens) * VOTE_WEIGHT_SCALE_FACTOR / lptAMMBalance));
141 
142  // Find an entry with the least tokens/fee. Make the order deterministic
143  // if the tokens/fees are equal.
144  if (!minTokens ||
145  (lpTokens < *minTokens ||
146  (lpTokens == *minTokens &&
147  (feeVal < minFee || (feeVal == minFee && account < minAccount)))))
148  {
149  minTokens = lpTokens;
150  minPos = updatedVoteSlots.size();
151  minAccount = account;
152  minFee = feeVal;
153  }
154  updatedVoteSlots.push_back(std::move(newEntry));
155  }
156 
157  // The account doesn't have the vote entry.
158  if (!foundAccount)
159  {
160  auto update = [&](std::optional<std::uint8_t> const& minPos =
161  std::nullopt) {
163  if (feeNew != 0)
164  newEntry.setFieldU16(sfTradingFee, feeNew);
165  newEntry.setFieldU32(
166  sfVoteWeight,
167  static_cast<std::int64_t>(
168  Number(lpTokensNew) * VOTE_WEIGHT_SCALE_FACTOR /
169  lptAMMBalance));
170  newEntry.setAccountID(sfAccount, account_);
171  num += feeNew * lpTokensNew;
172  den += lpTokensNew;
173  if (minPos)
174  *(updatedVoteSlots.begin() + *minPos) = std::move(newEntry);
175  else
176  updatedVoteSlots.push_back(std::move(newEntry));
177  };
178  // Add new entry if the number of the vote entries
179  // is less than Max.
180  if (updatedVoteSlots.size() < VOTE_MAX_SLOTS)
181  update();
182  // Add the entry if the account has more tokens than
183  // the least token holder or same tokens and higher fee.
184  else if (
185  lpTokensNew > *minTokens ||
186  (lpTokensNew == *minTokens && feeNew > minFee))
187  {
188  auto const entry = updatedVoteSlots.begin() + minPos;
189  // Remove the least token vote entry.
190  num -= Number((*entry)[~sfTradingFee].value_or(0)) * *minTokens;
191  den -= *minTokens;
192  update(minPos);
193  }
194  // All slots are full and the account does not hold more LPTokens.
195  // Update anyway to refresh the slots.
196  else
197  {
198  JLOG(j_.debug()) << "AMMVote::applyVote, insufficient tokens to "
199  "override other votes";
200  }
201  }
202 
203  assert(
204  !ctx_.view().rules().enabled(fixInnerObjTemplate) ||
205  ammSle->isFieldPresent(sfAuctionSlot));
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 
238 TER
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
ripple::STArray::size
size_type size() const
Definition: STArray.h:248
ripple::STObject::setAccountID
void setAccountID(SField const &field, AccountID const &)
Definition: STObject.cpp:710
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::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:94
ripple::PreclaimContext::view
ReadView const & view
Definition: Transactor.h:56
ripple::STObject::setFieldU16
void setFieldU16(SField const &field, std::uint16_t)
Definition: STObject.cpp:674
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:636
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:312
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::STObject::makeInnerObject
static STObject makeInnerObject(SField const &name, Rules const &rules)
Definition: STObject.cpp:63
ripple::tecAMM_INVALID_TOKENS
@ tecAMM_INVALID_TOKENS
Definition: TER.h:311
ripple::TERSubset< CanCvtToTER >
ripple::AMMVote::doApply
TER doApply() override
Definition: AMMVote.cpp:239
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:607
ripple::STAmount
Definition: STAmount.h:46
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:290
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:496
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:54
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::ReadView::rules
virtual Rules const & rules() const =0
Returns the tx processing rules.
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::fixInnerObjTemplate
const uint256 fixInnerObjTemplate
ripple::STObject::setFieldU32
void setFieldU32(SField const &field, std::uint32_t)
Definition: STObject.cpp:680
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:235
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:567