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/protocol/Feature.h>
24#include <xrpl/protocol/InnerObjectFormats.h>
25#include <xrpl/protocol/TxFlags.h>
26#include <xrpl/protocol/digest.h>
27
28namespace ripple {
29
32{
33 return std::make_pair(
34 pair.getFieldCurrency(sfBaseAsset).currency(),
35 pair.getFieldCurrency(sfQuoteAsset).currency());
36}
37
40{
41 if (!ctx.rules.enabled(featurePriceOracle))
42 return temDISABLED;
43
44 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
45 return ret;
46
47 if (ctx.tx.getFlags() & tfUniversalMask)
48 return temINVALID_FLAG;
49
50 auto const& dataSeries = ctx.tx.getFieldArray(sfPriceDataSeries);
51 if (dataSeries.empty())
52 return temARRAY_EMPTY;
53 if (dataSeries.size() > maxOracleDataSeries)
54 return temARRAY_TOO_LARGE;
55
56 auto isInvalidLength = [&](auto const& sField, std::size_t length) {
57 return ctx.tx.isFieldPresent(sField) &&
58 (ctx.tx[sField].length() == 0 || ctx.tx[sField].length() > length);
59 };
60
61 if (isInvalidLength(sfProvider, maxOracleProvider) ||
62 isInvalidLength(sfURI, maxOracleURI) ||
63 isInvalidLength(sfAssetClass, maxOracleSymbolClass))
64 return temMALFORMED;
65
66 return preflight2(ctx);
67}
68
69TER
71{
72 auto const sleSetter =
73 ctx.view.read(keylet::account(ctx.tx.getAccountID(sfAccount)));
74 if (!sleSetter)
75 return terNO_ACCOUNT; // LCOV_EXCL_LINE
76
77 // lastUpdateTime must be within maxLastUpdateTimeDelta seconds
78 // of the last closed ledger
79 using namespace std::chrono;
80 std::size_t const closeTime =
81 duration_cast<seconds>(ctx.view.info().closeTime.time_since_epoch())
82 .count();
83 std::size_t const lastUpdateTime = ctx.tx[sfLastUpdateTime];
84 if (lastUpdateTime < epoch_offset.count())
86 std::size_t const lastUpdateTimeEpoch =
87 lastUpdateTime - epoch_offset.count();
88 if (closeTime < maxLastUpdateTimeDelta)
89 return tecINTERNAL; // LCOV_EXCL_LINE
90 if (lastUpdateTimeEpoch < (closeTime - maxLastUpdateTimeDelta) ||
91 lastUpdateTimeEpoch > (closeTime + maxLastUpdateTimeDelta))
93
94 auto const sle = ctx.view.read(keylet::oracle(
95 ctx.tx.getAccountID(sfAccount), ctx.tx[sfOracleDocumentID]));
96
97 // token pairs to add/update
99 // token pairs to delete. if a token pair doesn't include
100 // the price then this pair should be deleted from the object.
102 for (auto const& entry : ctx.tx.getFieldArray(sfPriceDataSeries))
103 {
104 if (entry[sfBaseAsset] == entry[sfQuoteAsset])
105 return temMALFORMED;
106 auto const key = tokenPairKey(entry);
107 if (pairs.contains(key) || pairsDel.contains(key))
108 return temMALFORMED;
109 if (entry[~sfScale] > maxPriceScale)
110 return temMALFORMED;
111 if (entry.isFieldPresent(sfAssetPrice))
112 pairs.emplace(key);
113 else if (sle)
114 pairsDel.emplace(key);
115 else
116 return temMALFORMED;
117 }
118
119 // Lambda is used to check if the value of a field, passed
120 // in the transaction, is equal to the value of that field
121 // in the on-ledger object.
122 auto isConsistent = [&ctx, &sle](auto const& field) {
123 auto const v = ctx.tx[~field];
124 return !v || *v == (*sle)[field];
125 };
126
127 std::uint32_t adjustReserve = 0;
128 if (sle)
129 {
130 // update
131 // Account is the Owner since we can get sle
132
133 // lastUpdateTime must be more recent than the previous one
134 if (ctx.tx[sfLastUpdateTime] <= (*sle)[sfLastUpdateTime])
136
137 if (!isConsistent(sfProvider) || !isConsistent(sfAssetClass))
138 return temMALFORMED;
139
140 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
141 {
142 auto const key = tokenPairKey(entry);
143 if (!pairs.contains(key))
144 {
145 if (pairsDel.contains(key))
146 pairsDel.erase(key);
147 else
148 pairs.emplace(key);
149 }
150 }
151 if (!pairsDel.empty())
153
154 auto const oldCount =
155 sle->getFieldArray(sfPriceDataSeries).size() > 5 ? 2 : 1;
156 auto const newCount = pairs.size() > 5 ? 2 : 1;
157 adjustReserve = newCount - oldCount;
158 }
159 else
160 {
161 // create
162
163 if (!ctx.tx.isFieldPresent(sfProvider) ||
164 !ctx.tx.isFieldPresent(sfAssetClass))
165 return temMALFORMED;
166 adjustReserve = pairs.size() > 5 ? 2 : 1;
167 }
168
169 if (pairs.empty())
170 return tecARRAY_EMPTY;
171 if (pairs.size() > maxOracleDataSeries)
172 return tecARRAY_TOO_LARGE;
173
174 auto const reserve = ctx.view.fees().accountReserve(
175 sleSetter->getFieldU32(sfOwnerCount) + adjustReserve);
176 auto const& balance = sleSetter->getFieldAmount(sfBalance);
177
178 if (balance < reserve)
180
181 return tesSUCCESS;
182}
183
184static bool
186{
187 if (auto const sleAccount =
188 ctx.view().peek(keylet::account(ctx.tx[sfAccount])))
189 {
190 adjustOwnerCount(ctx.view(), sleAccount, count, ctx.journal);
191 return true;
192 }
193
194 return false; // LCOV_EXCL_LINE
195}
196
197static void
199{
200 if (SOTemplate const* elements =
201 InnerObjectFormats::getInstance().findSOTemplateBySField(
202 sfPriceData))
203 obj.set(*elements);
204}
205
206TER
208{
209 auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
210
211 if (auto sle = ctx_.view().peek(oracleID))
212 {
213 // update
214 // the token pair that doesn't have their price updated will not
215 // include neither price nor scale in the updated PriceDataSeries
216
218 // collect current token pairs
219 for (auto const& entry : sle->getFieldArray(sfPriceDataSeries))
220 {
221 STObject priceData{sfPriceData};
223 priceData.setFieldCurrency(
224 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
225 priceData.setFieldCurrency(
226 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
227 pairs.emplace(tokenPairKey(entry), std::move(priceData));
228 }
229 auto const oldCount = pairs.size() > 5 ? 2 : 1;
230 // update/add/delete pairs
231 for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries))
232 {
233 auto const key = tokenPairKey(entry);
234 if (!entry.isFieldPresent(sfAssetPrice))
235 {
236 // delete token pair
237 pairs.erase(key);
238 }
239 else if (auto iter = pairs.find(key); iter != pairs.end())
240 {
241 // update the price
242 iter->second.setFieldU64(
243 sfAssetPrice, entry.getFieldU64(sfAssetPrice));
244 if (entry.isFieldPresent(sfScale))
245 iter->second.setFieldU8(sfScale, entry.getFieldU8(sfScale));
246 }
247 else
248 {
249 // add a token pair with the price
250 STObject priceData{sfPriceData};
252 priceData.setFieldCurrency(
253 sfBaseAsset, entry.getFieldCurrency(sfBaseAsset));
254 priceData.setFieldCurrency(
255 sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset));
256 priceData.setFieldU64(
257 sfAssetPrice, entry.getFieldU64(sfAssetPrice));
258 if (entry.isFieldPresent(sfScale))
259 priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale));
260 pairs.emplace(key, std::move(priceData));
261 }
262 }
263 STArray updatedSeries;
264 for (auto const& iter : pairs)
265 updatedSeries.push_back(std::move(iter.second));
266 sle->setFieldArray(sfPriceDataSeries, updatedSeries);
267 if (ctx_.tx.isFieldPresent(sfURI))
268 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
269 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
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 sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]);
285 if (ctx_.tx.isFieldPresent(sfURI))
286 sle->setFieldVL(sfURI, ctx_.tx[sfURI]);
287 auto const& series = ctx_.tx.getFieldArray(sfPriceDataSeries);
288 sle->setFieldArray(sfPriceDataSeries, series);
289 sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]);
290 sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
291
292 auto page = ctx_.view().dirInsert(
294 if (!page)
295 return tecDIR_FULL; // LCOV_EXCL_LINE
296
297 (*sle)[sfOwnerNode] = *page;
298
299 auto const count = series.size() > 5 ? 2 : 1;
300 if (!adjustOwnerCount(ctx_, count))
301 return tefINTERNAL; // LCOV_EXCL_LINE
302
303 ctx_.view().insert(sle);
304 }
305
306 return tesSUCCESS;
307}
308
309} // 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:130
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:114
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:651
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:686
void set(const SOTemplate &)
Definition: STObject.cpp:156
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
const STCurrency & getFieldCurrency(SField const &field) const
Definition: STObject.cpp:693
std::uint32_t getFlags() const
Definition: STObject.cpp:537
static NotTEC preflight(PreflightContext const &ctx)
Definition: SetOracle.cpp:39
TER doApply() override
Definition: SetOracle.cpp:207
static TER preclaim(PreclaimContext const &ctx)
Definition: SetOracle.cpp:70
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:503
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:175
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:365
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:198
bool isConsistent(Book const &book)
Definition: Book.cpp:29
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
std::size_t constexpr maxOracleProvider
The maximum length of a Provider inside an Oracle.
Definition: Protocol.h:137
bool isTesSuccess(TER x)
Definition: TER.h:656
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:924
static std::pair< Currency, Currency > tokenPairKey(STObject const &pair)
Definition: SetOracle.cpp:31
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:185
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
@ 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:55
std::size_t constexpr maxOracleSymbolClass
The maximum length of a SymbolClass inside an Oracle.
Definition: Protocol.h:143
@ 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:140
@ 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:148
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)