-
Notifications
You must be signed in to change notification settings - Fork 76
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
feat(sequencer): implement ICS20 withdrawals #609
Conversation
} | ||
} | ||
|
||
impl From<raw::IbcHeight> for IbcHeight { |
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.
We have a Protobuf
trait for conversions on foreign types. Can you impl it instead of From
? Use type Error = ::std::convert::Infallible
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 implemented From
because the conversion never fails, so I left this for now, but I can change to a from_raw
or something instead?
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.
Your logic for implementing From
is sound. The reason I don't like From
here is consistency. The rest of the crate is has from_raw
and try_from_raw
so readers will expect to find it.
I intend to expand the Protobuf
trait to all our proto types to document all (raw, native)
pairs.
} | ||
} | ||
|
||
impl Error for Ics20WithdrawalError {} |
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 impl would swallow all sources. This should implement the fn source(&self) -> Option<&dyn std::error::Error>
that returns the sources for those variants that are actually wrapping errors.
You can avoid some boiler plate by using thiserror
which I will probably use to migrate all errors over.
I would also like to make use of an extra indirection like below to hide the actual errors behind an opaque facade (see how the top level is pub but the variants are priv):
pub struct Ics20WithdrawalError {
kind: Ics20WithdrawalErrorKind,
}
enum Ics20WithdrawalErrorKind {
TheOtherVariants,
}
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.
Note on this: in #646 I have reimplemented all errors in terms of thiserror using something like this:
#[derive(thiserror::Error)]
#[error(transparent)]
struct PublicError(PrivateError);
#[derive(thiserror::Error)]
enum PrivateError{}
@@ -563,6 +580,7 @@ enum ActionErrorKind { | |||
SudoAddressChange(SudoAddressChangeActionError), | |||
Mint(MintActionError), | |||
Ibc(Box<dyn Error + Send + Sync>), | |||
Ics20Withdrawal(Box<dyn Error + Send + Sync>), |
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 is not necessary, you can toss the actual error type in here. We used the boxed error trait object because I don't want anyhow in here. :)
pub source_channel: ChannelId, | ||
} | ||
|
||
impl From<Ics20Withdrawal> for FungibleTokenPacketData { |
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 impl this as an inherent method on Ics20Withdrawal
? fn to_fungible_token_packet_data(&self)
crates/astria-proto/Cargo.toml
Outdated
penumbra-ibc = { workspace = true } | ||
penumbra-proto = { workspace = true } | ||
prost = { workspace = true } | ||
prost-types = { workspace = true } | ||
serde = { workspace = true, optional = true } | ||
serde_json = { workspace = true } |
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.
Note below: please remove
.await | ||
.context("failed getting `from` account balance for transfer")?; | ||
ensure!( | ||
from_transfer_balance > self.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.
>
or >=
?
); | ||
|
||
state | ||
.put_account_balance(from, self.denom.id(), from_transfer_balance - self.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 use checked math 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.
the "insufficient funds for transfer"
check above covers this
.put_ibc_channel_balance( | ||
&self.source_channel, | ||
self.denom.id(), | ||
channel_balance + self.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.
checked math
.context("failed to get channel balance")?; | ||
|
||
state | ||
.put_ibc_channel_balance( |
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 we need to worry about making the entirety of these steps a transaction similar to how SQL transactions are atomic (if one fails, all fail or are rolled back)?
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.
mentioned this before, but check deliver_tx
, it starts an atomic tx for the db that's only committed if the tx succeeds
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.
OK, cool. I have to go over how new entries in the DB are created to understand the flow again.
}; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Ics20Withdrawal { |
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 increasingly lean toward abstaining from making all of these private pub by default.
Can you lock it down?
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 mean make the fields private? yeah i changed it, made getters instead
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, that's what I meant! Sorry, I meant to write public
instead of private
.
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.
} | ||
} | ||
|
||
impl From<IbcHeight> for raw::IbcHeight { |
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.
Remove so there is only a single way to do this.
.put_account_balance( | ||
from, | ||
self.denom().id(), | ||
from_transfer_balance - self.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.
You can do checked math && the check in one go:
let Some(new_balance) = from_transfer_balance.checked_sub(self.amount()) else {
bail!("insufficient funds for transfer");
}
This addresses my ask for checked math while also defending against accidental refactor.
.context("failed to get channel balance")?; | ||
|
||
state | ||
.put_ibc_channel_balance( |
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.
OK, cool. I have to go over how new entries in the DB are created to understand the flow again.
amount: Some(self.amount.into()), | ||
denom: self.denom.to_string(), | ||
destination_chain_address: self.destination_chain_address, | ||
return_address: self.return_address.0.to_vec(), |
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.
return_address: self.return_address.0.to_vec(), | |
return_address: self.return_address.to_vec(), |
Address
has a to_vec
method
} | ||
} | ||
|
||
impl crate::native::Protobuf for IbcHeight { |
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 all the fields of raw::IbcHeight
are Copy
you don't have to impl all the trait methods. The trait definition provides default implementations for try_from_raw
(in terms of try_from_raw_ref
) and into_raw
(in terms of to_raw)
. So if you impl to_raw
and try_from_raw_ref
you get the other two for free.
try_from_raw
and into_raw
can be implemented by non-copy types to avoid allocations.
|
||
#[allow(clippy::module_name_repetitions)] | ||
#[derive(Debug)] | ||
pub enum Ics20WithdrawalError { |
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.
See how this is done in PR #646: derive thiserror::Error
for this and split into a public wrapper type that has #[error(transparent)]
and a private error type that has all the enums.
@@ -0,0 +1,140 @@ | |||
use anyhow::{ | |||
ensure, | |||
Context, |
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.
Context, | |
Context as _, |
@@ -75,3 +76,18 @@ message MintAction { | |||
bytes to = 1; | |||
astria.primitive.v1.Uint128 amount = 2; | |||
} | |||
|
|||
message Ics20Withdrawal { |
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 add comments to this type, what IBC spec it matches, maybe with links. and how this is used?
Please also cover the fields and their constraints, i.e. the expected format of the strings, the number of bytes for things like the return address, units of the time.
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.
yep, added more description, and added a longer comment + spec link to the rust type
string destination_chain_address = 3; | ||
bytes return_address = 4; | ||
IbcHeight timeout_height = 5; | ||
uint64 timeout_time = 6; |
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.
units? greater 0? Also is this used as a time ("timeout at time T") or as a duration ("timeout in duration D seconds")? This here is probably used a duration, so I would name it thus.
I have seen in other projects that that lead to confusion, so IMO it's worth bikeshedding this naming.
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.
copied the comments on the rust type here - this is a unix timestamp
astria.primitive.v1.Uint128 amount = 1; | ||
string denom = 2; | ||
string destination_chain_address = 3; | ||
bytes return_address = 4; |
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.
Our address type? 20 bytes?
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 it is, in the rust Ics20Withdrawal
type this is an Address
Summary
Counterpart to #579 which supports incoming ICS20 transfers, this PR implements outgoing ICS20 transfers.
Background
required for IBC token transfer support.
See https://github.com/cosmos/ibc/blob/fe150abb629de5c1a598e8c7896a7568f2083681/spec/app/ics-020-fungible-token-transfer/README.md
sendFungibleTokens
for spec.Changes
Ics20Withdrawal
proto and native type which is an action (maybe rename toIcs20WithdrawalAction
?)Testing
not really - same issue as #579
Related Issues
closes #597