rippled
Loading...
Searching...
No Matches
VaultCreate.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2025 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/MPTokenAuthorize.h>
21#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
22#include <xrpld/app/tx/detail/VaultCreate.h>
23
24#include <xrpl/ledger/View.h>
25#include <xrpl/protocol/Asset.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/Issue.h>
29#include <xrpl/protocol/MPTIssue.h>
30#include <xrpl/protocol/Protocol.h>
31#include <xrpl/protocol/SField.h>
32#include <xrpl/protocol/STNumber.h>
33#include <xrpl/protocol/TER.h>
34#include <xrpl/protocol/TxFlags.h>
35
36namespace ripple {
37
40{
41 if (!ctx.rules.enabled(featureSingleAssetVault) ||
42 !ctx.rules.enabled(featureMPTokensV1))
43 return temDISABLED;
44
45 if (ctx.tx.isFieldPresent(sfDomainID) &&
46 !ctx.rules.enabled(featurePermissionedDomains))
47 return temDISABLED;
48
49 if (auto const ter = preflight1(ctx))
50 return ter;
51
52 if (ctx.tx.getFlags() & tfVaultCreateMask)
53 return temINVALID_FLAG;
54
55 if (auto const data = ctx.tx[~sfData])
56 {
57 if (data->empty() || data->length() > maxDataPayloadLength)
58 return temMALFORMED;
59 }
60
61 if (auto const withdrawalPolicy = ctx.tx[~sfWithdrawalPolicy])
62 {
63 // Enforce valid withdrawal policy
64 if (*withdrawalPolicy != vaultStrategyFirstComeFirstServe)
65 return temMALFORMED;
66 }
67
68 if (auto const domain = ctx.tx[~sfDomainID])
69 {
70 if (*domain == beast::zero)
71 return temMALFORMED;
72 else if ((ctx.tx.getFlags() & tfVaultPrivate) == 0)
73 return temMALFORMED; // DomainID only allowed on private vaults
74 }
75
76 if (auto const assetMax = ctx.tx[~sfAssetsMaximum])
77 {
78 if (*assetMax < beast::zero)
79 return temMALFORMED;
80 }
81
82 if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
83 {
84 if (metadata->length() == 0 ||
85 metadata->length() > maxMPTokenMetadataLength)
86 return temMALFORMED;
87 }
88
89 if (auto const scale = ctx.tx[~sfScale])
90 {
91 auto const vaultAsset = ctx.tx[sfAsset];
92 if (vaultAsset.holds<MPTIssue>() || vaultAsset.native())
93 return temMALFORMED;
94
95 if (scale > vaultMaximumIOUScale)
96 return temMALFORMED;
97 }
98
99 return preflight2(ctx);
100}
101
104{
105 // One reserve increment is typically much greater than one base fee.
106 return view.fees().increment;
107}
108
109TER
111{
112 auto const vaultAsset = ctx.tx[sfAsset];
113 auto const account = ctx.tx[sfAccount];
114
115 if (vaultAsset.native())
116 ; // No special checks for XRP
117 else if (vaultAsset.holds<MPTIssue>())
118 {
119 auto mptID = vaultAsset.get<MPTIssue>().getMptID();
120 auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
121 if (!issuance)
122 return tecOBJECT_NOT_FOUND;
123 if (!issuance->isFlag(lsfMPTCanTransfer))
124 {
125 // NOTE: flag lsfMPTCanTransfer is immutable, so this is debug in
126 // VaultCreate only; in other vault function it's an error.
127 JLOG(ctx.j.debug())
128 << "VaultCreate: vault assets are non-transferable.";
129 return tecNO_AUTH;
130 }
131 }
132 else if (vaultAsset.holds<Issue>())
133 {
134 auto const issuer =
135 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
136 if (!issuer)
137 return terNO_ACCOUNT;
138 else if (!issuer->isFlag(lsfDefaultRipple))
139 return terNO_RIPPLE;
140 }
141
142 // Check for pseudo-account issuers - we do not want a vault to hold such
143 // assets (e.g. MPT shares to other vaults or AMM LPTokens) as they would be
144 // impossible to clawback (should the need arise)
145 if (!vaultAsset.native())
146 {
147 if (isPseudoAccount(ctx.view, vaultAsset.getIssuer()))
148 return tecWRONG_ASSET;
149 }
150
151 // Cannot create Vault for an Asset frozen for the vault owner
152 if (isFrozen(ctx.view, account, vaultAsset))
153 return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
154
155 if (auto const domain = ctx.tx[~sfDomainID])
156 {
157 auto const sleDomain =
159 if (!sleDomain)
160 return tecOBJECT_NOT_FOUND;
161 }
162
163 auto const sequence = ctx.tx.getSeqValue();
164 if (auto const accountId = pseudoAccountAddress(
165 ctx.view, keylet::vault(account, sequence).key);
166 accountId == beast::zero)
168
169 return tesSUCCESS;
170}
171
172TER
174{
175 // All return codes in `doApply` must be `tec`, `ter`, or `tes`.
176 // As we move checks into `preflight` and `preclaim`,
177 // we can consider downgrading them to `tef` or `tem`.
178
179 auto const& tx = ctx_.tx;
180 auto const sequence = tx.getSeqValue();
181 auto const owner = view().peek(keylet::account(account_));
182 if (owner == nullptr)
183 return tefINTERNAL; // LCOV_EXCL_LINE
184
185 auto vault = std::make_shared<SLE>(keylet::vault(account_, sequence));
186
187 if (auto ter = dirLink(view(), account_, vault))
188 return ter;
189 adjustOwnerCount(view(), owner, 1, j_);
190 auto ownerCount = owner->at(sfOwnerCount);
191 if (mPriorBalance < view().fees().accountReserve(ownerCount))
193
194 auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID);
195 if (!maybePseudo)
196 return maybePseudo.error(); // LCOV_EXCL_LINE
197 auto& pseudo = *maybePseudo;
198 auto pseudoId = pseudo->at(sfAccount);
199 auto asset = tx[sfAsset];
200
201 if (auto ter = addEmptyHolding(view(), pseudoId, mPriorBalance, asset, j_);
202 !isTesSuccess(ter))
203 return ter;
204
205 std::uint8_t const scale = (asset.holds<MPTIssue>() || asset.native())
206 ? 0
207 : ctx_.tx[~sfScale].value_or(vaultDefaultIOUScale);
208
209 auto txFlags = tx.getFlags();
210 std::uint32_t mptFlags = 0;
211 if ((txFlags & tfVaultShareNonTransferable) == 0)
213 if (txFlags & tfVaultPrivate)
214 mptFlags |= lsfMPTRequireAuth;
215
216 // Note, here we are **not** creating an MPToken for the assets held in
217 // the vault. That MPToken or TrustLine/RippleState is created above, in
218 // addEmptyHolding. Here we are creating MPTokenIssuance for the shares
219 // in the vault
220 auto maybeShare = MPTokenIssuanceCreate::create(
221 view(),
222 j_,
223 {
224 .priorBalance = std::nullopt,
225 .account = pseudoId->value(),
226 .sequence = 1,
227 .flags = mptFlags,
228 .assetScale = scale,
229 .metadata = tx[~sfMPTokenMetadata],
230 .domainId = tx[~sfDomainID],
231 });
232 if (!maybeShare)
233 return maybeShare.error(); // LCOV_EXCL_LINE
234 auto const& mptIssuanceID = *maybeShare;
235
236 vault->setFieldIssue(sfAsset, STIssue{sfAsset, asset});
237 vault->at(sfFlags) = txFlags & tfVaultPrivate;
238 vault->at(sfSequence) = sequence;
239 vault->at(sfOwner) = account_;
240 vault->at(sfAccount) = pseudoId;
241 vault->at(sfAssetsTotal) = Number(0);
242 vault->at(sfAssetsAvailable) = Number(0);
243 vault->at(sfLossUnrealized) = Number(0);
244 // Leave default values for AssetTotal and AssetAvailable, both zero.
245 if (auto value = tx[~sfAssetsMaximum])
246 vault->at(sfAssetsMaximum) = *value;
247 vault->at(sfShareMPTID) = mptIssuanceID;
248 if (auto value = tx[~sfData])
249 vault->at(sfData) = *value;
250 // Required field, default to vaultStrategyFirstComeFirstServe
251 if (auto value = tx[~sfWithdrawalPolicy])
252 vault->at(sfWithdrawalPolicy) = *value;
253 else
254 vault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe;
255 if (scale)
256 vault->at(sfScale) = scale;
257 view().insert(vault);
258
259 // Explicitly create MPToken for the vault owner
260 if (auto const err = authorizeMPToken(
261 view(), mPriorBalance, mptIssuanceID, account_, ctx_.journal);
262 !isTesSuccess(err))
263 return err;
264
265 // If the vault is private, set the authorized flag for the vault owner
266 if (txFlags & tfVaultPrivate)
267 {
268 if (auto const err = authorizeMPToken(
269 view(),
271 mptIssuanceID,
272 pseudoId,
274 {},
275 account_);
276 !isTesSuccess(err))
277 return err;
278 }
279
280 return tesSUCCESS;
281}
282
283} // namespace ripple
Stream debug() const
Definition Journal.h:328
beast::Journal const journal
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state 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:33
bool native() const
Definition MPTIssue.h:64
static Expected< MPTID, TER > create(ApplyView &view, beast::Journal journal, MPTCreateArgs const &args)
A view into a ledger.
Definition ReadView.h:51
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.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
std::uint32_t getFlags() const
Definition STObject.cpp:537
std::uint32_t getSeqValue() const
Returns the first non-zero value of (Sequence, TicketSequence).
Definition STTx.cpp:231
AccountID const account_
Definition Transactor.h:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
T is_same_v
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:570
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:526
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
@ lsfMPTCanTransfer
@ lsfMPTCanEscrow
@ lsfDefaultRipple
@ lsfMPTRequireAuth
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:1029
constexpr std::uint32_t const tfVaultPrivate
Definition TxFlags.h:270
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:119
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
@ tefINTERNAL
Definition TER.h:173
std::size_t constexpr maxMPTokenMetadataLength
The maximum length of MPTokenMetadata.
Definition Protocol.h:113
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Expected< std::shared_ptr< SLE >, TER > createPseudoAccount(ApplyView &view, uint256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
Definition View.cpp:1129
std::uint8_t constexpr vaultMaximumIOUScale
Maximum scale factor for a Vault.
Definition Protocol.h:129
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecFROZEN
Definition TER.h:303
@ tecWRONG_ASSET
Definition TER.h:360
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:300
@ tecLOCKED
Definition TER.h:358
@ tesSUCCESS
Definition TER.h:244
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
Definition View.cpp:1213
constexpr std::uint32_t const tfVaultShareNonTransferable
Definition TxFlags.h:272
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1066
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:122
@ terADDRESS_COLLISION
Definition TER.h:228
@ terNO_ACCOUNT
Definition TER.h:217
@ terNO_RIPPLE
Definition TER.h:224
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:1281
constexpr std::uint32_t const tfVaultCreateMask
Definition TxFlags.h:273
std::uint8_t constexpr vaultDefaultIOUScale
Default IOU scale factor for a Vault.
Definition Protocol.h:125
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct)
Definition View.cpp:1115
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object)
Definition View.cpp:1055
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
XRPAmount increment
uint256 key
Definition Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35