Skip to content

Commit

Permalink
feat(ucs01): denom metadata update packet
Browse files Browse the repository at this point in the history
  • Loading branch information
hussein-aitlahcen committed Jun 26, 2024
1 parent ee5b223 commit 9271c23
Show file tree
Hide file tree
Showing 12 changed files with 888 additions and 350 deletions.
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cosmwasm/ucs01-relay-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ cosmwasm-schema = { version = "2.0.0" }
cosmwasm-std = { version = "2.0.0", features = ["stargate"] }
ethabi = { workspace = true }
go-parse-duration = { workspace = true }
num-derive = { version = "0.4" }
num-traits = { version = "0.2" }
prost = { workspace = true }
protos = { workspace = true }
serde = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions cosmwasm/ucs01-relay-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[macro_use]
extern crate num_derive;

pub mod middleware;
pub mod protocol;
pub mod types;
55 changes: 36 additions & 19 deletions cosmwasm/ucs01-relay-api/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use unionlabs::encoding::{self, Decode, DecodeErrorOf, Encode};

use crate::{
middleware::{InFlightPfmPacket, Memo, PacketForward},
types::{EncodingError, GenericAck, TransferPacket, TransferPacketCommon, TransferToken},
types::{
EncodingError, GenericAck, PacketTag, TransferPacket, TransferPacketCommon, TransferToken,
},
};

// https://github.com/cosmos/ibc-go/blob/8218aeeef79d556852ec62a773f2bc1a013529d4/modules/apps/transfer/types/keys.go#L12
Expand Down Expand Up @@ -49,7 +51,7 @@ pub enum ProtocolError {
Unauthorized,
}

pub type PacketExtensionOf<T> = <<T as TransferProtocol>::Packet as TransferPacket>::Extension;
pub type PacketExtensionOf<T> = <<T as TransferProtocol>::TokenPacket as TransferPacket>::Extension;

pub struct TransferInput {
pub current_time: Timestamp,
Expand Down Expand Up @@ -88,7 +90,7 @@ pub trait TransferProtocol {
const ORDERING: IbcOrder;
const RECEIVE_REPLY_ID: u64;

type Packet: Decode<Self::Encoding> + Encode<Self::Encoding> + TransferPacket;
type TokenPacket: Decode<Self::Encoding> + Encode<Self::Encoding> + TransferPacket;

type Ack: Decode<Self::Encoding> + Encode<Self::Encoding> + Into<GenericAck>;

Expand All @@ -99,7 +101,7 @@ pub trait TransferProtocol {
type Error: Debug
+ From<ProtocolError>
+ From<EncodingError>
+ From<DecodeErrorOf<Self::Encoding, Self::Packet>>
+ From<DecodeErrorOf<Self::Encoding, Self::TokenPacket>>
+ From<DecodeErrorOf<Self::Encoding, Self::Ack>>;

fn channel_endpoint(&self) -> &IbcEndpoint;
Expand All @@ -112,7 +114,7 @@ pub trait TransferProtocol {
fn common_to_protocol_packet(
&self,
packet: TransferPacketCommon<PacketExtensionOf<Self>>,
) -> Result<Self::Packet, EncodingError>;
) -> Result<Self::TokenPacket, EncodingError>;

fn ack_success() -> Self::Ack;

Expand All @@ -125,22 +127,22 @@ pub trait TransferProtocol {

fn send_tokens(
&mut self,
sender: &AddrOf<Self::Packet>,
receiver: &AddrOf<Self::Packet>,
sender: &AddrOf<Self::TokenPacket>,
receiver: &AddrOf<Self::TokenPacket>,
tokens: Vec<TransferToken>,
) -> Result<Vec<CosmosMsg<Self::CustomMsg>>, Self::Error>;

fn send_tokens_success(
&mut self,
sender: &AddrOf<Self::Packet>,
receiver: &AddrOf<Self::Packet>,
sender: &AddrOf<Self::TokenPacket>,
receiver: &AddrOf<Self::TokenPacket>,
tokens: Vec<TransferToken>,
) -> Result<Vec<CosmosMsg<Self::CustomMsg>>, Self::Error>;

fn send_tokens_failure(
&mut self,
sender: &AddrOf<Self::Packet>,
receiver: &AddrOf<Self::Packet>,
sender: &AddrOf<Self::TokenPacket>,
receiver: &AddrOf<Self::TokenPacket>,
tokens: Vec<TransferToken>,
) -> Result<Vec<CosmosMsg<Self::CustomMsg>>, Self::Error>;

Expand Down Expand Up @@ -194,15 +196,14 @@ pub trait TransferProtocol {
]))
}

fn send_ack(
fn send_ack_relay(
&mut self,
ibc_packet: IbcPacketAckMsg,
) -> Result<IbcBasicResponse<Self::CustomMsg>, Self::Error> {
// NOTE: `ibc_packet.original_packet` here refers to the packet that is being acknowledged, not the
// original packet in the pfm chain. At this point in the ack handling process, we don't even know
// if this is a pfm message anyways.

let packet = Self::Packet::decode(ibc_packet.original_packet.data.as_slice())?;
let packet = Self::TokenPacket::decode(ibc_packet.original_packet.data.as_slice())?;

// https://github.com/cosmos/ibc-go/blob/5ca37ef6e56a98683cf2b3b1570619dc9b322977/modules/apps/transfer/ibc_module.go#L261
let ack: GenericAck = Self::Ack::decode(ibc_packet.acknowledgement.data.as_slice())?.into();
Expand Down Expand Up @@ -274,11 +275,27 @@ pub trait TransferProtocol {
.add_messages(ack_msgs))
}

fn send_ack_metadata(
&mut self,
ibc_packet: IbcPacketAckMsg,
) -> Result<IbcBasicResponse<Self::CustomMsg>, Self::Error> {
}

fn send_ack(
&mut self,
ibc_packet: IbcPacketAckMsg,
) -> Result<IbcBasicResponse<Self::CustomMsg>, Self::Error> {
match PacketTag::decode(ibc_packet.original_packet.data.as_slice())? {
PacketTag::Relay => self.send_ack_relay(ibc_packet),
PacketTag::Metadata => todo!(),
}
}

fn send_timeout(
&mut self,
ibc_packet: IbcPacket,
) -> Result<IbcBasicResponse<Self::CustomMsg>, Self::Error> {
let packet = Self::Packet::decode(ibc_packet.clone().data.as_slice())?;
let packet = Self::TokenPacket::decode(ibc_packet.clone().data.as_slice())?;
// same branch as failure ack
let memo = packet.extension().to_string();
let ack = GenericAck::Err(ACK_ERR_TIMEOUT_MSG.to_vec());
Expand Down Expand Up @@ -317,13 +334,13 @@ pub trait TransferProtocol {
#[allow(clippy::type_complexity)]
fn receive_transfer(
&mut self,
receiver: &AddrOf<Self::Packet>,
receiver: &AddrOf<Self::TokenPacket>,
tokens: Vec<TransferToken>,
) -> Result<(Vec<TransferToken>, Vec<CosmosMsg<Self::CustomMsg>>), Self::Error>;

fn receive(&mut self, original_packet: IbcPacket) -> IbcReceiveResponse<Self::CustomMsg> {
let handle = || -> Result<IbcReceiveResponse<Self::CustomMsg>, Self::Error> {
let packet = Self::Packet::decode(original_packet.data.as_slice())?;
let packet = Self::TokenPacket::decode(original_packet.data.as_slice())?;

let memo = packet.extension().to_string();

Expand Down Expand Up @@ -389,7 +406,7 @@ pub trait TransferProtocol {
/// Extracts and processes the forward information from a messages memo. Initiates the forward transfer process.
fn packet_forward(
&mut self,
packet: Self::Packet,
packet: Self::TokenPacket,
original_packet: IbcPacket,
forward: PacketForward,
processed: bool,
Expand Down Expand Up @@ -419,7 +436,7 @@ pub trait TransferProtocol {
ack: GenericAck,
ibc_packet: IbcPacket,
refund_info: InFlightPfmPacket,
sender: &AddrOf<Self::Packet>,
sender: &AddrOf<Self::TokenPacket>,
tokens: Vec<TransferToken>,
) -> Result<(Vec<CosmosMsg<Self::CustomMsg>>, Vec<Attribute>), Self::Error>;

Expand Down
133 changes: 132 additions & 1 deletion cosmwasm/ucs01-relay-api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub enum EncodingError {
InvalidSender { value: String, err: StdError },
#[error("Invalid receiver address: receiver: `{value}`, err: {err}")]
InvalidReceiver { value: String, err: StdError },
#[error("Invalid packet tag: {value}")]
InvalidPacketTag { value: u8 },
}

/// A json encoding specific to [`serde_json_wasm`] as it does not use the same error types as `serde_json`.
Expand Down Expand Up @@ -122,6 +124,11 @@ impl Ucs01TransferPacket {
impl Encode<encoding::EthAbi> for Ucs01TransferPacket {
fn encode(self) -> Vec<u8> {
ethabi::encode(&[
Token::Uint(
num_traits::ToPrimitive::to_u8(&PacketTag::Relay)
.expect("impossible")
.into(),
),
Token::Bytes(self.sender.into()),
Token::Bytes(self.receiver.into()),
Token::Array(
Expand All @@ -146,6 +153,8 @@ impl Decode<encoding::EthAbi> for Ucs01TransferPacket {
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let encoded_packet = ethabi::decode(
&[
// The packet tag
ParamType::Uint(8),
ParamType::Bytes,
ParamType::Bytes,
ParamType::Array(Box::new(ParamType::Tuple(vec![
Expand All @@ -163,7 +172,8 @@ impl Decode<encoding::EthAbi> for Ucs01TransferPacket {
// NOTE: at this point, it is technically impossible to have any other branch than the one we
// match unless there is a bug in the underlying `ethabi` crate
match &encoded_packet[..] {
[Token::Bytes(sender), Token::Bytes(receiver), Token::Array(tokens), Token::String(memo)] => {
// Discard the tag
[Token::Uint(_), Token::Bytes(sender), Token::Bytes(receiver), Token::Array(tokens), Token::String(memo)] => {
Ok(Ucs01TransferPacket {
sender: sender.clone().into(),
receiver: receiver.clone().into(),
Expand Down Expand Up @@ -372,6 +382,127 @@ impl<'a> From<(&'a str, &IbcEndpoint)> for DenomOrigin<'a> {
}
}

pub struct Ucs01Metadata {
pub denom: String,
pub name: String,
pub symbol: String,
pub decimals: u8,
}

pub struct Ucs01MetadataPacket {
pub tokens_metadata: Vec<Ucs01Metadata>,
}

impl Encode<encoding::EthAbi> for Ucs01MetadataPacket {
fn encode(self) -> Vec<u8> {
ethabi::encode(&[
Token::Uint(
num_traits::ToPrimitive::to_u8(&PacketTag::Metadata)
.expect("impossible")
.into(),
),
Token::Array(
self.tokens_metadata
.into_iter()
.map(
|Ucs01Metadata {
denom,
name,
symbol,
decimals,
}| {
Token::Tuple(vec![
Token::String(denom),
Token::String(name),
Token::String(symbol),
Token::Uint(decimals.into()),
])
},
)
.collect(),
),
])
}
}

impl Decode<encoding::EthAbi> for Ucs01MetadataPacket {
type Error = EncodingError;

fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let encoded_packet = ethabi::decode(
&[
ParamType::Uint(8),
ParamType::Array(Box::new(ParamType::Tuple(vec![
ParamType::String,
ParamType::String,
ParamType::String,
ParamType::String,
ParamType::Bytes,
ParamType::Uint(8),
]))),
],
bytes,
)
.map_err(|err| EncodingError::InvalidUCS01PacketEncoding {
value: bytes.to_vec(),
err,
})?;
match &encoded_packet[..] {
[Token::Uint(_), Token::Array(tokens_metadata)] => {
Ok(Ucs01MetadataPacket {
tokens_metadata: tokens_metadata
.iter()
.map(|token_metadata| {
if let Token::Tuple(encoded_token_metadata_inner) = token_metadata {
match &encoded_token_metadata_inner[..] {
[Token::String(denom), Token::String(name), Token::String(symbol), Token::Uint(decimals)] => {
Ucs01Metadata {
denom: denom.clone(),
name: name.clone(),
symbol: symbol.clone(),
decimals: decimals.as_u128() as u8,
}
}
_ => unreachable!(),
}
} else {
unreachable!()
}
})
.collect(),
})
}
_ => unreachable!(),
}
}
}

#[derive(FromPrimitive, ToPrimitive)]
pub enum PacketTag {
Relay = 0,
Metadata = 1,
}

impl Decode<encoding::EthAbi> for PacketTag {
type Error = EncodingError;

fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
match ethabi::decode(&[ParamType::Uint(8)], bytes) {
Ok(tokens) => match &tokens[..] {
&[Token::Uint(tag)] => num_traits::FromPrimitive::from_u8(tag.as_u128() as u8)
.ok_or(EncodingError::InvalidPacketTag {
value: tag.as_u128() as u8,
}),
_ => unreachable!(),
},
Err(err) => Err(EncodingError::InvalidUCS01PacketEncoding {
value: bytes.to_vec(),
err,
}),
}
}
}

#[cfg(test)]
mod tests {
use cosmwasm_std::{IbcEndpoint, Uint128};
Expand Down
Loading

0 comments on commit 9271c23

Please sign in to comment.