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