-
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
Update amm_info to fetch AMM by amm account and add owner directory for AMM object #4682
Conversation
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.
Incremental "first impressions" review.
@@ -160,6 +160,7 @@ JSS(alternatives); // out: PathRequest, RipplePathFind | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: Unless I'm misunderstanding something—What's the difference between
account
andamm_account
in this request?
The way I understand it,
amm_account
is ther...
address of the account that is part of the AMM itself. It's an alternative toasset
andasset2
as a way to look up the AMM.account
is ther...
address of (possibly) a liquidity provider for the AMM.
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.
Yes, this is correct.
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.
Expanding on this for future readers and tech writers / documenters:
amm_account
is AMM account ID. It allows users to fetch AMM Object with AMM account ID instead of using the asset pair.- Either
amm_account
orasset
andasset2
have to be included. account
is a liquidity provider (LP) account. Ifaccount
is included, thenlp_token
is set to that LP's token balance.account
is anr...
address and is kind of like the "perspective" account.- It is like the
peer
inaccount_lines
, or thetaker
inbook_offers
.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
|
||
if (!params.isMember(jss::asset) || !params.isMember(jss::asset2)) | ||
if ((isAssets && isAMMAccount) || (!isAssets && !isAMMAccount)) |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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;
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.
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
@@ -469,8 +478,7 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); | |||
cleanupOnAccountDelete( | |||
ApplyView& view, | |||
Keylet const& ownerDirKeylet, | |||
std::function<TER(LedgerEntryType, uint256 const&, std::shared_ptr<SLE>&)> | |||
deleter, | |||
EntryDeleter deleter, |
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 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. |
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.
Other than the ref issue pointed out by @seelabs, and the parameter name question, I think this is good.
// Skip AMM | ||
if (nodeType == LedgerEntryType::ltAMM) | ||
return tecOBJECT_NOT_FOUND; | ||
return {tesSUCCESS, SkipEntry::Yes}; |
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 so much better.
@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
@@ -119,7 +122,8 @@ doAMMInfo(RPC::JsonContext& context) | |||
ammID = sle->getFieldH256(sfAMMID); | |||
} | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -478,7 +478,7 @@ using EntryDeleter = std::function<std::pair<TER, SkipEntry>( | |||
cleanupOnAccountDelete( | |||
ApplyView& view, | |||
Keylet const& ownerDirKeylet, | |||
EntryDeleter deleter, | |||
EntryDeleter const& deleter, |
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would not compile otherwise.
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.
Ah, because we're binding temporaries. We have to make it a template and use a forwarding ref. Fine as-is for now.
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.
👍
src/ripple/rpc/handlers/AMMInfo.cpp
Outdated
@@ -119,7 +122,8 @@ doAMMInfo(RPC::JsonContext& context) | |||
ammID = sle->getFieldH256(sfAMMID); | |||
} | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -160,6 +160,7 @@ JSS(alternatives); // out: PathRequest, RipplePathFind | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: Unless I'm misunderstanding something—What's the difference between
account
andamm_account
in this request?
The way I understand it,
amm_account
is ther...
address of the account that is part of the AMM itself. It's an alternative toasset
andasset2
as a way to look up the AMM.account
is 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_objects
foramm
type. This PR adds back the owner directory and also updatesamm_info
to fetch AMM object via amm account id instead of the asset pair.Type of Change
Before / After
account_objects
works withamm
type.amm_info
can be used with either the asset pair or amm account id to retrieve AMM object.Test Plan
AccountObjects is extended for
amm
type tests.AMMInfo is extended to test for
amm_info
with amm account id.