1#![deny(unused_crate_dependencies)]
68
69pub mod byte_str;
70pub mod compatibility;
71
72use anyhow::{
73 Context,
74 Result,
75};
76use semver::Version;
77use serde::{
78 de,
79 Deserialize,
80 Serialize,
81 Serializer,
82};
83use serde_json::{
84 Map,
85 Value,
86};
87use std::{
88 fmt::{
89 Display,
90 Formatter,
91 Result as DisplayResult,
92 },
93 fs::File,
94 path::Path,
95 str::FromStr,
96};
97use url::Url;
98
99#[derive(Clone, Debug, Deserialize, Serialize)]
101pub struct ContractMetadata {
102 pub source: Source,
104 pub contract: Contract,
106 pub image: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub user: Option<User>,
112 #[serde(flatten)]
114 pub abi: Map<String, Value>,
115}
116
117impl ContractMetadata {
118 pub fn new(
120 source: Source,
121 contract: Contract,
122 image: Option<String>,
123 user: Option<User>,
124 abi: Map<String, Value>,
125 ) -> Self {
126 Self {
127 source,
128 contract,
129 image,
130 user,
131 abi,
132 }
133 }
134
135 pub fn remove_source_wasm_attribute(&mut self) {
136 self.source.wasm = None;
137 }
138
139 pub fn load<P>(metadata_path: P) -> Result<Self>
141 where
142 P: AsRef<Path>,
143 {
144 let path = metadata_path.as_ref();
145 let file = File::open(path)
146 .context(format!("Failed to open metadata file {}", path.display()))?;
147 serde_json::from_reader(file).context(format!(
148 "Failed to deserialize metadata file {}",
149 path.display()
150 ))
151 }
152
153 pub fn check_ink_compatibility(&self) -> Result<()> {
156 if let Language::Ink = self.source.language.language {
157 compatibility::check_contract_ink_compatibility(
158 &self.source.language.version,
159 None,
160 )?;
161 }
162 Ok(())
163 }
164}
165
166#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
168pub struct CodeHash(
169 #[serde(
170 serialize_with = "byte_str::serialize_as_byte_str",
171 deserialize_with = "byte_str::deserialize_from_byte_str_array"
172 )]
173 pub [u8; 32],
175);
176
177impl From<[u8; 32]> for CodeHash {
178 fn from(value: [u8; 32]) -> Self {
179 CodeHash(value)
180 }
181}
182
183impl Display for CodeHash {
184 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
185 let raw_string = self
186 .0
187 .iter()
188 .map(|b| format!("{:x?}", b))
189 .collect::<Vec<String>>()
190 .join("");
191 f.write_fmt(format_args!("0x{}", raw_string))
192 }
193}
194
195#[derive(Clone, Debug, Deserialize, Serialize)]
197pub struct Source {
198 pub hash: CodeHash,
200 pub language: SourceLanguage,
202 pub compiler: SourceCompiler,
204 #[serde(skip_serializing_if = "Option::is_none")]
207 pub wasm: Option<SourceWasm>,
208 #[serde(skip_serializing_if = "Option::is_none")]
212 pub build_info: Option<Map<String, Value>>,
213}
214
215impl Source {
216 pub fn new(
218 wasm: Option<SourceWasm>,
219 hash: CodeHash,
220 language: SourceLanguage,
221 compiler: SourceCompiler,
222 build_info: Option<Map<String, Value>>,
223 ) -> Self {
224 Source {
225 hash,
226 language,
227 compiler,
228 wasm,
229 build_info,
230 }
231 }
232}
233
234#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
236pub struct SourceWasm(
237 #[serde(
238 serialize_with = "byte_str::serialize_as_byte_str",
239 deserialize_with = "byte_str::deserialize_from_byte_str"
240 )]
241 pub Vec<u8>,
243);
244
245impl SourceWasm {
246 pub fn new(wasm: Vec<u8>) -> Self {
248 SourceWasm(wasm)
249 }
250}
251
252impl Display for SourceWasm {
253 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
254 write!(f, "0x").expect("failed writing to string");
255 for byte in &self.0 {
256 write!(f, "{byte:02x}").expect("failed writing to string");
257 }
258 write!(f, "")
259 }
260}
261
262#[derive(Clone, Debug)]
264pub struct SourceLanguage {
265 pub language: Language,
267 pub version: Version,
269}
270
271impl SourceLanguage {
272 pub fn new(language: Language, version: Version) -> Self {
274 SourceLanguage { language, version }
275 }
276}
277
278impl Serialize for SourceLanguage {
279 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
280 where
281 S: Serializer,
282 {
283 serializer.serialize_str(&self.to_string())
284 }
285}
286
287impl<'de> Deserialize<'de> for SourceLanguage {
288 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
289 where
290 D: de::Deserializer<'de>,
291 {
292 let s = String::deserialize(deserializer)?;
293 FromStr::from_str(&s).map_err(de::Error::custom)
294 }
295}
296
297impl Display for SourceLanguage {
298 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
299 write!(f, "{} {}", self.language, self.version)
300 }
301}
302
303impl FromStr for SourceLanguage {
304 type Err = String;
305
306 fn from_str(s: &str) -> Result<Self, Self::Err> {
307 let mut parts = s.split_whitespace();
308
309 let language = parts
310 .next()
311 .ok_or_else(|| {
312 format!(
313 "SourceLanguage: Expected format '<language> <version>', got '{s}'"
314 )
315 })
316 .and_then(FromStr::from_str)?;
317
318 let version = parts
319 .next()
320 .ok_or_else(|| {
321 format!(
322 "SourceLanguage: Expected format '<language> <version>', got '{s}'"
323 )
324 })
325 .and_then(|v| {
326 <Version as FromStr>::from_str(v)
327 .map_err(|e| format!("Error parsing version {e}"))
328 })?;
329
330 Ok(Self { language, version })
331 }
332}
333
334#[derive(Clone, Debug)]
336pub enum Language {
337 Ink,
338 Solidity,
339 AssemblyScript,
340}
341
342impl Display for Language {
343 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
344 match self {
345 Self::Ink => write!(f, "ink!"),
346 Self::Solidity => write!(f, "Solidity"),
347 Self::AssemblyScript => write!(f, "AssemblyScript"),
348 }
349 }
350}
351
352impl FromStr for Language {
353 type Err = String;
354
355 fn from_str(s: &str) -> Result<Self, Self::Err> {
356 match s {
357 "ink!" => Ok(Self::Ink),
358 "Solidity" => Ok(Self::Solidity),
359 "AssemblyScript" => Ok(Self::AssemblyScript),
360 _ => Err(format!("Invalid language '{s}'")),
361 }
362 }
363}
364
365#[derive(Clone, Debug)]
367pub struct SourceCompiler {
368 pub compiler: Compiler,
370 pub version: Version,
372}
373
374impl Display for SourceCompiler {
375 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
376 write!(f, "{} {}", self.compiler, self.version)
377 }
378}
379
380impl FromStr for SourceCompiler {
381 type Err = String;
382
383 fn from_str(s: &str) -> Result<Self, Self::Err> {
384 let mut parts = s.split_whitespace();
385
386 let compiler = parts
387 .next()
388 .ok_or_else(|| {
389 format!(
390 "SourceCompiler: Expected format '<compiler> <version>', got '{s}'"
391 )
392 })
393 .and_then(FromStr::from_str)?;
394
395 let version = parts
396 .next()
397 .ok_or_else(|| {
398 format!(
399 "SourceCompiler: Expected format '<compiler> <version>', got '{s}'"
400 )
401 })
402 .and_then(|v| {
403 <Version as FromStr>::from_str(v)
404 .map_err(|e| format!("Error parsing version {e}"))
405 })?;
406
407 Ok(Self { compiler, version })
408 }
409}
410
411impl Serialize for SourceCompiler {
412 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
413 where
414 S: Serializer,
415 {
416 serializer.serialize_str(&self.to_string())
417 }
418}
419
420impl<'de> Deserialize<'de> for SourceCompiler {
421 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
422 where
423 D: de::Deserializer<'de>,
424 {
425 let s = String::deserialize(deserializer)?;
426 FromStr::from_str(&s).map_err(de::Error::custom)
427 }
428}
429
430impl SourceCompiler {
431 pub fn new(compiler: Compiler, version: Version) -> Self {
432 SourceCompiler { compiler, version }
433 }
434}
435
436#[derive(Clone, Debug, Deserialize, Serialize)]
438pub enum Compiler {
439 RustC,
441 Solang,
443}
444
445impl Display for Compiler {
446 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
447 match self {
448 Self::RustC => write!(f, "rustc"),
449 Self::Solang => write!(f, "solang"),
450 }
451 }
452}
453
454impl FromStr for Compiler {
455 type Err = String;
456
457 fn from_str(s: &str) -> Result<Self, Self::Err> {
458 match s {
459 "rustc" => Ok(Self::RustC),
460 "solang" => Ok(Self::Solang),
461 _ => Err(format!("Invalid compiler '{s}'")),
462 }
463 }
464}
465
466#[derive(Clone, Debug, Deserialize, Serialize)]
468pub struct Contract {
469 pub name: String,
471 pub version: Version,
473 pub authors: Vec<String>,
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub description: Option<String>,
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub documentation: Option<Url>,
481 #[serde(skip_serializing_if = "Option::is_none")]
483 pub repository: Option<Url>,
484 #[serde(skip_serializing_if = "Option::is_none")]
486 pub homepage: Option<Url>,
487 #[serde(skip_serializing_if = "Option::is_none")]
489 pub license: Option<String>,
490}
491
492impl Contract {
493 pub fn builder() -> ContractBuilder {
494 ContractBuilder::default()
495 }
496}
497
498#[derive(Clone, Debug, Deserialize, Serialize)]
500pub struct User {
501 #[serde(flatten)]
503 pub json: Map<String, Value>,
504}
505
506impl User {
507 pub fn new(json: Map<String, Value>) -> Self {
509 User { json }
510 }
511}
512
513#[derive(Default)]
515pub struct ContractBuilder {
516 name: Option<String>,
517 version: Option<Version>,
518 authors: Option<Vec<String>>,
519 description: Option<String>,
520 documentation: Option<Url>,
521 repository: Option<Url>,
522 homepage: Option<Url>,
523 license: Option<String>,
524}
525
526impl ContractBuilder {
527 pub fn name<S>(&mut self, name: S) -> &mut Self
529 where
530 S: AsRef<str>,
531 {
532 if self.name.is_some() {
533 panic!("name has already been set")
534 }
535 self.name = Some(name.as_ref().to_string());
536 self
537 }
538
539 pub fn version(&mut self, version: Version) -> &mut Self {
541 if self.version.is_some() {
542 panic!("version has already been set")
543 }
544 self.version = Some(version);
545 self
546 }
547
548 pub fn authors<I, S>(&mut self, authors: I) -> &mut Self
550 where
551 I: IntoIterator<Item = S>,
552 S: AsRef<str>,
553 {
554 if self.authors.is_some() {
555 panic!("authors has already been set")
556 }
557
558 let authors = authors
559 .into_iter()
560 .map(|s| s.as_ref().to_string())
561 .collect::<Vec<_>>();
562
563 if authors.is_empty() {
564 panic!("must have at least one author")
565 }
566
567 self.authors = Some(authors);
568 self
569 }
570
571 pub fn description<S>(&mut self, description: S) -> &mut Self
573 where
574 S: AsRef<str>,
575 {
576 if self.description.is_some() {
577 panic!("description has already been set")
578 }
579 self.description = Some(description.as_ref().to_string());
580 self
581 }
582
583 pub fn documentation(&mut self, documentation: Url) -> &mut Self {
585 if self.documentation.is_some() {
586 panic!("documentation is already set")
587 }
588 self.documentation = Some(documentation);
589 self
590 }
591
592 pub fn repository(&mut self, repository: Url) -> &mut Self {
594 if self.repository.is_some() {
595 panic!("repository is already set")
596 }
597 self.repository = Some(repository);
598 self
599 }
600
601 pub fn homepage(&mut self, homepage: Url) -> &mut Self {
603 if self.homepage.is_some() {
604 panic!("homepage is already set")
605 }
606 self.homepage = Some(homepage);
607 self
608 }
609
610 pub fn license<S>(&mut self, license: S) -> &mut Self
612 where
613 S: AsRef<str>,
614 {
615 if self.license.is_some() {
616 panic!("license has already been set")
617 }
618 self.license = Some(license.as_ref().to_string());
619 self
620 }
621
622 pub fn build(&self) -> Result<Contract, String> {
626 let mut required = Vec::new();
627
628 if let (Some(name), Some(version), Some(authors)) =
629 (&self.name, &self.version, &self.authors)
630 {
631 Ok(Contract {
632 name: name.to_string(),
633 version: version.clone(),
634 authors: authors.to_vec(),
635 description: self.description.clone(),
636 documentation: self.documentation.clone(),
637 repository: self.repository.clone(),
638 homepage: self.homepage.clone(),
639 license: self.license.clone(),
640 })
641 } else {
642 if self.name.is_none() {
643 required.push("name");
644 }
645 if self.version.is_none() {
646 required.push("version")
647 }
648 if self.authors.is_none() {
649 required.push("authors")
650 }
651 Err(format!(
652 "Missing required non-default fields: {}",
653 required.join(", ")
654 ))
655 }
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use pretty_assertions::assert_eq;
663 use serde_json::json;
664
665 #[test]
666 fn builder_fails_with_missing_required_fields() {
667 let missing_name = Contract::builder()
668 .version(Version::new(2, 1, 0))
670 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
671 .build();
672
673 assert_eq!(
674 missing_name.unwrap_err(),
675 "Missing required non-default fields: name"
676 );
677
678 let missing_version = Contract::builder()
679 .name("incrementer")
680 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
682 .build();
683
684 assert_eq!(
685 missing_version.unwrap_err(),
686 "Missing required non-default fields: version"
687 );
688
689 let missing_authors = Contract::builder()
690 .name("incrementer")
691 .version(Version::new(2, 1, 0))
692 .build();
694
695 assert_eq!(
696 missing_authors.unwrap_err(),
697 "Missing required non-default fields: authors"
698 );
699
700 let missing_all = Contract::builder()
701 .build();
705
706 assert_eq!(
707 missing_all.unwrap_err(),
708 "Missing required non-default fields: name, version, authors"
709 );
710 }
711
712 #[test]
713 fn json_with_optional_fields() {
714 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
715 let compiler = SourceCompiler::new(
716 Compiler::RustC,
717 Version::parse("1.46.0-nightly").unwrap(),
718 );
719 let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]);
720 let build_info = json! {
721 {
722 "example_compiler_version": 42,
723 "example_settings": [],
724 "example_name": "increment"
725 }
726 }
727 .as_object()
728 .unwrap()
729 .clone();
730
731 let source = Source::new(
732 Some(wasm),
733 CodeHash([0u8; 32]),
734 language,
735 compiler,
736 Some(build_info),
737 );
738
739 let contract = Contract::builder()
740 .name("incrementer")
741 .version(Version::new(2, 1, 0))
742 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
743 .description("increment a value")
744 .documentation(Url::parse("http://docs.rs/").unwrap())
745 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
746 .homepage(Url::parse("http://example.com/").unwrap())
747 .license("Apache-2.0")
748 .build()
749 .unwrap();
750
751 let user_json = json! {
752 {
753 "more-user-provided-fields": [
754 "and",
755 "their",
756 "values"
757 ],
758 "some-user-provided-field": "and-its-value"
759 }
760 };
761 let user = User::new(user_json.as_object().unwrap().clone());
762 let abi_json = json! {
763 {
764 "spec": {},
765 "storage": {},
766 "types": []
767 }
768 }
769 .as_object()
770 .unwrap()
771 .clone();
772
773 let metadata = ContractMetadata::new(
774 source,
775 contract,
776 Some(String::from("paritytech/contracts-verifiable:3.0.1")),
777 Some(user),
778 abi_json,
779 );
780 let json = serde_json::to_value(&metadata).unwrap();
781
782 let expected = json! {
783 {
784 "source": {
785 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
786 "language": "ink! 2.1.0",
787 "compiler": "rustc 1.46.0-nightly",
788 "wasm": "0x000102",
789 "build_info": {
790 "example_compiler_version": 42,
791 "example_settings": [],
792 "example_name": "increment"
793 }
794 },
795 "image": "paritytech/contracts-verifiable:3.0.1",
796 "contract": {
797 "name": "incrementer",
798 "version": "2.1.0",
799 "authors": [
800 "Use Ink <ink@use.ink>"
801 ],
802 "description": "increment a value",
803 "documentation": "http://docs.rs/",
804 "repository": "http://github.com/use-ink/ink/",
805 "homepage": "http://example.com/",
806 "license": "Apache-2.0",
807 },
808 "user": {
809 "more-user-provided-fields": [
810 "and",
811 "their",
812 "values"
813 ],
814 "some-user-provided-field": "and-its-value"
815 },
816 "spec": {},
818 "storage": {},
819 "types": []
820 }
821 };
822
823 assert_eq!(json, expected);
824 }
825
826 #[test]
827 fn json_excludes_optional_fields() {
828 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
829 let compiler = SourceCompiler::new(
830 Compiler::RustC,
831 Version::parse("1.46.0-nightly").unwrap(),
832 );
833 let source = Source::new(None, CodeHash([0u8; 32]), language, compiler, None);
834 let contract = Contract::builder()
835 .name("incrementer")
836 .version(Version::new(2, 1, 0))
837 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
838 .build()
839 .unwrap();
840 let abi_json = json! {
841 {
842 "spec": {},
843 "storage": {},
844 "types": []
845 }
846 }
847 .as_object()
848 .unwrap()
849 .clone();
850
851 let metadata = ContractMetadata::new(source, contract, None, None, abi_json);
852 let json = serde_json::to_value(&metadata).unwrap();
853
854 let expected = json! {
855 {
856 "contract": {
857 "name": "incrementer",
858 "version": "2.1.0",
859 "authors": [
860 "Use Ink <ink@use.ink>"
861 ],
862 },
863 "image": serde_json::Value::Null,
864 "source": {
865 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
866 "language": "ink! 2.1.0",
867 "compiler": "rustc 1.46.0-nightly"
868 },
869 "spec": {},
871 "storage": {},
872 "types": []
873 }
874 };
875
876 assert_eq!(json, expected);
877 }
878
879 #[test]
880 fn decoding_works() {
881 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
882 let compiler = SourceCompiler::new(
883 Compiler::RustC,
884 Version::parse("1.46.0-nightly").unwrap(),
885 );
886 let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]);
887 let build_info = json! {
888 {
889 "example_compiler_version": 42,
890 "example_settings": [],
891 "example_name": "increment",
892 }
893 }
894 .as_object()
895 .unwrap()
896 .clone();
897
898 let source = Source::new(
899 Some(wasm),
900 CodeHash([0u8; 32]),
901 language,
902 compiler,
903 Some(build_info),
904 );
905 let contract = Contract::builder()
906 .name("incrementer")
907 .version(Version::new(2, 1, 0))
908 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
909 .description("increment a value")
910 .documentation(Url::parse("http://docs.rs/").unwrap())
911 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
912 .homepage(Url::parse("http://example.com/").unwrap())
913 .license("Apache-2.0")
914 .build()
915 .unwrap();
916
917 let user_json = json! {
918 {
919 "more-user-provided-fields": [
920 "and",
921 "their",
922 "values"
923 ],
924 "some-user-provided-field": "and-its-value"
925 }
926 };
927 let user = User::new(user_json.as_object().unwrap().clone());
928 let abi_json = json! {
929 {
930 "spec": {},
931 "storage": {},
932 "types": []
933 }
934 }
935 .as_object()
936 .unwrap()
937 .clone();
938
939 let metadata =
940 ContractMetadata::new(source, contract, None, Some(user), abi_json);
941 let json = serde_json::to_value(&metadata).unwrap();
942
943 let decoded = serde_json::from_value::<ContractMetadata>(json);
944 assert!(decoded.is_ok())
945 }
946}