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