penumbra_shielded_pool/
ics20_withdrawal.rsuse ibc_types::core::{channel::ChannelId, channel::PortId, client::Height as IbcHeight};
use penumbra_asset::{
asset::{self, Metadata},
Balance, Value,
};
use penumbra_keys::Address;
use penumbra_num::Amount;
use penumbra_proto::{
penumbra::core::component::ibc::v1::{self as pb, FungibleTokenPacketData},
DomainType,
};
use penumbra_txhash::{EffectHash, EffectingData};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[cfg(feature = "component")]
use penumbra_ibc::component::packet::{IBCPacket, Unchecked};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "pb::Ics20Withdrawal", into = "pb::Ics20Withdrawal")]
pub struct Ics20Withdrawal {
pub amount: Amount,
pub denom: asset::Metadata,
pub destination_chain_address: String,
pub return_address: Address,
pub timeout_height: IbcHeight,
pub timeout_time: u64,
pub source_channel: ChannelId,
pub use_compat_address: bool,
}
#[cfg(feature = "component")]
impl From<Ics20Withdrawal> for IBCPacket<Unchecked> {
fn from(withdrawal: Ics20Withdrawal) -> Self {
Self::new(
PortId::transfer(),
withdrawal.source_channel.clone(),
withdrawal.timeout_height,
withdrawal.timeout_time,
withdrawal.packet_data(),
)
}
}
impl Ics20Withdrawal {
pub fn value(&self) -> Value {
Value {
amount: self.amount,
asset_id: self.denom.id(),
}
}
pub fn balance(&self) -> Balance {
-Balance::from(self.value())
}
pub fn packet_data(&self) -> Vec<u8> {
let ftpd: FungibleTokenPacketData = self.clone().into();
serde_json::to_vec(&ftpd).expect("can serialize FungibleTokenPacketData as JSON")
}
pub fn validate(&self) -> anyhow::Result<()> {
if self.timeout_time == 0 {
anyhow::bail!("timeout time must be non-zero");
}
if self.timeout_time % 60_000_000_000 != 0 {
anyhow::bail!(
"withdrawal timeout timestamp {} is not rounded to one minute",
self.timeout_time
);
}
Ok(())
}
}
impl EffectingData for Ics20Withdrawal {
fn effect_hash(&self) -> EffectHash {
EffectHash::from_proto_effecting_data(&self.to_proto())
}
}
impl DomainType for Ics20Withdrawal {
type Proto = pb::Ics20Withdrawal;
}
impl From<Ics20Withdrawal> for pb::Ics20Withdrawal {
fn from(w: Ics20Withdrawal) -> Self {
pb::Ics20Withdrawal {
amount: Some(w.amount.into()),
denom: Some(w.denom.base_denom().into()),
destination_chain_address: w.destination_chain_address,
return_address: Some(w.return_address.into()),
timeout_height: Some(w.timeout_height.into()),
timeout_time: w.timeout_time,
source_channel: w.source_channel.to_string(),
use_compat_address: w.use_compat_address,
}
}
}
impl TryFrom<pb::Ics20Withdrawal> for Ics20Withdrawal {
type Error = anyhow::Error;
fn try_from(s: pb::Ics20Withdrawal) -> Result<Self, Self::Error> {
Ok(Self {
amount: s
.amount
.ok_or_else(|| anyhow::anyhow!("missing amount"))?
.try_into()?,
denom: Metadata::default_for(
&s.denom
.ok_or_else(|| anyhow::anyhow!("missing denom metadata"))?
.try_into()?,
)
.ok_or_else(|| anyhow::anyhow!("could not generate default denom metadata"))?,
destination_chain_address: s.destination_chain_address,
return_address: s
.return_address
.ok_or_else(|| anyhow::anyhow!("missing sender"))?
.try_into()?,
timeout_height: s
.timeout_height
.ok_or_else(|| anyhow::anyhow!("missing timeout height"))?
.try_into()?,
timeout_time: s.timeout_time,
source_channel: ChannelId::from_str(&s.source_channel)?,
use_compat_address: s.use_compat_address,
})
}
}
impl From<Ics20Withdrawal> for pb::FungibleTokenPacketData {
fn from(w: Ics20Withdrawal) -> Self {
let return_address = match w.use_compat_address {
true => w.return_address.compat_encoding(),
false => w.return_address.to_string(),
};
pb::FungibleTokenPacketData {
amount: w.value().amount.to_string(),
denom: w.denom.to_string(),
receiver: w.destination_chain_address,
sender: return_address,
memo: "".to_string(),
}
}
}