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