hpl-interface 0.0.6

Interface definitions for CosmWasm Hyperlane contracts
Documentation
use bech32::{FromBase32, ToBase32};
use cosmwasm_std::{Addr, StdError, StdResult};

pub fn bech32_to_h256(target: &str) -> StdResult<[u8; 32]> {
    let raw_addr = bech32_decode(target)?;

    let mut addr = [0u8; 32];
    addr[32 - raw_addr.len()..].copy_from_slice(&raw_addr);

    Ok(addr)
}

pub fn bech32_decode(target: &str) -> StdResult<Vec<u8>> {
    let (_, raw_addr_u5, _) = bech32::decode(target)
        .map_err(|e| StdError::generic_err(format!("invalid bech32 bytes. err: {e}")))?;

    let raw_addr = Vec::<u8>::from_base32(&raw_addr_u5)
        .map_err(|e| StdError::generic_err(format!("failed to parse [u5] to [u8]. err: {e}")))?;

    Ok(raw_addr)
}

pub fn bech32_encode(hrp: &str, raw_addr: &[u8]) -> StdResult<Addr> {
    if raw_addr.len() != 32 && raw_addr.len() != 20 {
        return Err(StdError::generic_err(format!(
            "invalid raw address length. expected: 32 or 20. got: {}",
            raw_addr.len()
        )));
    }

    if raw_addr.len() == 32 {
        let mut bz = 0u128.to_be_bytes();
        bz[4..].copy_from_slice(&raw_addr[0..12]);
        let check = u128::from_be_bytes(bz);

        if check == 0 {
            return bech32_encode(hrp, &raw_addr[12..]);
        }
    }

    let enc_addr = bech32::encode(hrp, raw_addr.to_base32(), bech32::Variant::Bech32)
        .map_err(|e| StdError::generic_err(format!("invalid bech32 address. err: {e}")))?;

    Ok(Addr::unchecked(enc_addr))
}

#[cfg(test)]
mod tests {
    use cosmwasm_std::HexBinary;
    use rstest::rstest;

    use crate::types::bech32_to_h256;

    use super::{bech32_decode, bech32_encode};

    #[test]
    fn addr_conv() {
        println!(
            "{}",
            bech32_encode(
                "neutron",
                &bech32_decode("osmo1mhnkm6fwaq53yzu7c0r3khhy60v04vse4c6gk5").unwrap(),
            )
            .unwrap()
        );
    }

    #[test]
    fn conv() {
        let addr = "dual1dwnrgwsf5c9vqjxsax04pdm0mx007yrraj2dgn";
        let addr_bin = bech32_decode(addr).unwrap();
        let addr_hex = HexBinary::from(addr_bin).to_hex();

        println!("{}", addr_hex);
    }

    #[test]
    fn conv_rev() {
        let addr_hex = "f3aa0d652226e21ae35cd9035c492ae41725edc9036edf0d6a48701b153b90a0";
        let addr_bin = HexBinary::from_hex(addr_hex).unwrap();
        let addr = bech32_encode("dual", &addr_bin).unwrap();

        println!("{}", addr);
    }

    #[rstest]
    #[case(
        "osmo",
        "osmo1466nf3zuxpya8q9emxukd7vftaf6h4psr0a07srl5zw74zh84yjqkk0zfx"
    )]
    #[case("osmo", "osmo14n3a65fnqz9jve85l23al6m3pjugf0atvrfqh5")]
    #[case(
        "neutron",
        "neutron1gajw625kz8el4ayk8fwpy7r6ew0m7zrg9jdd6grg85fle39shuxqezuz2c"
    )]
    #[case("neutron", "neutron1xle8l3h0wkcp6tsxmkc6n4vqyfkhwnukevwwsk")]
    fn test_decode_encode(#[case] hrp: &str, #[case] src_addr: &str) {
        let dec_addr = bech32_to_h256(src_addr).unwrap();
        let enc_addr = bech32_encode(hrp, &dec_addr).unwrap();

        assert_eq!(src_addr, enc_addr.as_str());
    }

    #[rstest]
    #[case("osmo", "osmo1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qnuqjrc5")]
    #[case("neutron", "neutron1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qnsyg35p")]
    fn test_decode_encode_acc(#[case] hrp: &str, #[case] src_addr: &str) {
        let dec_addr = bech32_decode(src_addr).unwrap();
        let enc_addr = bech32_encode(hrp, &dec_addr).unwrap();

        assert_eq!(src_addr, enc_addr.as_str());
    }

    #[rstest]
    #[case(&[
        "cosmos1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qn5mpnwx",
        "axelar1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qns4hm98",
        "osmo1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qnuqjrc5",
        "neutron1d6a3j0kkpc8eac0j8h6ypyevfz8hd3qnsyg35p"
    ])]
    fn test_acc_cross(#[case] addrs: &[&str]) {
        for (i, test_target_addr) in addrs.iter().enumerate() {
            let mut test_addr_group = addrs.to_vec();
            test_addr_group.remove(i);

            let test_target_addr_bin = bech32_decode(test_target_addr).unwrap();

            for test_addr in test_addr_group {
                let hrp = test_addr.split('1').collect::<Vec<_>>()[0];

                let encoded = bech32_encode(hrp, &test_target_addr_bin).unwrap();
                assert_eq!(test_addr, encoded.as_str());
            }
        }
    }
}