layer_climb_address/
address.rs

1pub mod cosmos;
2pub mod evm;
3
4use anyhow::{bail, Result};
5use cosmwasm_schema::cw_serde;
6use std::hash::Hash;
7
8use cosmos::CosmosAddr;
9use evm::EvmAddr;
10
11/// The canonical type used everywhere for addresses
12/// Display is implemented as plain string
13// cw_serde implements Serialize/Deserialize, Clone, Debug
14#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
15#[derive(Eq, PartialOrd, Ord, Hash)]
16#[cw_serde]
17pub enum Address {
18    Cosmos(CosmosAddr),
19    Evm(EvmAddr),
20}
21
22impl Address {
23    pub fn as_bytes(&self) -> Vec<u8> {
24        match self {
25            Address::Cosmos(addr_cosmos) => addr_cosmos.to_vec(),
26            Address::Evm(addr_evm) => addr_evm.as_bytes().to_vec(),
27        }
28    }
29
30    pub fn try_from_str(value: &str, addr_kind: &AddrKind) -> Result<Self> {
31        match addr_kind {
32            AddrKind::Cosmos { prefix } => {
33                CosmosAddr::new_string(value, Some(prefix)).map(Self::Cosmos)
34            }
35            AddrKind::Evm => EvmAddr::new_string(value).map(Self::Evm),
36        }
37    }
38
39    pub fn try_from_pub_key(
40        pub_key: &tendermint::PublicKey,
41        addr_kind: &AddrKind,
42    ) -> Result<Address> {
43        match addr_kind {
44            AddrKind::Cosmos { prefix } => {
45                CosmosAddr::new_pub_key(pub_key, prefix).map(Self::Cosmos)
46            }
47            AddrKind::Evm => EvmAddr::new_pub_key(pub_key).map(Self::Evm),
48        }
49    }
50}
51
52// the display impl ignores the kind
53impl std::fmt::Display for Address {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Self::Cosmos(addr_cosmos) => {
57                write!(f, "{addr_cosmos}")
58            }
59            Self::Evm(addr_evm) => {
60                write!(f, "{addr_evm}")
61            }
62        }
63    }
64}
65
66// TryFrom<Address>
67impl TryFrom<Address> for EvmAddr {
68    type Error = anyhow::Error;
69
70    fn try_from(addr: Address) -> Result<Self> {
71        match addr {
72            Address::Evm(addr) => Ok(addr),
73            Address::Cosmos(_) => bail!("Address is not EVM"),
74        }
75    }
76}
77
78impl TryFrom<Address> for CosmosAddr {
79    type Error = anyhow::Error;
80
81    fn try_from(addr: Address) -> Result<Self> {
82        match addr {
83            Address::Cosmos(addr) => Ok(addr),
84            Address::Evm(_) => bail!("Address is not Cosmos"),
85        }
86    }
87}
88
89impl TryFrom<Address> for alloy_primitives::Address {
90    type Error = anyhow::Error;
91
92    fn try_from(addr: Address) -> Result<Self> {
93        match addr {
94            Address::Evm(addr) => Ok(addr.into()),
95            Address::Cosmos(_) => bail!("Address is not EVM"),
96        }
97    }
98}
99
100impl TryFrom<Address> for cosmwasm_std::Addr {
101    type Error = anyhow::Error;
102
103    fn try_from(addr: Address) -> Result<Self> {
104        match addr {
105            Address::Cosmos(addr) => Ok(addr.into()),
106            Address::Evm(_) => bail!("Address is not Cosmos"),
107        }
108    }
109}
110
111// Into<Address>
112impl From<EvmAddr> for Address {
113    fn from(addr: EvmAddr) -> Self {
114        Self::Evm(addr)
115    }
116}
117
118impl From<CosmosAddr> for Address {
119    fn from(addr: CosmosAddr) -> Self {
120        Self::Cosmos(addr)
121    }
122}
123
124impl From<alloy_primitives::Address> for Address {
125    fn from(addr: alloy_primitives::Address) -> Self {
126        Self::Evm(addr.into())
127    }
128}
129
130impl TryFrom<cosmwasm_std::Addr> for Address {
131    type Error = anyhow::Error;
132
133    fn try_from(addr: cosmwasm_std::Addr) -> Result<Self> {
134        Ok(Self::Cosmos(addr.try_into()?))
135    }
136}
137
138#[cw_serde]
139#[derive(Eq, Hash)]
140pub enum AddrKind {
141    Cosmos { prefix: String },
142    Evm,
143}
144
145impl AddrKind {
146    pub fn parse_address(&self, value: &str) -> Result<Address> {
147        Address::try_from_str(value, self)
148    }
149
150    pub fn address_from_pub_key(&self, pub_key: &tendermint::PublicKey) -> Result<Address> {
151        Address::try_from_pub_key(pub_key, self)
152    }
153}
154
155#[cfg(test)]
156mod test {
157    use crate::cosmos::CosmosAddr;
158
159    use super::{Address, EvmAddr};
160
161    // TODO get addresses that are actually the same underlying public key
162
163    const TEST_COSMOS_STR: &str = "osmo1h5qke5tzc0fgz93wcxg8da2en3advfect0gh4a";
164    const TEST_COSMOS_PREFIX: &str = "osmo";
165    const TEST_EVM_STR: &str = "0xb794f5ea0ba39494ce839613fffba74279579268";
166
167    #[test]
168    fn test_basic_roundtrip_evm() {
169        let test_string = TEST_EVM_STR;
170        let addr_evm: EvmAddr = test_string.parse().unwrap();
171        let addr: Address = addr_evm.clone().into();
172
173        assert_eq!(addr.to_string(), test_string);
174
175        let addr_evm_2: EvmAddr = addr.clone().try_into().unwrap();
176        assert_eq!(addr_evm_2, addr_evm);
177
178        // serde should be as hex string
179        assert_eq!(
180            serde_json::to_string(&addr_evm).unwrap(),
181            format!("\"{test_string}\"")
182        );
183        assert_eq!(
184            serde_json::from_str::<EvmAddr>(&format!("\"{test_string}\"")).unwrap(),
185            addr_evm
186        );
187    }
188
189    #[test]
190    fn test_basic_roundtrip_cosmos() {
191        let cosmos_addr = CosmosAddr::new_string(TEST_COSMOS_STR, None).unwrap();
192        let addr: Address = cosmos_addr.clone().into();
193
194        assert_eq!(addr.to_string(), TEST_COSMOS_STR);
195        assert_eq!(cosmos_addr.prefix(), TEST_COSMOS_PREFIX);
196    }
197
198    #[test]
199    fn test_serde_roundtrip_cosmos() {
200        #[derive(serde::Serialize, serde::Deserialize)]
201        struct TestStruct {
202            addr: CosmosAddr,
203        }
204
205        let test_struct: TestStruct =
206            serde_json::from_str(&format!(r#"{{ "addr": "{TEST_COSMOS_STR}"}}"#)).unwrap();
207        let addr: Address = test_struct.addr.clone().into();
208
209        let test_struct_2 = TestStruct {
210            addr: addr.try_into().unwrap(),
211        };
212
213        assert_eq!(
214            serde_json::to_string(&test_struct_2).unwrap().trim(),
215            format!(r#"{{"addr":"{TEST_COSMOS_STR}"}}"#).trim()
216        );
217        assert_eq!(test_struct_2.addr.prefix(), TEST_COSMOS_PREFIX);
218    }
219
220    #[test]
221    fn test_convert_evm_to_cosmos() {
222        // let test_string = "0xb794f5ea0ba39494ce839613fffba74279579268";
223        // let addr_bytes:EvmAddr= test_string.try_into().unwrap();
224        // let addr_string:AddrString = (&addr_bytes).into();
225        // let addr_string_cosmos = addr_string.convert_into_cosmos("osmo".to_string()).unwrap();
226        // assert_eq!(addr_string_cosmos.to_string(), "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv");
227    }
228
229    #[test]
230    fn test_convert_cosmos_to_evm() {
231        // let test_string = "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv";
232        // let account_id:AccountId = test_string.parse().unwrap();
233        // let addr_string:AddrString = (&account_id).try_into().unwrap();
234        // let addr_string_evm = addr_string.convert_into_evm().unwrap();
235        // assert_eq!(addr_string_evm.to_string(), "0xb794f5ea0ba39494ce839613fffba74279579268");
236    }
237}