rippled
Loading...
Searching...
No Matches
AMMBid.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2022 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/misc/AMMHelpers.h>
21#include <xrpld/app/misc/AMMUtils.h>
22#include <xrpld/app/tx/detail/AMMBid.h>
23#include <xrpld/ledger/Sandbox.h>
24#include <xrpld/ledger/View.h>
25
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31namespace ripple {
32
35{
36 if (!ammEnabled(ctx.rules))
37 return temDISABLED;
38
39 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
40 return ret;
41
42 if (ctx.tx.getFlags() & tfUniversalMask)
43 {
44 JLOG(ctx.j.debug()) << "AMM Bid: invalid flags.";
45 return temINVALID_FLAG;
46 }
47
48 if (auto const res = invalidAMMAssetPair(
49 ctx.tx[sfAsset].get<Issue>(), ctx.tx[sfAsset2].get<Issue>()))
50 {
51 JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
52 return res;
53 }
54
55 if (auto const bidMin = ctx.tx[~sfBidMin])
56 {
57 if (auto const res = invalidAMMAmount(*bidMin))
58 {
59 JLOG(ctx.j.debug()) << "AMM Bid: invalid min slot price.";
60 return res;
61 }
62 }
63
64 if (auto const bidMax = ctx.tx[~sfBidMax])
65 {
66 if (auto const res = invalidAMMAmount(*bidMax))
67 {
68 JLOG(ctx.j.debug()) << "AMM Bid: invalid max slot price.";
69 return res;
70 }
71 }
72
73 if (ctx.tx.isFieldPresent(sfAuthAccounts))
74 {
75 if (auto const authAccounts = ctx.tx.getFieldArray(sfAuthAccounts);
76 authAccounts.size() > AUCTION_SLOT_MAX_AUTH_ACCOUNTS)
77 {
78 JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts.";
79 return temMALFORMED;
80 }
81 }
82
83 return preflight2(ctx);
84}
85
86TER
88{
89 auto const ammSle =
90 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
91 if (!ammSle)
92 {
93 JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
94 return terNO_AMM;
95 }
96
97 auto const lpTokensBalance = (*ammSle)[sfLPTokenBalance];
98 if (lpTokensBalance == beast::zero)
99 return tecAMM_EMPTY;
100
101 if (ctx.tx.isFieldPresent(sfAuthAccounts))
102 {
103 for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
104 {
105 if (!ctx.view.read(keylet::account(account[sfAccount])))
106 {
107 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
108 return terNO_ACCOUNT;
109 }
110 }
111 }
112
113 auto const lpTokens =
114 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
115 // Not LP
116 if (lpTokens == beast::zero)
117 {
118 JLOG(ctx.j.debug()) << "AMM Bid: account is not LP.";
120 }
121
122 auto const bidMin = ctx.tx[~sfBidMin];
123
124 if (bidMin)
125 {
126 if (bidMin->issue() != lpTokens.issue())
127 {
128 JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
129 return temBAD_AMM_TOKENS;
130 }
131 if (*bidMin > lpTokens || *bidMin >= lpTokensBalance)
132 {
133 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
135 }
136 }
137
138 auto const bidMax = ctx.tx[~sfBidMax];
139 if (bidMax)
140 {
141 if (bidMax->issue() != lpTokens.issue())
142 {
143 JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
144 return temBAD_AMM_TOKENS;
145 }
146 if (*bidMax > lpTokens || *bidMax >= lpTokensBalance)
147 {
148 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
150 }
151 }
152
153 if (bidMin && bidMax && bidMin > bidMax)
154 {
155 JLOG(ctx.j.debug()) << "AMM Bid: Invalid Max/MinSlotPrice.";
157 }
158
159 return tesSUCCESS;
160}
161
164 ApplyContext& ctx_,
165 Sandbox& sb,
166 AccountID const& account_,
168{
169 using namespace std::chrono;
170 auto const ammSle =
171 sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
172 if (!ammSle)
173 return {tecINTERNAL, false};
174 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
175 auto const lpTokens = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
176 auto const& rules = ctx_.view().rules();
177 if (!rules.enabled(fixInnerObjTemplate))
178 {
179 if (!ammSle->isFieldPresent(sfAuctionSlot))
180 ammSle->makeFieldPresent(sfAuctionSlot);
181 }
182 else
183 {
184 XRPL_ASSERT(
185 ammSle->isFieldPresent(sfAuctionSlot),
186 "ripple::applyBid : has auction slot");
187 if (!ammSle->isFieldPresent(sfAuctionSlot))
188 return {tecINTERNAL, false};
189 }
190 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
191 auto const current =
192 duration_cast<seconds>(
194 .count();
195 // Auction slot discounted fee
196 auto const discountedFee =
197 (*ammSle)[sfTradingFee] / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION;
198 auto const tradingFee = getFee((*ammSle)[sfTradingFee]);
199 // Min price
200 auto const minSlotPrice =
201 lptAMMBalance * tradingFee / AUCTION_SLOT_MIN_FEE_FRACTION;
202
203 std::uint32_t constexpr tailingSlot = AUCTION_SLOT_TIME_INTERVALS - 1;
204
205 // If seated then it is the current slot-holder time slot, otherwise
206 // the auction slot is not owned. Slot range is in {0-19}
207 auto const timeSlot = ammAuctionTimeSlot(current, auctionSlot);
208
209 // Account must exist and the slot not expired.
210 auto validOwner = [&](AccountID const& account) {
211 // Valid range is 0-19 but the tailing slot pays MinSlotPrice
212 // and doesn't refund so the check is < instead of <= to optimize.
213 return timeSlot && *timeSlot < tailingSlot &&
214 sb.read(keylet::account(account));
215 };
216
217 auto updateSlot = [&](std::uint32_t fee,
218 Number const& minPrice,
219 Number const& burn) -> TER {
220 auctionSlot.setAccountID(sfAccount, account_);
221 auctionSlot.setFieldU32(sfExpiration, current + TOTAL_TIME_SLOT_SECS);
222 if (fee != 0)
223 auctionSlot.setFieldU16(sfDiscountedFee, fee);
224 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
225 auctionSlot.makeFieldAbsent(sfDiscountedFee);
226 auctionSlot.setFieldAmount(
227 sfPrice, toSTAmount(lpTokens.issue(), minPrice));
228 if (ctx_.tx.isFieldPresent(sfAuthAccounts))
229 auctionSlot.setFieldArray(
230 sfAuthAccounts, ctx_.tx.getFieldArray(sfAuthAccounts));
231 else
232 auctionSlot.makeFieldAbsent(sfAuthAccounts);
233 // Burn the remaining bid amount
234 auto const saBurn = adjustLPTokens(
235 lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false);
236 if (saBurn >= lptAMMBalance)
237 {
238 // This error case should never occur.
239 JLOG(ctx_.journal.fatal())
240 << "AMM Bid: LP Token burn exceeds AMM balance " << burn << " "
241 << lptAMMBalance;
242 return tecINTERNAL;
243 }
244 auto res =
245 redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal);
246 if (res != tesSUCCESS)
247 {
248 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem.";
249 return res;
250 }
251 ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn);
252 sb.update(ammSle);
253 return tesSUCCESS;
254 };
255
256 TER res = tesSUCCESS;
257
258 auto const bidMin = ctx_.tx[~sfBidMin];
259 auto const bidMax = ctx_.tx[~sfBidMax];
260
261 auto getPayPrice =
262 [&](Number const& computedPrice) -> Expected<Number, TER> {
263 auto const payPrice = [&]() -> std::optional<Number> {
264 // Both min/max bid price are defined
265 if (bidMin && bidMax)
266 {
267 if (computedPrice <= *bidMax)
268 return std::max(computedPrice, Number(*bidMin));
269 JLOG(ctx_.journal.debug())
270 << "AMM Bid: not in range " << computedPrice << " "
271 << *bidMin << " " << *bidMax;
272 return std::nullopt;
273 }
274 // Bidder pays max(bidPrice, computedPrice)
275 if (bidMin)
276 {
277 return std::max(computedPrice, Number(*bidMin));
278 }
279 else if (bidMax)
280 {
281 if (computedPrice <= *bidMax)
282 return computedPrice;
283 JLOG(ctx_.journal.debug()) << "AMM Bid: not in range "
284 << computedPrice << " " << *bidMax;
285 return std::nullopt;
286 }
287 else
288 return computedPrice;
289 }();
290 if (!payPrice)
292 else if (payPrice > lpTokens)
294 return *payPrice;
295 };
296
297 // No one owns the slot or expired slot.
298 if (auto const acct = auctionSlot[~sfAccount]; !acct || !validOwner(*acct))
299 {
300 if (auto const payPrice = getPayPrice(minSlotPrice); !payPrice)
301 return {payPrice.error(), false};
302 else
303 res = updateSlot(discountedFee, *payPrice, *payPrice);
304 }
305 else
306 {
307 // Price the slot was purchased at.
308 STAmount const pricePurchased = auctionSlot[sfPrice];
309 XRPL_ASSERT(timeSlot, "ripple::applyBid : timeSlot is set");
310 auto const fractionUsed =
311 (Number(*timeSlot) + 1) / AUCTION_SLOT_TIME_INTERVALS;
312 auto const fractionRemaining = Number(1) - fractionUsed;
313 auto const computedPrice = [&]() -> Number {
314 auto const p1_05 = Number(105, -2);
315 // First interval slot price
316 if (*timeSlot == 0)
317 return pricePurchased * p1_05 + minSlotPrice;
318 // Other intervals slot price
319 return pricePurchased * p1_05 * (1 - power(fractionUsed, 60)) +
320 minSlotPrice;
321 }();
322
323 auto const payPrice = getPayPrice(computedPrice);
324
325 if (!payPrice)
326 return {payPrice.error(), false};
327
328 // Refund the previous owner. If the time slot is 0 then
329 // the owner is refunded 95% of the amount.
330 auto const refund = fractionRemaining * pricePurchased;
331 if (refund > *payPrice)
332 {
333 // This error case should never occur.
334 JLOG(ctx_.journal.fatal()) << "AMM Bid: refund exceeds payPrice "
335 << refund << " " << *payPrice;
336 return {tecINTERNAL, false};
337 }
338 res = accountSend(
339 sb,
340 account_,
341 auctionSlot[sfAccount],
342 toSTAmount(lpTokens.issue(), refund),
343 ctx_.journal);
344 if (res != tesSUCCESS)
345 {
346 JLOG(ctx_.journal.debug()) << "AMM Bid: failed to refund.";
347 return {res, false};
348 }
349
350 auto const burn = *payPrice - refund;
351 res = updateSlot(discountedFee, *payPrice, burn);
352 }
353
354 return {res, res == tesSUCCESS};
355}
356
357TER
359{
360 // This is the ledger view that we work against. Transactions are applied
361 // as we go on processing transactions.
362 Sandbox sb(&ctx_.view());
363
364 auto const result = applyBid(ctx_, sb, account_, j_);
365 if (result.second)
366 sb.apply(ctx_.rawView());
367
368 return result.first;
369}
370
371} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream debug() const
Definition: Journal.h:328
TER doApply() override
Definition: AMMBid.cpp:358
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:87
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMBid.cpp:34
State information when applying a tx.
Definition: ApplyContext.h:37
RawView & rawView()
Definition: ApplyContext.h:68
ApplyView & view()
Definition: ApplyContext.h:55
beast::Journal const journal
Definition: ApplyContext.h:52
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 LedgerInfo const & info() const =0
Returns information about the ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
Issue const & issue() const
Definition: STAmount.h:487
size_type size() const
Definition: STArray.h:248
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:686
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
AccountID const account_
Definition: Transactor.h:93
beast::Journal const j_
Definition: Transactor.h:91
ApplyContext & ctx_
Definition: Transactor.h:90
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T max(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:438
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:176
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:107
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:133
std::uint32_t constexpr TOTAL_TIME_SLOT_SECS
Definition: AMMCore.h:34
std::uint16_t constexpr AUCTION_SLOT_TIME_INTERVALS
Definition: AMMCore.h:35
std::optional< std::uint8_t > ammAuctionTimeSlot(std::uint64_t current, STObject const &auctionSlot)
Get time slot of the auction slot.
Definition: AMMCore.cpp:120
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1783
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
bool isTesSuccess(TER x)
Definition: TER.h:656
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:141
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:83
@ current
This was a new validation and was added.
std::uint32_t constexpr AUCTION_SLOT_MIN_FEE_FRACTION
Definition: AMMCore.h:39
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:112
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:144
@ tecAMM_EMPTY
Definition: TER.h:319
@ tecINTERNAL
Definition: TER.h:297
@ tecAMM_FAILED
Definition: TER.h:317
@ tecAMM_INVALID_TOKENS
Definition: TER.h:318
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:109
Number power(Number const &f, unsigned n)
Definition: Number.cpp:613
@ 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:92
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
static std::pair< TER, bool > applyBid(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMBid.cpp:163
@ terNO_ACCOUNT
Definition: TER.h:217
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
std::uint16_t constexpr AUCTION_SLOT_MAX_AUTH_ACCOUNTS
Definition: AMMCore.h:36
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1609
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
std::uint32_t constexpr AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition: AMMCore.h:38
@ temMALFORMED
Definition: TER.h:87
@ temBAD_AMM_TOKENS
Definition: TER.h:129
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:55
ReadView const & view
Definition: Transactor.h:58
beast::Journal const j
Definition: Transactor.h:62
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:40
T time_since_epoch(T... args)