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#include <xrpl/basics/UnorderedContainers.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 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 if (auto sle = ctx_.view().peek(oracleID))
213 {
214 // update
215 // the token pair that doesn't have their price updated will not
216 // include neither price nor scale in the updated PriceDataSeries
217
219 // collect current token pairs
220 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
221 {
222 STObject priceData{sfPriceData};
224 priceData.setFieldCurrency(
225 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
226 priceData.setFieldCurrency(
227 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
228 pairs.emplace(tokenPairKey(entry), std::move(priceData));
229 }
230 auto const oldCount = pairs.size() > 5 ? 2 : 1;
231 // update/add/delete pairs
232 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
233 {
234 auto const key = tokenPairKey(entry);
235 if (!entry.isFieldPresent(sfAssetPrice))
236 {
237 // delete token pair
238 pairs.erase(key);
239 }
240 else if (auto iter = pairs.find(key); iter != pairs.end())
241 {
242 // update the price
243 iter->second.setFieldU64(
244 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};
253 priceData.setFieldCurrency(
254 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
255 priceData.setFieldCurrency(
256 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
257 priceData.setFieldU64(
258 sfAssetPrice, entry.getFieldU64(sfAssetPrice));
259 if (entry.isFieldPresent(sfScale))
260 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
261 pairs.emplace(key, std::move(priceData));
262 }
263 }
264 STArray updatedSeries;
265 for (auto const& iter : pairs)
266 updatedSeries.push_back(std::move(iter.second));
267 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
268 if (ctx_.tx.isFieldPresent(sfURI))
269 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
270 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
271
272 auto const newCount = pairs.size() > 5 ? 2 : 1;
273 auto const adjust = newCount - oldCount;
274 if (adjust != 0 && !adjustOwnerCount(ctx_, adjust))
275 return tefINTERNAL; // LCOV_EXCL_LINE
276
277 ctx_.view().update(sle);
278 }
279 else
280 {
281 // create
282
283 sle = std::make_shared<SLE>(oracleID);
284 sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount));
285 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
286 if (ctx_.tx.isFieldPresent(sfURI))
287 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
288 auto const& series = ctx_.tx.getFieldArray(sfPriceDataSeries);
289 sle->setFieldArray(sfPriceDataSeries, series);
290 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
291 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
292
293 auto page = ctx_.view().dirInsert(
295 if (!page)
296 return tecDIR_FULL; // LCOV_EXCL_LINE
297
298 (*sle)[sfOwnerNode] = *page;
299
300 auto const count = series.size() > 5 ? 2 : 1;
301 if (!adjustOwnerCount(ctx_, count))
302 return tefINTERNAL; // LCOV_EXCL_LINE
303
304 ctx_.view().insert(sle);
305 }
306
307 return tesSUCCESS;
308}
309
310} // namespace ripple
State information when applying a tx.
Definition: ApplyContext.h:36
ApplyView & view()
Definition: ApplyContext.h:54
beast::Journal const journal
Definition: ApplyContext.h:51
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:314
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.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:113
void push_back(STObject const &object)
Definition: STArray.h:212
Currency const & currency() const
Definition: STCurrency.h:89
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:621
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:656
void set(const SOTemplate &)
Definition: STObject.cpp:126
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
const STCurrency & getFieldCurrency(SField const &field) const
Definition: STObject.cpp:663
std::uint32_t getFlags() const
Definition: STObject.cpp:507
static NotTEC preflight(PreflightContext const &ctx)
Definition: SetOracle.cpp:40
TER doApply() override
Definition: SetOracle.cpp:208
static TER preclaim(PreclaimContext const &ctx)
Definition: SetOracle.cpp:71
AccountID const account_
Definition: Transactor.h:91
ApplyContext & ctx_
Definition: Transactor.h:88
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 make_pair(T... args)
Keylet oracle(AccountID const &account, std::uint32_t const &documentID) noexcept
Definition: Indexes.cpp:488
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static void setPriceDataInnerObjTemplate(STObject &obj)
Definition: SetOracle.cpp:199
bool isConsistent(Book const &book)
Definition: Book.cpp:25
std::size_t constexpr maxPriceScale
The maximum price scaling factor.
Definition: Protocol.h:151
std::size_t constexpr maxOracleURI
The maximum length of a URI inside an Oracle.
Definition: Protocol.h:133
std::size_t constexpr maxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition: Protocol.h:136
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:887
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.
Definition: Transactor.cpp:82
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
@ tecDIR_FULL
Definition: TER.h:274
@ tecINTERNAL
Definition: TER.h:297
@ tecARRAY_TOO_LARGE
Definition: TER.h:344
@ tecINVALID_UPDATE_TIME
Definition: TER.h:341
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecARRAY_EMPTY
Definition: TER.h:343
@ tecTOKEN_PAIR_NOT_FOUND
Definition: TER.h:342
static constexpr std::chrono::seconds epoch_offset
Clock for measuring the network time.
Definition: chrono.h:56
std::size_t constexpr maxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition: Protocol.h:142
@ tesSUCCESS
Definition: TER.h:242
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:62
std::size_t constexpr maxOracleDataSeries
The maximum size of a data series array inside an Oracle.
Definition: Protocol.h:139
@ terNO_ACCOUNT
Definition: TER.h:217
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
std::size_t constexpr maxLastUpdateTimeDelta
The maximum allowed time difference between lastUpdateTime and the time of the last closed ledger.
Definition: Protocol.h:147
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ 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.
Definition: protocol/Fees.h:49
uint256 key
Definition: Keylet.h:40
NetClock::time_point closeTime
Definition: LedgerHeader.h:72
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
State information when preflighting a tx.
Definition: Transactor.h:32
T time_since_epoch(T... args)