-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce MPT support (XLS-33d): #5143
base: develop
Are you sure you want to change the base?
Conversation
4ca85a2
to
ba5f67c
Compare
New Transactions: - MPTokenIssuanceCreate - MPTokenIssuanceDestory - MPTokenIssuanceSet - MPTokenAuthorize Modified Transactions: - Payment - Clawback New Objects: - MPTokenIssuance - MPTokenAuthorize API updates: - ledger_entry - account_objects - ledger_data Read full spec: https:/XRPLF/XRPL-Standards/tree/master/XLS-0033d-multi-purpose-tokens --------- Co-authored-by: Shawn Xie <[email protected]> Co-authored-by: Howard Hinnant <[email protected]>
ba5f67c
to
6d6fda2
Compare
lsfMPTCanClawback = 0x00000040, | ||
|
||
// ltMPTOKEN | ||
lsfMPTAuthorized = 0x00000002, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lsfMPTLocked
, lsfMPTCanLock
and lsfMPTAuthorized
do not have unique values within this enum
. Is that on purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LedgerSpecificFlags
enum is really just a way to define a bunch of constants. The flags are unique per ledger object type. Those are divided by the comment / label before each one. It might be worthwhile to split them into one per object type, but that's probably beyond the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Submitting the first phase of my review, a complete review of the changes to libxrpl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review:
Co-authored-by: Ed Hennis <[email protected]>
Update MPT Payment errors to be consistent with IOU Payment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review
|
||
namespace ripple { | ||
|
||
class MPTAmount : private boost::totally_ordered<MPTAmount>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing how similar MPTAmount
is to XRPAmount
it's a pity that they aren't implemented with common code. I.e. through a base class, or a template or something. Rather than diving down the potential rabbit hole of implementing it, I'm just going to leave this note here for someone else or for future reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My opinion:
- They should be called
MPTNumber
andXRPNumber
because they represent quantities, whileSTAmount
represents a quantity plus an asset / issue / unit (however you want to think of it). These are more likeNumber
, and convert directly to and from it. - All of the arithmetic is moving to
Number
after the switchover anyway. In due time, when we retire that amendment, effectively locking it in permanently, then I think we can removeMPTAmount
andXRPAmount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should IOUAmount
change too? I think that Amount
better communicates what the value is. I don't think you'd say number when talking about tokens or currencies even if the values don't have associate unit. And XRPAmount
doesn't need an issue, it's implicit. Also doesn't seem like this refactoring has to be done in MPT PR. But this is just my opinion. I'll change if everyone thinks Number
is better. There are over 300 instances of XRPAmount
, MPTAmount
, IOUAmount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a ticket to refactor MPTAmount
and XRPAmount
to use a common code + renaming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm not asking it to be done here, or at all even. Was just stating my position. My opinion is that IOUAmount
should be renamed too (in an ideal world), and my only reason for these renames is that STAmount
has quantity + issue, while these {XRP,IOU,MPT}Amount
s only have quantity, like Number
, which is their common representation. The alternative fix is to rename STAmount
to a different suffix, but I think that would be even more disruptive, especially to external projects.
Co-authored-by: Ed Hennis <[email protected]>
include/xrpl/protocol/Asset.h
Outdated
/* Asset is a variant of Issue (XRP and IOU) and MPTIssue (MPT). | ||
* It enables handling of different issues when either one is expected. | ||
* For instance, it extends STAmount class to support either issue | ||
* in a general way. It handles specifics such arithmetics and serialization | ||
* depending on specific issue type held by Asset. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it odd that you think of this as "either one" (of two, Issue
or MPTIssue
) instead of "any one" (of three, XRP, IOU, or MPT). Don't we want our abstract ledger functions to think in terms of 3 issue types instead of 2 specific implementations?
I find the example (STAmount
) a little hard to understand, too. The "it" at the beginning of the last sentence refers to STAmount
, while the "it" in the sentence before is Asset
? I don't think "extends" is the right word either. Is the example just trying to convey that STAmount
uses Asset
to abstract arithmetic and serialization operations over all 3 issue types?
I'm left wondering why we have this Asset
instead of making Issue
support 3 issue types. STAmount
has a native() -> bool
method. Can Asset
have that too? Issue
has a getIssuer() -> AccountID
method. Can Asset
have that too? Why does Asset
exist if it's not going to offer any helper methods? With no helpers, it could just be a type alias for std::variant
.
|
||
namespace ripple { | ||
|
||
class MPTAmount : private boost::totally_ordered<MPTAmount>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My opinion:
- They should be called
MPTNumber
andXRPNumber
because they represent quantities, whileSTAmount
represents a quantity plus an asset / issue / unit (however you want to think of it). These are more likeNumber
, and convert directly to and from it. - All of the arithmetic is moving to
Number
after the switchover anyway. In due time, when we retire that amendment, effectively locking it in permanently, then I think we can removeMPTAmount
andXRPAmount
.
include/xrpl/basics/MPTAmount.h
Outdated
friend std::istream& | ||
operator>>(std::istream& s, MPTAmount& val); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was left behind.
friend std::istream& | |
operator>>(std::istream& s, MPTAmount& val); |
include/xrpl/protocol/Indexes.h
Outdated
MPTID | ||
makeMptID(AccountID const& account, std::uint32_t sequence); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of putting sequence
first in the parameter list? (Here and in the overload of mptIssuance()
, and maybe elsewhere.)
include/xrpl/protocol/Indexes.h
Outdated
mptoken(MPTID const& issuanceID, AccountID const& holder) noexcept; | ||
|
||
inline Keylet | ||
mptoken(uint256 const& issuanceKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This parameter should be mptokenKey
right? I think I confused you with my earlier comment on the other parameters. I was just saying that the issuance
parameter name should end with Key
like issuanceKey
and mptokenKey
.
include/xrpl/protocol/jss.h
Outdated
JSS(MPTokenIssuance); // ledger type. | ||
JSS(MPTokenIssuanceCreate); // transaction type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth it so we don't get radical re-formatting every time a few strings are added. We want the change set to highlight meaningful changes. GitHub's whitespace exclusion can hide these changes, but it can mask unwanted changes too. It's fine to restore the previous formatting so that this changeset has only the added strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review
Co-authored-by: Ed Hennis <[email protected]>
use std::min, std::max Co-authored-by: Ed Hennis <[email protected]>
* Refactor getSNValue/getMPTValue * Use std::min, std::max * Fix min/max values for MPT
// MPTokenIssuanceCreate flags: | ||
constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = | ||
~(tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback | tfUniversal); | ||
|
||
// MPTokenIssuanceDestroy flags: | ||
constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; | ||
|
||
// MPTokenAuthorize flags: | ||
constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfMPTUnauthorize | tfUniversal); | ||
|
||
// MPTokenIssuanceSet flags: | ||
constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfMPTLock | tfMPTUnlock | tfUniversal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move the masks next to their flags, like the pattern for AMM
, and please put tfUniversal
first in the sequences.
* Fix paths comments in Payment * Refactor View
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial feedback review. I forgot to post this first comment last week, so I'll share these now, and continue reviewing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review
* Simplify updating some MPT fields in View
* ledger_entry test * remove unneed line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review
* Add STObject unit-test for +=, -= * Update STTx unit-test
* add mpt id * refactor
TER | ||
doApply() override; | ||
|
||
static TER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my vault
branch, I make this method return Expected<MPTID, TER>
. Can you please make that change here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we necessarily need to make this change in this PR. These refactors could be done at a later time when needed (perhaps include the refactor you open the vault
PR).
if (args.priorBalance < | ||
view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) | ||
return tecINSUFFICIENT_RESERVE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my vault
branch, I move this check out of this function and into its (for now) only caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
|
||
struct MPTCreateArgs | ||
{ | ||
XRPAmount const& priorBalance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my branch, this field is removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
// Concept to constrain STVar constructors, which | ||
// instantiate ST* types from SerializedTypeID | ||
// clang-format off | ||
template <typename... Args> | ||
concept ValidConstructSTArgs = | ||
(std::is_same_v< | ||
std::tuple<std::remove_cvref_t<Args>...>, | ||
std::tuple<SField>> || | ||
std::is_same_v< | ||
std::tuple<std::remove_cvref_t<Args>...>, | ||
std::tuple<SerialIter, SField>>); | ||
// clang-format on | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This declaration of ValidConstructSTArgs
, and the one below of constructST
, don't need to be in this header. They are only used in STVar.cpp
, where they are defined. This header doesn't need the new includes, then, either, but STVar.cpp
might.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean to make it non-member function defined in STVar.cpp
? It calls private STVar::construct()
.
src/libxrpl/protocol/STAmount.cpp
Outdated
@@ -278,7 +276,7 @@ STAmount::xrp() const | |||
IOUAmount | |||
STAmount::iou() const | |||
{ | |||
if (native() || holds<MPTIssue>()) | |||
if (native() || !holds<Issue>()) | |||
Throw<std::logic_error>("Cannot return native STAmount as IOUAmount"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throw<std::logic_error>("Cannot return native STAmount as IOUAmount"); | |
Throw<std::logic_error>("Cannot return non-IOU STAmount as IOUAmount"); |
[[nodiscard]] bool | ||
isGlobalFrozen(ReadView const& view, AccountID const& issuer); | ||
|
||
[[nodiscard]] bool | ||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add isGlobalFrozen
, isIndividualFrozen
, and isFrozen
overloads for Asset
?
@@ -209,6 +237,9 @@ forEachItemAfter( | |||
[[nodiscard]] Rate | |||
transferRate(ReadView const& view, AccountID const& issuer); | |||
|
|||
[[nodiscard]] Rate | |||
transferRate(ReadView const& view, MPTID const& issuanceID); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no docstring for either of the overloads. Can you add one, please?
src/xrpld/ledger/View.h
Outdated
bool bCheckIssuer, | ||
beast::Journal j); | ||
|
||
[[nodiscard]] TER | ||
rippleCreditMPT( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike the others, which accept Issue
vs MPTIssue
, there is no type difference in the parameter list to distinguish these "overloads". What do you think of renaming the original function and all existing callers rippleCreditIOU
? Could then add a function rippleCredit
that takes an STAmount
and works for any asset by delegating to the other overloads. Similar for accountSend
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Let me give it a try.
bool | ||
canHaveMPTokenIssuanceID( | ||
std::shared_ptr<STTx const> const& serializedTx, | ||
TxMeta const& transactionMeta); | ||
|
||
std::optional<uint192> | ||
getIDFromCreatedIssuance(TxMeta const& transactionMeta); | ||
|
||
void | ||
insertMPTokenIssuanceID( | ||
Json::Value& response, | ||
std::shared_ptr<STTx const> const& transaction, | ||
TxMeta const& transactionMeta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some comments explaining why these functions exist, when they should be used, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in fixed in gregtatcam#43
Json::Value& response, | ||
std::shared_ptr<STTx const> const& transaction, | ||
TxMeta const& transactionMeta); | ||
/** @} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is going on this line?
/** @} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldve been a comment, added in gregtatcam#43
MPTIssue const& mptIssue); | ||
|
||
[[nodiscard]] bool | ||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ld: warning: ignoring duplicate libraries: '-lc++'
duplicate symbol 'ripple::isFrozen(ripple::ReadView const&, ripple::base_uint<160ul, ripple::detail::AccountIDTag> const&, ripple::Asset const&)'
Missing inline
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have already pushed a fix.
* Add static functions rippleCredit[MPT/IOU]() and accountSend[MPT/IOU]() * Change rippleCredit() and accountSend() to work for any asset by calling the static versions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a partial review. Here is the list of files I've reviewed:
AmountConversions.h
Asset.h
base_uint.h
Feature.h
LedgerFormats.h
MPTAmount.h
MPTAmount.cpp
MPTIssue.h
MPTIssue.cpp
MPTokenIssuanceSet.h
Number.h
Number.cpp
Protocol.h
Serializer.h
STAmount.h
STAmount.cpp
STBitString.h
STInteger.cpp
View.h
XRPAmount.h
All comments I have made have been resolved.
All unit tests pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not as much progress today, but pointed out a couple of small issues.
to_string(Asset const& asset); | ||
|
||
bool | ||
validJSONAsset(Json::Value const& jv); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please write an assetFromJson
? You may want to delegate to issueFromJson
(which exists) and mptIssueFromJson
(which does not exist). I need in the stissue
branch, in STParsedJSON.cpp
, in the case for STI_ISSUE
(which right now calls issueFromJson
).
* Add assetFromJson, mptIssueFromJson * Minor refactoring
* removed unused declarations * comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review. Left off at testMPTInvalidInTx
in MPToken_test.cpp.
A couple of general points
- I tagged a bunch of
assert
s with suggested rewrites. We really want to avoidassert
s in test code unless there's absolutely no other way to do it. If I missed any, please rewrite those, too. - I haven't tagged any more copyright date issues, but some remain. Please take care of those.
"mptIssueFromJson, MPTIssue should not have currency or issuer"); | ||
} | ||
|
||
Json::Value const idStr = v[jss::mpt_issuance_id]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think idStr
can be a const ref
Json::Value const idStr = v[jss::mpt_issuance_id]; | |
Json::Value const& idStr = v[jss::mpt_issuance_id]; |
std::unordered_map<std::string, Account> | ||
makeHolders(std::vector<Account> const& holders); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it again, makeHolders
should be static
.
else | ||
{ | ||
assert(id_); | ||
jv[sfMPTokenIssuanceID] = to_string(*id_); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else | |
{ | |
assert(id_); | |
jv[sfMPTokenIssuanceID] = to_string(*id_); | |
} | |
else if (env_.test.BEAST_EXPECT(id_)) | |
{ | |
jv[sfMPTokenIssuanceID] = to_string(*id_); | |
} |
If id_
is undefined, the transaction will be generated without one, and will fail, which is good.
else if (arg.flags) | ||
env_.require(mptflags(*this, *arg.flags)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else if (arg.flags) | |
env_.require(mptflags(*this, *arg.flags)); | |
else | |
env_.require(mptflags(*this, arg.flags.value_or(0))); |
This ensures the Issuance is created regardless, and that no unwanted flags are set.
assert(it != holders_.cend()); | ||
if (it == holders_.cend()) | ||
Throw<std::runtime_error>("Holder is not found"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert(it != holders_.cend()); | |
if (it == holders_.cend()) | |
Throw<std::runtime_error>("Holder is not found"); | |
if (it == holders_.cend()) | |
Throw<std::runtime_error>("Holder is not found"); |
Get rid of the assert
so that other runners aren't affected, but the Throw
will still cause the test suite to fail.
// Check that bob cannot delete MPToken when his balance is | ||
// non-zero | ||
{ | ||
// alice pays bob 100 tokens | ||
mptAlice.pay(alice, bob, 100); | ||
|
||
// bob tries to delete his MPToken, but fails since he still | ||
// holds tokens | ||
mptAlice.authorize( | ||
{.account = bob, | ||
.flags = tfMPTUnauthorize, | ||
.err = tecHAS_OBLIGATIONS}); | ||
|
||
// bob pays back alice 100 tokens | ||
mptAlice.pay(bob, alice, 100); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: These {}
are redundant, since nothing was created inside the scope.
MPTTester mptAlice1( | ||
env, | ||
alice, | ||
{.holders = {bob}, | ||
.xrpHolders = acctReserve + XRP(1).value().xrp()}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MPTTester mptAlice1( | |
env, | |
alice, | |
{.holders = {bob}, | |
.xrpHolders = acctReserve + XRP(1).value().xrp()}); | |
// 1 drop | |
BEAST_EXPECT(incReserve > XRPAmount(1)); | |
MPTTester mptAlice1( | |
env, | |
alice, | |
{.holders = {bob}, | |
.xrpHolders = acctReserve + (incReserve - 1)}); |
// test invalid flag | ||
mptAlice.set( | ||
{.account = alice, | ||
.flags = 0x00000008, | ||
.err = temINVALID_FLAG}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// test invalid flag | |
mptAlice.set( | |
{.account = alice, | |
.flags = 0x00000008, | |
.err = temINVALID_FLAG}); | |
// test invalid flag - only valid flags are tfMPTLock (1) and Unlock (2) | |
mptAlice.set( | |
{.account = alice, | |
.flags = 0x00000008, | |
.err = temINVALID_FLAG}); |
jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); | ||
jv[jss::tx_json] = pay(alice, carol, mpt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will overwrite Fee
.
jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); | |
jv[jss::tx_json] = pay(alice, carol, mpt); | |
jv[jss::tx_json] = pay(alice, carol, mpt); | |
jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); |
mptAlice.pay(alice, bob, -1, temBAD_AMOUNT); | ||
|
||
env(pay(alice, bob, MPT(10)), sendmax(MPT(-1)), ter(temBAD_AMOUNT)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a few more cases here with bob
trying to pay another holder (carol
), and bob
trying to pay back to alice
. That'll probably require alice
sending a valid amount to bob
first.
High Level Overview of Change
New Transactions:
Modified Transactions:
New Objects:
API updates:
Refactor:
Context of Change
Type of Change
.gitignore
, formatting, dropping support for older tooling)API Impact
libxrpl
change (any change that may affectlibxrpl
or dependents oflibxrpl
)Test Plan
Added test for new feature:
Future Tasks
Integrate MPT into XRPL DEX.