rippled
Loading...
Searching...
No Matches
OracleSet.cpp
1#include <xrpl/ledger/Sandbox.h>
2#include <xrpl/ledger/View.h>
3#include <xrpl/ledger/helpers/AccountRootHelpers.h>
4#include <xrpl/ledger/helpers/DirectoryHelpers.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/InnerObjectFormats.h>
7#include <xrpl/protocol/TxFlags.h>
8#include <xrpl/protocol/digest.h>
9#include <xrpl/tx/transactors/oracle/OracleSet.h>
10
11namespace xrpl {
12
15{
16 return std::make_pair(
17 pair.getFieldCurrency(sfBaseAsset).currency(),
18 pair.getFieldCurrency(sfQuoteAsset).currency());
19}
20
23{
24 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
25 if (dataSeries.empty())
26 return temARRAY_EMPTY;
27 if (dataSeries.size() > maxOracleDataSeries)
28 return temARRAY_TOO_LARGE;
29
30 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
31 return ctx.tx.isFieldPresent(sField) &&
32 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
33 };
34
35 if (isInvalidLength(sfProvider, maxOracleProvider) || isInvalidLength(sfURI, maxOracleURI) ||
36 isInvalidLength(sfAssetClass, maxOracleSymbolClass))
37 return temMALFORMED;
38
39 return tesSUCCESS;
40}
41
42TER
44{
45 auto const sleSetter = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
46 if (!sleSetter)
47 return terNO_ACCOUNT; // LCOV_EXCL_LINE
48
49 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
50 // of the last closed ledger
51 using namespace std::chrono;
52 std::size_t const closeTime =
53 duration_cast<seconds>(ctx.view.header().closeTime.time_since_epoch()).count();
54 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
55 if (lastUpdateTime < epoch_offset.count())
57 std::size_t const lastUpdateTimeEpoch = lastUpdateTime - epoch_offset.count();
58 if (closeTime < maxLastUpdateTimeDelta)
59 return tecINTERNAL; // LCOV_EXCL_LINE
60 if (lastUpdateTimeEpoch < (closeTime - maxLastUpdateTimeDelta) ||
61 lastUpdateTimeEpoch > (closeTime + maxLastUpdateTimeDelta))
63
64 auto const sle =
65 ctx.view.read(keylet::oracle(ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
66
67 // token pairs to add/update
69 // token pairs to delete. if a token pair doesn't include
70 // the price then this pair should be deleted from the object.
72 for (auto const& entry : ctx.tx.getFieldArray(sfPriceDataSeries))
73 {
74 if (entry[sfBaseAsset] == entry[sfQuoteAsset])
75 return temMALFORMED;
76 auto const key = tokenPairKey(entry);
77 if (pairs.contains(key) || pairsDel.contains(key))
78 return temMALFORMED;
79 if (entry[~sfScale] > maxPriceScale)
80 return temMALFORMED;
81 if (entry.isFieldPresent(sfAssetPrice))
82 {
83 pairs.emplace(key);
84 }
85 else if (sle)
86 {
87 pairsDel.emplace(key);
88 }
89 else
90 {
91 return temMALFORMED;
92 }
93 }
94
95 // Lambda is used to check if the value of a field, passed
96 // in the transaction, is equal to the value of that field
97 // in the on-ledger object.
98 auto isConsistent = [&ctx, &sle](auto const& field) {
99 auto const v = ctx.tx[~field];
100 return !v || *v == (*sle)[field];
101 };
102
103 std::int8_t adjustReserve = 0;
104 if (sle)
105 {
106 // update
107 // Account is the Owner since we can get sle
108
109 // lastUpdateTime must be more recent than the previous one
110 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
112
113 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
114 return temMALFORMED;
115
116 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
117 {
118 auto const key = tokenPairKey(entry);
119 if (!pairs.contains(key))
120 {
121 if (pairsDel.contains(key))
122 {
123 pairsDel.erase(key);
124 }
125 else
126 {
127 pairs.emplace(key);
128 }
129 }
130 }
131 if (!pairsDel.empty())
133
134 auto const oldCount = sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
135 auto const newCount = pairs.size() > 5 ? 2 : 1;
136 adjustReserve = newCount - oldCount;
137 }
138 else
139 {
140 // create
141
142 if (!ctx.tx.isFieldPresent(sfProvider) || !ctx.tx.isFieldPresent(sfAssetClass))
143 return temMALFORMED;
144 adjustReserve = pairs.size() > 5 ? 2 : 1;
145 }
146
147 if (pairs.empty())
148 return tecARRAY_EMPTY;
149 if (pairs.size() > maxOracleDataSeries)
150 return tecARRAY_TOO_LARGE;
151
152 auto const reserve =
153 ctx.view.fees().accountReserve(sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
154 auto const& balance = sleSetter->getFieldAmount(sfBalance);
155
156 if (balance < reserve)
158
159 return tesSUCCESS;
160}
161
162static bool
164{
165 if (auto const sleAccount = ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
166 {
167 adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
168 return true;
169 }
170
171 return false; // LCOV_EXCL_LINE
172}
173
174static void
176{
177 if (SOTemplate const* elements =
178 InnerObjectFormats::getInstance().findSOTemplateBySField(sfPriceData))
179 obj.set(*elements);
180}
181
182TER
184{
185 auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
186
187 auto populatePriceData = [](STObject& priceData, STObject const& entry) {
189 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
190 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
191 priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
192 if (entry.isFieldPresent(sfScale))
193 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
194 };
195
196 if (auto sle = ctx_.view().peek(oracleID))
197 {
198 // update
199 // the token pair that doesn't have their price updated will not
200 // include neither price nor scale in the updated PriceDataSeries
201
203 // collect current token pairs
204 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
205 {
206 STObject priceData{sfPriceData};
208 priceData.setFieldCurrency(sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
209 priceData.setFieldCurrency(sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
210 pairs.emplace(tokenPairKey(entry), std::move(priceData));
211 }
212 auto const oldCount = pairs.size() > 5 ? 2 : 1;
213 // update/add/delete pairs
214 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
215 {
216 auto const key = tokenPairKey(entry);
217 if (!entry.isFieldPresent(sfAssetPrice))
218 {
219 // delete token pair
220 pairs.erase(key);
221 }
222 else if (auto iter = pairs.find(key); iter != pairs.end())
223 {
224 // update the price
225 iter->second.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
226 if (entry.isFieldPresent(sfScale))
227 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
228 }
229 else
230 {
231 // add a token pair with the price
232 STObject priceData{sfPriceData};
233 populatePriceData(priceData, entry);
234 pairs.emplace(key, std::move(priceData));
235 }
236 }
237 STArray updatedSeries;
238 for (auto const& iter : pairs)
239 updatedSeries.push_back(iter.second);
240 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
241 if (ctx_.tx.isFieldPresent(sfURI))
242 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
243 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
244 if (!sle->isFieldPresent(sfOracleDocumentID) &&
245 ctx_.view().rules().enabled(fixIncludeKeyletFields))
246 {
247 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
248 }
249
250 auto const newCount = pairs.size() > 5 ? 2 : 1;
251 auto const adjust = newCount - oldCount;
252 if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
253 return tefINTERNAL; // LCOV_EXCL_LINE
254
255 ctx_.view().update(sle);
256 }
257 else
258 {
259 // create
260
261 sle = std::make_shared<SLE>(oracleID);
262 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
263 if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
264 {
265 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
266 }
267 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
268 if (ctx_.tx.isFieldPresent(sfURI))
269 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
270
271 STArray series;
272 if (!ctx_.view().rules().enabled(fixPriceOracleOrder))
273 {
274 series = ctx_.tx.getFieldArray(sfPriceDataSeries);
275 }
276 else
277 {
279 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
280 {
281 auto const key = tokenPairKey(entry);
282 STObject priceData{sfPriceData};
283 populatePriceData(priceData, entry);
284 pairs.emplace(key, std::move(priceData));
285 }
286 for (auto const& iter : pairs)
287 series.push_back(iter.second);
288 }
289
290 sle->setFieldArray(sfPriceDataSeries, series);
291 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
292 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
293
294 auto page = ctx_.view().dirInsert(
296 if (!page)
297 return tecDIR_FULL; // LCOV_EXCL_LINE
298
299 (*sle)[sfOwnerNode] = *page;
300
301 auto const count = series.size() > 5 ? 2 : 1;
302 if (!adjustOwnerCount(ctx_, count))
303 return tefINTERNAL; // LCOV_EXCL_LINE
304
305 ctx_.view().insert(sle);
306 }
307
308 return tesSUCCESS;
309}
310
311} // namespace xrpl
State information when applying a tx.
STTx const & tx
beast::Journal const journal
ApplyView & view()
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:289
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static InnerObjectFormats const & getInstance()
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
Definition OracleSet.cpp:43
static NotTEC preflight(PreflightContext const &ctx)
Definition OracleSet.cpp:22
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
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:120
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:92
void push_back(STObject const &object)
Definition STArray.h:189
size_type size() const
Definition STArray.h:225
Currency const & currency() const
Definition STCurrency.h:69
void setFieldU8(SField const &field, unsigned char)
Definition STObject.cpp:723
STCurrency const & getFieldCurrency(SField const &field) const
Definition STObject.cpp:687
void setFieldU64(SField const &field, std::uint64_t)
Definition STObject.cpp:741
void setFieldVL(SField const &field, Blob const &)
Definition STObject.cpp:777
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:680
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
void set(SOTemplate const &)
Definition STObject.cpp:134
void setFieldCurrency(SField const &field, STCurrency const &)
Definition STObject.cpp:795
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:635
AccountID const account_
Definition Transactor.h:116
ApplyContext & ctx_
Definition Transactor.h:112
T contains(T... args)
T count(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T is_same_v
T make_pair(T... args)
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition Indexes.cpp:468
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:336
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:5
bool isConsistent(Book const &book)
Definition Book.cpp:10
@ terNO_ACCOUNT
Definition TER.h:197
std::size_t constexpr maxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition Protocol.h:293
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition OracleSet.cpp:14
std::size_t constexpr maxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:285
@ tefINTERNAL
Definition TER.h:153
TERSubset< CanCvtToTER > TER
Definition TER.h:622
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
static void setPriceDataInnerObjTemplate(STObject &obj)
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition Protocol.h:297
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
std::size_t constexpr maxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition Protocol.h:288
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:33
@ temARRAY_TOO_LARGE
Definition TER.h:121
@ temMALFORMED
Definition TER.h:67
@ temARRAY_EMPTY
Definition TER.h:120
@ tecINVALID_UPDATE_TIME
Definition TER.h:335
@ tecDIR_FULL
Definition TER.h:268
@ tecINTERNAL
Definition TER.h:291
@ tecARRAY_TOO_LARGE
Definition TER.h:338
@ tecARRAY_EMPTY
Definition TER.h:337
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:336
std::size_t constexpr maxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition Protocol.h:282
std::size_t constexpr maxOracleURI
The maximum length of a URI inside an Oracle.
Definition Protocol.h:279
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:582
@ tesSUCCESS
Definition TER.h:225
T size(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
uint256 key
Definition Keylet.h:20
NetClock::time_point closeTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
ReadView const & view
Definition Transactor.h:60
State information when preflighting a tx.
Definition Transactor.h:14
T time_since_epoch(T... args)