mavryk_smart_rollup_encoding/
contract.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum Contract {
27 Implicit(PublicKeyHash),
29 Originated(ContractKt1Hash),
31}
32
33impl Contract {
34 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 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 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 #[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); 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 #[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); 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 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}