rippled
Loading...
Searching...
No Matches
MPTokenAuthorize.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 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/ledger/View.h>
22
23#include <xrpl/protocol/Feature.h>
24#include <xrpl/protocol/TxFlags.h>
25#include <xrpl/protocol/st.h>
26
27namespace ripple {
28
31{
32 if (!ctx.rules.enabled(featureMPTokensV1))
33 return temDISABLED;
34
35 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
36 return ret;
37
39 return temINVALID_FLAG;
40
41 if (ctx.tx[sfAccount] == ctx.tx[~sfHolder])
42 return temMALFORMED;
43
44 return preflight2(ctx);
45}
46
47TER
49{
50 auto const accountID = ctx.tx[sfAccount];
51 auto const holderID = ctx.tx[~sfHolder];
52
53 // if non-issuer account submits this tx, then they are trying either:
54 // 1. Unauthorize/delete MPToken
55 // 2. Use/create MPToken
56 //
57 // Note: `accountID` is holder's account
58 // `holderID` is NOT used
59 if (!holderID)
60 {
62 keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID));
63
64 // There is an edge case where all holders have zero balance, issuance
65 // is legally destroyed, then outstanding MPT(s) are deleted afterwards.
66 // Thus, there is no need to check for the existence of the issuance if
67 // the MPT is being deleted with a zero balance. Check for unauthorize
68 // before fetching the MPTIssuance object.
69
70 // if holder wants to delete/unauthorize a mpt
71 if (ctx.tx.getFlags() & tfMPTUnauthorize)
72 {
73 if (!sleMpt)
75
76 if ((*sleMpt)[sfMPTAmount] != 0)
77 {
78 auto const sleMptIssuance = ctx.view.read(
79 keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
80 if (!sleMptIssuance)
81 return tefINTERNAL;
82
83 return tecHAS_OBLIGATIONS;
84 }
85
86 if ((*sleMpt)[~sfLockedAmount].value_or(0) != 0)
87 {
88 auto const sleMptIssuance = ctx.view.read(
89 keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
90 if (!sleMptIssuance)
91 return tefINTERNAL; // LCOV_EXCL_LINE
92
93 return tecHAS_OBLIGATIONS;
94 }
95 if (ctx.view.rules().enabled(featureSingleAssetVault) &&
96 sleMpt->isFlag(lsfMPTLocked))
97 return tecNO_PERMISSION;
98
99 return tesSUCCESS;
100 }
101
102 // Now test when the holder wants to hold/create/authorize a new MPT
103 auto const sleMptIssuance =
104 ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
105
106 if (!sleMptIssuance)
107 return tecOBJECT_NOT_FOUND;
108
109 if (accountID == (*sleMptIssuance)[sfIssuer])
110 return tecNO_PERMISSION;
111
112 // if holder wants to use and create a mpt
113 if (sleMpt)
114 return tecDUPLICATE;
115
116 return tesSUCCESS;
117 }
118
119 if (!ctx.view.exists(keylet::account(*holderID)))
120 return tecNO_DST;
121
122 auto const sleMptIssuance =
123 ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
124 if (!sleMptIssuance)
125 return tecOBJECT_NOT_FOUND;
126
127 std::uint32_t const mptIssuanceFlags = sleMptIssuance->getFieldU32(sfFlags);
128
129 // If tx is submitted by issuer, they would either try to do the following
130 // for allowlisting:
131 // 1. authorize an account
132 // 2. unauthorize an account
133 //
134 // Note: `accountID` is issuer's account
135 // `holderID` is holder's account
136 if (accountID != (*sleMptIssuance)[sfIssuer])
137 return tecNO_PERMISSION;
138
139 // If tx is submitted by issuer, it only applies for MPT with
140 // lsfMPTRequireAuth set
141 if (!(mptIssuanceFlags & lsfMPTRequireAuth))
142 return tecNO_AUTH;
143
144 // The holder must create the MPT before the issuer can authorize it.
145 if (!ctx.view.exists(
146 keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], *holderID)))
147 return tecOBJECT_NOT_FOUND;
148
149 return tesSUCCESS;
150}
151
152TER
154 ApplyView& view,
155 MPTID const& mptIssuanceID,
156 AccountID const& account,
157 std::uint32_t const flags)
158{
159 auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
160
161 auto const ownerNode = view.dirInsert(
162 keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
163
164 if (!ownerNode)
165 return tecDIR_FULL; // LCOV_EXCL_LINE
166
167 auto mptoken = std::make_shared<SLE>(mptokenKey);
168 (*mptoken)[sfAccount] = account;
169 (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
170 (*mptoken)[sfFlags] = flags;
171 (*mptoken)[sfOwnerNode] = *ownerNode;
172
173 view.insert(mptoken);
174
175 return tesSUCCESS;
176}
177
178TER
180 ApplyView& view,
181 beast::Journal journal,
182 MPTAuthorizeArgs const& args)
183{
184 auto const sleAcct = view.peek(keylet::account(args.account));
185 if (!sleAcct)
186 return tecINTERNAL;
187
188 // If the account that submitted the tx is a holder
189 // Note: `account_` is holder's account
190 // `holderID` is NOT used
191 if (!args.holderID)
192 {
193 // When a holder wants to unauthorize/delete a MPT, the ledger must
194 // - delete mptokenKey from owner directory
195 // - delete the MPToken
196 if (args.flags & tfMPTUnauthorize)
197 {
198 auto const mptokenKey =
200 auto const sleMpt = view.peek(mptokenKey);
201 if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
202 return tecINTERNAL; // LCOV_EXCL_LINE
203
204 if (!view.dirRemove(
206 (*sleMpt)[sfOwnerNode],
207 sleMpt->key(),
208 false))
209 return tecINTERNAL; // LCOV_EXCL_LINE
210
211 adjustOwnerCount(view, sleAcct, -1, journal);
212
213 view.erase(sleMpt);
214 return tesSUCCESS;
215 }
216
217 // A potential holder wants to authorize/hold a mpt, the ledger must:
218 // - add the new mptokenKey to the owner directory
219 // - create the MPToken object for the holder
220
221 // The reserve that is required to create the MPToken. Note
222 // that although the reserve increases with every item
223 // an account owns, in the case of MPTokens we only
224 // *enforce* a reserve if the user owns more than two
225 // items. This is similar to the reserve requirements of trust lines.
226 std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
227 XRPAmount const reserveCreate(
228 (uOwnerCount < 2) ? XRPAmount(beast::zero)
229 : view.fees().accountReserve(uOwnerCount + 1));
230
231 if (args.priorBalance < reserveCreate)
233
234 auto const mptokenKey =
236 auto mptoken = std::make_shared<SLE>(mptokenKey);
237 if (auto ter = dirLink(view, args.account, mptoken))
238 return ter; // LCOV_EXCL_LINE
239
240 (*mptoken)[sfAccount] = args.account;
241 (*mptoken)[sfMPTokenIssuanceID] = args.mptIssuanceID;
242 (*mptoken)[sfFlags] = 0;
243 view.insert(mptoken);
244
245 // Update owner count.
246 adjustOwnerCount(view, sleAcct, 1, journal);
247
248 return tesSUCCESS;
249 }
250
251 auto const sleMptIssuance =
253 if (!sleMptIssuance)
254 return tecINTERNAL;
255
256 // If the account that submitted this tx is the issuer of the MPT
257 // Note: `account_` is issuer's account
258 // `holderID` is holder's account
259 if (args.account != (*sleMptIssuance)[sfIssuer])
260 return tecINTERNAL;
261
262 auto const sleMpt =
264 if (!sleMpt)
265 return tecINTERNAL;
266
267 std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags);
268 std::uint32_t flagsOut = flagsIn;
269
270 // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
271 // their MPToken
272 if (args.flags & tfMPTUnauthorize)
273 flagsOut &= ~lsfMPTAuthorized;
274 // Issuer wants to authorize a holder, set lsfMPTAuthorized on their
275 // MPToken
276 else
277 flagsOut |= lsfMPTAuthorized;
278
279 if (flagsIn != flagsOut)
280 sleMpt->setFieldU32(sfFlags, flagsOut);
281
282 view.update(sleMpt);
283 return tesSUCCESS;
284}
285
286TER
288{
289 auto const& tx = ctx_.tx;
290 return authorize(
291 ctx_.view(),
293 {.priorBalance = mPriorBalance,
294 .mptIssuanceID = tx[sfMPTokenIssuanceID],
295 .account = account_,
296 .flags = tx.getFlags(),
297 .holderID = tx[~sfHolder]});
298}
299
300} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
ApplyView & view()
Definition: ApplyContext.h:78
beast::Journal const journal
Definition: ApplyContext.h:75
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:190
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:318
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
static TER createMPToken(ApplyView &view, MPTID const &mptIssuanceID, AccountID const &account, std::uint32_t const flags)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static TER authorize(ApplyView &view, beast::Journal journal, MPTAuthorizeArgs const &args)
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 bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
std::uint32_t getFlags() const
Definition: STObject.cpp:537
ApplyView & view()
Definition: Transactor.h:159
ApplyContext & ctx_
Definition: Transactor.h:140
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition: Indexes.cpp:540
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition: Indexes.cpp:526
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:374
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
@ lsfMPTRequireAuth
@ lsfMPTAuthorized
@ lsfMPTLocked
constexpr std::uint32_t const tfMPTokenAuthorizeMask
Definition: TxFlags.h:156
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:1049
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
@ tefINTERNAL
Definition: TER.h:173
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
constexpr std::uint32_t const tfMPTUnauthorize
Definition: TxFlags.h:155
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
@ tecNO_DST
Definition: TER.h:290
@ tecOBJECT_NOT_FOUND
Definition: TER.h:326
@ tecDIR_FULL
Definition: TER.h:287
@ tecDUPLICATE
Definition: TER.h:315
@ tecINTERNAL
Definition: TER.h:310
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecHAS_OBLIGATIONS
Definition: TER.h:317
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:307
@ tecNO_AUTH
Definition: TER.h:300
@ tesSUCCESS
Definition: TER.h:244
bool isTesSuccess(TER x) noexcept
Definition: TER.h:674
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:605
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object)
Definition: View.cpp:1057
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
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
AccountID const & account
std::optional< AccountID > holderID
XRPAmount const & priorBalance
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
State information when preflighting a tx.
Definition: Transactor.h:34