layer_climb_address/
address.rs

1mod cosmos;
2mod evm;
3
4use anyhow::{bail, Result};
5use cosmwasm_schema::cw_serde;
6use std::hash::Hash;
7
8pub use cosmos::CosmosAddr;
9pub use 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 super::{Address, CosmosAddr, EvmAddr};
158
159    // TODO get addresses that are actually the same underlying public key
160
161    const TEST_COSMOS_STR: &str = "osmo1h5qke5tzc0fgz93wcxg8da2en3advfect0gh4a";
162    const TEST_COSMOS_PREFIX: &str = "osmo";
163    const TEST_EVM_STR: &str = "0xb794f5ea0ba39494ce839613fffba74279579268";
164
165    #[test]
166    fn test_basic_roundtrip_evm() {
167        let test_string = TEST_EVM_STR;
168        let addr_evm: EvmAddr = test_string.parse().unwrap();
169        let addr: Address = addr_evm.clone().into();
170
171        assert_eq!(addr.to_string(), test_string);
172
173        let addr_evm_2: EvmAddr = addr.clone().try_into().unwrap();
174        assert_eq!(addr_evm_2, addr_evm);
175
176        // serde should be as hex string
177        assert_eq!(
178            serde_json::to_string(&addr_evm).unwrap(),
179            format!("\"{test_string}\"")
180        );
181        assert_eq!(
182            serde_json::from_str::<EvmAddr>(&format!("\"{test_string}\"")).unwrap(),
183            addr_evm
184        );
185    }
186
187    #[test]
188    fn test_basic_roundtrip_cosmos() {
189        let cosmos_addr = CosmosAddr::new_string(TEST_COSMOS_STR, None).unwrap();
190        let addr: Address = cosmos_addr.clone().into();
191
192        assert_eq!(addr.to_string(), TEST_COSMOS_STR);
193        assert_eq!(cosmos_addr.prefix(), TEST_COSMOS_PREFIX);
194    }
195
196    #[test]
197    fn test_serde_roundtrip_cosmos() {
198        #[derive(serde::Serialize, serde::Deserialize)]
199        struct TestStruct {
200            addr: CosmosAddr,
201        }
202
203        let test_struct: TestStruct =
204            serde_json::from_str(&format!(r#"{{ "addr": "{TEST_COSMOS_STR}"}}"#)).unwrap();
205        let addr: Address = test_struct.addr.clone().into();
206
207        let test_struct_2 = TestStruct {
208            addr: addr.try_into().unwrap(),
209        };
210
211        assert_eq!(
212            serde_json::to_string(&test_struct_2).unwrap().trim(),
213            format!(r#"{{"addr":"{TEST_COSMOS_STR}"}}"#).trim()
214        );
215        assert_eq!(test_struct_2.addr.prefix(), TEST_COSMOS_PREFIX);
216    }
217
218    #[test]
219    fn test_convert_evm_to_cosmos() {
220        // let test_string = "0xb794f5ea0ba39494ce839613fffba74279579268";
221        // let addr_bytes:EvmAddr= test_string.try_into().unwrap();
222        // let addr_string:AddrString = (&addr_bytes).into();
223        // let addr_string_cosmos = addr_string.convert_into_cosmos("osmo".to_string()).unwrap();
224        // assert_eq!(addr_string_cosmos.to_string(), "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv");
225    }
226
227    #[test]
228    fn test_convert_cosmos_to_evm() {
229        // let test_string = "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv";
230        // let account_id:AccountId = test_string.parse().unwrap();
231        // let addr_string:AddrString = (&account_id).try_into().unwrap();
232        // let addr_string_evm = addr_string.convert_into_evm().unwrap();
233        // assert_eq!(addr_string_evm.to_string(), "0xb794f5ea0ba39494ce839613fffba74279579268");
234    }
235}