Skip to content
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(ucs01): denom metadata update packet #2284

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading