1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ProgramABI {
15 pub program_type: String,
16 pub spec_version: Version,
17 pub encoding_version: Version,
18 pub concrete_types: Vec<TypeConcreteDeclaration>,
19 pub metadata_types: Vec<TypeMetadataDeclaration>,
20 pub functions: Vec<ABIFunction>,
21 pub logged_types: Option<Vec<LoggedType>>,
22 pub messages_types: Option<Vec<MessageType>>,
23 pub configurables: Option<Vec<Configurable>>,
24 pub error_codes: Option<BTreeMap<u64, ErrorDetails>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub panicking_calls: Option<BTreeMap<u64, PanickingCall>>,
27}
28
29#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct Version(pub String);
31
32impl From<&str> for Version {
33 fn from(value: &str) -> Self {
34 Version(value.into())
35 }
36}
37
38impl Version {
39 pub fn major(&self) -> Option<&str> {
40 let s = self.0.split('.').next().map(|x| x.trim());
41 match s {
42 Some("") => None,
43 s => s,
44 }
45 }
46
47 pub fn minor(&self) -> Option<&str> {
48 let s = self.0.split('.').nth(1).map(|x| x.trim());
49 match s {
50 Some("") => None,
51 s => s,
52 }
53 }
54
55 pub fn is_v1_1(&self) -> bool {
60 self.0 == "1.1"
61 }
62
63 pub fn is_v1_2(&self) -> bool {
69 self.0 == "1.2"
70 }
71}
72
73#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
74pub struct ConcreteTypeId(pub String);
75
76impl From<&str> for ConcreteTypeId {
77 fn from(value: &str) -> Self {
78 ConcreteTypeId(value.into())
79 }
80}
81
82#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
83pub struct MetadataTypeId(pub usize);
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
86#[serde(untagged)]
87pub enum TypeId {
88 Concrete(ConcreteTypeId),
89 Metadata(MetadataTypeId),
90}
91
92impl Default for TypeId {
93 fn default() -> Self {
94 TypeId::Metadata(MetadataTypeId(usize::MAX))
95 }
96}
97
98#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct ABIFunction {
101 pub name: String,
102 pub inputs: Vec<TypeConcreteParameter>,
103 pub output: ConcreteTypeId,
104 pub attributes: Option<Vec<Attribute>>,
105}
106
107impl ABIFunction {
108 pub fn is_payable(&self) -> bool {
109 self.attributes
110 .iter()
111 .flatten()
112 .any(|attr| attr.name == "payable")
113 }
114}
115
116#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct TypeMetadataDeclaration {
119 #[serde(rename = "type")]
120 pub type_field: String,
121 pub metadata_type_id: MetadataTypeId,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub components: Option<Vec<TypeApplication>>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub type_parameters: Option<Vec<MetadataTypeId>>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub alias_of: Option<MetadataTypeId>,
128}
129
130#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct TypeConcreteDeclaration {
133 #[serde(rename = "type")]
134 pub type_field: String,
135 pub concrete_type_id: ConcreteTypeId,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub metadata_type_id: Option<MetadataTypeId>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub type_arguments: Option<Vec<ConcreteTypeId>>,
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub alias_of: Option<ConcreteTypeId>,
142}
143
144#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct TypeConcreteParameter {
147 pub name: String,
148 pub concrete_type_id: ConcreteTypeId,
149}
150
151#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct TypeApplication {
154 pub name: String,
155 pub type_id: TypeId,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub error_message: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub offset: Option<u16>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub type_arguments: Option<Vec<TypeApplication>>,
162}
163
164#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct LoggedType {
167 pub log_id: String,
168 pub concrete_type_id: ConcreteTypeId,
169}
170
171#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct MessageType {
174 pub message_id: String,
175 pub concrete_type_id: ConcreteTypeId,
176}
177
178#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct Configurable {
181 pub name: String,
182 pub concrete_type_id: ConcreteTypeId,
183 pub offset: u64,
184 #[serde(default)]
185 pub indirect: bool,
186}
187
188#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Attribute {
191 pub name: String,
192 pub arguments: Vec<String>,
193}
194
195#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct ErrorPosition {
201 #[serde(default)]
202 pub function: String,
204 pub pkg: String,
205 pub file: String,
206 pub line: u64,
207 pub column: u64,
208}
209
210#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct ErrorDetails {
213 pub pos: ErrorPosition,
214 pub log_id: Option<String>,
215 pub msg: Option<String>,
216}
217
218#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct PanickingCall {
242 pub pos: ErrorPosition,
243 pub function: String,
245}
246
247#[test]
248fn version_extraction_test() {
249 let v = Version("1.2".to_string());
250 assert_eq!(v.major(), Some("1"));
251 assert_eq!(v.minor(), Some("2"));
252
253 let v = Version("1".to_string());
254 assert_eq!(v.major(), Some("1"));
255 assert_eq!(v.minor(), None);
256
257 let v = Version("".to_string());
258 assert_eq!(v.major(), None);
259 assert_eq!(v.minor(), None);
260}
261
262#[test]
265#[ignore = "not a test, just a convenient way to try the serialization out"]
266fn serde_json_serialization_tryout() {
267 let mut abi = ProgramABI::default();
268
269 abi.concrete_types.push(TypeConcreteDeclaration {
270 type_field: "()".into(),
271 concrete_type_id: ConcreteTypeId(
272 "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d".into(),
273 ),
274 metadata_type_id: None,
275 type_arguments: None,
276 alias_of: None,
277 });
278
279 abi.concrete_types.push(TypeConcreteDeclaration {
280 type_field: "enum MyError".into(),
281 concrete_type_id: ConcreteTypeId(
282 "44781f4b1eb667f225275b0a1c877dd4b9a8ab01f3cd01f8ed84f95c6cd2f363".into(),
283 ),
284 metadata_type_id: Some(MetadataTypeId(0)),
285 type_arguments: None,
286 alias_of: None,
287 });
288
289 abi.metadata_types.push(TypeMetadataDeclaration {
290 type_field: "enum MyError".into(),
291 metadata_type_id: MetadataTypeId(0),
292 components: Some(vec![TypeApplication {
293 offset: None,
294 name: "MyErrorVariant".into(),
295 type_id: TypeId::Concrete(ConcreteTypeId(
296 "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d".into(),
297 )),
298 error_message: Some("My error variant error message.".into()),
299 type_arguments: None,
300 }]),
301 type_parameters: None,
302 alias_of: None,
303 });
304
305 let mut error_codes = BTreeMap::new();
306
307 error_codes.insert(
308 0,
309 ErrorDetails {
310 pos: ErrorPosition {
311 function: "my_function".to_string(),
312 pkg: "my_lib".to_string(),
313 file: "lib.rs".to_string(),
314 line: 42,
315 column: 13,
316 },
317 log_id: None,
318 msg: Some("Error message.".to_string()),
319 },
320 );
321 error_codes.insert(
322 1,
323 ErrorDetails {
324 pos: ErrorPosition {
325 function: "another_function".to_string(),
326 pkg: "my_contract".to_string(),
327 file: "main.rs".to_string(),
328 line: 21,
329 column: 34,
330 },
331 log_id: Some("4933727799282657266".to_string()),
332 msg: None,
333 },
334 );
335
336 abi.error_codes = Some(error_codes);
337
338 let mut panicking_calls = BTreeMap::new();
339
340 panicking_calls.insert(
341 1,
342 PanickingCall {
343 pos: ErrorPosition {
344 function: "my_function".to_string(),
345 pkg: "my_lib".to_string(),
346 file: "lib.rs".to_string(),
347 line: 38,
348 column: 13,
349 },
350 function: "this_function_might_panic".to_string(),
351 },
352 );
353
354 abi.panicking_calls = Some(panicking_calls);
355
356 println!("{}", serde_json::to_string_pretty(&abi).unwrap());
357}