use bech32::{self, FromBase32, ToBase32};
use crate::errors::{NibiruError, NibiruResult};
pub fn nibiru_bech32_to_eth_address(bech32_addr: &str) -> NibiruResult<String> {
let (hrp, data, _variant) = bech32::decode(bech32_addr)?;
if hrp != "nibi" {
return Err(NibiruError::InvalidBech32Prefix {
expected: "nibi".to_string(),
actual: hrp,
});
}
let bytes = Vec::<u8>::from_base32(&data)?;
if bytes.len() < 20 {
return Err(NibiruError::InvalidAddressLength);
}
let eth_addr = format!("0x{}", hex::encode(&bytes[..20]));
Ok(eth_addr)
}
pub fn eth_address_to_nibiru_bech32(eth_addr: &str) -> NibiruResult<String> {
let hex_str = eth_addr.strip_prefix("0x").unwrap_or(eth_addr);
if hex_str.len() != 40 {
return Err(NibiruError::InvalidEthAddress(format!(
"Ethereum address must be 20 bytes (40 hex chars), got {} chars",
hex_str.len()
)));
}
let bytes = hex::decode(hex_str)?;
if bytes.len() != 20 {
return Err(NibiruError::InvalidEthAddress(format!(
"Invalid Ethereum address length: expected 20 bytes, got {}",
bytes.len()
)));
}
let bech32_addr =
bech32::encode("nibi", bytes.to_base32(), bech32::Variant::Bech32)?;
Ok(bech32_addr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nibiru_bech32_to_eth_address_valid() {
let bech32_addr = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let expected_eth = "0x46155fafd58660583ac0d23d8e22b9a13ca0fb31";
let result = nibiru_bech32_to_eth_address(bech32_addr).unwrap();
assert_eq!(result.to_lowercase(), expected_eth);
}
#[test]
fn test_nibiru_bech32_to_eth_address_invalid_prefix() {
let bech32_addr = "cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnrql8a";
let result = nibiru_bech32_to_eth_address(bech32_addr);
match result {
Err(NibiruError::InvalidBech32Prefix { expected, actual }) => {
assert_eq!(expected, "nibi");
assert_eq!(actual, "cosmos");
}
_ => panic!("Expected InvalidBech32Prefix error, got: {:?}", result),
}
}
#[test]
fn test_nibiru_bech32_to_eth_address_invalid_bech32() {
let invalid_addr = "nibi1invalid!@#$";
let result = nibiru_bech32_to_eth_address(invalid_addr);
assert!(matches!(result, Err(NibiruError::Bech32Error(_))));
}
#[test]
fn test_nibiru_bech32_to_eth_address_length_validation() {
use bech32::ToBase32;
let short_data = vec![0u8; 10];
let short_addr = bech32::encode(
"nibi",
short_data.to_base32(),
bech32::Variant::Bech32,
)
.unwrap();
let result = nibiru_bech32_to_eth_address(&short_addr);
match result {
Err(NibiruError::InvalidAddressLength) => {}
_ => {
panic!("Expected InvalidAddressLength error, got: {:?}", result)
}
}
}
#[test]
fn test_nibiru_bech32_to_eth_address_case_sensitivity() {
let bech32_addr = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let result = nibiru_bech32_to_eth_address(bech32_addr).unwrap();
assert!(result.starts_with("0x"));
assert_eq!(
result.to_lowercase(),
"0x46155fafd58660583ac0d23d8e22b9a13ca0fb31"
);
}
#[test]
fn test_eth_address_to_nibiru_bech32_valid() {
let eth_addr = "0x46155fAfd58660583ac0d23d8E22B9A13Ca0fb31";
let expected_bech32 = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let result = eth_address_to_nibiru_bech32(eth_addr).unwrap();
assert_eq!(result, expected_bech32);
}
#[test]
fn test_eth_address_to_nibiru_bech32_without_prefix() {
let eth_addr = "46155fAfd58660583ac0d23d8E22B9A13Ca0fb31";
let expected_bech32 = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let result = eth_address_to_nibiru_bech32(eth_addr).unwrap();
assert_eq!(result, expected_bech32);
}
#[test]
fn test_eth_address_to_nibiru_bech32_lowercase() {
let eth_addr = "0x46155fafd58660583ac0d23d8e22b9a13ca0fb31";
let expected_bech32 = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let result = eth_address_to_nibiru_bech32(eth_addr).unwrap();
assert_eq!(result, expected_bech32);
}
#[test]
fn test_eth_address_to_nibiru_bech32_invalid_length() {
let short_addr = "0x46155fafd58660583ac0d23d8e22b9a13ca0fb";
let result = eth_address_to_nibiru_bech32(short_addr);
match result {
Err(NibiruError::InvalidEthAddress(msg)) => {
assert!(msg.contains("40 hex chars"));
}
_ => panic!("Expected InvalidEthAddress error"),
}
let long_addr = "0x46155fafd58660583ac0d23d8e22b9a13ca0fb3100";
let result = eth_address_to_nibiru_bech32(long_addr);
match result {
Err(NibiruError::InvalidEthAddress(msg)) => {
assert!(msg.contains("40 hex chars"));
}
_ => panic!("Expected InvalidEthAddress error"),
}
}
#[test]
fn test_eth_address_to_nibiru_bech32_invalid_hex() {
let invalid_addr = "0x46155fXXd58660583ac0d23d8e22b9a13ca0fb31";
let result = eth_address_to_nibiru_bech32(invalid_addr);
assert!(matches!(result, Err(NibiruError::HexError(_))));
}
#[test]
fn test_round_trip_conversion() {
let original_bech32 = "nibi1gc24lt74ses9swkq6g7cug4e5y72p7e34jqgul";
let eth_addr = nibiru_bech32_to_eth_address(original_bech32).unwrap();
let result_bech32 = eth_address_to_nibiru_bech32(ð_addr).unwrap();
assert_eq!(original_bech32, result_bech32);
}
#[test]
fn test_multiple_round_trips() {
use bech32::ToBase32;
let test_bytes = vec![
vec![0u8; 20], vec![255u8; 20], vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20,
], ];
for bytes in test_bytes {
let original_bech32 = bech32::encode(
"nibi",
bytes.to_base32(),
bech32::Variant::Bech32,
)
.unwrap();
let eth_addr =
nibiru_bech32_to_eth_address(&original_bech32).unwrap();
let result_bech32 = eth_address_to_nibiru_bech32(ð_addr).unwrap();
assert_eq!(
original_bech32, result_bech32,
"Round trip failed for address"
);
}
}
}