-
Notifications
You must be signed in to change notification settings - Fork 586
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
Chore: Make a state entry to find total amount of native assets IBC'd out #3019
Conversation
bdcb5b6
to
5d4f691
Compare
Awesome! Thanks for the PR @stackman27, will try to review this soon! |
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 some comments about name changes for clarity.
Other than that and getting CI to pass, this looks good to me
5d4f691
to
590593f
Compare
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.
Thanks for this contribution, @stackman27! I have a few of questions/remarks:
- It would be great to have some tests for this!
- The escrowed amount for a particular denom would be set on the first transfer of that denom. So until a transfer is done, the query would return 0, right? Would you consider adding a migration that calculates the amount in escrow and sets it beforehand?
- When native tokens return back to the source chain, the total amount in escrow would not be updated until there's a transfer. Should you maybe update the total amount in escrow in the
OnRecvPacket
callback as well? Similar comment in case the transfer times out or the acknowledgement received is an error, then the tokens are refunded, so maybe it's good to update the value as well inrefundTokens
. In general, I would try to think in what places this value needs to be updated so it doesn't get out of sync with the actual amount in escrow. - Reading the original issue, I think the implementation is missing one requirement:
Add a safety check if this value is ever attempted to be serialized to a negative value
. - Following on @nicolaslara comment about naming, I think it would be nice to keep it consistent and use
TotalEscrow
instead ofIBCOut
everywhere.
Another thing that maybe deserves some thinking: in case there are other applications or middleware (e.g. rate limiting) relying on this value, we should be careful that the value cannot be tampered with to fool those applications. For example: one possible situation that comes to mind is that a malicious actor could transfer native tokens directly to one of the escrow accounts for the denom, then the value that you calculate based on the balance doesn't exactly reflect the true amount of native tokens that have been transferred out of the chain.
existingAmount := k.bankKeeper.GetBalance(ctx, escrowAddress, token.Denom) | ||
|
||
// store the token about to be IBC'd Out here | ||
k.SetIBCOutDenomAmount(ctx, token.GetDenom(), existingAmount.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.
Maybe I'm missing something, but I think that this is setting the total amount escrowed for token.Denom
on a particular channel, not across all channels, right? In the example you mention in the description of the PR, I think this code would set the total amount in escrow in channel-1
, channel-2
or channel-3
depending on what channel the transfer is going out at any given time. For example, if I do a transfer on channel-1
it sets the total amount in escrow for channel-1
, but if afterwards I do a transfer on channel-2
then the previous vale would get overwritten with the total amount in escrow for channel-2
...
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.
aah i see, i went with the assumption that all channels share same escrowAccount. So here, i'll probably have to get all the channel's escrow account balance sum it up and then store in every transfer right?
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, exactly. Tokens can be transferred out of a chain to the same destination chain through different channels, so you'll have add up the the balances of potentially multiple escrow accounts.
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 great. I have addressed most of it as per this comment
|
||
// TotalNativeIBCOut returns the total native token that's IBC'd out. | ||
rpc TotalNativeIBCOut(QueryTotalNativeIBCOutRequest) returns (QueryTotalNativeIBCOutResponse) { | ||
option (google.api.http).get = "/ibc/apps/transfer/v1/total_native_ibc_out/{denom}"; |
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.
Maybe for naming consistency and in case in the future we want to have other queries related to denoms?
option (google.api.http).get = "/ibc/apps/transfer/v1/total_native_ibc_out/{denom}"; | |
option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{denom}/total_escrow"; |
And would this work fine if the denom contains slashes?
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.
Wondering also if the port_id
should be part of the URI...
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 the tracking is only happening for the transfer port, I think it makes sense to keep the port out of the uri or hard-code it to "transfer". Otherwise it seems like we could get this data for other channels, for which it just won't be available
590593f
to
f2aa9e0
Compare
Thanks you for your feedback @crodriguezvega. I have few questions/comments
Finalizing some tests. Want to make sure I have approach ACK
Addressed! here
Addressed! transfer, onRecvPacket, refund I originally thought we were only tracking total channel's IBC out instead of tracking channel's total escrow amount so it got me a little confused
Addressed! here
Working on this, I still am unsure if native denoms contains slash("/") or not. I imagine it to be a standalone denomination like uosmo, uatom etc
I didn't really think about this beforehand because i thought this security was already in place. If a random actor transfers $$ to channel-0 escrowAddress without proper transfer/refund, doesnot that violate current security gurantee as well |
Thanks very much for addressing the comment so quickly, @stackman27! ❤️ I will give the PR another review in the next couple of days. To reply to your last comment:
This is a good question. We both had different interpretations of the requirements :) I assumed that we wanted to track the total amount of native assets that are outside of the chain (and hence the total balance of the escrow accounts would give us), but your interpretation might as well be correct (track how many tokens have flowed out of the chain regardless of whether they came back). I think it would be good to hear @ValarDragon thoughts on what he originally wanted to track when he wrote the issue. I am also curious to hear @womensrights opinion on this.
Native denoms might contain slashes, so we should support this. We had an issue in the past that we didn't support denom with slashes correctly and it was not possible to transferred them.
I think at the moment this doesn't pose any risk. The only thing is that the tokens will stay in the escrow account, probably locked forever (or until governance decides to take them out) since there will be no counterparty vouchers created for them in any other chain. So basically the user will waste their tokens without causing any risk to the application. But I think my point still stands: applications monitoring the total amount escrowed, might be fooled if this value is tampered with. I will discuss this with the rest of the team. |
Hi @crodriguezvega had a chat with @nicolaslara about this and we are tracking "total amount of native assets that are outside of the chain (and hence the total balance of the escrow accounts would give us)". The new commit i pushed highlights those changes. Please lmk what you think about it |
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.
Thanks for continue working on this, @stackman27! I have left a few comments with for some things that I think still need some more work. Let me know what you think!
dc1a91c
to
794801b
Compare
@crodriguezvega @nicolaslara apologies for the delay, i was busy with some other work. However, I've addressed all the comments and this PR is ready for re-review :)) |
Hi @stackman27, thanks for addressing the comments! I've started reviewing again and there's a few small changes that I would like to do, but if you don't mind, I think it would help to move this PR faster if I push the changes directly to your branch. Is that ok? There's also a scenario that I think it's not covered yet. Imagine the following situation for tokens traveling from chain A (where the tokens are native) to chain C:
When the tokens are transferred from chain B to chain C, the I am writing some tests to confirm this, and if my reasoning is not wrong, then I will implement the necessary changes in the code to handle this scenario and I will also push them to your branch. I hope this is also ok. I will comment on the PR, as soon as I push the changes, so that you can also review. I think I will be able to push to your branch, in case I can, I will also comment here to ask you to give me permissions. Thanks a lot for all this work so far! |
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.
@stackman27 I made some changes (most notably adding checks to e2e test, genesis migration) so this PR is ready for another review now. There are a few items pending that I will work on in a follow up PR.
e2e/tests/transfer/base_test.go
Outdated
t.Run("escrow amount for native denom is updated", func(t *testing.T) { | ||
actualTotalEscrow, err := s.QueryTotalEscrowForDenom(ctx, chainA, chainADenom) | ||
s.Require().NoError(err) | ||
s.Require().Equal(math.ZeroInt(), actualTotalEscrow) |
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 zero because the tokens are back from chain B.
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 this be added as an in code comment?
|
||
for i := 0; i < 5; 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 refactored this to make it more table-test like.
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 we pull these changes out into a separate pr to main?
host "github.com/cosmos/ibc-go/v7/modules/core/24-host" | ||
) | ||
|
||
// NewGenesisState creates a new ibc-transfer GenesisState instance. | ||
func NewGenesisState(portID string, denomTraces Traces, params Params) *GenesisState { | ||
func NewGenesisState(portID string, denomTraces Traces, params Params, denomEscrows sdk.Coins) *GenesisState { |
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.
After discussing with @colin-axner, we can merge this API breaking change to main and in the backport revert it and add an extra constructor that takes the extra 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.
Thanks for all the followup fixes! First pass, will continue reviewing next week
modules/apps/transfer/types/keys.go
Outdated
KeyTotalEscrowPrefix = "totalEscrow" | ||
KeyDenomsPrefix = "denoms" |
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 I'd prefer it to be one key, I don't find the pattern used for channel/port id path's to be equivalent. In referenced case, channelEnds
is a key for the 04-channel keeper. Here the totalEscrow
and denoms
are referencing the same key, is there a scenario when we use totalEscrow
as a prefix for a different key? Otherwise I'd prefer it to just be totalEscrow/{denom}
|
||
for i := 0; i < 5; 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.
Can we pull these changes out into a separate pr to main?
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.
Almost done! Thanks for all the work! I only have a few files left to review (migrations). I left many suggestions/comments. Let me know if you have any questions
// get the existing total amount in escrow | ||
currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) | ||
newTotalEscrow := currentTotalEscrow.Add(token.Amount) | ||
|
||
// store the new total amount in escrow | ||
k.SetTotalEscrowForDenom(ctx, token.GetDenom(), newTotalEscrow) |
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.
Maybe we can update the comments to indicate why we are doing this as opposed to what the function means. Maybe something like:
// get the existing total amount in escrow | |
currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) | |
newTotalEscrow := currentTotalEscrow.Add(token.Amount) | |
// store the new total amount in escrow | |
k.SetTotalEscrowForDenom(ctx, token.GetDenom(), newTotalEscrow) | |
// track the total amount in escrow keyed by denomination to allow for efficient iteration | |
currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) | |
newTotalEscrow := currentTotalEscrow.Add(token.Amount) | |
k.SetTotalEscrowForDenom(ctx, token.GetDenom(), newTotalEscrow) |
I wonder if we should dump the attached issue into an ADR and then reference the ADR number. If there's an architectural change a few years from now that makes this extra storing unnecessary, it might help to have context of why we made this decision
@@ -149,16 +157,95 @@ func (suite *KeeperTestSuite) TestSendTransfer() { | |||
} | |||
} | |||
|
|||
func (suite *KeeperTestSuite) TestSendTransferSetsTotalEscrowAmountForSourceIBCToken() { |
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 this test doing that TestSendTransfer
isn't? I think this could be a test case of TestSendTransfer
which uses a helper function to generate an IBC token that is sent to the third chain (or different channel in this case)?
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, I could look into that, but to be honest in my opinion the TestSendTransfer
is a bit overloaded already (I was actually thinking of opening a PR with a suggestion to split it, since every time I have to come back to it I have a hard time understanding the tests cases)...
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.
Simplifying the tests makes sense to me, I agree these tests are hard to follow. My concern is just having 1 test per test case. Did you open an issue?
amount = math.NewInt(50) | ||
expEscrowAmount = math.NewInt(50) |
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's the reasoning for this test case?
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 I send back the full amount I wouldn't be sure of whether the subtraction happened correctly when the tokens are sent back or something else is wrong in the code, since there could be a situation where we have a bug in setting the escrow amount both on send and recv and the result would be the same as if it worked (the total amount in escrow at the end is 0).
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, this is true because recvIsSource
. Should we add a comment?
e2e/tests/transfer/base_test.go
Outdated
t.Run("escrow amount for native denom is updated", func(t *testing.T) { | ||
actualTotalEscrow, err := s.QueryTotalEscrowForDenom(ctx, chainA, chainADenom) | ||
s.Require().NoError(err) | ||
s.Require().Equal(math.ZeroInt(), actualTotalEscrow) |
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 this be added as an in code comment?
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.
Just flagging import grouping nits as I expect you would like to merge and complete the remainder with follow ups.
Awesome work on this! Thanks @stackman27 @crodriguezvega
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.
Thanks a lot for all your work, @stackman27. We will continue with #3368 to wrap up the feature.
…out (#3019) (cherry picked from commit fa9418f) # Conflicts: # .github/workflows/e2e-upgrade.yaml # e2e/go.mod # e2e/tests/transfer/base_test.go # e2e/tests/upgrades/upgrade_test.go # e2e/testsuite/grpc_query.go # modules/apps/transfer/types/genesis.pb.go # proto/ibc/applications/transfer/v1/genesis.proto # proto/ibc/applications/transfer/v1/transfer.proto
// actualTotalEscrow, err := s.QueryTotalEscrowForDenom(ctx, chainA, chainADenom) | ||
// s.Require().NoError(err) |
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.
Is there an issue for these? Why are they commented out?
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 is a proto deserialisation issue. See: this thread
|
||
for _, pathAndEscrowMount := range pathsAndEscrowAmounts { |
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.
typo? pathAndEscrowAmount
?
} | ||
|
||
genesis := suite.chainA.GetSimApp().TransferKeeper.ExportGenesis(suite.chainA.GetContext()) | ||
|
||
suite.Require().Equal(types.PortID, genesis.PortId) | ||
suite.Require().Equal(traces.Sort(), genesis.DenomTraces) | ||
suite.Require().Equal(escrows.Sort(), genesis.TotalEscrowed) | ||
|
||
suite.Require().NotPanics(func() { | ||
suite.chainA.GetSimApp().TransferKeeper.InitGenesis(suite.chainA.GetContext(), *genesis) |
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.
Is there a test for InitGenesis
?
true, | ||
}, | ||
{ | ||
"valid ibc denom treated as native denom", |
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 this test case checking? The total escrow is empty for this denom
true, // denom trace is not found, thus the denom is considered a native token | ||
}, | ||
{ | ||
"invalid ibc denom treated as valid native denom", |
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
@@ -149,16 +157,95 @@ func (suite *KeeperTestSuite) TestSendTransfer() { | |||
} | |||
} | |||
|
|||
func (suite *KeeperTestSuite) TestSendTransferSetsTotalEscrowAmountForSourceIBCToken() { |
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.
Simplifying the tests makes sense to me, I agree these tests are hard to follow. My concern is just having 1 test per test case. Did you open an issue?
"tries to unescrow more tokens than allowed", | ||
func() { | ||
amount = sdk.NewInt(1000000) | ||
expEscrowAmount = math.NewInt(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.
Oh this is only true because recvIsSource is set to true. Should we add a comment?
amount = math.NewInt(50) | ||
expEscrowAmount = math.NewInt(50) |
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, this is true because recvIsSource
. Should we add a comment?
@@ -252,6 +383,17 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() { | |||
|
|||
err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket(suite.chainB.GetContext(), packet, data) | |||
|
|||
// check total amount in escrow of received token denom on receiving chain | |||
var denom string | |||
var totalEscrow math.Int |
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 this needs separate declaration?
escrowAddress := types.GetEscrowAddress(path2.EndpointB.ChannelConfig.PortID, path2.EndpointB.ChannelID) | ||
coin := sdk.NewCoin(denomTrace.IBCDenom(), amount) | ||
suite.Require().NoError( | ||
banktestutil.FundAccount( |
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 tests to be more maintainable when we use transfer logic to setup the state we want to test from. This code seems to be manually inserting ibc tokens rather then sending them between channels. If a feature is added, it'd need to modify this setup logic even if this test doesn't test the feature
… out (backport #3019) (#3505) * feat: make a state entry to find total amount of native assets IBC'd out (#3019) (cherry picked from commit fa9418f) # Conflicts: # .github/workflows/e2e-upgrade.yaml # e2e/go.mod # e2e/tests/transfer/base_test.go # e2e/tests/upgrades/upgrade_test.go # e2e/testsuite/grpc_query.go # modules/apps/transfer/types/genesis.pb.go # proto/ibc/applications/transfer/v1/genesis.proto # proto/ibc/applications/transfer/v1/transfer.proto * fixing conflicts * linter fix * fix * remove api breaking change * address review comments * chore: remove unnecessary file --------- Co-authored-by: Sishir Giri <sishirg27@gmail.com> Co-authored-by: Carlos Rodriguez <carlos@interchain.io> Co-authored-by: Colin Axnér <25233464+colin-axner@users.noreply.github.com>
Description
Currently every channel has its own escrow bank account, which is a great design! So its very easy to see the amount of native assets IBC'd in or out on any IBC channel.
However, there are use cases that would like us to be able to reason about the total amount of a native asset that has been IBC'd out.
So if there are say three channels from Osmosis to Juno:
I would like to be find that its been 60 osmo IBC'd out, with no iteration.
closes: #2664
closes: #3187
Commit Message / Changelog Entry
Chore: Make a state entry to find total amount of native assets IBC'd out
see the guidelines for commit messages. (view raw markdown for examples)
Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.
docs/
) or specification (x/<module>/spec/
).godoc
comments.Files changed
in the Github PR explorer.Codecov Report
in the comment section below once CI passes.