rippled
Loading...
Searching...
No Matches
VaultDeposit.cpp
1#include <xrpl/ledger/View.h>
2#include <xrpl/ledger/helpers/CredentialHelpers.h>
3#include <xrpl/ledger/helpers/MPTokenHelpers.h>
4#include <xrpl/ledger/helpers/TokenHelpers.h>
5#include <xrpl/ledger/helpers/VaultHelpers.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/STTakesAsset.h>
13#include <xrpl/protocol/TER.h>
14#include <xrpl/protocol/TxFlags.h>
15#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
16#include <xrpl/tx/transactors/vault/VaultDeposit.h>
17
18namespace xrpl {
19
22{
23 if (ctx.tx[sfVaultID] == beast::zero)
24 {
25 JLOG(ctx.j.debug()) << "VaultDeposit: zero/empty vault ID.";
26 return temMALFORMED;
27 }
28
29 if (ctx.tx[sfAmount] <= beast::zero)
30 return temBAD_AMOUNT;
31
32 return tesSUCCESS;
33}
34
35TER
37{
38 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
39 if (!vault)
40 return tecNO_ENTRY;
41
42 auto const& account = ctx.tx[sfAccount];
43 auto const assets = ctx.tx[sfAmount];
44 auto const vaultAsset = vault->at(sfAsset);
45 if (assets.asset() != vaultAsset)
46 return tecWRONG_ASSET;
47
48 auto const& vaultAccount = vault->at(sfAccount);
49 if (auto ter = canTransfer(ctx.view, vaultAsset, account, vaultAccount); !isTesSuccess(ter))
50 {
51 JLOG(ctx.j.debug()) << "VaultDeposit: vault assets are non-transferable.";
52 return ter;
53 }
54
55 auto const mptIssuanceID = vault->at(sfShareMPTID);
56 auto const vaultShare = MPTIssue(mptIssuanceID);
57 if (vaultShare == assets.asset())
58 {
59 // LCOV_EXCL_START
60 JLOG(ctx.j.error()) << "VaultDeposit: vault shares and assets cannot be same.";
61 return tefINTERNAL;
62 // LCOV_EXCL_STOP
63 }
64
65 auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
66 if (!sleIssuance)
67 {
68 // LCOV_EXCL_START
69 JLOG(ctx.j.error()) << "VaultDeposit: missing issuance of vault shares.";
70 return tefINTERNAL;
71 // LCOV_EXCL_STOP
72 }
73
74 if (sleIssuance->isFlag(lsfMPTLocked))
75 {
76 // LCOV_EXCL_START
77 JLOG(ctx.j.error()) << "VaultDeposit: issuance of vault shares is locked.";
78 return tefINTERNAL;
79 // LCOV_EXCL_STOP
80 }
81
82 // Cannot deposit inside Vault an Asset frozen for the depositor
83 if (isFrozen(ctx.view, account, vaultAsset))
84 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
85
86 // Cannot deposit if the shares of the vault are frozen
87 if (isFrozen(ctx.view, account, vaultShare))
88 return tecLOCKED;
89
90 if (vault->isFlag(lsfVaultPrivate) && account != vault->at(sfOwner))
91 {
92 auto const maybeDomainID = sleIssuance->at(~sfDomainID);
93 // Since this is a private vault and the account is not its owner, we
94 // perform authorization check based on DomainID read from sleIssuance.
95 // Had the vault shares been a regular MPToken, we would allow
96 // authorization granted by the Issuer explicitly, but Vault uses Issuer
97 // pseudo-account, which cannot grant an authorization.
98 if (maybeDomainID)
99 {
100 // As per validDomain documentation, we suppress tecEXPIRED error
101 // here, so we can delete any expired credentials inside doApply.
102 if (auto const err = credentials::validDomain(ctx.view, *maybeDomainID, account);
103 !isTesSuccess(err) && err != tecEXPIRED)
104 return err;
105 }
106 else
107 {
108 return tecNO_AUTH;
109 }
110 }
111
112 // Source MPToken must exist (if asset is an MPT)
113 if (auto const ter = requireAuth(ctx.view, vaultAsset, account); !isTesSuccess(ter))
114 return ter;
115
116 if (accountHolds(
117 ctx.view,
118 account,
119 vaultAsset,
122 ctx.j,
125
126 return tesSUCCESS;
127}
128
129TER
131{
132 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
133 if (!vault)
134 return tefINTERNAL; // LCOV_EXCL_LINE
135 auto const vaultAsset = vault->at(sfAsset);
136
137 auto const amount = ctx_.tx[sfAmount];
138 // Make sure the depositor can hold shares.
139 auto const mptIssuanceID = (*vault)[sfShareMPTID];
140 auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
141 if (!sleIssuance)
142 {
143 // LCOV_EXCL_START
144 JLOG(j_.error()) << "VaultDeposit: missing issuance of vault shares.";
145 return tefINTERNAL;
146 // LCOV_EXCL_STOP
147 }
148
149 auto const& vaultAccount = vault->at(sfAccount);
150 // Note, vault owner is always authorized
151 if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
152 {
153 if (auto const err = enforceMPTokenAuthorization(
154 ctx_.view(), mptIssuanceID, account_, preFeeBalance_, j_);
155 !isTesSuccess(err))
156 return err;
157 }
158 else // !vault->isFlag(lsfVaultPrivate) || account_ == vault->at(sfOwner)
159 {
160 // No authorization needed, but must ensure there is MPToken
161 if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
162 {
163 if (auto const err = authorizeMPToken(
164 view(), preFeeBalance_, mptIssuanceID->value(), account_, ctx_.journal);
165 !isTesSuccess(err))
166 return err;
167 }
168
169 // If the vault is private, set the authorized flag for the vault owner
170 if (vault->isFlag(lsfVaultPrivate))
171 {
172 // This follows from the reverse of the outer enclosing if condition
173 XRPL_ASSERT(
174 account_ == vault->at(sfOwner), "xrpl::VaultDeposit::doApply : account is owner");
175 if (auto const err = authorizeMPToken(
176 view(),
177 preFeeBalance_, // priorBalance
178 mptIssuanceID->value(), // mptIssuanceID
179 sleIssuance->at(sfIssuer), // account
181 {}, // flags
182 account_ // holderID
183 );
184 !isTesSuccess(err))
185 return err;
186 }
187 }
188
189 STAmount sharesCreated = {vault->at(sfShareMPTID)}, assetsDeposited;
190 try
191 {
192 // Compute exchange before transferring any amounts.
193 {
194 auto const maybeShares = assetsToSharesDeposit(vault, sleIssuance, amount);
195 if (!maybeShares)
196 return tecINTERNAL; // LCOV_EXCL_LINE
197 sharesCreated = *maybeShares;
198 }
199 if (sharesCreated == beast::zero)
200 return tecPRECISION_LOSS;
201
202 auto const maybeAssets = sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
203 if (!maybeAssets)
204 {
205 return tecINTERNAL; // LCOV_EXCL_LINE
206 }
207 if (*maybeAssets > amount)
208 {
209 // LCOV_EXCL_START
210 JLOG(j_.error()) << "VaultDeposit: would take more than offered.";
211 return tecINTERNAL;
212 // LCOV_EXCL_STOP
213 }
214 assetsDeposited = *maybeAssets;
215 }
216 catch (std::overflow_error const&)
217 {
218 // It's easy to hit this exception from Number with large enough Scale
219 // so we avoid spamming the log and only use debug here.
220 JLOG(j_.debug()) //
221 << "VaultDeposit: overflow error with"
222 << " scale=" << (int)vault->at(sfScale).value() //
223 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
224 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount) << ", amount=" << amount;
225 return tecPATH_DRY;
226 }
227
228 XRPL_ASSERT(
229 sharesCreated.asset() != assetsDeposited.asset(),
230 "xrpl::VaultDeposit::doApply : assets are not shares");
231
232 vault->at(sfAssetsTotal) += assetsDeposited;
233 vault->at(sfAssetsAvailable) += assetsDeposited;
234 view().update(vault);
235
236 // A deposit must not push the vault over its limit.
237 auto const maximum = *vault->at(sfAssetsMaximum);
238 if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum)
239 return tecLIMIT_EXCEEDED;
240
241 // Transfer assets from depositor to vault.
242 if (auto const ter =
243 accountSend(view(), account_, vaultAccount, assetsDeposited, j_, WaiveTransferFee::Yes);
244 !isTesSuccess(ter))
245 return ter;
246
247 // Sanity check
248 if (accountHolds(
249 view(),
250 account_,
251 assetsDeposited.asset(),
254 j_) < beast::zero)
255 {
256 // LCOV_EXCL_START
257 JLOG(j_.error()) << "VaultDeposit: negative balance of account assets.";
258 return tefINTERNAL;
259 // LCOV_EXCL_STOP
260 }
261
262 // Transfer shares from vault to depositor.
263 if (auto const ter =
264 accountSend(view(), vaultAccount, account_, sharesCreated, j_, WaiveTransferFee::Yes);
265 !isTesSuccess(ter))
266 return ter;
267
268 associateAsset(*vault, vaultAsset);
269
270 return tesSUCCESS;
271}
272
273} // namespace xrpl
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
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 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:13
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Asset const & asset() const
Definition STAmount.h:457
AccountID const account_
Definition Transactor.h:116
beast::Journal const j_
Definition Transactor.h:114
ApplyView & view()
Definition Transactor.h:132
XRPAmount preFeeBalance_
Definition Transactor.h:117
ApplyContext & ctx_
Definition Transactor.h:112
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:218
TER validDomain(ReadView const &view, uint256 domainID, AccountID const &subject)
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:474
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:504
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:486
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::optional< STAmount > sharesToAssetsDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &shares)
From the perspective of a vault, return the number of assets to take from depositor when they receive...
@ fhZERO_IF_FROZEN
@ fhIGNORE_FREEZE
@ shFULL_BALANCE
@ tefINTERNAL
Definition TER.h:153
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
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)
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.
std::optional< STAmount > assetsToSharesDeposit(std::shared_ptr< SLE const > const &vault, std::shared_ptr< SLE const > const &issuance, STAmount const &assets)
From the perspective of a vault, return the number of shares to give depositor when they offer a fixe...
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
@ ahIGNORE_AUTH
@ ahZERO_IF_UNAUTHORIZED
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to)
Check if the destination account is allowed to receive MPT.
@ temMALFORMED
Definition TER.h:67
@ temBAD_AMOUNT
Definition TER.h:69
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
@ tecWRONG_ASSET
Definition TER.h:341
@ tecLOCKED
Definition TER.h:339
@ tecNO_ENTRY
Definition TER.h:287
@ tecPATH_DRY
Definition TER.h:275
@ tecNO_AUTH
Definition TER.h:281
@ tecINTERNAL
Definition TER.h:291
@ tecFROZEN
Definition TER.h:284
@ tecINSUFFICIENT_FUNDS
Definition TER.h:306
@ tecEXPIRED
Definition TER.h:295
@ tecPRECISION_LOSS
Definition TER.h:344
@ tecLIMIT_EXCEEDED
Definition TER.h:342
TER enforceMPTokenAuthorization(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, XRPAmount const &priorBalance, beast::Journal j)
Enforce account has MPToken to match its authorization.
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:582
@ tesSUCCESS
Definition TER.h:225
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, int depth=0)
Check if the account lacks required authorization for MPT.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
ReadView const & view
Definition Transactor.h:60
beast::Journal const j
Definition Transactor.h:65
State information when preflighting a tx.
Definition Transactor.h:14
beast::Journal const j
Definition Transactor.h:21