1use std::{collections::BTreeSet, env, path::PathBuf};
7
8pub use casper_contract_schema;
9use casper_contract_schema::{
10 Access, Argument, CallMethod, ContractSchema, CustomType, Entrypoint, EnumVariant, Event,
11 NamedCLType, StructMember, UserError
12};
13
14use odra_core::args::EntrypointArgument;
15
16const CCSV: u8 = 1;
17
18mod custom_type;
19mod ty;
20
21pub use ty::NamedCLTyped;
22
23pub trait SchemaEntrypoints {
25 fn schema_entrypoints() -> Vec<Entrypoint>;
27}
28
29pub trait SchemaEvents {
31 fn schema_events() -> Vec<Event> {
33 vec![]
34 }
35
36 fn custom_types() -> Vec<Option<CustomType>> {
41 vec![]
42 }
43}
44
45pub trait SchemaCustomTypes {
47 fn schema_types() -> Vec<Option<CustomType>> {
49 vec![]
50 }
51}
52
53pub trait SchemaErrors {
55 fn schema_errors() -> Vec<UserError> {
57 vec![]
58 }
59}
60
61pub trait SchemaCustomElement {}
63
64impl<T: SchemaCustomElement> SchemaErrors for T {}
65impl<T: SchemaCustomElement> SchemaEvents for T {}
66
67pub fn argument<T: NamedCLTyped + EntrypointArgument>(name: &str) -> Argument {
69 if T::is_required() {
70 Argument::new(name, "", <T as NamedCLTyped>::ty())
71 } else {
72 Argument::new_opt(name, "", <T as NamedCLTyped>::ty())
73 }
74}
75
76pub fn entry_point<T: NamedCLTyped>(
78 name: &str,
79 description: &str,
80 is_mutable: bool,
81 arguments: Vec<Argument>
82) -> Entrypoint {
83 Entrypoint {
84 name: name.into(),
85 description: Some(description.to_string()),
86 is_mutable,
87 arguments,
88 return_ty: T::ty().into(),
89 is_contract_context: true,
90 access: Access::Public
91 }
92}
93
94pub fn struct_member<T: NamedCLTyped>(name: &str) -> StructMember {
96 StructMember {
97 name: name.to_string(),
98 description: None,
99 ty: T::ty().into()
100 }
101}
102
103pub fn enum_typed_variant<T: NamedCLTyped>(name: &str, discriminant: u16) -> EnumVariant {
105 EnumVariant {
106 name: name.to_string(),
107 description: None,
108 discriminant,
109 ty: T::ty().into()
110 }
111}
112
113pub fn enum_variant(name: &str, discriminant: u16) -> EnumVariant {
115 enum_typed_variant::<()>(name, discriminant)
116}
117
118pub fn enum_custom_type_variant(name: &str, discriminant: u16, custom_type: &str) -> EnumVariant {
120 EnumVariant {
121 name: name.to_string(),
122 description: None,
123 discriminant,
124 ty: NamedCLType::Custom(custom_type.into()).into()
125 }
126}
127
128pub fn custom_struct(name: &str, members: Vec<StructMember>) -> CustomType {
130 CustomType::Struct {
131 name: name.into(),
132 description: None,
133 members
134 }
135}
136
137pub fn custom_enum(name: &str, variants: Vec<EnumVariant>) -> CustomType {
139 CustomType::Enum {
140 name: name.into(),
141 description: None,
142 variants
143 }
144}
145
146pub fn event(name: &str) -> Event {
148 Event {
149 name: name.into(),
150 ty: name.into()
151 }
152}
153
154pub fn error(name: &str, description: &str, discriminant: u16) -> UserError {
156 UserError {
157 name: name.into(),
158 description: Some(description.into()),
159 discriminant
160 }
161}
162
163pub fn schema<T: SchemaEntrypoints + SchemaEvents + SchemaCustomTypes + SchemaErrors>(
168 module_name: &str,
169 contract_name: &str,
170 contract_version: &str,
171 authors: Vec<String>,
172 repository: &str,
173 homepage: &str
174) -> ContractSchema {
175 let entry_points = T::schema_entrypoints();
176 let events = T::schema_events();
177 let errors = T::schema_errors();
178 let types = BTreeSet::from_iter(T::schema_types())
179 .into_iter()
180 .flatten()
181 .collect();
182
183 let init_ep = entry_points.iter().find(|e| e.name == "init");
184
185 let init_args = init_ep.map(|e| e.arguments.clone()).unwrap_or_default();
186
187 let init_description = init_ep.and_then(|e| e.description.clone());
188
189 let entry_points = entry_points
190 .into_iter()
191 .filter(|e| e.name != "init")
192 .collect();
193
194 let wasm_file_name = format!("{}.wasm", module_name);
195
196 let repository = match repository {
197 "" => None,
198 _ => Some(repository.to_string())
199 };
200
201 let homepage = match homepage {
202 "" => None,
203 _ => Some(homepage.to_string())
204 };
205
206 ContractSchema {
207 casper_contract_schema_version: CCSV,
208 toolchain: env!("RUSTC_VERSION").to_string(),
209 contract_name: contract_name.to_string(),
210 contract_version: contract_version.to_string(),
211 types,
212 entry_points,
213 events,
214 call: Some(call_method(wasm_file_name, init_description, &init_args)),
215 authors,
216 repository,
217 homepage,
218 errors
219 }
220}
221
222pub fn find_schema_file_path(
224 contract_name: &str,
225 root_path: PathBuf
226) -> Result<PathBuf, &'static str> {
227 let mut path = root_path
228 .join(format!("{}_schema.json", contract_name.to_lowercase()))
229 .with_extension("json");
230
231 let mut checked_paths = vec![];
232 for _ in 0..2 {
233 if path.exists() && path.is_file() {
234 return Ok(path);
235 } else {
236 checked_paths.push(path.clone());
237 path = path.parent().unwrap().to_path_buf();
238 }
239 }
240 Err("Schema not found")
241}
242
243fn call_method(
244 file_name: String,
245 description: Option<String>,
246 constructor_args: &[Argument]
247) -> CallMethod {
248 CallMethod {
249 wasm_file_name: file_name.to_string(),
250 description: description.map(String::from),
251 arguments: vec![
252 Argument {
253 name: odra_core::consts::PACKAGE_HASH_KEY_NAME_ARG.to_string(),
254 description: Some("The arg name for the package hash key name.".to_string()),
255 ty: NamedCLType::String.into(),
256 optional: false
257 },
258 Argument {
259 name: odra_core::consts::ALLOW_KEY_OVERRIDE_ARG.to_string(),
260 description: Some("If true and the key specified in odra_cfg_package_hash_key_name already exists, it will be overwritten.".to_string()),
261 ty: NamedCLType::Bool.into(),
262 optional: false
263 },
264 Argument {
265 name: odra_core::consts::IS_UPGRADABLE_ARG.to_string(),
266 description: Some(
267 "The arg name for the contract upgradeability setting.".to_string()
268 ),
269 ty: NamedCLType::Bool.into(),
270 optional: false
271 },
272 ]
273 .iter()
274 .chain(constructor_args.iter())
275 .cloned()
276 .collect()
277 }
278}
279
280#[cfg(test)]
281mod test {
282 use odra_core::args::Maybe;
283 use odra_core::prelude::Address;
284
285 use super::*;
286
287 #[test]
288 fn test_argument() {
289 let arg = super::argument::<u32>("arg1");
290 assert_eq!(arg.name, "arg1");
291 assert_eq!(arg.ty, casper_contract_schema::NamedCLType::U32.into());
292 }
293
294 #[test]
295 fn test_opt_argument() {
296 let arg = super::argument::<Maybe<u32>>("arg1");
297 assert_eq!(arg.name, "arg1");
298 assert_eq!(arg.ty, casper_contract_schema::NamedCLType::U32.into());
299 }
300
301 #[test]
302 fn test_entry_point() {
303 let arg = super::argument::<u32>("arg1");
304 let entry_point = super::entry_point::<u32>("entry1", "description", true, vec![arg]);
305 assert_eq!(entry_point.name, "entry1");
306 assert_eq!(entry_point.description, Some("description".to_string()));
307 assert!(entry_point.is_mutable);
308 assert_eq!(entry_point.arguments.len(), 1);
309 assert_eq!(
310 entry_point.return_ty,
311 casper_contract_schema::NamedCLType::U32.into()
312 );
313 }
314
315 #[test]
316 fn test_struct_member() {
317 let member = super::struct_member::<u32>("member1");
318 assert_eq!(member.name, "member1");
319 assert_eq!(member.ty, casper_contract_schema::NamedCLType::U32.into());
320 }
321
322 #[test]
323 fn test_enum_typed_variant() {
324 let variant = super::enum_typed_variant::<Address>("variant1", 1);
325 assert_eq!(variant.name, "variant1");
326 assert_eq!(variant.discriminant, 1);
327 assert_eq!(variant.ty, casper_contract_schema::NamedCLType::Key.into());
328 }
329
330 #[test]
331 fn test_enum_variant() {
332 let variant = super::enum_variant("variant1", 1);
333 assert_eq!(variant.name, "variant1");
334 assert_eq!(variant.discriminant, 1);
335 assert_eq!(variant.ty, casper_contract_schema::NamedCLType::Unit.into());
336 }
337
338 #[test]
339 fn test_custom_struct() {
340 let member = super::struct_member::<u32>("member1");
341 let custom_struct = super::custom_struct("struct1", vec![member]);
342 match custom_struct {
343 casper_contract_schema::CustomType::Struct { name, members, .. } => {
344 assert_eq!(name, "struct1".into());
345 assert_eq!(members.len(), 1);
346 }
347 _ => panic!("Expected CustomType::Struct")
348 }
349 }
350
351 #[test]
352 fn test_custom_enum() {
353 let variant1 = super::enum_variant("variant1", 1);
354 let variant2 = super::enum_typed_variant::<String>("v2", 2);
355 let variant3 = super::enum_custom_type_variant("v3", 3, "Type1");
356 let custom_enum = super::custom_enum("enum1", vec![variant1, variant2, variant3]);
357 match custom_enum {
358 casper_contract_schema::CustomType::Enum { name, variants, .. } => {
359 assert_eq!(name, "enum1".into());
360 assert_eq!(variants.len(), 3);
361 assert_eq!(variants[0].ty, NamedCLType::Unit.into());
362 assert_eq!(variants[1].ty, NamedCLType::String.into());
363 assert_eq!(variants[2].ty, NamedCLType::Custom("Type1".into()).into());
364 }
365 _ => panic!("Expected CustomType::Enum")
366 }
367 }
368
369 #[test]
370 fn test_event() {
371 let event = super::event("event1");
372 assert_eq!(event.name, "event1");
373 }
374
375 #[test]
376 fn test_error() {
377 let error = super::error("error1", "description", 1);
378 assert_eq!(error.name, "error1");
379 assert_eq!(error.description, Some("description".to_string()));
380 assert_eq!(error.discriminant, 1);
381 }
382
383 #[test]
384 fn test_schema() {
385 struct TestSchema;
386
387 impl SchemaEntrypoints for TestSchema {
388 fn schema_entrypoints() -> Vec<Entrypoint> {
389 vec![entry_point::<u32>(
390 "entry1",
391 "description",
392 true,
393 vec![super::argument::<u32>("arg1")]
394 )]
395 }
396 }
397
398 impl SchemaEvents for TestSchema {
399 fn schema_events() -> Vec<Event> {
400 vec![event("event1")]
401 }
402 }
403
404 impl SchemaCustomTypes for TestSchema {
405 fn schema_types() -> Vec<Option<CustomType>> {
406 vec![
407 Some(custom_struct(
408 "struct1",
409 vec![struct_member::<u32>("member1")]
410 )),
411 Some(custom_enum("struct1", vec![enum_variant("variant1", 1)])),
412 ]
413 }
414 }
415
416 impl SchemaErrors for TestSchema {
417 fn schema_errors() -> Vec<UserError> {
418 vec![]
419 }
420 }
421
422 let schema = super::schema::<TestSchema>(
423 "module_name",
424 "contract_name",
425 "contract_version",
426 vec!["author".to_string()],
427 "repository",
428 "homepage"
429 );
430
431 assert_eq!(schema.contract_name, "contract_name");
432 assert_eq!(schema.contract_version, "contract_version");
433 assert_eq!(schema.authors, vec!["author".to_string()]);
434 assert_eq!(schema.repository, Some("repository".to_string()));
435 assert_eq!(schema.homepage, Some("homepage".to_string()));
436 assert_eq!(schema.entry_points.len(), 1);
437 assert_eq!(schema.types.len(), 2);
438 assert_eq!(schema.errors.len(), 0);
439 assert_eq!(schema.events.len(), 1);
440 }
441}