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_contract_binary_attribute(&mut self) {
136 self.source.contract_binary = 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 contract_binary: Option<SourceContractBinary>,
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 contract_binary: Option<SourceContractBinary>,
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 contract_binary,
229 build_info,
230 }
231 }
232}
233
234#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
236pub struct SourceContractBinary(
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 SourceContractBinary {
246 pub fn new(contract_binary: Vec<u8>) -> Self {
248 SourceContractBinary(contract_binary)
249 }
250}
251
252impl Display for SourceContractBinary {
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,
444}
445
446impl Display for Compiler {
447 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
448 match self {
449 Self::RustC => write!(f, "rustc"),
450 Self::Solang => write!(f, "solang"),
451 }
452 }
453}
454
455impl FromStr for Compiler {
456 type Err = String;
457
458 fn from_str(s: &str) -> Result<Self, Self::Err> {
459 match s {
460 "rustc" => Ok(Self::RustC),
461 "solang" => Ok(Self::Solang),
462 _ => Err(format!("Invalid compiler '{s}'")),
463 }
464 }
465}
466
467#[derive(Clone, Debug, Deserialize, Serialize)]
469pub struct Contract {
470 pub name: String,
472 pub version: Version,
474 pub authors: Vec<String>,
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub description: Option<String>,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub documentation: Option<Url>,
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub repository: Option<Url>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub homepage: Option<Url>,
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub license: Option<String>,
491}
492
493impl Contract {
494 pub fn builder() -> ContractBuilder {
495 ContractBuilder::default()
496 }
497}
498
499#[derive(Clone, Debug, Deserialize, Serialize)]
501pub struct User {
502 #[serde(flatten)]
504 pub json: Map<String, Value>,
505}
506
507impl User {
508 pub fn new(json: Map<String, Value>) -> Self {
510 User { json }
511 }
512}
513
514#[derive(Default)]
516pub struct ContractBuilder {
517 name: Option<String>,
518 version: Option<Version>,
519 authors: Option<Vec<String>>,
520 description: Option<String>,
521 documentation: Option<Url>,
522 repository: Option<Url>,
523 homepage: Option<Url>,
524 license: Option<String>,
525}
526
527impl ContractBuilder {
528 pub fn name<S>(&mut self, name: S) -> &mut Self
530 where
531 S: AsRef<str>,
532 {
533 if self.name.is_some() {
534 panic!("name has already been set")
535 }
536 self.name = Some(name.as_ref().to_string());
537 self
538 }
539
540 pub fn version(&mut self, version: Version) -> &mut Self {
542 if self.version.is_some() {
543 panic!("version has already been set")
544 }
545 self.version = Some(version);
546 self
547 }
548
549 pub fn authors<I, S>(&mut self, authors: I) -> &mut Self
551 where
552 I: IntoIterator<Item = S>,
553 S: AsRef<str>,
554 {
555 if self.authors.is_some() {
556 panic!("authors has already been set")
557 }
558
559 let authors = authors
560 .into_iter()
561 .map(|s| s.as_ref().to_string())
562 .collect::<Vec<_>>();
563
564 if authors.is_empty() {
565 panic!("must have at least one author")
566 }
567
568 self.authors = Some(authors);
569 self
570 }
571
572 pub fn description<S>(&mut self, description: S) -> &mut Self
574 where
575 S: AsRef<str>,
576 {
577 if self.description.is_some() {
578 panic!("description has already been set")
579 }
580 self.description = Some(description.as_ref().to_string());
581 self
582 }
583
584 pub fn documentation(&mut self, documentation: Url) -> &mut Self {
586 if self.documentation.is_some() {
587 panic!("documentation is already set")
588 }
589 self.documentation = Some(documentation);
590 self
591 }
592
593 pub fn repository(&mut self, repository: Url) -> &mut Self {
595 if self.repository.is_some() {
596 panic!("repository is already set")
597 }
598 self.repository = Some(repository);
599 self
600 }
601
602 pub fn homepage(&mut self, homepage: Url) -> &mut Self {
604 if self.homepage.is_some() {
605 panic!("homepage is already set")
606 }
607 self.homepage = Some(homepage);
608 self
609 }
610
611 pub fn license<S>(&mut self, license: S) -> &mut Self
613 where
614 S: AsRef<str>,
615 {
616 if self.license.is_some() {
617 panic!("license has already been set")
618 }
619 self.license = Some(license.as_ref().to_string());
620 self
621 }
622
623 pub fn build(&self) -> Result<Contract, String> {
627 let mut required = Vec::new();
628
629 if let (Some(name), Some(version), Some(authors)) =
630 (&self.name, &self.version, &self.authors)
631 {
632 Ok(Contract {
633 name: name.to_string(),
634 version: version.clone(),
635 authors: authors.to_vec(),
636 description: self.description.clone(),
637 documentation: self.documentation.clone(),
638 repository: self.repository.clone(),
639 homepage: self.homepage.clone(),
640 license: self.license.clone(),
641 })
642 } else {
643 if self.name.is_none() {
644 required.push("name");
645 }
646 if self.version.is_none() {
647 required.push("version")
648 }
649 if self.authors.is_none() {
650 required.push("authors")
651 }
652 Err(format!(
653 "Missing required non-default fields: {}",
654 required.join(", ")
655 ))
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663 use pretty_assertions::assert_eq;
664 use serde_json::json;
665
666 #[test]
667 fn builder_fails_with_missing_required_fields() {
668 let missing_name = Contract::builder()
669 .version(Version::new(2, 1, 0))
671 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
672 .build();
673
674 assert_eq!(
675 missing_name.unwrap_err(),
676 "Missing required non-default fields: name"
677 );
678
679 let missing_version = Contract::builder()
680 .name("incrementer")
681 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
683 .build();
684
685 assert_eq!(
686 missing_version.unwrap_err(),
687 "Missing required non-default fields: version"
688 );
689
690 let missing_authors = Contract::builder()
691 .name("incrementer")
692 .version(Version::new(2, 1, 0))
693 .build();
695
696 assert_eq!(
697 missing_authors.unwrap_err(),
698 "Missing required non-default fields: authors"
699 );
700
701 let missing_all = Contract::builder()
702 .build();
706
707 assert_eq!(
708 missing_all.unwrap_err(),
709 "Missing required non-default fields: name, version, authors"
710 );
711 }
712
713 #[test]
714 fn json_with_optional_fields() {
715 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
716 let compiler = SourceCompiler::new(
717 Compiler::RustC,
718 Version::parse("1.46.0-nightly").unwrap(),
719 );
720 let contract_binary = SourceContractBinary::new(vec![0u8, 1u8, 2u8]);
721 let build_info = json! {
722 {
723 "example_compiler_version": 42,
724 "example_settings": [],
725 "example_name": "increment"
726 }
727 }
728 .as_object()
729 .unwrap()
730 .clone();
731
732 let source = Source::new(
733 Some(contract_binary),
734 CodeHash([0u8; 32]),
735 language,
736 compiler,
737 Some(build_info),
738 );
739
740 let contract = Contract::builder()
741 .name("incrementer")
742 .version(Version::new(2, 1, 0))
743 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
744 .description("increment a value")
745 .documentation(Url::parse("http://docs.rs/").unwrap())
746 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
747 .homepage(Url::parse("http://example.com/").unwrap())
748 .license("Apache-2.0")
749 .build()
750 .unwrap();
751
752 let user_json = json! {
753 {
754 "more-user-provided-fields": [
755 "and",
756 "their",
757 "values"
758 ],
759 "some-user-provided-field": "and-its-value"
760 }
761 };
762 let user = User::new(user_json.as_object().unwrap().clone());
763 let abi_json = json! {
764 {
765 "spec": {},
766 "storage": {},
767 "types": []
768 }
769 }
770 .as_object()
771 .unwrap()
772 .clone();
773
774 let metadata = ContractMetadata::new(
775 source,
776 contract,
777 Some(String::from("paritytech/contracts-verifiable:3.0.1")),
778 Some(user),
779 abi_json,
780 );
781 let json = serde_json::to_value(&metadata).unwrap();
782
783 let expected = json! {
784 {
785 "source": {
786 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
787 "language": "ink! 2.1.0",
788 "compiler": "rustc 1.46.0-nightly",
789 "contract_binary": "0x000102",
790 "build_info": {
791 "example_compiler_version": 42,
792 "example_settings": [],
793 "example_name": "increment"
794 }
795 },
796 "image": "paritytech/contracts-verifiable:3.0.1",
797 "contract": {
798 "name": "incrementer",
799 "version": "2.1.0",
800 "authors": [
801 "Use Ink <ink@use.ink>"
802 ],
803 "description": "increment a value",
804 "documentation": "http://docs.rs/",
805 "repository": "http://github.com/use-ink/ink/",
806 "homepage": "http://example.com/",
807 "license": "Apache-2.0",
808 },
809 "user": {
810 "more-user-provided-fields": [
811 "and",
812 "their",
813 "values"
814 ],
815 "some-user-provided-field": "and-its-value"
816 },
817 "spec": {},
819 "storage": {},
820 "types": []
821 }
822 };
823
824 assert_eq!(json, expected);
825 }
826
827 #[test]
828 fn json_excludes_optional_fields() {
829 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
830 let compiler = SourceCompiler::new(
831 Compiler::RustC,
832 Version::parse("1.46.0-nightly").unwrap(),
833 );
834 let source = Source::new(None, CodeHash([0u8; 32]), language, compiler, None);
835 let contract = Contract::builder()
836 .name("incrementer")
837 .version(Version::new(2, 1, 0))
838 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
839 .build()
840 .unwrap();
841 let abi_json = json! {
842 {
843 "spec": {},
844 "storage": {},
845 "types": []
846 }
847 }
848 .as_object()
849 .unwrap()
850 .clone();
851
852 let metadata = ContractMetadata::new(source, contract, None, None, abi_json);
853 let json = serde_json::to_value(&metadata).unwrap();
854
855 let expected = json! {
856 {
857 "contract": {
858 "name": "incrementer",
859 "version": "2.1.0",
860 "authors": [
861 "Use Ink <ink@use.ink>"
862 ],
863 },
864 "image": serde_json::Value::Null,
865 "source": {
866 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
867 "language": "ink! 2.1.0",
868 "compiler": "rustc 1.46.0-nightly"
869 },
870 "spec": {},
872 "storage": {},
873 "types": []
874 }
875 };
876
877 assert_eq!(json, expected);
878 }
879
880 #[test]
881 fn decoding_works() {
882 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
883 let compiler = SourceCompiler::new(
884 Compiler::RustC,
885 Version::parse("1.46.0-nightly").unwrap(),
886 );
887 let contract_binary = SourceContractBinary::new(vec![0u8, 1u8, 2u8]);
888 let build_info = json! {
889 {
890 "example_compiler_version": 42,
891 "example_settings": [],
892 "example_name": "increment",
893 }
894 }
895 .as_object()
896 .unwrap()
897 .clone();
898
899 let source = Source::new(
900 Some(contract_binary),
901 CodeHash([0u8; 32]),
902 language,
903 compiler,
904 Some(build_info),
905 );
906 let contract = Contract::builder()
907 .name("incrementer")
908 .version(Version::new(2, 1, 0))
909 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
910 .description("increment a value")
911 .documentation(Url::parse("http://docs.rs/").unwrap())
912 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
913 .homepage(Url::parse("http://example.com/").unwrap())
914 .license("Apache-2.0")
915 .build()
916 .unwrap();
917
918 let user_json = json! {
919 {
920 "more-user-provided-fields": [
921 "and",
922 "their",
923 "values"
924 ],
925 "some-user-provided-field": "and-its-value"
926 }
927 };
928 let user = User::new(user_json.as_object().unwrap().clone());
929 let abi_json = json! {
930 {
931 "spec": {},
932 "storage": {},
933 "types": []
934 }
935 }
936 .as_object()
937 .unwrap()
938 .clone();
939
940 let metadata =
941 ContractMetadata::new(source, contract, None, Some(user), abi_json);
942 let json = serde_json::to_value(&metadata).unwrap();
943
944 let decoded = serde_json::from_value::<ContractMetadata>(json);
945 assert!(decoded.is_ok())
946 }
947}