use crate::constants::METACHAIN_SHARD_ID;
use crate::error::CoreError;
pub fn shard_of(address: &str, num_shards: u32) -> Result<u32, CoreError> {
let (_hrp, raw) =
bech32::decode(address).map_err(|e| CoreError::InvalidBech32(e.to_string()))?;
shard_of_address_bytes(&raw, num_shards)
}
pub fn shard_of_address_bytes(address: &[u8], num_shards: u32) -> Result<u32, CoreError> {
if address.is_empty() {
return Err(CoreError::EmptyAddress);
}
let (mask_high, mask_low) = calculate_masks(num_shards);
Ok(compute_shard_id(address, num_shards, mask_high, mask_low))
}
fn calculate_masks(num_shards: u32) -> (u32, u32) {
let n = f64::from(num_shards).log2().ceil() as u32;
let mask_high = (1u32 << n) - 1;
let mask_low = if n > 0 { (1u32 << (n - 1)) - 1 } else { 0 };
(mask_high, mask_low)
}
fn compute_shard_id(address: &[u8], num_shards: u32, mask_high: u32, mask_low: u32) -> u32 {
let bytes_needed = if num_shards <= 256 {
1
} else if num_shards <= 65536 {
2
} else if num_shards <= 16_777_216 {
3
} else {
4
};
let starting_index = if address.len() > bytes_needed {
address.len() - bytes_needed
} else {
0
};
let buff_needed = &address[starting_index..];
if is_smart_contract_on_metachain(buff_needed, address) {
return METACHAIN_SHARD_ID;
}
let mut addr: u32 = 0;
for &byte in buff_needed {
addr = (addr << 8) + u32::from(byte);
}
let mut shard = addr & mask_high;
if shard > num_shards - 1 {
shard = addr & mask_low;
}
shard
}
fn is_smart_contract_on_metachain(_buff_needed: &[u8], address: &[u8]) -> bool {
if address.len() < 8 || address[..8] != [0u8; 8] {
return false;
}
if address.len() >= 2 {
let shard_byte_index = address.len() - 2;
if address[shard_byte_index] == 0xFF {
return true;
}
}
false
}
#[inline]
pub fn select_shard(last_byte: u8, num_shards: u32) -> u32 {
if num_shards == 3 {
u32::from(last_byte & 0x03)
} else {
u32::from(last_byte) % num_shards
}
}
#[inline]
pub fn shard_of_bytes(raw: &[u8], num_shards: u32) -> Option<u32> {
let last = *raw.last()?;
if last == 0xFF {
Some(0xFF) } else {
Some(select_shard(last, num_shards))
}
}
pub fn decode_embedded_receiver(data: &[u8]) -> Option<Vec<u8>> {
fn parse(txt: &str) -> Option<Vec<u8>> {
let mut parts = txt.split('@');
let func = parts.next()?.to_ascii_lowercase();
match func.as_str() {
"esdtnfttransfer" => parts.nth(3).and_then(|h| hex::decode(h).ok()),
"multiesdtnfttransfer" => parts.next().and_then(|h| hex::decode(h).ok()),
"esdttransfer" => parts.nth(2).and_then(|h| hex::decode(h).ok()),
_ => None,
}
}
if let Ok(txt) = std::str::from_utf8(data)
&& let Some(res) = parse(txt)
{
return Some(res);
}
if let Ok(decoded) = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, data)
&& let Ok(txt) = std::str::from_utf8(&decoded)
{
return parse(txt);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shard_calculation() {
let cases = [(
"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
1,
)];
for (addr, expected_shard) in cases {
let shard = shard_of(addr, 3).unwrap();
assert_eq!(
shard, expected_shard,
"address {addr} expected shard {expected_shard}"
);
}
}
#[test]
fn test_mask_calculation() {
let (mask_high, mask_low) = calculate_masks(3);
assert_eq!(mask_high, 0b11);
assert_eq!(mask_low, 0b01);
let (mask_high, mask_low) = calculate_masks(4);
assert_eq!(mask_high, 0b11);
assert_eq!(mask_low, 0b01);
}
#[test]
fn test_empty_address() {
let result = shard_of_address_bytes(&[], 3);
assert!(result.is_err());
}
#[test]
fn test_select_shard() {
assert_eq!(select_shard(0b0000_0000, 3), 0); assert_eq!(select_shard(0b0000_0001, 3), 1); assert_eq!(select_shard(0b0000_0010, 3), 2); assert_eq!(select_shard(0b0000_0011, 3), 3); assert_eq!(select_shard(0b1111_1100, 3), 0);
assert_eq!(select_shard(5, 4), 1); assert_eq!(select_shard(10, 7), 3); }
#[test]
fn test_shard_of_bytes() {
assert_eq!(shard_of_bytes(&[0x00], 3), Some(0));
assert_eq!(shard_of_bytes(&[0x01], 3), Some(1));
assert_eq!(shard_of_bytes(&[0x02], 3), Some(2));
assert_eq!(shard_of_bytes(&[0x03], 3), Some(3));
assert_eq!(shard_of_bytes(&[0xFF], 3), Some(0xFF));
assert_eq!(shard_of_bytes(&[], 3), None);
assert_eq!(shard_of_bytes(&[0xAA, 0xBB, 0x02], 3), Some(2));
}
#[test]
fn test_decode_embedded_receiver_esdt_nft_transfer() {
let data = b"ESDTNFTTransfer@544f4b454e@01@0a@aabbccdd";
let result = decode_embedded_receiver(data);
assert_eq!(result, Some(vec![0xaa, 0xbb, 0xcc, 0xdd]));
}
#[test]
fn test_decode_embedded_receiver_multi_esdt_nft_transfer() {
let data = b"MultiESDTNFTTransfer@aabbccdd@02";
let result = decode_embedded_receiver(data);
assert_eq!(result, Some(vec![0xaa, 0xbb, 0xcc, 0xdd]));
}
#[test]
fn test_decode_embedded_receiver_esdt_transfer() {
let data = b"ESDTTransfer@544f4b454e@0a@aabbccdd";
let result = decode_embedded_receiver(data);
assert_eq!(result, Some(vec![0xaa, 0xbb, 0xcc, 0xdd]));
}
#[test]
fn test_decode_embedded_receiver_case_insensitive() {
let data1 = b"esdttransfer@544f4b454e@0a@aabbccdd";
let data2 = b"ESDTTRANSFER@544f4b454e@0a@aabbccdd";
assert_eq!(
decode_embedded_receiver(data1),
decode_embedded_receiver(data2)
);
}
#[test]
fn test_decode_embedded_receiver_base64() {
let data = b"RVNEVFRyYW5zZmVyQDU0NGY0YjQ1NGVAMGFAYWFiYmNjZGQ=";
let result = decode_embedded_receiver(data);
assert_eq!(result, Some(vec![0xaa, 0xbb, 0xcc, 0xdd]));
}
#[test]
fn test_decode_embedded_receiver_invalid() {
assert_eq!(decode_embedded_receiver(b"SomeOtherFunction@arg"), None);
assert_eq!(decode_embedded_receiver(b"ESDTTransfer@token"), None);
assert_eq!(
decode_embedded_receiver(b"ESDTTransfer@token@amount@zzzz"),
None
);
assert_eq!(decode_embedded_receiver(b""), None);
}
}