use std::fmt::{
self,
Debug,
Display,
Formatter,
};
use std::str::FromStr;
use hedera_proto::services;
use crate::entity_id::{
Checksum,
PartialEntityId,
ValidateChecksums,
};
use crate::evm_address::IdEvmAddress;
use crate::{
Client,
EntityId,
Error,
FromProtobuf,
LedgerId,
ToProtobuf,
};
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "ffi", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
pub struct ContractId {
pub shard: u64,
pub realm: u64,
pub num: u64,
pub checksum: Option<Checksum>,
pub evm_address: Option<[u8; 20]>,
}
impl ContractId {
#[must_use]
pub fn new(shard: u64, realm: u64, num: u64) -> Self {
Self { shard, realm, num, evm_address: None, checksum: None }
}
#[must_use]
pub fn from_evm_address_bytes(shard: u64, realm: u64, evm_address: [u8; 20]) -> Self {
Self { shard, realm, num: 0, evm_address: Some(evm_address), checksum: None }
}
pub fn from_evm_address(shard: u64, realm: u64, address: &str) -> crate::Result<Self> {
Ok(Self {
shard,
realm,
num: 0,
evm_address: Some(IdEvmAddress::from_str(address)?.to_bytes()),
checksum: None,
})
}
pub fn from_solidity_address(address: &str) -> crate::Result<Self> {
let EntityId { shard, realm, num, checksum } = EntityId::from_solidity_address(address)?;
Ok(Self { shard, realm, num, evm_address: None, checksum })
}
pub fn from_bytes(bytes: &[u8]) -> crate::Result<Self> {
FromProtobuf::from_bytes(bytes)
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
ToProtobuf::to_bytes(self)
}
pub fn to_solidity_address(&self) -> crate::Result<String> {
if let Some(address) = self.evm_address {
return Ok(hex::encode(address));
}
EntityId { shard: self.shard, realm: self.realm, num: self.num, checksum: None }
.to_solidity_address()
}
pub async fn to_string_with_checksum(&self, client: &Client) -> Result<String, Error> {
if self.evm_address.is_some() {
Err(Error::CannotToStringWithChecksum)
} else {
EntityId::to_string_with_checksum(self.to_string(), client).await
}
}
pub async fn validate_checksum(&self, client: &Client) -> Result<(), Error> {
if self.evm_address.is_some() {
Ok(())
} else {
EntityId::validate_checksum(self.shard, self.realm, self.num, &self.checksum, client)
.await
}
}
}
impl ValidateChecksums for ContractId {
fn validate_checksums(&self, ledger_id: &LedgerId) -> Result<(), Error> {
if self.evm_address.is_some() {
Ok(())
} else {
EntityId::validate_checksum_for_ledger_id(
self.shard,
self.realm,
self.num,
&self.checksum,
ledger_id,
)
}
}
}
impl Debug for ContractId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "\"{self}\"")
}
}
impl Display for ContractId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(address) = &self.evm_address {
write!(f, "{}.{}.{}", self.shard, self.realm, IdEvmAddress::from_ref(address))
} else {
write!(f, "{}.{}.{}", self.shard, self.realm, self.num)
}
}
}
impl FromProtobuf<services::ContractId> for ContractId {
fn from_protobuf(pb: services::ContractId) -> crate::Result<Self> {
let contract = pb_getf!(pb, contract)?;
let (num, evm_address) = match contract {
services::contract_id::Contract::ContractNum(it) => (it as u64, None),
services::contract_id::Contract::EvmAddress(it) => {
(0, Some(IdEvmAddress::try_from(it)?.to_bytes()))
}
};
Ok(Self {
evm_address,
num,
shard: pb.shard_num as u64,
realm: pb.realm_num as u64,
checksum: None,
})
}
}
impl ToProtobuf for ContractId {
type Protobuf = services::ContractId;
fn to_protobuf(&self) -> Self::Protobuf {
services::ContractId {
contract: Some(match &self.evm_address {
Some(address) => services::contract_id::Contract::EvmAddress(address.to_vec()),
None => services::contract_id::Contract::ContractNum(self.num as i64),
}),
realm_num: self.realm as i64,
shard_num: self.shard as i64,
}
}
}
impl From<[u8; 20]> for ContractId {
fn from(address: [u8; 20]) -> Self {
Self { shard: 0, realm: 0, num: 0, evm_address: Some(address), checksum: None }
}
}
impl From<u64> for ContractId {
fn from(num: u64) -> Self {
Self { num, shard: 0, realm: 0, evm_address: None, checksum: None }
}
}
impl FromStr for ContractId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let partial = PartialEntityId::from_str(s).map_err(|_| {
Error::basic_parse(format!(
"expecting <shard>.<realm>.<num> or <shard>.<realm>.<evm_address>, got `{s}`"
))
})?;
match partial {
PartialEntityId::ShortNum(it) => Ok(Self::from(it)),
PartialEntityId::LongNum(it) => Ok(Self::from(it)),
PartialEntityId::ShortOther(_) => Err(Error::basic_parse(format!(
"expecting <shard>.<realm>.<num> or <shard>.<realm>.<evm_address>, got `{s}`"
))),
PartialEntityId::LongOther { shard, realm, last } => {
let evm_address = Some(IdEvmAddress::from_str(last)?.to_bytes());
Ok(Self { shard, realm, num: 0, evm_address, checksum: None })
}
}
}
}
impl From<EntityId> for ContractId {
fn from(value: EntityId) -> Self {
let EntityId { shard, realm, num, checksum } = value;
Self { shard, realm, num, evm_address: None, checksum }
}
}