rippled
Loading...
Searching...
No Matches
SetOracle.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/SetOracle.h>
21
22#include <xrpl/ledger/Sandbox.h>
23#include <xrpl/ledger/View.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/InnerObjectFormats.h>
26#include <xrpl/protocol/TxFlags.h>
27#include <xrpl/protocol/digest.h>
28
29namespace ripple {
30
33{
34 return std::make_pair(
35 pair.getFieldCurrency(sfBaseAsset).currency(),
36 pair.getFieldCurrency(sfQuoteAsset).currency());
37}
38
41{
42 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
43 if (dataSeries.empty())
44 return temARRAY_EMPTY;
45 if (dataSeries.size() > maxOracleDataSeries)
46 return temARRAY_TOO_LARGE;
47
48 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
49 return ctx.tx.isFieldPresent(sField) &&
50 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
51 };
52
53 if (isInvalidLength(sfProvider, maxOracleProvider) ||
54 isInvalidLength(sfURI, maxOracleURI) ||
55 isInvalidLength(sfAssetClass, maxOracleSymbolClass))
56 return temMALFORMED;
57
58 return tesSUCCESS;
59}
60
61TER
63{
64 auto const sleSetter =
65 ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
66 if (!sleSetter)
67 return terNO_ACCOUNT; // LCOV_EXCL_LINE
68
69 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
70 // of the last closed ledger
71 using namespace std::chrono;
72 std::size_t const closeTime =
73 duration_cast<seconds>(ctx.view.info().closeTime.time_since_epoch())
74 .count();
75 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
76 if (lastUpdateTime < epoch_offset.count())
78 std::size_t const lastUpdateTimeEpoch =
79 lastUpdateTime - epoch_offset.count();
80 if (closeTime < maxLastUpdateTimeDelta)
81 return tecINTERNAL; // LCOV_EXCL_LINE
82 if (lastUpdateTimeEpoch < (closeTime - maxLastUpdateTimeDelta) ||
83 lastUpdateTimeEpoch > (closeTime + maxLastUpdateTimeDelta))
85
86 auto const sle = ctx.view.read(keylet::oracle(
87 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] > maxPriceScale)
102 return temMALFORMED;
103 if (entry.isFieldPresent(sfAssetPrice))
104 pairs.emplace(key);
105 else if (sle)
106 pairsDel.emplace(key);
107 else
108 return temMALFORMED;
109 }
110
111 // Lambda is used to check if the value of a field, passed
112 // in the transaction, is equal to the value of that field
113 // in the on-ledger object.
114 auto isConsistent = [&ctx, &sle](auto const& field) {
115 auto const v = ctx.tx[~field];
116 return !v || *v == (*sle)[field];
117 };
118
119 std::uint32_t adjustReserve = 0;
120 if (sle)
121 {
122 // update
123 // Account is the Owner since we can get sle
124
125 // lastUpdateTime must be more recent than the previous one
126 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
128
129 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
130 return temMALFORMED;
131
132 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
133 {
134 auto const key = tokenPairKey(entry);
135 if (!pairs.contains(key))
136 {
137 if (pairsDel.contains(key))
138 pairsDel.erase(key);
139 else
140 pairs.emplace(key);
141 }
142 }
143 if (!pairsDel.empty())
145
146 auto const oldCount =
147 sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
148 auto const newCount = pairs.size() > 5 ? 2 : 1;
149 adjustReserve = newCount - oldCount;
150 }
151 else
152 {
153 // create
154
155 if (!ctx.tx.isFieldPresent(sfProvider) ||
156 !ctx.tx.isFieldPresent(sfAssetClass))
157 return temMALFORMED;
158 adjustReserve = pairs.size() > 5 ? 2 : 1;
159 }
160
161 if (pairs.empty())
162 return tecARRAY_EMPTY;
163 if (pairs.size() > maxOracleDataSeries)
164 return tecARRAY_TOO_LARGE;
165
166 auto const reserve = ctx.view.fees().accountReserve(
167 sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
168 auto const& balance = sleSetter->getFieldAmount(sfBalance);
169
170 if (balance < reserve)
172
173 return tesSUCCESS;
174}
175
176static bool
178{
179 if (auto const sleAccount =
180 ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
181 {
182 adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
183 return true;
184 }
185
186 return false; // LCOV_EXCL_LINE
187}
188
189static void
191{
192 if (SOTemplate const* elements =
193 InnerObjectFormats::getInstance().findSOTemplateBySField(
194 sfPriceData))
195 obj.set(*elements);
196}
197
198TER
200{
201 auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
202
203 auto populatePriceData = [](STObject& priceData, STObject const& entry) {
205 priceData.setFieldCurrency(
206 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
207 priceData.setFieldCurrency(
208 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
209 priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
210 if (entry.isFieldPresent(sfScale))
211 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
212 };
213
214 if (auto sle = ctx_.view().peek(oracleID))
215 {
216 // update
217 // the token pair that doesn't have their price updated will not
218 // include neither price nor scale in the updated PriceDataSeries
219
221 // collect current token pairs
222 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
223 {
224 STObject priceData{sfPriceData};
226 priceData.setFieldCurrency(
227 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
228 priceData.setFieldCurrency(
229 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
230 pairs.emplace(tokenPairKey(entry), std::move(priceData));
231 }
232 auto const oldCount = pairs.size() > 5 ? 2 : 1;
233 // update/add/delete pairs
234 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
235 {
236 auto const key = tokenPairKey(entry);
237 if (!entry.isFieldPresent(sfAssetPrice))
238 {
239 // delete token pair
240 pairs.erase(key);
241 }
242 else if (auto iter = pairs.find(key); iter != pairs.end())
243 {
244 // update the price
245 iter->second.setFieldU64(
246 sfAssetPrice, entry.getFieldU64(sfAssetPrice));
247 if (entry.isFieldPresent(sfScale))
248 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
249 }
250 else
251 {
252 // add a token pair with the price
253 STObject priceData{sfPriceData};
254 populatePriceData(priceData, entry);
255 pairs.emplace(key, std::move(priceData));
256 }
257 }
258 STArray updatedSeries;
259 for (auto const& iter : pairs)
260 updatedSeries.push_back(std::move(iter.second));
261 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
262 if (ctx_.tx.isFieldPresent(sfURI))
263 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
264 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
265 if (!sle->isFieldPresent(sfOracleDocumentID) &&
266 ctx_.view().rules().enabled(fixIncludeKeyletFields))
267 {
268 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
269 }
270
271 auto const newCount = pairs.size() > 5 ? 2 : 1;
272 auto const adjust = newCount - oldCount;
273 if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
274 return tefINTERNAL; // LCOV_EXCL_LINE
275
276 ctx_.view().update(sle);
277 }
278 else
279 {
280 // create
281
282 sle = std::make_shared<SLE>(oracleID);
283 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
284 if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
285 {
286 (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID];
287 }
288 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
289 if (ctx_.tx.isFieldPresent(sfURI))
290 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
291
292 STArray series;
293 if (!ctx_.view().rules().enabled(fixPriceOracleOrder))
294 {
295 series = ctx_.tx.getFieldArray(sfPriceDataSeries);
296 }
297 else
298 {
300 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
301 {
302 auto const key = tokenPairKey(entry);
303 STObject priceData{sfPriceData};
304 populatePriceData(priceData, entry);
305 pairs.emplace(key, std::move(priceData));
306 }
307 for (auto const& iter : pairs)
308 series.push_back(std::move(iter.second));
309 }
310
311 sle->setFieldArray(sfPriceDataSeries, series);
312 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
313 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
314
315 auto page = ctx_.view().dirInsert(
317 if (!page)
318 return tecDIR_FULL; // LCOV_EXCL_LINE
319
320 (*sle)[sfOwnerNode] = *page;
321
322 auto const count = series.size() > 5 ? 2 : 1;
323 if (!adjustOwnerCount(ctx_, count))
324 return tefINTERNAL; // LCOV_EXCL_LINE
325
326 ctx_.view().insert(sle);
327 }
328
329 return tesSUCCESS;
330}
331
332} // namespace ripple
State information when applying a tx.
ApplyView & view()
beast::Journal const journal
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:319
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static InnerObjectFormats const & getInstance()
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
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:130
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:113
void push_back(STObject const &object)
Definition STArray.h:212
size_type size() const
Definition STArray.h:248
Currency const & currency() const
Definition STCurrency.h:89
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:657
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:702
void setFieldCurrency(SField const &field, STCurrency const &)
Definition STObject.cpp:817
void setFieldU8(SField const &field, unsigned char)
Definition STObject.cpp:745
void set(SOTemplate const &)
Definition STObject.cpp:156
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
STCurrency const & getFieldCurrency(SField const &field) const
Definition STObject.cpp:709
void setFieldU64(SField const &field, std::uint64_t)
Definition STObject.cpp:763
void setFieldVL(SField const &field, Blob const &)
Definition STObject.cpp:799
static NotTEC preflight(PreflightContext const &ctx)
Definition SetOracle.cpp:40
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
Definition SetOracle.cpp:62
AccountID const account_
Definition Transactor.h:147
ApplyContext & ctx_
Definition Transactor.h:143
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:520
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
static void setPriceDataInnerObjTemplate(STObject &obj)
bool isConsistent(Book const &book)
Definition Book.cpp:29
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition Protocol.h:171
std::size_t constexpr maxOracleURI
The maximum length of a URI inside an Oracle.
Definition Protocol.h:153
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1032
std::size_t constexpr maxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition Protocol.h:156
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1050
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition SetOracle.cpp:32
@ tefINTERNAL
Definition TER.h:173
@ tecDIR_FULL
Definition TER.h:288
@ tecINTERNAL
Definition TER.h:311
@ tecARRAY_TOO_LARGE
Definition TER.h:358
@ tecINVALID_UPDATE_TIME
Definition TER.h:355
@ tecINSUFFICIENT_RESERVE
Definition TER.h:308
@ tecARRAY_EMPTY
Definition TER.h:357
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:356
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition chrono.h:55
std::size_t constexpr maxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition Protocol.h:162
@ tesSUCCESS
Definition TER.h:245
std::size_t constexpr maxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:159
@ terNO_ACCOUNT
Definition TER.h:217
TERSubset< CanCvtToTER > TER
Definition TER.h:649
std::size_t constexpr maxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition Protocol.h:167
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:609
@ temMALFORMED
Definition TER.h:87
@ temARRAY_EMPTY
Definition TER.h:140
@ temARRAY_TOO_LARGE
Definition TER.h:141
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:40
NetClock::time_point closeTime
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
State information when preflighting a tx.
Definition Transactor.h:35
T time_since_epoch(T... args)