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#include <xrpld/ledger/Sandbox.h>
22#include <xrpld/ledger/View.h>
23
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 if (!ctx.rules.enabled(featurePriceOracle))
43 return temDISABLED;
44
45 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
46 return ret;
47
48 if (ctx.tx.getFlags() & tfUniversalMask)
49 return temINVALID_FLAG;
50
51 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
52 if (dataSeries.empty())
53 return temARRAY_EMPTY;
54 if (dataSeries.size() > maxOracleDataSeries)
55 return temARRAY_TOO_LARGE;
56
57 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
58 return ctx.tx.isFieldPresent(sField) &&
59 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
60 };
61
62 if (isInvalidLength(sfProvider, maxOracleProvider) ||
63 isInvalidLength(sfURI, maxOracleURI) ||
64 isInvalidLength(sfAssetClass, maxOracleSymbolClass))
65 return temMALFORMED;
66
67 return preflight2(ctx);
68}
69
70TER
72{
73 auto const sleSetter =
74 ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
75 if (!sleSetter)
76 return terNO_ACCOUNT; // LCOV_EXCL_LINE
77
78 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
79 // of the last closed ledger
80 using namespace std::chrono;
81 std::size_t const closeTime =
82 duration_cast<seconds>(ctx.view.info().closeTime.time_since_epoch())
83 .count();
84 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
85 if (lastUpdateTime < epoch_offset.count())
87 std::size_t const lastUpdateTimeEpoch =
88 lastUpdateTime - epoch_offset.count();
89 if (closeTime < maxLastUpdateTimeDelta)
90 return tecINTERNAL; // LCOV_EXCL_LINE
91 if (lastUpdateTimeEpoch < (closeTime - maxLastUpdateTimeDelta) ||
92 lastUpdateTimeEpoch > (closeTime + maxLastUpdateTimeDelta))
94
95 auto const sle = ctx.view.read(keylet::oracle(
96 ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
97
98 // token pairs to add/update
100 // token pairs to delete. if a token pair doesn't include
101 // the price then this pair should be deleted from the object.
103 for (auto const& entry : ctx.tx.getFieldArray(sfPriceDataSeries))
104 {
105 if (entry[sfBaseAsset] == entry[sfQuoteAsset])
106 return temMALFORMED;
107 auto const key = tokenPairKey(entry);
108 if (pairs.contains(key) || pairsDel.contains(key))
109 return temMALFORMED;
110 if (entry[~sfScale] > maxPriceScale)
111 return temMALFORMED;
112 if (entry.isFieldPresent(sfAssetPrice))
113 pairs.emplace(key);
114 else if (sle)
115 pairsDel.emplace(key);
116 else
117 return temMALFORMED;
118 }
119
120 // Lambda is used to check if the value of a field, passed
121 // in the transaction, is equal to the value of that field
122 // in the on-ledger object.
123 auto isConsistent = [&ctx, &sle](auto const& field) {
124 auto const v = ctx.tx[~field];
125 return !v || *v == (*sle)[field];
126 };
127
128 std::uint32_t adjustReserve = 0;
129 if (sle)
130 {
131 // update
132 // Account is the Owner since we can get sle
133
134 // lastUpdateTime must be more recent than the previous one
135 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
137
138 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
139 return temMALFORMED;
140
141 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
142 {
143 auto const key = tokenPairKey(entry);
144 if (!pairs.contains(key))
145 {
146 if (pairsDel.contains(key))
147 pairsDel.erase(key);
148 else
149 pairs.emplace(key);
150 }
151 }
152 if (!pairsDel.empty())
154
155 auto const oldCount =
156 sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
157 auto const newCount = pairs.size() > 5 ? 2 : 1;
158 adjustReserve = newCount - oldCount;
159 }
160 else
161 {
162 // create
163
164 if (!ctx.tx.isFieldPresent(sfProvider) ||
165 !ctx.tx.isFieldPresent(sfAssetClass))
166 return temMALFORMED;
167 adjustReserve = pairs.size() > 5 ? 2 : 1;
168 }
169
170 if (pairs.empty())
171 return tecARRAY_EMPTY;
172 if (pairs.size() > maxOracleDataSeries)
173 return tecARRAY_TOO_LARGE;
174
175 auto const reserve = ctx.view.fees().accountReserve(
176 sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
177 auto const& balance = sleSetter->getFieldAmount(sfBalance);
178
179 if (balance < reserve)
181
182 return tesSUCCESS;
183}
184
185static bool
187{
188 if (auto const sleAccount =
189 ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
190 {
191 adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
192 return true;
193 }
194
195 return false; // LCOV_EXCL_LINE
196}
197
198static void
200{
201 if (SOTemplate const* elements =
202 InnerObjectFormats::getInstance().findSOTemplateBySField(
203 sfPriceData))
204 obj.set(*elements);
205}
206
207TER
209{
210 auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
211
212 auto populatePriceData = [](STObject& priceData, STObject const& entry) {
214 priceData.setFieldCurrency(
215 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
216 priceData.setFieldCurrency(
217 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
218 priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice));
219 if (entry.isFieldPresent(sfScale))
220 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
221 };
222
223 if (auto sle = ctx_.view().peek(oracleID))
224 {
225 // update
226 // the token pair that doesn't have their price updated will not
227 // include neither price nor scale in the updated PriceDataSeries
228
230 // collect current token pairs
231 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
232 {
233 STObject priceData{sfPriceData};
235 priceData.setFieldCurrency(
236 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
237 priceData.setFieldCurrency(
238 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
239 pairs.emplace(tokenPairKey(entry), std::move(priceData));
240 }
241 auto const oldCount = pairs.size() > 5 ? 2 : 1;
242 // update/add/delete pairs
243 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
244 {
245 auto const key = tokenPairKey(entry);
246 if (!entry.isFieldPresent(sfAssetPrice))
247 {
248 // delete token pair
249 pairs.erase(key);
250 }
251 else if (auto iter = pairs.find(key); iter != pairs.end())
252 {
253 // update the price
254 iter->second.setFieldU64(
255 sfAssetPrice, entry.getFieldU64(sfAssetPrice));
256 if (entry.isFieldPresent(sfScale))
257 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
258 }
259 else
260 {
261 // add a token pair with the price
262 STObject priceData{sfPriceData};
263 populatePriceData(priceData, entry);
264 pairs.emplace(key, std::move(priceData));
265 }
266 }
267 STArray updatedSeries;
268 for (auto const& iter : pairs)
269 updatedSeries.push_back(std::move(iter.second));
270 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
271 if (ctx_.tx.isFieldPresent(sfURI))
272 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
273 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
274
275 auto const newCount = pairs.size() > 5 ? 2 : 1;
276 auto const adjust = newCount - oldCount;
277 if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
278 return tefINTERNAL; // LCOV_EXCL_LINE
279
280 ctx_.view().update(sle);
281 }
282 else
283 {
284 // create
285
286 sle = std::make_shared<SLE>(oracleID);
287 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
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:318
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:651
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:686
void setFieldCurrency(SField const &field, STCurrency const &)
Definition STObject.cpp:795
void setFieldU8(SField const &field, unsigned char)
Definition STObject.cpp:729
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:693
void setFieldU64(SField const &field, std::uint64_t)
Definition STObject.cpp:747
std::uint32_t getFlags() const
Definition STObject.cpp:537
static NotTEC preflight(PreflightContext const &ctx)
Definition SetOracle.cpp:40
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
Definition SetOracle.cpp:71
AccountID const account_
Definition Transactor.h:143
ApplyContext & ctx_
Definition Transactor.h:140
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:162
std::size_t constexpr maxOracleURI
The maximum length of a URI inside an Oracle.
Definition Protocol.h:144
std::size_t constexpr maxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition Protocol.h:147
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1048
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition SetOracle.cpp:32
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
@ tefINTERNAL
Definition TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
@ tecDIR_FULL
Definition TER.h:287
@ tecINTERNAL
Definition TER.h:310
@ tecARRAY_TOO_LARGE
Definition TER.h:357
@ tecINVALID_UPDATE_TIME
Definition TER.h:354
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecARRAY_EMPTY
Definition TER.h:356
@ tecTOKEN_PAIR_NOT_FOUND
Definition TER.h:355
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:153
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
std::size_t constexpr maxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition Protocol.h:150
@ terNO_ACCOUNT
Definition TER.h:217
TERSubset< CanCvtToTER > TER
Definition TER.h:645
std::size_t constexpr maxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition Protocol.h:158
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temARRAY_EMPTY
Definition TER.h:140
@ temARRAY_TOO_LARGE
Definition TER.h:141
@ temDISABLED
Definition TER.h:114
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:79
ReadView const & view
Definition Transactor.h:82
State information when preflighting a tx.
Definition Transactor.h:34
T time_since_epoch(T... args)