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