Update amm_info to fetch AMM by amm account and add owner directory for AMM object#4682
Conversation
ximinez
left a comment
There was a problem hiding this comment.
Incremental "first impressions" review.
| JSS(amendment_blocked); // out: NetworkOPs | ||
| JSS(amendments); // in: AccountObjects, out: NetworkOPs | ||
| JSS(amm); // out: amm_info | ||
| JSS(amm_account); // in: amm_info |
There was a problem hiding this comment.
This is going to cause confusion. I think it would make much more sense to reuse account to provide the AMM account ID, and peer (currently account) to designate the second / user account. This is how account_lines does it, so it'll be familiar to people.
Unfortunately, it will require updating any documentation and libraries, but I think it makes way more sense than introducing a new similarly-named parameter.
There was a problem hiding this comment.
I thought that was pretty clear. But I'm not good with names. @mDuo13 what do you think about the name change?
There was a problem hiding this comment.
Both seem fine to me. EDIT: Unless I'm misunderstanding something—What's the difference between account and amm_account in this request?
There was a problem hiding this comment.
EDIT: Unless I'm misunderstanding something—What's the difference between
accountandamm_accountin this request?
The way I understand it,
amm_accountis ther...address of the account that is part of the AMM itself. It's an alternative toassetandasset2as a way to look up the AMM.accountis ther...address of (possibly) a liquidity provider for the AMM.
There was a problem hiding this comment.
Yes, this is correct.
There was a problem hiding this comment.
Expanding on this for future readers and tech writers / documenters:
amm_accountis AMM account ID. It allows users to fetch AMM Object with AMM account ID instead of using the asset pair.- Either
amm_accountorassetandasset2have to be included. accountis a liquidity provider (LP) account. Ifaccountis included, thenlp_tokenis set to that LP's token balance.accountis anr...address and is kind of like the "perspective" account.- It is like the
peerinaccount_lines, or thetakerinbook_offers.
seelabs
left a comment
There was a problem hiding this comment.
Left some suggestions about code cleanup. Functionality looks good.
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
| uint256 ammID; | ||
| bool const isAssets = | ||
| params.isMember(jss::asset) && params.isMember(jss::asset2); | ||
| bool const isAMMAccount = params.isMember(jss::amm_account); |
There was a problem hiding this comment.
If the variables are made optional we can remove the isAssets and isAMMAccount and just check if the optional is set.
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
| bool const isAMMAccount = params.isMember(jss::amm_account); | ||
|
|
||
| if (!params.isMember(jss::asset) || !params.isMember(jss::asset2)) | ||
| if ((isAssets && isAMMAccount) || (!isAssets && !isAMMAccount)) |
There was a problem hiding this comment.
You could just check for equality.
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
| return result; | ||
| } | ||
| else | ||
| issue1 = *i; |
There was a problem hiding this comment.
I see this pattern in this code a lot - where the error condition doesn't use the "if local" variable, and we handle the error condition first. I think this is simpler to read by handling the non-error case in the main branch and removing the ; !i section. Here and other places as well.
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
| return result; | ||
| } | ||
| else | ||
| ammID = sle->getFieldH256(sfAMMID); |
There was a problem hiding this comment.
I think this code is clearer without the cascading local variables declared in the if statements. That pattern is OK if it prevents the declared variables from polluting the parent scope. Here the parent scope can be small.
| { | ||
| issue1 = (*amm)[sfAsset]; | ||
| issue2 = (*amm)[sfAsset2]; | ||
| } |
There was a problem hiding this comment.
This whole section is used to get the accountID, issue1 issue2 and amm. Consider re-writing this with a lambda. Something like this (this incorporates the comments above as well):
Json::Value
doAMMInfo(RPC::JsonContext& context)
{
auto const& params(context.params);
Json::Value result;
std::shared_ptr<ReadView const> ledger;
result = RPC::lookupLedger(ledger, context);
if (!ledger)
return result;
struct ValuesFromContextParams
{
std::optional<AccountID> accountID;
Issue issue1;
Issue issue2;
std::shared_ptr<SLE const> amm;
};
auto getValuesFromContextParams =
[&]() -> Expected<ValuesFromContextParams, error_code_i> {
std::optional<AccountID> accountID;
std::optional<Issue> issue1;
std::optional<Issue> issue2;
std::optional<uint256> ammID;
if (params.isMember(jss::asset) && params.isMember(jss::asset2))
{
if (auto const i = getIssue(params[jss::asset], context.j))
issue1 = *i;
else
return Unexpected(i.error());
if (auto const i = getIssue(params[jss::asset2], context.j))
issue2 = *i;
else
return Unexpected(i.error());
}
if (params.isMember(jss::amm_account))
{
auto const id = getAccount(params[jss::amm_account], result);
if (!id)
return Unexpected(rpcACT_MALFORMED);
auto const sle = ledger->read(keylet::account(*id));
if (!sle)
return Unexpected(rpcACT_MALFORMED);
ammID = sle->getFieldH256(sfAMMID);
}
if (issue1.has_value() == ammID.has_value())
return Unexpected(rpcINVALID_PARAMS);
if (params.isMember(jss::account))
{
accountID = getAccount(params[jss::account], result);
if (!accountID || !ledger->read(keylet::account(*accountID)))
return Unexpected(rpcACT_MALFORMED);
}
auto const ammKeylet = [&]() {
if (issue1 && issue2)
return keylet::amm(*issue1, *issue2);
assert(ammID);
return keylet::amm(*ammID);
}();
auto const amm = ledger->read(ammKeylet);
if (!amm)
return Unexpected(rpcACT_NOT_FOUND);
if (!issue1 && !issue2)
{
issue1 = (*amm)[sfAsset];
issue2 = (*amm)[sfAsset2];
}
return ValuesFromContextParams{
accountID, *issue1, *issue2, std::move(amm)};
};
auto r = getValuesFromContextParams();
if (!r)
{
RPC::inject_error(r.error(), result);
return result;
}
auto& [accountID, issue1, issue2, amm] = *r;
seelabs
left a comment
There was a problem hiding this comment.
Found one minor nit; other than that LGTM. I'll look at Ed's comment's too, of course.
src/ripple/ledger/View.h
Outdated
| Keylet const& ownerDirKeylet, | ||
| std::function<TER(LedgerEntryType, uint256 const&, std::shared_ptr<SLE>&)> | ||
| deleter, | ||
| EntryDeleter deleter, |
There was a problem hiding this comment.
This shouldn't be passed by value. Pass by ref instead.
Thanks, will fix. I'll wait with pushing another commit until you review Ed's comments. |
| // Skip AMM | ||
| if (nodeType == LedgerEntryType::ltAMM) | ||
| return tecOBJECT_NOT_FOUND; | ||
| return {tesSUCCESS, SkipEntry::Yes}; |
|
@gregtatcam "Update amm_info to fetch AMM object by amm account id instead of the assets pair." If I'm understanding this correctly, is amm account id replacing asset pair params in So will this be no longer valid? {
"command": "amm_info",
"asset": {
"currency": "XRP"
},
"asset2": {
"currency": "TST",
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"
}
} |
It doesn't replace the asset pair. Either one but not both can be provided. You can either fetch amm info by |
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
|
|
||
| if (issue1.has_value() == ammID.has_value()) | ||
| if ((issue1.has_value() ^ issue2.has_value()) || | ||
| (issue1.has_value() == ammID.has_value())) |
There was a problem hiding this comment.
Since this isn't a bitwise operation, I'd prefer != instead of exclusive or (yes, I understand they act the same here). Also, if issue1 has a value and issue2 does not, that's a logic error, not a malformed error. I don't object to the check, but I'd assert and return rpcINTERNAL on that error.
There was a problem hiding this comment.
Now that I look, we should probably detect when params.isMember(jss::asset) != params.isMember(jss::asset2) and return malformed on that too. Right now if someone specifies jss::asset, jss::amm_account, and doesn't specify jss::asset2 there's no error.
There was a problem hiding this comment.
Isn't it invalid parameters if one issue is set but not the other? It means that either asset or asset2 is included but not both. params.isMember(jss::asset) != params.isMember(jss::asset2) is handled by issue1.has_value() != issue2.has_value().
There was a problem hiding this comment.
Since you split up the parsing of jss::asset and jss::asset2, checking here works. However, it would save some effort to check the various params.isMember combinations before trying to parse things and reading from the ledger. So at the top of the lambda:
if ((params.isMember(jss::asset) != params.isMember(jss::asset2)) ||
(params.isMember(jss::asset) == params.isMember(jss::amm_account)))
return Unexpected(rpcINVALID_PARAMS);
Then this check could be replaced with an assert:
assert(
(issue1.has_value() == issue2.has_value()) &&
(issue1.has_value() != ammID.has_value()));
Also, you should add a few unit test cases to check out the various malformed combinations.
| ApplyView& view, | ||
| Keylet const& ownerDirKeylet, | ||
| EntryDeleter deleter, | ||
| EntryDeleter const& deleter, |
There was a problem hiding this comment.
I probably wouldn't have made this const. If the callback wants to change itself (increment some counter say), the interface shouldn't stop that.
There was a problem hiding this comment.
It would not compile otherwise.
There was a problem hiding this comment.
Ah, because we're binding temporaries. We have to make it a template and use a forwarding ref. Fine as-is for now.
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
|
|
||
| if (issue1.has_value() == ammID.has_value()) | ||
| if ((issue1.has_value() ^ issue2.has_value()) || | ||
| (issue1.has_value() == ammID.has_value())) |
There was a problem hiding this comment.
Since you split up the parsing of jss::asset and jss::asset2, checking here works. However, it would save some effort to check the various params.isMember combinations before trying to parse things and reading from the ledger. So at the top of the lambda:
if ((params.isMember(jss::asset) != params.isMember(jss::asset2)) ||
(params.isMember(jss::asset) == params.isMember(jss::amm_account)))
return Unexpected(rpcINVALID_PARAMS);
Then this check could be replaced with an assert:
assert(
(issue1.has_value() == issue2.has_value()) &&
(issue1.has_value() != ammID.has_value()));
Also, you should add a few unit test cases to check out the various malformed combinations.
| JSS(amendment_blocked); // out: NetworkOPs | ||
| JSS(amendments); // in: AccountObjects, out: NetworkOPs | ||
| JSS(amm); // out: amm_info | ||
| JSS(amm_account); // in: amm_info |
There was a problem hiding this comment.
EDIT: Unless I'm misunderstanding something—What's the difference between
accountandamm_accountin this request?
The way I understand it,
amm_accountis ther...address of the account that is part of the AMM itself. It's an alternative toassetandasset2as a way to look up the AMM.accountis ther...address of (possibly) a liquidity provider for the AMM.
- Update amm_info to fetch AMM by amm account id. - This is an additional way to retrieve an AMM object. - Alternatively, AMM can still be fetched by the asset pair as well. - Add owner directory entry for AMM object. Context: - Add back the AMM object directory entry, which was deleted by XRPLF#4626. - This fixes `account_objects` for `amm` type.
- Update amm_info to fetch AMM by amm account id. - This is an additional way to retrieve an AMM object. - Alternatively, AMM can still be fetched by the asset pair as well. - Add owner directory entry for AMM object. Context: - Add back the AMM object directory entry, which was deleted by XRPLF#4626. - This fixes `account_objects` for `amm` type.
- Update amm_info to fetch AMM by amm account id. - This is an additional way to retrieve an AMM object. - Alternatively, AMM can still be fetched by the asset pair as well. - Add owner directory entry for AMM object. Context: - Add back the AMM object directory entry, which was deleted by XRPLF#4626. - This fixes `account_objects` for `amm` type.
High Level Overview of Change
Add back the owner directory, which was deleted by #4626.
Update amm_info to fetch AMM object by amm account id instead of the assets pair.
Context of Change
#4626 removed the AMM object directory entry and added AMMID. This was done for efficiency consideration because it could take up to 32 iterations to get AMM object. This change broke
account_objectsforammtype. This PR adds back the owner directory and also updatesamm_infoto fetch AMM object via amm account id instead of the asset pair.Type of Change
Before / After
account_objectsworks withammtype.amm_infocan be used with either the asset pair or amm account id to retrieve AMM object.Test Plan
AccountObjects is extended for
ammtype tests.AMMInfo is extended to test for
amm_infowith amm account id.