xrpld
Loading...
Searching...
No Matches
OracleSet.cpp
1#include <xrpl/tx/transactors/oracle/OracleSet.h>
2
3#include <xrpl/basics/chrono.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/core/ServiceRegistry.h>
6#include <xrpl/ledger/helpers/AccountRootHelpers.h>
7#include <xrpl/ledger/helpers/DirectoryHelpers.h>
8#include <xrpl/ledger/helpers/OracleHelpers.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/InnerObjectFormats.h>
12#include <xrpl/protocol/Protocol.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/SOTemplate.h>
15#include <xrpl/protocol/STLedgerEntry.h>
16#include <xrpl/protocol/STObject.h>
17#include <xrpl/protocol/STTx.h>
18#include <xrpl/protocol/TER.h>
19#include <xrpl/protocol/UintTypes.h>
20#include <xrpl/protocol/XRPAmount.h>
21#include <xrpl/tx/ApplyContext.h>
22#include <xrpl/tx/Transactor.h>
23
24#include <chrono>
25#include <cstddef>
26#include <cstdint>
27#include <cstdlib>
28#include <map>
29#include <memory>
30#include <set>
31#include <utility>
32
33namespace xrpl {
34
35static inline std::pair<Currency, Currency>
37{
38 return std::make_pair(
39 pair.getFieldCurrency(sfBaseAsset).currency(),
40 pair.getFieldCurrency(sfQuoteAsset).currency());
41}
42
45{
46 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
47 if (dataSeries.empty())
48 return temARRAY_EMPTY;
49 if (dataSeries.size() > kMaxOracleDataSeries)
50 return temARRAY_TOO_LARGE;
51
52 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
53 return ctx.tx.isFieldPresent(sField) &&
54 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
55 };
56
57 if (isInvalidLength(sfProvider, kMaxOracleProvider) || isInvalidLength(sfURI, kMaxOracleUri) ||
58 isInvalidLength(sfAssetClass, kMaxOracleSymbolClass))
59 return temMALFORMED;
60
61 return tesSUCCESS;
62}
63
64TER
66{
67 auto const sleSetter = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
68 if (!sleSetter)
69 return terNO_ACCOUNT; // LCOV_EXCL_LINE
70
71 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
72 // of the last closed ledger
73 using namespace std::chrono;
74 std::size_t const closeTime =
76 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
77 if (lastUpdateTime < kEpochOffset.count())
79 std::size_t const lastUpdateTimeEpoch = lastUpdateTime - kEpochOffset.count();
80 if (closeTime < kMaxLastUpdateTimeDelta)
81 return tecINTERNAL; // LCOV_EXCL_LINE
82 if (lastUpdateTimeEpoch < (closeTime - kMaxLastUpdateTimeDelta) ||
83 lastUpdateTimeEpoch > (closeTime + kMaxLastUpdateTimeDelta))
85
86 auto const sle =
87 ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
88
89 // token pairs to add/update
91 // token pairs to delete. if a token pair doesn't include
92 // the price then this pair should be deleted from the object.
94 for (auto const& entry : ctx.tx.getFieldArray(sfPriceDataSeries))
95 {
96 if (entry[sfBaseAsset] == entry[sfQuoteAsset])
97 return temMALFORMED;
98 auto const key = tokenPairKey(entry);
99 if (pairs.contains(key) || pairsDel.contains(key))
100 return temMALFORMED;
101 if (entry[~sfScale] > kMaxPriceScale)
102 return temMALFORMED;
103 if (entry.isFieldPresent(sfAssetPrice))
104 {
105 pairs.emplace(key);
106 }
107 else if (sle)
108 {
109 pairsDel.emplace(key);
110 }
111 else
112 {
113 return temMALFORMED;
114 }
115 }
116
117 // Lambda is used to check if the value of a field, passed
118 // in the transaction, is equal to the value of that field
119 // in the on-ledger object.
120 auto isConsistent = [&ctx, &sle](auto const& field) {
121 auto const v = ctx.tx[~field];
122 return !v || *v == (*sle)[field];
123 };
124
125 std::int8_t adjustReserve = 0;
126 if (sle)
127 {
128 // update
129 // Account is the Owner since we can get sle
130
131 // lastUpdateTime must be more recent than the previous one
132 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
134
135 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
136 return temMALFORMED;
137
138 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
139 {
140 auto const key = tokenPairKey(entry);
141 if (!pairs.contains(key))
142 {
143 if (pairsDel.contains(key))
144 {
145 pairsDel.erase(key);
146 }
147 else
148 {
149 pairs.emplace(key);
150 }
151 }
152 }
153 if (!pairsDel.empty())
155
156 auto const oldCount = calculateOracleReserve(sle);
157 auto const newCount = calculateOracleReserve(pairs);
158
159 adjustReserve = newCount - oldCount;
160 }
161 else
162 {
163 // create
164
165 if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass))
166 return temMALFORMED;
167 adjustReserve = calculateOracleReserve(pairs);
168 }
169
170 if (pairs.empty())
171 return tecARRAY_EMPTY;
172 if (pairs.size() > kMaxOracleDataSeries)
173 return tecARRAY_TOO_LARGE;
174
175 auto const reserve =
176 accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve});
177 auto const& balance = sleSetter->getFieldAmount(sfBalance);
178
179 if (balance < reserve)
181
182 return tesSUCCESS;
183}
184
185static bool
187{
188 XRPL_ASSERT(std::abs(count) <= 2, "xrpl::adjustOracleOwnerCount abs(counter) <= 2");
189
190 if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
191 {
192 if (count > 0)
193 {
195 ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(count), ctx.journal);
196 }
197 else if (count < 0)
198 {
200 ctx.view(), sleAccount, {}, static_cast<std::uint32_t>(-count), ctx.journal);
201 }
202 return true;
203 }
204
205 return false; // LCOV_EXCL_LINE
206}
207
208static void
210{
211 if (SOTemplate const* elements =
212 InnerObjectFormats::getInstance().findSOTemplateBySField(sfPriceData))
213 obj.set(*elements);
214}
215
216TER
218{
219 auto const oracleID = keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID]);
220
221 auto populatePriceData = [](STObject& priceData, STObject const& entry) {
223 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
224 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
225 priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
226 if (entry.isFieldPresent(sfScale))
227 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
228 };
229
230 if (auto sle = ctx_.view().peek(oracleID))
231 {
232 // update
233 // the token pair that doesn't have their price updated will not
234 // include neither price nor scale in the updated PriceDataSeries
235
237 // collect current token pairs
238 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
239 {
240 STObject priceData{sfPriceData};
242 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
243 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
244 pairs.emplace(tokenPairKey(entry), std::move(priceData));
245 }
246 auto const oldCount = calculateOracleReserve(pairs);
247 // update/add/delete pairs
248 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
249 {
250 auto const key = tokenPairKey(entry);
251 if (!entry.isFieldPresent(sfAssetPrice))
252 {
253 // delete token pair
254 pairs.erase(key);
255 }
256 else if (auto iter = pairs.find(key); iter != pairs.end())
257 {
258 // update the price
259 iter->second.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
260 if (entry.isFieldPresent(sfScale))
261 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
262 }
263 else
264 {
265 // add a token pair with the price
266 STObject priceData{sfPriceData};
267 populatePriceData(priceData, entry);
268 pairs.emplace(key, std::move(priceData));
269 }
270 }
271 STArray updatedSeries;
272 for (auto const& iter : pairs)
273 updatedSeries.pushBack(iter.second);
274 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
275 if (ctx_.tx.isFieldPresent(sfURI))
276 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
277 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
278 if (!sle->isFieldPresent(sfOracleDocumentID) &&
279 ctx_.view().rules().enabled(fixIncludeKeyletFields))
280 {
281 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
282 }
283
284 auto const newCount = calculateOracleReserve(pairs);
285 int32_t const adjust = newCount - oldCount;
286
287 if (adjust != 0 && !adjustOracleOwnerCount(ctx_, adjust))
288 return tefINTERNAL; // LCOV_EXCL_LINE
289
290 ctx_.view().update(sle);
291 }
292 else
293 {
294 // create
295
296 sle = std::make_shared<SLE>(oracleID);
297 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
298 if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
299 {
300 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
301 }
302 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
303 if (ctx_.tx.isFieldPresent(sfURI))
304 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
305
306 STArray series;
307 if (!ctx_.view().rules().enabled(fixPriceOracleOrder))
308 {
309 series = ctx_.tx.getFieldArray(sfPriceDataSeries);
310 }
311 else
312 {
314 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
315 {
316 auto const key = tokenPairKey(entry);
317 STObject priceData{sfPriceData};
318 populatePriceData(priceData, entry);
319 pairs.emplace(key, std::move(priceData));
320 }
321 for (auto const& iter : pairs)
322 series.pushBack(iter.second);
323 }
324
325 sle->setFieldArray(sfPriceDataSeries, series);
326 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
327 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
328
329 auto page = ctx_.view().dirInsert(
331 if (!page)
332 return tecDIR_FULL; // LCOV_EXCL_LINE
333
334 (*sle)[sfOwnerNode] = *page;
335
336 auto const count = calculateOracleReserve(series);
338 return tefINTERNAL; // LCOV_EXCL_LINE
339
340 ctx_.view().insert(sle);
341 }
342
343 return tesSUCCESS;
344}
345
346void
348{
349 // No transaction-specific invariants yet (future work).
350}
351
352bool
354{
355 // No transaction-specific invariants yet (future work).
356 return true;
357}
358
359} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
State information when applying a tx.
STTx const & tx
beast::Journal const journal
ApplyView & view()
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static InnerObjectFormats const & getInstance()
TER doApply() override
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static TER preclaim(PreclaimContext const &ctx)
Definition OracleSet.cpp:65
static NotTEC preflight(PreflightContext const &ctx)
Definition OracleSet.cpp:44
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:105
void pushBack(STObject const &object)
Definition STArray.h:212
Currency const & currency() const
Definition STCurrency.h:73
std::shared_ptr< STLedgerEntry const > const & const_ref
void setFieldU8(SField const &field, unsigned char)
Definition STObject.cpp:731
STCurrency const & getFieldCurrency(SField const &field) const
Definition STObject.cpp:695
void setFieldU64(SField const &field, std::uint64_t)
Definition STObject.cpp:749
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:688
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
void set(SOTemplate const &)
Definition STObject.cpp:138
void setFieldCurrency(SField const &field, STCurrency const &)
Definition STObject.cpp:809
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:643
AccountID const accountID_
Definition Transactor.h:157
ApplyContext & ctx_
Definition Transactor.h:153
T contains(T... args)
T count(T... args)
T duration_cast(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T make_pair(T... args)
T make_shared(T... args)
Keylet oracle(AccountID const &account, std::uint32_t const documentID) noexcept
Definition Indexes.cpp:531
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static constexpr std::chrono::seconds kEpochOffset
Clock for measuring the network time.
Definition chrono.h:35
@ terNO_ACCOUNT
Definition TER.h:213
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition OracleSet.cpp:36
void increaseOwnerCount(ApplyView &view, SLE::ref accountSle, SLE::ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
constexpr std::size_t kMaxPriceScale
The maximum price scaling factor.
Definition Protocol.h:428
@ tefINTERNAL
Definition TER.h:165
constexpr std::size_t kMaxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition Protocol.h:423
static bool adjustOracleOwnerCount(ApplyContext &ctx, int count)
constexpr std::size_t kMaxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition Protocol.h:417
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
static void setPriceDataInnerObjTemplate(STObject &obj)
constexpr std::size_t kMaxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition Protocol.h:407
void decreaseOwnerCount(ApplyView &view, SLE::ref accountSle, SLE::ref sponsorSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields when the caller supplies the sponsor.
constexpr std::size_t kMaxOracleUri
The maximum length of a URI inside an Oracle.
Definition Protocol.h:402
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
std::uint32_t calculateOracleReserve(T const &priceDataSeries)
@ temARRAY_TOO_LARGE
Definition TER.h:129
@ temMALFORMED
Definition TER.h:75
@ temARRAY_EMPTY
Definition TER.h:128
TERSubset< CanCvtToTER > TER
Definition TER.h:647
bool isConsistent(Asset const &asset)
Definition Asset.h:323
@ tecINVALID_UPDATE_TIME
Definition TER.h:357
@ tecDIR_FULL
Definition TER.h:290
@ tecINTERNAL
Definition TER.h:313
@ tecARRAY_TOO_LARGE
Definition TER.h:360
@ tecARRAY_EMPTY
Definition TER.h:359
@ tecINSUFFICIENT_RESERVE
Definition TER.h:310
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:358
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
@ tesSUCCESS
Definition TER.h:245
constexpr std::size_t kMaxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:412
T size(T... args)
NetClock::time_point closeTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
T time_since_epoch(T... args)