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, bincode::Encode, bincode::Decode)]
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_str(value, Some(prefix)).map(Self::Cosmos)
34            }
35            AddrKind::Evm => EvmAddr::new_str(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
138impl TryFrom<&cosmwasm_std::Addr> for Address {
139    type Error = anyhow::Error;
140
141    fn try_from(addr: &cosmwasm_std::Addr) -> Result<Self> {
142        Ok(Self::Cosmos(addr.try_into()?))
143    }
144}
145
146#[cw_serde]
147#[derive(Eq, Hash)]
148pub enum AddrKind {
149    Cosmos { prefix: String },
150    Evm,
151}
152
153impl AddrKind {
154    pub fn parse_address(&self, value: &str) -> Result<Address> {
155        Address::try_from_str(value, self)
156    }
157
158    pub fn address_from_pub_key(&self, pub_key: &tendermint::PublicKey) -> Result<Address> {
159        Address::try_from_pub_key(pub_key, self)
160    }
161}
162
163#[cfg(test)]
164mod test {
165    use super::{Address, CosmosAddr, EvmAddr};
166
167    // TODO get addresses that are actually the same underlying public key
168
169    const TEST_COSMOS_STR: &str = "osmo1h5qke5tzc0fgz93wcxg8da2en3advfect0gh4a";
170    const TEST_COSMOS_PREFIX: &str = "osmo";
171    const TEST_EVM_STR: &str = "0xb794f5ea0ba39494ce839613fffba74279579268";
172
173    #[test]
174    fn test_basic_roundtrip_evm() {
175        let test_string = TEST_EVM_STR;
176        let addr_evm: EvmAddr = test_string.parse().unwrap();
177        let addr: Address = addr_evm.clone().into();
178
179        assert_eq!(addr.to_string(), test_string);
180
181        let addr_evm_2: EvmAddr = addr.clone().try_into().unwrap();
182        assert_eq!(addr_evm_2, addr_evm);
183
184        // serde should be as hex string
185        assert_eq!(
186            serde_json::to_string(&addr_evm).unwrap(),
187            format!("\"{test_string}\"")
188        );
189        assert_eq!(
190            serde_json::from_str::<EvmAddr>(&format!("\"{test_string}\"")).unwrap(),
191            addr_evm
192        );
193    }
194
195    #[test]
196    fn test_basic_roundtrip_cosmos() {
197        let cosmos_addr = CosmosAddr::new_str(TEST_COSMOS_STR, None).unwrap();
198        let addr: Address = cosmos_addr.clone().into();
199
200        assert_eq!(addr.to_string(), TEST_COSMOS_STR);
201        assert_eq!(cosmos_addr.prefix(), TEST_COSMOS_PREFIX);
202    }
203
204    #[test]
205    fn test_serde_roundtrip_cosmos() {
206        #[derive(serde::Serialize, serde::Deserialize)]
207        struct TestStruct {
208            addr: CosmosAddr,
209        }
210
211        let test_struct: TestStruct =
212            serde_json::from_str(&format!(r#"{{ "addr": "{TEST_COSMOS_STR}"}}"#)).unwrap();
213        let addr: Address = test_struct.addr.clone().into();
214
215        let test_struct_2 = TestStruct {
216            addr: addr.try_into().unwrap(),
217        };
218
219        assert_eq!(
220            serde_json::to_string(&test_struct_2).unwrap().trim(),
221            format!(r#"{{"addr":"{TEST_COSMOS_STR}"}}"#).trim()
222        );
223        assert_eq!(test_struct_2.addr.prefix(), TEST_COSMOS_PREFIX);
224    }
225
226    #[test]
227    fn test_convert_evm_to_cosmos() {
228        // let test_string = "0xb794f5ea0ba39494ce839613fffba74279579268";
229        // let addr_bytes:EvmAddr= test_string.try_into().unwrap();
230        // let addr_string:AddrString = (&addr_bytes).into();
231        // let addr_string_cosmos = addr_string.convert_into_cosmos("osmo".to_string()).unwrap();
232        // assert_eq!(addr_string_cosmos.to_string(), "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv");
233    }
234
235    #[test]
236    fn test_convert_cosmos_to_evm() {
237        // let test_string = "osmo1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsll0sqv";
238        // let account_id:AccountId = test_string.parse().unwrap();
239        // let addr_string:AddrString = (&account_id).try_into().unwrap();
240        // let addr_string_evm = addr_string.convert_into_evm().unwrap();
241        // assert_eq!(addr_string_evm.to_string(), "0xb794f5ea0ba39494ce839613fffba74279579268");
242    }
243}