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/core/ServiceRegistry.h>
5#include <xrpl/ledger/helpers/AccountRootHelpers.h>
6#include <xrpl/ledger/helpers/DirectoryHelpers.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/InnerObjectFormats.h>
10#include <xrpl/protocol/Protocol.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/SOTemplate.h>
13#include <xrpl/protocol/STLedgerEntry.h>
14#include <xrpl/protocol/STObject.h>
15#include <xrpl/protocol/STTx.h>
16#include <xrpl/protocol/TER.h>
17#include <xrpl/protocol/UintTypes.h>
18#include <xrpl/protocol/XRPAmount.h>
19#include <xrpl/tx/ApplyContext.h>
20#include <xrpl/tx/Transactor.h>
21
22#include <chrono>
23#include <cstddef>
24#include <cstdint>
25#include <map>
26#include <memory>
27#include <set>
28#include <utility>
29
30namespace xrpl {
31
32static inline std::pair<Currency, Currency>
34{
35 return std::make_pair(
36 pair.getFieldCurrency(sfBaseAsset).currency(),
37 pair.getFieldCurrency(sfQuoteAsset).currency());
38}
39
42{
43 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
44 if (dataSeries.empty())
45 return temARRAY_EMPTY;
46 if (dataSeries.size() > kMaxOracleDataSeries)
47 return temARRAY_TOO_LARGE;
48
49 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
50 return ctx.tx.isFieldPresent(sField) &&
51 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
52 };
53
54 if (isInvalidLength(sfProvider, kMaxOracleProvider) || isInvalidLength(sfURI, kMaxOracleUri) ||
55 isInvalidLength(sfAssetClass, kMaxOracleSymbolClass))
56 return temMALFORMED;
57
58 return tesSUCCESS;
59}
60
61TER
63{
64 auto const sleSetter = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
65 if (!sleSetter)
66 return terNO_ACCOUNT; // LCOV_EXCL_LINE
67
68 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
69 // of the last closed ledger
70 using namespace std::chrono;
71 std::size_t const closeTime =
73 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
74 if (lastUpdateTime < kEpochOffset.count())
76 std::size_t const lastUpdateTimeEpoch = lastUpdateTime - kEpochOffset.count();
77 if (closeTime < kMaxLastUpdateTimeDelta)
78 return tecINTERNAL; // LCOV_EXCL_LINE
79 if (lastUpdateTimeEpoch < (closeTime - kMaxLastUpdateTimeDelta) ||
80 lastUpdateTimeEpoch > (closeTime + kMaxLastUpdateTimeDelta))
82
83 auto const sle =
84 ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
85
86 // token pairs to add/update
88 // token pairs to delete. if a token pair doesn't include
89 // the price then this pair should be deleted from the object.
91 for (auto const& entry : ctx.tx.getFieldArray(sfPriceDataSeries))
92 {
93 if (entry[sfBaseAsset] == entry[sfQuoteAsset])
94 return temMALFORMED;
95 auto const key = tokenPairKey(entry);
96 if (pairs.contains(key) || pairsDel.contains(key))
97 return temMALFORMED;
98 if (entry[~sfScale] > kMaxPriceScale)
99 return temMALFORMED;
100 if (entry.isFieldPresent(sfAssetPrice))
101 {
102 pairs.emplace(key);
103 }
104 else if (sle)
105 {
106 pairsDel.emplace(key);
107 }
108 else
109 {
110 return temMALFORMED;
111 }
112 }
113
114 // Lambda is used to check if the value of a field, passed
115 // in the transaction, is equal to the value of that field
116 // in the on-ledger object.
117 auto isConsistent = [&ctx, &sle](auto const& field) {
118 auto const v = ctx.tx[~field];
119 return !v || *v == (*sle)[field];
120 };
121
122 std::int8_t adjustReserve = 0;
123 if (sle)
124 {
125 // update
126 // Account is the Owner since we can get sle
127
128 // lastUpdateTime must be more recent than the previous one
129 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
131
132 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
133 return temMALFORMED;
134
135 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
136 {
137 auto const key = tokenPairKey(entry);
138 if (!pairs.contains(key))
139 {
140 if (pairsDel.contains(key))
141 {
142 pairsDel.erase(key);
143 }
144 else
145 {
146 pairs.emplace(key);
147 }
148 }
149 }
150 if (!pairsDel.empty())
152
153 auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
154 auto const newCount = pairs.size() > 5 ? 2 : 1;
155 adjustReserve = newCount - oldCount;
156 }
157 else
158 {
159 // create
160
161 if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass))
162 return temMALFORMED;
163 adjustReserve = pairs.size() > 5 ? 2 : 1;
164 }
165
166 if (pairs.empty())
167 return tecARRAY_EMPTY;
168 if (pairs.size() > kMaxOracleDataSeries)
169 return tecARRAY_TOO_LARGE;
170
171 auto const reserve =
172 ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
173 auto const& balance = sleSetter->getFieldAmount(sfBalance);
174
175 if (balance < reserve)
177
178 return tesSUCCESS;
179}
180
181static bool
183{
184 if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
185 {
186 adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
187 return true;
188 }
189
190 return false; // LCOV_EXCL_LINE
191}
192
193static void
195{
196 if (SOTemplate const* elements =
197 InnerObjectFormats::getInstance().findSOTemplateBySField(sfPriceData))
198 obj.set(*elements);
199}
200
201TER
203{
204 auto const oracleID = keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID]);
205
206 auto populatePriceData = [](STObject& priceData, STObject const& entry) {
208 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
209 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
210 priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
211 if (entry.isFieldPresent(sfScale))
212 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
213 };
214
215 if (auto sle = ctx_.view().peek(oracleID))
216 {
217 // update
218 // the token pair that doesn't have their price updated will not
219 // include neither price nor scale in the updated PriceDataSeries
220
222 // collect current token pairs
223 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
224 {
225 STObject priceData{sfPriceData};
227 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
228 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
229 pairs.emplace(tokenPairKey(entry), std::move(priceData));
230 }
231 auto const oldCount = pairs.size() > 5 ? 2 : 1;
232 // update/add/delete pairs
233 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
234 {
235 auto const key = tokenPairKey(entry);
236 if (!entry.isFieldPresent(sfAssetPrice))
237 {
238 // delete token pair
239 pairs.erase(key);
240 }
241 else if (auto iter = pairs.find(key); iter != pairs.end())
242 {
243 // update the price
244 iter->second.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
245 if (entry.isFieldPresent(sfScale))
246 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
247 }
248 else
249 {
250 // add a token pair with the price
251 STObject priceData{sfPriceData};
252 populatePriceData(priceData, entry);
253 pairs.emplace(key, std::move(priceData));
254 }
255 }
256 STArray updatedSeries;
257 for (auto const& iter : pairs)
258 updatedSeries.pushBack(iter.second);
259 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
260 if (ctx_.tx.isFieldPresent(sfURI))
261 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
262 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
263 if (!sle->isFieldPresent(sfOracleDocumentID) &&
264 ctx_.view().rules().enabled(fixIncludeKeyletFields))
265 {
266 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
267 }
268
269 auto const newCount = pairs.size() > 5 ? 2 : 1;
270 auto const adjust = newCount - oldCount;
271 if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
272 return tefINTERNAL; // LCOV_EXCL_LINE
273
274 ctx_.view().update(sle);
275 }
276 else
277 {
278 // create
279
280 sle = std::make_shared<SLE>(oracleID);
281 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
282 if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
283 {
284 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
285 }
286 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
287 if (ctx_.tx.isFieldPresent(sfURI))
288 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
289
290 STArray series;
291 if (!ctx_.view().rules().enabled(fixPriceOracleOrder))
292 {
293 series = ctx_.tx.getFieldArray(sfPriceDataSeries);
294 }
295 else
296 {
298 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
299 {
300 auto const key = tokenPairKey(entry);
301 STObject priceData{sfPriceData};
302 populatePriceData(priceData, entry);
303 pairs.emplace(key, std::move(priceData));
304 }
305 for (auto const& iter : pairs)
306 series.pushBack(iter.second);
307 }
308
309 sle->setFieldArray(sfPriceDataSeries, series);
310 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
311 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
312
313 auto page = ctx_.view().dirInsert(
315 if (!page)
316 return tecDIR_FULL; // LCOV_EXCL_LINE
317
318 (*sle)[sfOwnerNode] = *page;
319
320 auto const count = series.size() > 5 ? 2 : 1;
322 return tefINTERNAL; // LCOV_EXCL_LINE
323
324 ctx_.view().insert(sle);
325 }
326
327 return tesSUCCESS;
328}
329
330void
332{
333 // No transaction-specific invariants yet (future work).
334}
335
336bool
338{
339 // No transaction-specific invariants yet (future work).
340 return true;
341}
342
343} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
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:62
static NotTEC preflight(PreflightContext const &ctx)
Definition OracleSet.cpp:41
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:31
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
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:96
size_type size() const
Definition STArray.h:240
void pushBack(STObject const &object)
Definition STArray.h:204
Currency const & currency() const
Definition STCurrency.h:69
std::shared_ptr< STLedgerEntry const > const & const_ref
void setFieldU8(SField const &field, unsigned char)
Definition STObject.cpp:721
STCurrency const & getFieldCurrency(SField const &field) const
Definition STObject.cpp:685
void setFieldU64(SField const &field, std::uint64_t)
Definition STObject.cpp:739
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:678
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
void set(SOTemplate const &)
Definition STObject.cpp:135
void setFieldCurrency(SField const &field, STCurrency const &)
Definition STObject.cpp:799
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
AccountID const accountID_
Definition Transactor.h:120
ApplyContext & ctx_
Definition Transactor.h:116
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:515
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
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:33
@ terNO_ACCOUNT
Definition TER.h:209
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition OracleSet.cpp:33
constexpr std::size_t kMaxPriceScale
The maximum price scaling factor.
Definition Protocol.h:301
@ tefINTERNAL
Definition TER.h:163
constexpr std::size_t kMaxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition Protocol.h:297
constexpr std::size_t kMaxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition Protocol.h:292
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
static void setPriceDataInnerObjTemplate(STObject &obj)
constexpr std::size_t kMaxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition Protocol.h:286
constexpr std::size_t kMaxOracleUri
The maximum length of a URI inside an Oracle.
Definition Protocol.h:283
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
@ temARRAY_TOO_LARGE
Definition TER.h:127
@ temMALFORMED
Definition TER.h:73
@ temARRAY_EMPTY
Definition TER.h:126
TERSubset< CanCvtToTER > TER
Definition TER.h:634
bool isConsistent(Asset const &asset)
Definition Asset.h:312
@ tecINVALID_UPDATE_TIME
Definition TER.h:352
@ tecDIR_FULL
Definition TER.h:285
@ tecINTERNAL
Definition TER.h:308
@ tecARRAY_TOO_LARGE
Definition TER.h:355
@ tecARRAY_EMPTY
Definition TER.h:354
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:353
@ tesSUCCESS
Definition TER.h:240
constexpr std::size_t kMaxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:289
T size(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
NetClock::time_point closeTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
State information when preflighting a tx.
Definition Transactor.h:18
T time_since_epoch(T... args)