mavryk_smart_rollup_encoding/
contract.rs

1// SPDX-FileCopyrightText: 2023 TriliTech <contact@trili.tech>
2//
3// SPDX-License-Identifier: MIT
4
5//! Definitions relating to Layer-1 accounts, which the kernel may interact with.
6
7use crypto::base58::{FromBase58Check, FromBase58CheckError};
8use crypto::hash::{ContractKt1Hash, Hash, HashTrait, HashType};
9use mavryk_data_encoding::enc::{self, BinResult, BinWriter};
10use mavryk_data_encoding::encoding::{Encoding, HasEncoding};
11use mavryk_data_encoding::has_encoding;
12use mavryk_data_encoding::nom::{NomReader, NomResult};
13use nom::branch::alt;
14use nom::bytes::complete::tag;
15use nom::combinator::map;
16use nom::sequence::delimited;
17use nom::sequence::preceded;
18
19use super::public_key_hash::PublicKeyHash;
20
21#[cfg(feature = "testing")]
22pub mod testing;
23
24/// Contract id - of either an implicit account or originated account.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum Contract {
27    /// User account
28    Implicit(PublicKeyHash),
29    /// Smart contract account
30    Originated(ContractKt1Hash),
31}
32
33impl Contract {
34    /// Converts from a *base58-encoded* string, checking for the prefix.
35    pub fn from_b58check(data: &str) -> Result<Self, FromBase58CheckError> {
36        let bytes = data.from_base58check()?;
37        match bytes {
38            _ if bytes.starts_with(HashType::ContractKt1Hash.base58check_prefix()) => {
39                Ok(Self::Originated(ContractKt1Hash::from_b58check(data)?))
40            }
41            _ => Ok(Self::Implicit(PublicKeyHash::from_b58check(data)?)),
42        }
43    }
44
45    /// Converts to a *base58-encoded* string, including the prefix.
46    pub fn to_b58check(&self) -> String {
47        match self {
48            Self::Implicit(pkh) => pkh.to_b58check(),
49            Self::Originated(kt1) => kt1.to_b58check(),
50        }
51    }
52}
53
54impl From<Contract> for Hash {
55    fn from(c: Contract) -> Self {
56        match c {
57            Contract::Implicit(pkh) => pkh.into(),
58            Contract::Originated(ckt1) => ckt1.into(),
59        }
60    }
61}
62
63impl TryFrom<String> for Contract {
64    type Error = FromBase58CheckError;
65
66    fn try_from(value: String) -> Result<Self, Self::Error> {
67        Contract::from_b58check(value.as_str())
68    }
69}
70
71#[allow(clippy::from_over_into)]
72impl Into<String> for Contract {
73    fn into(self) -> String {
74        self.to_b58check()
75    }
76}
77
78has_encoding!(Contract, CONTRACT_ENCODING, { Encoding::Custom });
79
80impl NomReader for Contract {
81    fn nom_read(input: &[u8]) -> NomResult<Self> {
82        alt((
83            map(
84                preceded(tag([0]), PublicKeyHash::nom_read),
85                Contract::Implicit,
86            ),
87            map(
88                delimited(tag([1]), ContractKt1Hash::nom_read, tag([0])),
89                Contract::Originated,
90            ),
91        ))(input)
92    }
93}
94
95impl BinWriter for Contract {
96    fn bin_write(&self, output: &mut Vec<u8>) -> BinResult {
97        match self {
98            Self::Implicit(implicit) => {
99                enc::put_byte(&0, output);
100                BinWriter::bin_write(implicit, output)
101            }
102            Self::Originated(originated) => {
103                enc::put_byte(&1, output);
104                let mut bytes: Hash = originated.as_ref().to_vec();
105                // Originated is padded
106                bytes.push(0);
107                enc::bytes(&mut bytes, output)?;
108                Ok(())
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117
118    #[test]
119    fn mv1_b58check() {
120        let mv1 = "mv1E7Ms4p1e3jV2WMehLB3FBFwbV56GiRQfe";
121
122        let pkh = Contract::from_b58check(mv1);
123
124        assert!(matches!(
125            pkh,
126            Ok(Contract::Implicit(PublicKeyHash::Ed25519(_)))
127        ));
128
129        let mv1_from_pkh = pkh.unwrap().to_b58check();
130
131        assert_eq!(mv1, &mv1_from_pkh);
132    }
133
134    #[test]
135    fn mv2_b58check() {
136        let mv2 = "mv2adMpwrH3DLJ8x227d5u2egH9m4JvUFBNd";
137
138        let pkh = Contract::from_b58check(mv2);
139
140        assert!(matches!(
141            pkh,
142            Ok(Contract::Implicit(PublicKeyHash::Secp256k1(_)))
143        ));
144
145        let tz2_from_pkh = pkh.unwrap().to_b58check();
146
147        assert_eq!(mv2, &tz2_from_pkh);
148    }
149
150    #[test]
151    fn mv3_b58check() {
152        let mv3 = "mv3QLASYan3ScSgWsYbtfDbqLymr1simPjTb";
153
154        let pkh = Contract::from_b58check(mv3);
155
156        assert!(matches!(
157            pkh,
158            Ok(Contract::Implicit(PublicKeyHash::P256(_)))
159        ));
160
161        let tz3_from_pkh = pkh.unwrap().to_b58check();
162
163        assert_eq!(mv3, &tz3_from_pkh);
164    }
165
166    #[test]
167    fn kt1_b58check() {
168        let kt1 = "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc";
169
170        let pkh = Contract::from_b58check(kt1);
171
172        assert!(matches!(pkh, Ok(Contract::Originated(ContractKt1Hash(_)))));
173
174        let kt1_from_pkh = pkh.unwrap().to_b58check();
175
176        assert_eq!(kt1, &kt1_from_pkh);
177    }
178
179    #[test]
180    fn mv1_encoding() {
181        let mv1 = "mv18Cw7psUrAAPBpXYd9CtCpHg9EgjHP9KTe";
182
183        let contract = Contract::from_b58check(mv1).expect("expected valid mv1 hash");
184
185        let mut bin = Vec::new();
186        contract
187            .bin_write(&mut bin)
188            .expect("serialization should work");
189
190        let deserde_contract = NomReader::nom_read(bin.as_slice())
191            .expect("deserialization should work")
192            .1;
193
194        check_implicit_serialized(&bin, mv1);
195
196        assert_eq!(contract, deserde_contract);
197    }
198
199    #[test]
200    fn mv2_encoding() {
201        let mv2 = "mv2XGzG6KAEyFnrSD52qpMSxorCwjUjmbeep";
202
203        let contract = Contract::from_b58check(mv2).expect("expected valid mv2 hash");
204
205        let mut bin = Vec::new();
206        contract
207            .bin_write(&mut bin)
208            .expect("serialization should work");
209
210        let deserde_contract = NomReader::nom_read(bin.as_slice())
211            .expect("deserialization should work")
212            .1;
213
214        check_implicit_serialized(&bin, mv2);
215
216        assert_eq!(contract, deserde_contract);
217    }
218
219    #[test]
220    fn mv3_encoding() {
221        let mv3 = "mv3Fus3D4ZGMrPKRQnNr1dNqDrZEAXeZk9ok";
222
223        let contract = Contract::from_b58check(mv3).expect("expected valid mv3 hash");
224
225        let mut bin = Vec::new();
226        contract
227            .bin_write(&mut bin)
228            .expect("serialization should work");
229
230        let deserde_contract = NomReader::nom_read(bin.as_slice())
231            .expect("deserialization should work")
232            .1;
233
234        check_implicit_serialized(&bin, mv3);
235
236        assert_eq!(contract, deserde_contract);
237    }
238
239    // Check encoding of originated contracts (aka smart-contract addresses)
240    #[test]
241    fn contract_encode_originated() {
242        let test = "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc";
243        let mut expected = vec![1];
244        let mut bytes = Contract::from_b58check(test).unwrap().into();
245        expected.append(&mut bytes);
246        expected.push(0); // padding
247
248        let contract = Contract::from_b58check(test).unwrap();
249
250        let mut bin = Vec::new();
251        contract.bin_write(&mut bin).unwrap();
252
253        assert_eq!(expected, bin);
254    }
255
256    // Check decoding of originated contracts (aka smart-contract addresses)
257    #[test]
258    fn contract_decode_originated() {
259        let expected = "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc";
260        let mut test = vec![1];
261        let mut bytes = Contract::from_b58check(expected).unwrap().into();
262        test.append(&mut bytes);
263        test.push(0); // padding
264
265        let expected_contract = Contract::from_b58check(expected).unwrap();
266
267        let (remaining_input, contract) = Contract::nom_read(test.as_slice()).unwrap();
268
269        assert!(remaining_input.is_empty());
270        assert_eq!(expected_contract, contract);
271    }
272
273    // Check that serialization of implicit PublicKeyHash is binary compatible
274    // with protocol encoding of implicit contract ids.
275    fn check_implicit_serialized(contract_bytes: &[u8], address: &str) {
276        let mut bin_pkh = Vec::new();
277        PublicKeyHash::from_b58check(address)
278            .expect("expected valid implicit contract")
279            .bin_write(&mut bin_pkh)
280            .expect("serialization should work");
281
282        assert!(matches!(
283            contract_bytes,
284            [0_u8, rest @ ..] if rest == bin_pkh));
285    }
286}