hedera/contract/
delegate_contract_id.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::fmt;
4use std::str::FromStr;
5
6use hedera_proto::services;
7
8use crate::entity_id::{
9    Checksum,
10    PartialEntityId,
11};
12use crate::ethereum::SolidityAddress;
13use crate::protobuf::{
14    FromProtobuf,
15    ToProtobuf,
16};
17use crate::{
18    EntityId,
19    Error,
20};
21
22/// A unique identifier for a smart contract on Hiero.
23#[derive(Hash, PartialEq, Eq, Clone, Copy)]
24pub struct DelegateContractId {
25    /// A non-negative number identifying the shard containing this contract instance.
26    pub shard: u64,
27
28    /// A non-negative number identifying the realm within the shard containing this contract instance.
29    pub realm: u64,
30
31    /// A non-negative number identifying the entity within the realm containing this contract instance.
32    ///
33    /// Note: Exactly one of `evm_address` and `num` must exist.
34    pub num: u64,
35
36    /// A checksum if the contract ID was read from a user inputted string which inclueded a checksum
37    pub checksum: Option<Checksum>,
38
39    /// EVM address identifying the entity within the realm containing this contract instance.
40    ///
41    /// Note: Exactly one of `evm_address` and `num` must exist.
42    pub evm_address: Option<[u8; 20]>,
43}
44
45impl DelegateContractId {
46    /// Create a `DelegateContractId` from the given shard/realm/num
47    #[must_use]
48    pub fn new(shard: u64, realm: u64, num: u64) -> Self {
49        Self { shard, realm, num, evm_address: None, checksum: None }
50    }
51
52    /// Create a `DelegateContractId` from a solidity address.
53    ///
54    /// # Errors
55    /// - [`Error::BasicParse`] if `address` cannot be parsed as a solidity address.
56    pub fn from_solidity_address(address: &str) -> crate::Result<Self> {
57        let EntityId { shard, realm, num, checksum } = EntityId::from_solidity_address(address)?;
58
59        Ok(Self { shard, realm, num, evm_address: None, checksum })
60    }
61}
62
63impl fmt::Debug for DelegateContractId {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "\"{self}\"")
66    }
67}
68
69impl fmt::Display for DelegateContractId {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        if let Some(address) = &self.evm_address {
72            write!(f, "{}.{}.{}", self.shard, self.realm, SolidityAddress::from_ref(address))
73        } else {
74            write!(f, "{}.{}.{}", self.shard, self.realm, self.num)
75        }
76    }
77}
78
79impl FromStr for DelegateContractId {
80    type Err = Error;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        // override the error message for better context.
84        let partial = PartialEntityId::from_str(s).map_err(|_| {
85            Error::basic_parse(format!(
86                "expecting <shard>.<realm>.<num> or <shard>.<realm>.<evm_address>, got `{s}`"
87            ))
88        })?;
89
90        match partial {
91            PartialEntityId::ShortNum(it) => Ok(Self::from(it)),
92            PartialEntityId::LongNum(it) => Ok(Self::from(it)),
93            PartialEntityId::ShortOther(_) => Err(Error::basic_parse(format!(
94                "expecting <shard>.<realm>.<num> or <shard>.<realm>.<evm_address>, got `{s}`"
95            ))),
96            PartialEntityId::LongOther { shard, realm, last } => {
97                let evm_address = Some(SolidityAddress::from_str(last)?.to_bytes());
98
99                Ok(Self { shard, realm, num: 0, evm_address, checksum: None })
100            }
101        }
102    }
103}
104
105impl From<[u8; 20]> for DelegateContractId {
106    fn from(address: [u8; 20]) -> Self {
107        Self { shard: 0, realm: 0, num: 0, evm_address: Some(address), checksum: None }
108    }
109}
110
111impl From<u64> for DelegateContractId {
112    fn from(num: u64) -> Self {
113        Self::new(0, 0, num)
114    }
115}
116
117impl From<EntityId> for DelegateContractId {
118    fn from(value: EntityId) -> Self {
119        let EntityId { shard, realm, num, checksum } = value;
120
121        Self { shard, realm, num, evm_address: None, checksum }
122    }
123}
124
125impl FromProtobuf<services::ContractId> for DelegateContractId {
126    fn from_protobuf(pb: services::ContractId) -> crate::Result<Self> {
127        let contract = pb_getf!(pb, contract)?;
128
129        let (num, evm_address) = match contract {
130            services::contract_id::Contract::ContractNum(it) => (it as u64, None),
131            services::contract_id::Contract::EvmAddress(it) => {
132                (0, Some(SolidityAddress::try_from(it)?.to_bytes()))
133            }
134        };
135
136        Ok(Self {
137            evm_address,
138            num,
139            shard: pb.shard_num as u64,
140            realm: pb.realm_num as u64,
141            checksum: None,
142        })
143    }
144}
145
146impl ToProtobuf for DelegateContractId {
147    type Protobuf = services::ContractId;
148
149    fn to_protobuf(&self) -> Self::Protobuf {
150        services::ContractId {
151            contract: Some(match &self.evm_address {
152                Some(address) => services::contract_id::Contract::EvmAddress(address.to_vec()),
153                None => services::contract_id::Contract::ContractNum(self.num as i64),
154            }),
155            realm_num: self.realm as i64,
156            shard_num: self.shard as i64,
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use std::str::FromStr;
164
165    use expect_test::expect;
166
167    use crate::protobuf::{
168        FromProtobuf,
169        ToProtobuf,
170    };
171    use crate::DelegateContractId;
172
173    #[test]
174    fn from_string() {
175        expect!["0.0.5005"]
176            .assert_eq(&DelegateContractId::from_str("0.0.5005").unwrap().to_string());
177    }
178
179    #[test]
180    fn from_solidity_address() {
181        expect!["0.0.5005"].assert_eq(
182            &DelegateContractId::from_solidity_address("000000000000000000000000000000000000138D")
183                .unwrap()
184                .to_string(),
185        );
186    }
187
188    #[test]
189    fn from_solidity_address_with_0x() {
190        expect!["0.0.5005"].assert_eq(
191            &DelegateContractId::from_solidity_address(
192                "0x000000000000000000000000000000000000138D",
193            )
194            .unwrap()
195            .to_string(),
196        );
197    }
198
199    #[test]
200    fn from_bytes() {
201        expect!["0.0.5005"].assert_eq(
202            &DelegateContractId::from_bytes(&DelegateContractId::new(0, 0, 5005).to_bytes())
203                .unwrap()
204                .to_string(),
205        );
206    }
207}