-
Notifications
You must be signed in to change notification settings - Fork 690
imp: transfer total escrow follow ups #3558
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
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
a448960
transfer (total escrow): add documentation and migration docs (#3509)
crodriguezvega 9f99eee
transfer (total escrow): some more review comments (#3519)
crodriguezvega f72de1f
transfer (total escrow): add invariant for total escrow (#3506)
crodriguezvega 5b6e8b4
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega ad7e390
imp: do not store total escrow when amount is zero (#3585)
crodriguezvega 28eab1e
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega 1065a4d
add feature release for total escrow state entry to conditionally que…
crodriguezvega e5021cd
fix total escrow cli documentation
crodriguezvega 83ccf6f
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega 7583427
Apply suggestions from code review
crodriguezvega cdc3941
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega 997bf2b
address review comments
crodriguezvega a120641
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega e558a0f
docs: adr 011 total escrow state entry (#3641)
crodriguezvega 3830d78
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega 3382717
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega d5bee91
Merge branch 'main' into feat/total-escrow-state-entry
colin-axner d8e200d
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega decbade
Merge branch 'main' into feat/total-escrow-state-entry
crodriguezvega File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<!-- | ||
order: 9 | ||
--> | ||
|
||
# Client | ||
|
||
## CLI | ||
|
||
A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: | ||
|
||
### Query | ||
|
||
The `query` commands allow users to query `transfer` state. | ||
|
||
```shell | ||
simd query ibc-transfer --help | ||
``` | ||
|
||
#### `total-escrow` | ||
|
||
The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. | ||
|
||
```shell | ||
simd query ibc-transfer total-escrow [denom] [flags] | ||
``` | ||
|
||
Example: | ||
|
||
```shell | ||
simd query ibc-transfer total-escrow samoleans | ||
``` | ||
|
||
Example Output: | ||
|
||
```shell | ||
amount: "100" | ||
``` | ||
|
||
## gRPC | ||
|
||
A user can query the `transfer` module using gRPC endpoints. | ||
|
||
### `TotalEscrowForDenom` | ||
|
||
The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. | ||
|
||
```shell | ||
ibc.applications.transfer.v1.Query/TotalEscrowForDenom | ||
``` | ||
|
||
Example: | ||
|
||
```shell | ||
grpcurl -plaintext \ | ||
-d '{"denom":"samoleans"}' \ | ||
localhost:9090 \ | ||
ibc.applications.transfer.v1.Query/TotalEscrowForDenom | ||
``` | ||
|
||
Example output: | ||
|
||
```shell | ||
{ | ||
"amount": "100" | ||
} | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
docs/architecture/adr-011-transfer-total-escrow-state-entry.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# ADR 011: ICS-20 transfer state entry for total amount of tokens in escrow | ||
|
||
## Changelog | ||
|
||
* 2023-05-24: Initial draft | ||
|
||
## Status | ||
|
||
Accepted and applied in v7.1 of ibc-go | ||
|
||
## Context | ||
|
||
Every ICS-20 transfer channel has its own escrow bank account. This account is used to lock tokens that are transferred out of a chain that acts as the source of the tokens (i.e. when the tokens being transferred have not returned to the originating chain). This design makes it easy to query the balance of the escrow accounts and find out the total amount of tokens in escrow in a particular channel. However, there are use cases where it would be useful to determine the total escrowed amount of a given denomination across all channels where those tokens have been transferred out. | ||
|
||
For example: assuming that there are three channels between Cosmos Hub to Osmosis and 10 ATOM have been transferred from the Cosmos Hub to Osmosis on each of those channels, then we would like to know that 30 ATOM have been transferred (i.e. are locked in the escrow accounts of each channel) without needing to iterate over each escrow account to add up the balances of each. | ||
|
||
For a sample use case where this feature would be useful, please refer to Osmosis' rate limiting use case described in [#2664](https://github.com/cosmos/ibc-go/issues/2664). | ||
|
||
## Decision | ||
|
||
### State entry denom -> amount | ||
|
||
The total amount of tokens in escrow (across all transfer channels) for a given denomination is stored in state in an entry keyed by the denomination: `totalEscrowForDenom/{denom}`. | ||
|
||
### Panic if amount is negative | ||
|
||
If a negative amount is ever attempted to be stored, then the keeper function will panic: | ||
|
||
```go | ||
if coin.Amount.IsNegative() { | ||
panic(fmt.Sprintf("amount cannot be negative: %s", coin.Amount)) | ||
} | ||
``` | ||
|
||
### Delete state entry if amount is zero | ||
|
||
When setting the amount for a particular denomination, the value might be zero if all tokens that were transferred out of the chain have been transferred back. If this happens, then the state entry for this particular denomination will be deleted, since Cosmos SDK's `x/bank` module prunes any non-zero balances: | ||
|
||
```go | ||
if coin.Amount.IsZero() { | ||
store.Delete(key) // delete the key since Cosmos SDK x/bank module will prune any non-zero balances | ||
return | ||
} | ||
``` | ||
|
||
### Bundle escrow/unescrow with setting state entry | ||
|
||
Two new functions are implemented that bundle together the operations of escrowing/unescrowing and setting the total escrow amount in state, since these operations need to be executed together. | ||
|
||
For escrowing tokens: | ||
|
||
```go | ||
// escrowToken will send the given token from the provided sender to the escrow address. It will also | ||
// update the total escrowed amount by adding the escrowed token to the current total escrow. | ||
func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error { | ||
if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil { | ||
// failure is expected for insufficient balances | ||
return err | ||
} | ||
|
||
// track the total amount in escrow keyed by denomination to allow for efficient iteration | ||
currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) | ||
newTotalEscrow := currentTotalEscrow.Add(token) | ||
k.SetTotalEscrowForDenom(ctx, newTotalEscrow) | ||
|
||
return nil | ||
} | ||
``` | ||
|
||
For unescrowing tokens: | ||
|
||
```go | ||
// unescrowToken will send the given token from the escrow address to the provided receiver. It will also | ||
// update the total escrow by deducting the unescrowed token from the current total escrow. | ||
func (k Keeper) unescrowToken(ctx sdk.Context, escrowAddress, receiver sdk.AccAddress, token sdk.Coin) error { | ||
if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)); err != nil { | ||
// NOTE: this error is only expected to occur given an unexpected bug or a malicious | ||
// counterparty module. The bug may occur in bank or any part of the code that allows | ||
// the escrow address to be drained. A malicious counterparty module could drain the | ||
// escrow address by allowing more tokens to be sent back then were escrowed. | ||
return errorsmod.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") | ||
} | ||
|
||
// track the total amount in escrow keyed by denomination to allow for efficient iteration | ||
currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) | ||
newTotalEscrow := currentTotalEscrow.Sub(token) | ||
k.SetTotalEscrowForDenom(ctx, newTotalEscrow) | ||
|
||
return nil | ||
} | ||
``` | ||
|
||
When tokens need to be escrowed in `sendTransfer`, then `escrowToken` is called; when tokens need to be unescrowed on execution of the `OnRecvPacket`, `OnAcknowledgementPacket` or `OnTimeoutPacket` callbacks, then `unescrowToken` is called. | ||
|
||
### gRPC query endpoint and CLI to retrieve amount | ||
|
||
A gRPC query endpoint is added so that it is possible to retrieve the total amount for a given denomination: | ||
|
||
```proto | ||
// TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. | ||
rpc TotalEscrowForDenom(QueryTotalEscrowForDenomRequest) returns (QueryTotalEscrowForDenomResponse) { | ||
option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{denom=**}/total_escrow"; | ||
} | ||
|
||
// QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method. | ||
message QueryTotalEscrowForDenomRequest { | ||
string denom = 1; | ||
} | ||
|
||
// QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method. | ||
message QueryTotalEscrowForDenomResponse { | ||
cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false]; | ||
} | ||
``` | ||
|
||
And a CLI query is also available to retrieve the total amount via the command line: | ||
|
||
```shell | ||
query ibc-transfer total-escrow [denom] | ||
``` | ||
|
||
## Consequences | ||
|
||
### Positive | ||
|
||
* Possibility to retrieve the total amount of a particular denomination in escrow across all transfer channels without iteration. | ||
|
||
### Negative | ||
|
||
No notable consequences | ||
|
||
### Neutral | ||
|
||
* A new entry is added to state for every denomination that is transferred out of the chain. | ||
|
||
## References | ||
|
||
Issues: | ||
|
||
* [#2664](https://github.com/cosmos/ibc-go/issues/2664) | ||
|
||
PRs: | ||
|
||
* [#3019](https://github.com/cosmos/ibc-go/pull/3019) | ||
* [#3558](https://github.com/cosmos/ibc-go/pull/3558) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package keeper | ||
|
||
import ( | ||
"fmt" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" | ||
) | ||
|
||
// RegisterInvariants registers all transfer invariants | ||
func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) { | ||
ir.RegisterRoute(types.ModuleName, "total-escrow-per-denom", | ||
TotalEscrowPerDenomInvariants(k)) | ||
} | ||
|
||
// AllInvariants runs all invariants of the transfer module. | ||
func AllInvariants(k *Keeper) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
return TotalEscrowPerDenomInvariants(k)(ctx) | ||
} | ||
} | ||
|
||
// TotalEscrowPerDenomInvariants checks that the total amount escrowed for | ||
// each denom is not smaller than the amount stored in the state entry. | ||
func TotalEscrowPerDenomInvariants(k *Keeper) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
var actualTotalEscrowed sdk.Coins | ||
|
||
expectedTotalEscrowed := k.GetAllTotalEscrowed(ctx) | ||
|
||
portID := k.GetPort(ctx) | ||
transferChannels := k.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) | ||
for _, channel := range transferChannels { | ||
escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) | ||
escrowBalances := k.bankKeeper.GetAllBalances(ctx, escrowAddress) | ||
|
||
actualTotalEscrowed = actualTotalEscrowed.Add(escrowBalances...) | ||
} | ||
|
||
// the actual escrowed amount must be greater than or equal to the expected amount for all denominations | ||
if !actualTotalEscrowed.IsAllGTE(expectedTotalEscrowed) { | ||
return sdk.FormatInvariant( | ||
types.ModuleName, | ||
"total escrow per denom invariance", | ||
fmt.Sprintf("found denom(s) with total escrow amount lower than expected:\nactual total escrowed: %s\nexpected total escrowed: %s", actualTotalEscrowed, expectedTotalEscrowed)), true | ||
} | ||
|
||
return "", false | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 dropping a comment here to make sure this TODO is not forgotten