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