rippled
Loading...
Searching...
No Matches
VaultDeposit.cpp
1#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
2#include <xrpld/app/tx/detail/VaultDeposit.h>
3
4#include <xrpl/ledger/CredentialHelpers.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/Indexes.h>
8#include <xrpl/protocol/LedgerFormats.h>
9#include <xrpl/protocol/MPTIssue.h>
10#include <xrpl/protocol/SField.h>
11#include <xrpl/protocol/STNumber.h>
12#include <xrpl/protocol/TER.h>
13#include <xrpl/protocol/TxFlags.h>
14
15namespace ripple {
16
19{
20 if (ctx.tx[sfVaultID] == beast::zero)
21 {
22 JLOG(ctx.j.debug()) << "VaultDeposit: zero/empty vault ID.";
23 return temMALFORMED;
24 }
25
26 if (ctx.tx[sfAmount] <= beast::zero)
27 return temBAD_AMOUNT;
28
29 return tesSUCCESS;
30}
31
32TER
34{
35 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
36 if (!vault)
37 return tecNO_ENTRY;
38
39 auto const account = ctx.tx[sfAccount];
40 auto const assets = ctx.tx[sfAmount];
41 auto const vaultAsset = vault->at(sfAsset);
42 if (assets.asset() != vaultAsset)
43 return tecWRONG_ASSET;
44
45 if (vaultAsset.native())
46 ; // No special checks for XRP
47 else if (vaultAsset.holds<MPTIssue>())
48 {
49 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
50 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
51 if (!issuance)
53 if (!issuance->isFlag(lsfMPTCanTransfer))
54 {
55 // LCOV_EXCL_START
56 JLOG(ctx.j.error())
57 << "VaultDeposit: vault assets are non-transferable.";
58 return tecNO_AUTH;
59 // LCOV_EXCL_STOP
60 }
61 }
62 else if (vaultAsset.holds<Issue>())
63 {
64 auto const issuer =
65 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
66 if (!issuer)
67 {
68 // LCOV_EXCL_START
69 JLOG(ctx.j.error())
70 << "VaultDeposit: missing issuer of vault assets.";
71 return tefINTERNAL;
72 // LCOV_EXCL_STOP
73 }
74 }
75
76 auto const mptIssuanceID = vault->at(sfShareMPTID);
77 auto const vaultShare = MPTIssue(mptIssuanceID);
78 if (vaultShare == assets.asset())
79 {
80 // LCOV_EXCL_START
81 JLOG(ctx.j.error())
82 << "VaultDeposit: vault shares and assets cannot be same.";
83 return tefINTERNAL;
84 // LCOV_EXCL_STOP
85 }
86
87 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
88 if (!sleIssuance)
89 {
90 // LCOV_EXCL_START
91 JLOG(ctx.j.error())
92 << "VaultDeposit: missing issuance of vault shares.";
93 return tefINTERNAL;
94 // LCOV_EXCL_STOP
95 }
96
97 if (sleIssuance->isFlag(lsfMPTLocked))
98 {
99 // LCOV_EXCL_START
100 JLOG(ctx.j.error())
101 << "VaultDeposit: issuance of vault shares is locked.";
102 return tefINTERNAL;
103 // LCOV_EXCL_STOP
104 }
105
106 // Cannot deposit inside Vault an Asset frozen for the depositor
107 if (isFrozen(ctx.view, account, vaultAsset))
108 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
109
110 // Cannot deposit if the shares of the vault are frozen
111 if (isFrozen(ctx.view, account, vaultShare))
112 return tecLOCKED;
113
114 if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
115 {
116 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
117 // Since this is a private vault and the account is not its owner, we
118 // perform authorization check based on DomainID read from sleIssuance.
119 // Had the vault shares been a regular MPToken, we would allow
120 // authorization granted by the Issuer explicitly, but Vault uses Issuer
121 // pseudo-account, which cannot grant an authorization.
122 if (maybeDomainID)
123 {
124 // As per validDomain documentation, we suppress tecEXPIRED error
125 // here, so we can delete any expired credentials inside doApply.
126 if (auto const err =
127 credentials::validDomain(ctx.view, *maybeDomainID, account);
128 !isTesSuccess(err) && err != tecEXPIRED)
129 return err;
130 }
131 else
132 return tecNO_AUTH;
133 }
134
135 // Source MPToken must exist (if asset is an MPT)
136 if (auto const ter = requireAuth(ctx.view, vaultAsset, account);
137 !isTesSuccess(ter))
138 return ter;
139
140 // Asset issuer does not have any balance, they can just create funds by
141 // depositing in the vault.
142 if ((vaultAsset.native() || vaultAsset.getIssuer() != account) &&
144 ctx.view,
145 account,
146 vaultAsset,
149 ctx.j) < assets)
151
152 return tesSUCCESS;
153}
154
155TER
157{
158 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
159 if (!vault)
160 return tefINTERNAL; // LCOV_EXCL_LINE
161
162 auto const amount = ctx_.tx[sfAmount];
163 // Make sure the depositor can hold shares.
164 auto const mptIssuanceID = (*vault)[sfShareMPTID];
165 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
166 if (!sleIssuance)
167 {
168 // LCOV_EXCL_START
169 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
170 return tefINTERNAL;
171 // LCOV_EXCL_STOP
172 }
173
174 auto const& vaultAccount = vault->at(sfAccount);
175 // Note, vault owner is always authorized
176 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
177 {
178 if (auto const err = enforceMPTokenAuthorization(
179 ctx_.view(), mptIssuanceID, account_, mPriorBalance, j_);
180 !isTesSuccess(err))
181 return err;
182 }
183 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
184 {
185 // No authorization needed, but must ensure there is MPToken
186 if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
187 {
188 if (auto const err = authorizeMPToken(
189 view(),
191 mptIssuanceID->value(),
192 account_,
193 ctx_.journal);
194 !isTesSuccess(err))
195 return err;
196 }
197
198 // If the vault is private, set the authorized flag for the vault owner
199 if (vault->isFlag(lsfVaultPrivate))
200 {
201 // This follows from the reverse of the outer enclosing if condition
202 XRPL_ASSERT(
203 account_ == vault->at(sfOwner),
204 "ripple::VaultDeposit::doApply : account is owner");
205 if (auto const err = authorizeMPToken(
206 view(),
207 mPriorBalance, // priorBalance
208 mptIssuanceID->value(), // mptIssuanceID
209 sleIssuance->at(sfIssuer), // account
211 {}, // flags
212 account_ // holderID
213 );
214 !isTesSuccess(err))
215 return err;
216 }
217 }
218
219 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
220 try
221 {
222 // Compute exchange before transferring any amounts.
223 {
224 auto const maybeShares =
225 assetsToSharesDeposit(vault, sleIssuance, amount);
226 if (!maybeShares)
227 return tecINTERNAL; // LCOV_EXCL_LINE
228 sharesCreated = *maybeShares;
229 }
230 if (sharesCreated == beast::zero)
231 return tecPRECISION_LOSS;
232
233 auto const maybeAssets =
234 sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
235 if (!maybeAssets)
236 return tecINTERNAL; // LCOV_EXCL_LINE
237 else if (*maybeAssets > amount)
238 {
239 // LCOV_EXCL_START
240 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
241 return tecINTERNAL;
242 // LCOV_EXCL_STOP
243 }
244 assetsDeposited = *maybeAssets;
245 }
246 catch (std::overflow_error const&)
247 {
248 // It's easy to hit this exception from Number with large enough Scale
249 // so we avoid spamming the log and only use debug here.
250 JLOG(j_.debug()) //
251 << "VaultDeposit: overflow error with"
252 << " scale=" << (int)vault->at(sfScale).value() //
253 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
254 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
255 << ", amount=" << amount;
256 return tecPATH_DRY;
257 }
258
259 XRPL_ASSERT(
260 sharesCreated.asset() != assetsDeposited.asset(),
261 "ripple::VaultDeposit::doApply : assets are not shares");
262
263 vault->at(sfAssetsTotal) += assetsDeposited;
264 vault->at(sfAssetsAvailable) += assetsDeposited;
265 view().update(vault);
266
267 // A deposit must not push the vault over its limit.
268 auto const maximum = *vault->at(sfAssetsMaximum);
269 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
270 return tecLIMIT_EXCEEDED;
271
272 // Transfer assets from depositor to vault.
273 if (auto const ter = accountSend(
274 view(),
275 account_,
276 vaultAccount,
277 assetsDeposited,
278 j_,
280 !isTesSuccess(ter))
281 return ter;
282
283 // Sanity check
284 if (accountHolds(
285 view(),
286 account_,
287 assetsDeposited.asset(),
290 j_) < beast::zero)
291 {
292 // LCOV_EXCL_START
293 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
294 return tefINTERNAL;
295 // LCOV_EXCL_STOP
296 }
297
298 // Transfer shares from vault to depositor.
299 if (auto const ter = accountSend(
300 view(),
301 vaultAccount,
302 account_,
303 sharesCreated,
304 j_,
306 !isTesSuccess(ter))
307 return ter;
308
309 return tesSUCCESS;
310}
311
312} // namespace ripple
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
ApplyView & view()
beast::Journal const journal
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
A currency issued by an account.
Definition Issue.h:14
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
Asset const & asset() const
Definition STAmount.h:464
AccountID const account_
Definition Transactor.h:128
ApplyView & view()
Definition Transactor.h:144
beast::Journal const j_
Definition Transactor.h:126
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
constexpr value_type value() const
Returns the underlying value.
Definition XRPAmount.h:220
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:521
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:507
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:545
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ fhZERO_IF_FROZEN
Definition View.h:58
@ fhIGNORE_FREEZE
Definition View.h:58
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
Definition View.cpp:2893
@ lsfMPTCanTransfer
@ lsfVaultPrivate
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
Definition View.cpp:2865
@ ahZERO_IF_UNAUTHORIZED
Definition View.h:61
@ ahIGNORE_AUTH
Definition View.h:61
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2172
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2466
@ tefINTERNAL
Definition TER.h:154
@ tecNO_ENTRY
Definition TER.h:288
@ tecLIMIT_EXCEEDED
Definition TER.h:343
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecINTERNAL
Definition TER.h:292
@ tecPRECISION_LOSS
Definition TER.h:345
@ tecWRONG_ASSET
Definition TER.h:342
@ tecPATH_DRY
Definition TER.h:276
@ tecEXPIRED
Definition TER.h:296
@ tecNO_AUTH
Definition TER.h:282
@ tecLOCKED
Definition TER.h:340
@ tesSUCCESS
Definition TER.h:226
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:368
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
Definition View.cpp:2584
TER authorizeMPToken(ApplyView &view, XRPAmount const &priorBalance, MPTID const &mptIssuanceID, AccountID const &account, beast::Journal journal, std::uint32_t flags=0, std::optional< AccountID > holderID=std::nullopt)
Definition View.cpp:1271
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ temBAD_AMOUNT
Definition TER.h:70
@ temMALFORMED
Definition TER.h:68
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23